Django大杂烩


一、web框架

本质socket接受请求返回response
socket可以封装,python标准库就有

关于python2与python3,在3中,unicode是字符串,需编码成字节bytes
① b'fffff'
② bytes('ffff',encoding='utf8')
③ 'ffff'.encode('utf8')

对于不同的url后缀/xxx,需作不同处理,封装成不同的def函数
进阶:将URL放在字典里, func = None
if current_url in URL_DICT;
func = URL_DICT[current_url]
if func;
return func()
else;
return ['404'.encode('utf-8')]
以后修改增添dict即可,这是必须的
进阶2:URL_DICT = {
"/index/\d+":func,

}
利用正则表示一类的URL,然后在返回函数的地方正则匹配,较复杂

可以将response内容写在一个html里(专门的文件夹view),response时用handle函数从文件中拿
<div>内容</div>

可以将引用的处理函数写在一个py文件(文件夹controller)里,然后import文件
f = open('view/index.html',mode='rb')
data = f.read()
f.close()
return [data,]

可以从数据库中拿内容,写一个文件夹model
data = data.replace(b'uuuu','内容'.encode('utf-8'))

MVC框架 model view controller

MTV框架 model template view


二、Django

1.目录结构:
mysite
- mysite文件夹 #对整个程序进行配置
-init.py
-settings.py #配置文件
-url.py #URL对应关系
-wsgi.py #django本身写不了socket,wsgi是创建socket的规范,网上有现成的
按照wsgi封装socket的模块,django用python内置的wsgiref,以后用uwsgi
-manage.py #管理django程序

app目录
-migrations文件夹 sqlalchemy通过类创建数据库,不能命令删除创建错误的列,
只能登陆数据库删除,再从类把那一行删掉
#数据修改表结构记录
-init.py #python2中标注这是属于python的一个文件夹,才能导入成功
-admin.py #Django的后台管理
-apps.py #配置当前app
-models.py #通过类创建数据库结构
-tests.py #单元测试
-views.py #业务逻辑代码

2.Django用户登录示例
①<label for="">指定相关联的表单控件input
②display:inline-block:将对象呈递为内联对象,但是对象的内容作为块对象呈递。
旁边的内联对象会被呈递在同一行内,允许空格。
③表单提交标签<form action method> <p> <label> <input type="text"> type="submit"
<style>label { width ....}


④把css和js文件放在static文件夹,css在<head>头部用<link rel="stylesheet" href=../>
rel 属性是必须的,规定当前文档与被链接文档/资源之间的关系。
而js用<script src=....>在标签的最后引入
⑤静态文件要配置settings,不能走url访问,不然会出现404
STATICFILES_DIRS = (
os.path.join(BASE_DIR,'static'),#记得加逗号
)


3.请求与响应:用户登录与前端交互

①form表单action提交到自身网页时,需csrf,在settings中注释掉MIDDLEWARE的csrf文件即可。
②views.py中的request包含了用户请求的所有信息,post or get,user-agent等,request.POST获取用户提交的所有信息

def login(request):
error_msg = ''
#print(request.method) 用户提交的方式
if request.method == "POST":
# user = request.POST['user'] 索引不存在会报错
# pwd = request.POST['pwd']
user = request.POST.get('user',None) get方法默认等于None,就不会报错
pwd = request.POST.get('pwd',None)
if user == 'root' and pwd == '123':
return redirect('http://www.baidu.com') 跳转到URL,重定向用redirect
else:
error_msg = '用户名或密码错误'
return render(request,'login.html',{'error_msg':error_msg})

③urlpattern中网址最后和form表单的action网址要一致,
url(r'^login/',views.login)和action="/login/"
urlpattern中的网址没有/时django默认会加上,这样访问login/也能访问
1. action中没有/,而urlpattern中有时,就会匹配不上
2. action中有/,而urlpattern中没有时,也能匹配

④表格<table></table>
<tr>定义表格的行
<td>定义表格的单元格
<th>定义表格的表头

⑤html模板中的循环
{% for row in user_list %} 循环时用{% %}还要有结尾endfor
<tr>
<td>{{ row.username }}</td> 取值时用{{}}
<td>{{ row.gender }}</td>
<td>{{ row.email }}</td>
</tr>
{% endfor %}

⑥定义全局变量,用于循环输出用户列表
def home(request):
if request.method == 'POST':
u = request.POST.get('username') 获取<input name="uesrname">提交的数据
g = request.POST.get('gender')
e = request.POST.get('email')
temp = {'username': u, 'email': e, 'gender': g} 字典格式
USER_LIST.append(temp) 添加到字典中用于循环
return render(request,'home.html',{'user_list':USER_LIST})

问题:重启后输入数据清空,数据库可以解决。

4.Django路由介绍
请求=>urls.py=>views.py(从数据库models和templates拿东西)=>浏览器


5.内容整理
①视图函数;
def func(request):
#request.method 就算小写,django转大写GET /POST

#Request.GET.get('nid',None) 获取url参数 如127.0.0.1:8000/home?nid=123

#Request.POST.get('',None) 获取提交的参数

#return HttpResponse('字符串')
#return render(request,'xx.html')
#return redirect('只能url')跳转,返回这个url给用户自己去访问

#/ULR 如/login 前面的/代指本地的域名127.0.0.1

②模板渲染
{{ }} 取值
{% %} 循环
对于索引,1. 列表 list.0 list.1
2. 字典 dict.k1 dict.k2

③查看详细:
<td>
</td>
<td>
<a href="/detail?nid={{ row.id }}">查看详细</a> 具体信息的链接
</td>
然后在urlpattern中配置,GET到id后去数据库取出,放到html中返回
request.GET.get('nid') filter或select后用于html循环输出

④删除数据

1.删除按钮 <a class="del" href="#" row-id="{{ row.id }}"></a> #表示不跳
2.jQuery $(.del).click(function(){
var row_id=$(this).attr('row-id') 获取删除的id
$('#nid').val(row_id) 赋值id给弹出的对话框
})
3.弹出对话框
<div>
<form action="/del_host" method="POST"> 跳转del的url,调用删除函数
<input style="display:none" id="nid" type="text" name="nid"/> 隐藏,用于存值
<p>
<input type="submit"/> 提交数据
<input type="button"/> 取消,设置display=none
</p>
</form>
</div>
4.提交到url再到视图函数
del_host -> delete_host函数

def delete_host(request):
nid = request.POST.get("nid")

delete from tb where id = nid 删除数据

return redirect('/home') 跳转回home

总结:
请求声明周期:
- 浏览器构造请求体、请求体,往服务器发送请求
- 服务器路由系统处理request,匹配视图函数
- 匹配成功后,执行views函数 CBV FBV
- views函数中业务处理,操作数据库(原生sql,ORM)等
- 响应内容 响应头设置ret.set_cookie ret['']=''


2018/04/28 周六

概要:路由、视图、模板、ORM操作(对应原生sql语句的用法)
回顾:django请求周期:django服务器前端有个url路由系统,对应不同的视图函数,发起请求时,
路由系统匹配触发视图函数,返回数据库、静态文件等与模板文件渲染好的字符串。

Django启动流程:加载URL对应关系,读取配置文件,启动socket

1. 获取多个数据以及文件上传
①radio单选框 request.POST.get
<input type="radio" name="gender" value="1"/>

②checkbox复选框 request.POST.getlist('')
<input type="checkbox" name="favor" value="11"/>
<input type="checkbox" name="favor" value="22"/>

v = request.POST.getlist('favor')

③选择
<select name="city"> #<select name="city" multiple>多选
<option value="sh">上海</option>
<option value="bj">北京</option>
<option value="tj">天津</option>
</select>

④文件上传 form标签属性enctype="multipart/form-data"
1.<input type="file" name="fafafa" />

2.obj = request.FILES.get('fafafa') #request.POST.get('fafafa')只能拿到文件名
返回一个文件对象,<class 'django.core.files.uploadedfile.InMemoryUploadedFile'>

html中form标签要设置enctype="multipart/form-data",这样就设定提交的文件放到file
而字符串放到POST里

3.print(obj,type(obj),obj.name) name返回的是名字,type返回django的一个文件类的对象
obj输出时会输出文件名字,类比面向对象的def __str__(self)方法,输出return obj.name值

4.保存文件
file_path = os.path.join('upload',obj.name) 保存到一个文件夹中
f=open(file_path,mode='wb') 打开文件
for i in obj.chunks(): chunks()是迭代器,yield获取一点一点上传的文件
f.write(i) 一点点保存
f.close()

2. Django的FBV与CBV

FBV:function base view url对应的视图函数

CBV: class base view

-> /index/ 调用函数
/index/ 调用类,执行类的指定方法
url(r'^home', views.Home.as_view()),固定写法

在views.py中写类
from django.views import View
class Home(View):
def get(self,request): 提交方法为get时执行这个方法
print(request.method)
return render(request,'home.html')

def post(self,request):
print(request.method)
return render(request, 'home.html')

get or post 或者其他方法,django靠的是内置的反射dispatch函数分析请求method找类的对应方法

通过自定义def dispatch(self,request,*args,**kwargs):
print('before') #自定义操作,类似装饰器的功能
result = super(Home, self).dispatch(request, *args, **kwargs)执行原来的方法
print('after')
return HttpResponse('OK') #get or post返回的结果给这个函数,这个函数return给页面,这里没用返回
扩展点:可把get和post方法都要的操作放在这里,比如验证是否登陆成功

扩展点总结:
a.一个函数内部根据选择分发执行函数,返回结果。
b.执行super()执行原父类的方法,前后即可自定制操作

3. Django目标语言循环字典(对于列表,直接循环)

<ul>
{% for row in user_dict.keys %} 循环keys,还可以.values和.items
{% for k,row in user_dict.items %}
<li>{{ row }}</li>
{% endfor %}
</ul>


4. 基于正则表达式的URL

详情页的方法:1.<a target="_blank" href="/detail/?nid={{ k }}">{{ row.name }}</a>
2.通过问号传递参数k,通过url匹配一个view后,
3.view函数中 nid = request.GET.get('nid')
detail_info = USER_DICT[nid]
return render(request,'detail.html',{'detail_info':detail_info})
4.detail.html中{{ detail_info.name }}

更好的方法:正则表达式
<a target="_blank" href="/detail-{{ k }}">{{ row.name }}</a>

url(r'^detail-(\d+).html', views.detail), 一类url对应一个函数

def detail(request,nid):传匹配的参数到视图中

# url(r'^detail-(?P<nid>\d+)-(?P<uid>\d+)',views.detail) 匹配形参分组

def detail(request,*args,**kwargs) *args指列表元组,**kwargs指字典

5. Django对应的路由名称

url(r'^index/', views.index,name="index") name索引这个url
<form action="{% url 'index' %}" method="post">

注:#request.path_info 当前访问url的后缀,写在form中返回当前页面

①url(r'^index/(\d+)', views.index,name="index"),

from django.urls import reverse 导入reverse
v = reverse('index',args=(90,)) reverse反转生成url
print(v)

html中生成url的方法:{% url 'name' 1 2 %}

②url(r'^index/(?P<nid>\d+)', views.index,name="index"),

from django.urls import reverse
v = reverse('index',kwargs={'nid':90})

html中生成url的方法:{% url 'name' nid=1 uid=2 %}

6.Django路由分发

url(r'^cmbd/', include('app.urls')), include app下的url

访问时,127.0.0.1/cmdb/login 即可分类访问


7.创建基本类型和生成数据库结构

ORM:创建一个类,生成数据库表(数字、字符串、时间)

两种:dbfor codeforst
第一种创建数据库,根据结构生成类的语句

第二种创建类生成数据库,根据类操作数据库
class UserInfo(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=64)
然后在settings中配置app,这样makemigrations才能找到该models


注:还能修改sqlites为mysql,要修改默认模块为pymysql
__init__中import pymysql,pymysql.install_as_MySQLdb()

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME':'dbname',
'USER': 'root',
'PASSWORD': 'xxx',
'HOST': '',
'PORT': '',
}
}
8. ORM操作

增:
①models.UserInfo.objects.create(username='root',password='123')

②dic = {'username':'eric','password':'123'}
models.UserInfo.objects.create(**dic)

③obj = models.UserInfo(username='alex',password='123')
obj.save() #原理是这个

查:
result = models.UserInfo.objects.all() result是django的Queryset,->列表[obj(xx,xx,xx),]
print(result)

result = models.UserInfo.objects.filter(username='root').first()

神奇的双下划线 __gt,能跨表,无限双下划线

obj_list = models.UserInfo.objects.all().values('id','username') 返回Queryset列表,每一个元素是对应的字典
obj_list = models.UserInfo.objects.all().values_list('id','username')返回Queryset列表,每一个元素是对应的元组

删:
models.UserInfo.objects.filter(id=4).delete()

更新:
models.UserInfo.objects.all().update(password='999')
models.UserInfo.objects.filter(id=3).update(password='999')


9.基于ORM实现用户登录
u = request.POST.get('user')
p = request.POST.get('pwd')
obj = models.UserInfo.objects.filter(username=u,password=p)搜索看是否存在,返回Queryset列表
obj = models.UserInfo.objects.filter(username=u,password=p).first()返回对象

返回用户管理页面

功能: 1.左侧导航栏:<a class="menu" href="/cmdb/user_info">用户管理</a>

2.点击后跳转用户列表:
user_list = models.UserInfo.objects.all()

{% for row in user_list %}
<li> <a href="/cmdb/user_detail_{{ row.id }}/">{{ row.username }}</a> </li>
{% endfor %}
3.点击后跳转用户详情页

url(r'^user_detail_(?P<nid>\d+)/', views.user_detail),

def user_detail(request,nid):
obj = models.UserInfo.objects.filter(id=nid).first()#不存在会返回none
# models.UserInfo.objects.get(id=nid) #不存在会报错

return render(request,'user_detail.html',{'obj':obj,})

<h1>用户信息</h1>
<h4>{{ obj.name }}</h4>
<h4>{{ obj.id }}</h4>
<h4>{{ obj.password }}</h4>


10. 基于ORM实现用户增删改查

增:①.html中添加表单
<h3>添加用户</h3>
<form action="/cmdb/user_info/" method="POST">
<input type="text" name="user" />
<input type="text" name="pwd" />
<input type="submit" value="添加" />
</form>
②. 点击表单跳转回用户列表,但不是get而是提交post
elif request.method == "POST":
u = request.POST.get('user')
p = request.POST.get('pwd')
models.UserInfo.objects.create(username=u,password=p)
return redirect('/cmdb/user_info/')

删:<a href="/cmdb/userdel-{{ row.id }}/">删除</a>|
点击后跳转到
url(r'^userdel-(?P<nid>\d+)/', views.user_del),

models.UserInfo.objects.filter(id=nid).delete()
return redirect('/cmdb/user_info/')

编辑:
<a href="/cmdb/useredit-{{ row.id }}/">编辑</a>|

点击后跳转到编辑页
url(r'^useredit-(?P<nid>\d+)/', views.user_edit),

obj = models.UserInfo.objects.filter(id=nid).first()
return render(request,'user_edit.html',{'obj':obj})

<form action="/cmdb/useredit-{{ obj.id }}/" method="post">
<input style="display: none" type="text" name="id" value="{{ obj.id }}"/> 隐藏id
<input type="text" name="username" value="{{ obj.username }}" />
<input type="text" name="password" value="{{ obj.password }}"/>
<input type="submit" value="提交" />
</form>

提交后,request.method为post
elif request.method == "POST":
nid = request.POST.get('id')
u = request.POST.get('username')
p = request.POST.get('password')
models.UserInfo.objects.filter(id=nid).update(username=u,password=p)
return redirect('/cmdb/user_info/')

11. Django字段类型

①增加数据库的列时,
email = models.CharField(max_length=64) makemigrations时要提供一个统一值
gender = models.CharField(max_length=64,null=True) 默认为空

②EmailField CharField如果不使用admin后台,效果都一样

四大基本类型:字符串、数字、时间、二进制
每个种类对应很多类型的field
AutoField 自增
uid = models.AutoField(primary_key=True)

12. ORM 参数
null 数据库中字段是否可以为空
default 默认值
db_column 数据库中字段的列名
db_tablespace
default 数据库中字段的默认值
primary_key 数据库中字段是否为主键

db_index 数据库中字段是否可以建立索引
unique 数据库中字段是否可以建立唯一索引
unique_for_date 数据库中字段【日期】部分是否可以建立唯一索引
unique_for_month 数据库中字段【月】部分是否可以建立唯一索引
unique_for_year 数据库中字段【年】部分是否可以建立唯一索引

auto_now 创建时,自动生成,obj.save会更新这个,而.update不会更新
#obj = UserGroup.objects.filter(id=1).first()
#obj.save()
#obj = UserGroup.objects.filter(id=1).update(caption='CEO')

auto_now_add 创建时,自动生成,

verbose_name Admin中显示的字段名称
blank Admin中是否允许用户输入为空
editable Admin中是否可以编辑
help_text Admin中该字段的提示信息

choices Admin中显示选择框的内容,用不变动的数据放在内存中从而避免跨表操作
如:gf = models.IntegerField(choices=[(0, '何穗'),(1, '大表姐'),],default=1)

error_messages 自定义错误信息(字典类型),从而定制想要显示的错误信息;
字典健:null, blank, invalid, invalid_choice, unique, and unique_for_date
如:{'null': "不能为空.", 'invalid': '格式错误'}

validators 自定义错误验证(列表类型),从而定制想要的验证规则


13.ORM外键操作

user_group = models.ForeignKey("UserGroup",to_field='uid',default=1)

to_field表示关联的字段,不指定时默认关联主键


获取时,{% for row in user_list %}会返回user_group,这是外键表UserGroup的对象封装,包含所有列

{{ row.user_group.caption }}


14.基于ORM的外键实现增加用户

添加操作:
models.UserInfo.objects.create(
...
user_group = models.UserGroup.objects.filter(id=1).first() 两次数据库操作
...
)

models.UserInfo.objects.create(
....
user_group_id = 1 更快速
....
)
html中添加用户时,选择用户组:
group_list = models.UserGroup.objects.all()

return render(request,'user_info.html',{'user_list':user_list,'group_list':group_list})

<select name="group_id">
{% for item in group_list %}
<option value=""{{ item.uid }}>{{ item.caption }}</option>
{% endfor %}
</select>

选择后,将uid通过form action传递post.get()


2018/04/29 周日 s20

1.创建一对多结构


b = models.ForeignKey(to="Bussiness",to_field="id")

2.获取单表单数据的三种方式

v1 = models.Bussiness.objects.all()
# 列表中是对象,模板中row.caption

v2 = models.Bussiness.objects.all().values('id','caption')
#列表中是字典,模板中row.caption

v3 = models.Bussiness.objects.all().values_list('id', 'caption')
# 列表中是元组,模板中row.1

models.Bussiness.objects.get(id=1) 获取一个对象,不存在会报错
models.Bussiness.objects.filter(id=1) 获取的是QuerySet,加上.first()获取的是对象

3.一对多跨表操作

<td>{{ row.b.caption }}</td> b封装了外键表的一整行对象,访问要跨表,b_id不用跨表
<td>{{ row.b.code }}</td>

4.一对多块表操作的三种方式 (与单数据表的不同在于,涉及到外键表的对象获取)

对于QuerySet列表包含对象
for 循环输出 for row in v1:row.b.caption
v1[0].b.caption 不循环时,通过.进行跨表


①v1 = models.Host.objects.filter(nid__gt=0) 返回[hostobj(ip,obj())]
# for row in v1:
# print(row.nid,row.b.caption)

返回的是列表,包含对象,[hostobj(ip,obj())],
模板中引用时: {{ row.nid }}
{{ row.b.caption }} 会跨表

②v2 = models.Host.objects.filter(nid__gt=0).values('nid','hostname','b_id','b__caption')
# for row in v2:
# print(row['nid'],row['b_id'],row['b__caption'])

返回的是列表包含字典,已经跨好表
模板中引用时: {{ row.nid }}
{{ row.b__caption }} 不会跨表了

③v3 = models.Host.objects.filter(nid__gt=0).values_list('nid', 'hostname', 'b_id', 'b__caption')
# for row in v3:
# print(row[0],row[1],row[2])

返回的是列表包含元组,已经跨好表
模板中引用时: {{ row.0 }}
{{ row.1 }}


5.增加一对多数据示例

自定义序号:forloop.counter

数据库前面的数据删除时,序号不是从1开始了,而{{forloop.counter}}可以1 2 3这样
{{forloop.counter0}}可以从0开始
{{forloop.revcounter}} 表示倒序
{{forloop.revcounter0}}

模态对话框:<div class="shade hide"></div> 遮罩层 z-index: 100;
<div class="add-model hide"> 对话层 z-index: 101;

然后在模态框中添加input框,提交后获取post请求,更新数据库,redirect

注:如果return render(request,'host.html')的话,由于没有传参数,html无数据,
可以redirect,请求方式为get,这样跳转早已写好的传参数的views函数

6.初识Ajax以及内容整理

$('#ajax_submit').click(function () {
$.ajax({ #开始语句
url: "/test_ajax/", #提交到哪个url
type: 'POST', #提交方式
data: {'k': 'v', 'list': [1,2,3,4], 'k3': JSON.stringfy({'k1': 'v'}))},$(form对象).serilize()
# 字典不能嵌套字典,可以将字典stringfy传过去
traditional:true, # 提交的data中value包括数组时要声明

// dataType:'JSON', # 后台发送的字符串自动转换为json对象,text\html不作处理,xml能转就转这格式
// data:$('#add_form').serialize(), # 封装提交更方便
success: function (data) { # 提交成功后执行函数
// JSON.parse(data); # 将字符串转为对象,转字符串是stringfy(),对应前端json.dumps\loads
# 声明dataType:'JSON'后,就不用转换了
if(data == 'ok'){
location.reload() # 重新加载
location.href = "某个地址" # 跳转,只能在这跳转,不能在视图函数中redirect
}
else{
$('#error_msg').text(obj.error);# 赋值
}
},
error:function(){
#网络异常,url错误等
}
});
})

此外还有:
$.get(url='',data={},success=)
$.post() #内部都是调用ajax方法

建议:永远让服务端返回一个字典



7.Django序列化与Ajax

通过Ajax发送数据,在返回时,只能返回字符串,故进行序列化

Django发送:
a.HttpResponse返回字符串时,无需序列化与反序列化
HttpResponse('ok')

b.返回render渲染模板后生成的字符串,也无需序列化与反序列化
user_list = models.UserInfo.objects.all()
return render(request,'get_data.html',{'user_list':user_list})

注:不能return redirect,ajax不受理;而render返回的是渲染好数据的字符串,不用再dumps和parse

c.返回一个普通的字典,定义一个字典用于返回,统一,判断时修改其中的value
ret = {'status':True,'error':None,'data':{}}
return HttpResponse(json.dumps(ret)) #只能返回字符串,用dumps序列化为形式字典

注:python中序列化与反序列化是json.dumps\loads(),JavaScript中是JSON.stringfy\parse()

d.返回的字典中value是对象的QuerySet时,需要先将对象序列化为字符串,再加入字典中
from django.core import serializers
user_list = models.BookType.objects.all()
ret['data']= serializers.serialize("json", user_list) #前端需要JSON.parse()转为对象列表即可循环,生成标签等

e.字典中value是字典的QuerySet时,list即可,将QuerySet变成python的list
user_list = models.UserInfo.objects.all().values('id','username')
return HttpResponse(json.dumps(ret))

g.字典中value是元组的QuerySet时,list即可,将QuerySet变成python的list
user_list = models.UserInfo.objects.all().values_list('id','username')
return HttpResponse(json.dumps(ret))

Ajax发送:
a.普通的字典
data: {'k': 'v'}

b.字典value有数组时,需要声明
data: {'k': 'v'}, 'list': [1,2,3,4]}
traditional:true #声明数组

c.字典value有字典时,需要序列化字典
data: {'k': 'v'}, 'list': [1,2,3,4], 'k3': JSON.stringfy({'k1': 'v'}))}

d.封装普通表单数据
data: $(form对象).serilize()

f.表单数据有文件时,需要创建FormData对象并声明
var form = new FormData();
form.append('avatar_img', file_obj);
form.append('csrfmiddlewaretoken','{{ csrf_token }}');

$.ajax({
url: '/avatar_upload.html',
type:'POST',
data: form,
processData: false, // tell jQuery not to process the data #声明1
contentType: false, // tell jQuery not to set contentType #声明2
success: function (arg) { #这里处理回调数据
var obj = JSON.parse(arg);
$('#previewImg').attr('src','/' + obj.data);
}
})

8.编辑一对多示例

删:form表单提交,post id过去
ajax悄悄提交过去然后location.reload
remove标签

编辑: ① <a class="edit">编辑</a>
② $('.shade,.edit-model').removeClass('hide'); 移除标签
③ 编辑框<input>,其中可以设置该行的信息作为默认填充值,
var bid = $(this).parent().parent().attr('bid'); 获取默认的选择值
var hid = $(this).parent().parent().attr('hid'); 获取要修改数据库进行filter的id

var host = $('#now_host').text(); text()与val()不同,一个js一个jquery
var ip = $('#now_ip').text(); 通过这个方法获取表单的数据
var port = $('#now_port').text();



$('#edit_form').find('select').val(bid); 找到该select框,赋默认值,多选框时返回列表
$('#edit_form').find('input[name="hid"]').val(hid); 找到name为hid的框,输入值,用于
下一步数据修改操作,不然ajax提交时
找不到点击编辑前的ID


④把修改信息提交
data:$('#edit_form').serialize(),视图函数接收后
models.Host.objects.update()即可完成编辑


添加:
$.ajax({
url: "/test_ajax/",
type: 'POST',
#data: {'hostname': $('#host').val(), 'ip': $('#ip').val(), 'port': $('#port').val(),'b_id': $('#sel').val()},#}
data:$('#add_form').serialize(), 封装提交更方便
success: function (data) {
}
}
});

9.创建多对多以及增加示例

方式1:自定义
class Host(models.Model):
nid = models.AutoField(primary_key=True)
hostname = models.CharField(max_length=32,db_index=True)
ip = models.GenericIPAddressField(protocol="ipv4",db_index=True)
port = models.IntegerField()

b = models.ForeignKey(to="Bussiness",to_field="id")

class Application(models.Model):
name = models.CharField(max_length=32)

class HostToApp(models.Model):
hobj = models.ForeignKey('Host',to_field='nid')
aobj = models.ForeignKey('Application')

方式2
class Application(models.Model):
name = models.CharField(max_length=32)
r = models.ManyToManyField("Host")

无法通过类进行数据库修改

obj = models.Application.objects.get(id=1) 1表示application_id
obj.name

# 第三张自动生成的表进行操作
obj.r.add(2) 只能这样通过r操作 2表示host_id
obj.r.add(2,3,4)
obj.r.add(*[1,2,3,4])

obj.r.remove(2,4) 类似

obj.r.clear 清除application_id=1的

obj.r.set([3,5,7]) 清除所有再保存3,5,7

====所有相关的主机对象“列表”=======

obj.r.all() 返回Queryset,里面的对象是Host object
r可以obj.r.get/all/filter,与objects一样


增加: ①写好模态框
②主机列表,由于r是包含多个对象的列表,两层循环
{% for app in app_list %}
<tr>
<td>{{ app.name }}</td>
<td>
{% for host in app.r.all %}
<span class="host-tag">
{{ host.hostname }}
</span>
{% endfor %}
</td>
</tr>
{% endfor %}
③输入框,获取Host主机名称进行选择
for op in host_list --->(models.Host.objects.all())

④post提交,form提交---->post跳转后obj.r.add(*host_list)即可 getlist获取多个选择
ajax提交---->$.ajax({
url:'/ajax_app/',
data:$('#add_form').serialize(),
type:'POST',
dataType:'JSON', #不用再转字符串为对象
traditional:true, #能够传输多个主机选择,即列表[1,2,3]
success:function (obj) {
if(obj.status){
location.reload()
}else{
alert('请输入内容')
}
}
})
⑤视图函数中接受,再更新数据库
app_name = request.POST.get('app_name')
host_list = request.POST.getlist('host_list')

obj = models.Application.objects.create(name=app_name)
obj.r.add(*host_list)

编辑: ==========l类似添加,重点在于多对多的值怎么获取========

val获取的是单选的值,一个数字或者字符串,设计多选时,拿到列表,也可以传一个列表给val
1.编辑框:
<select name="host_list" multiple>
{% for op in host_list %}
<option value="{{ op.nid }}">{{ op.hostname }}</option>
{% endfor %}
</select>

2.默认值
$('.edit').click(function () {
$('.edit-model,.shade').removeClass('hide');

var hid_list = [];
($(this)).parent().prev().children().each(function () { #应用对应的主机在多个span里
var hid = $(this).attr('hid'); #获取默认值,主机id
hid_list.push(hid) #增加
});
$('#edit_form').find('select').val(hid_list); #赋予默认值

3.获取修改值
data:$('#edit_form').serialize(), # 其中的host_list是一个多选,traditional=true

4.数据库更新
obj.r.set([list])

2018/04/30 周一 s21

知识点
URL
-
Views
- 请求的其他信息

- 装饰器

Templates
- 母版...html

- 自定义函数


分页(自定义的分页)

cookie

session :装饰器


Models
- 一大波操作

Form验证

①URL 默认值

url(r'^index/', views.index,{'name':'root'})
def index(request,name):

URL命名空间:
url(r'^a/',include('app01.urls',namespace='author')),
url(r'^index/', views.index,name="index"),
在视图函数中使用: reverse('author:index')

②两种提交方式

Form表单提交:
提交 -> url > 函数或类中的方法
- ....
HttpResponse('....')
render(request,'index.html')
redirect('/index/')
用户 < < 返回字符串
(当接受到redirect时)自动发起另外一个请求
--> url .....

Ajax:
$.ajax({
url: '/index/',
data: {'k': 'v', 'list': [1,2,3,4], 'k3': JSON.stringfy({'k1': 'v'}))}, $(form对象).serilize()
# 字典不能嵌套字典,只能序列化
type: 'POST',
dataType: 'JSON':
traditional: true,
success:function(d){
location.reload() # 刷新
location.href = "某个地址" # 跳转,只能在这跳转,不能在视图函数中redirect
}
})
提交 -> url -> 函数或类中的方法
HttpResponse('{}') 能返回内容更多
render(request, 'index.html', {'name': 'v1'}) 返回模板语言

如:<h1>{{ name }}</h1> --> <h1>v1</h1>

XXXXXXX redirect... 前端拿不到数据
用户 <<<<< 字符串
③视图函数
def func(request):
request.POST
request.GET
request.FILES
request.xxxx.getlist #获取多个值

request.body 包含post、file和其他发送方式发送的数据,而get发送的数据体现在url上

request.method 用于判断请求方法
request.path_info 请求网页

return render,HttpResponse,redirect

④模板
render(request, 'index.html')
# for
# if
# 索引. keys values items all(所有数据)

{{ item.m.all }} #模板语言中,执行方法不用加括号。

⑤models数据库表
class User(models.Model):
username = models.CharField(max_length=32)
email = models.EmailField()

有验证功能
Django Admin
无验证功能:
User.objects.create(username='root',email='asdfasdfasdfasdf')
User.objects.filter(id=1).update(email='666')


1).一对多
class UserType(models.Model):
name = models.CharField(max_length=32)


class User(models.Model):
username = models.CharField(max_length=32)
email = models.EmailField()
user_type = models.ForeignKey("UserType")

获取数据库数据
方式一
user_list = User.objects.all() 列表
for obj user_list:
obj.username,obj.email,obj.user_type_id,obj.user_type.name,obj.user_type.id
方式二
user = User.objects.get(id=1) 一个对象
user.
方式三:
User.objects.all().values("username","user_type__name",)

2).多对多
class UserType(models.Model):
name = models.CharField(max_length=32)


class User(models.Model):
username = models.CharField(max_length=32)
email = models.EmailField()
user_type = models.ForeignKey("UserType")
m = models.ManyToMany('UserGroup') #也能放置UserGroup表中,也可以第三张表ForeignKey两表


class UserGroup(models.Model):
name = ....


obj = User.objects.get(id=1) #一个User对象
obj.m.add(2) #多对多关系操作
obj.m.add(2,3)
obj.m.add(*[1,2,3]) #带*号

obj.m.remove(...)

obj.m.clear()


obj.m.set([1,2,3,4,5]) #不带*号

# 返回一个列表包含多个组,UserGroup对象
obj.m.all()
obj.m.filter(name='CTO') 返回一个对象

2.视图获取用户请求相关信息以及请求头
request.environ
request.environ['HTTP_USER_AGENT']

3.模板继承
{% extends 'xxx.html' %} 只能一个,要写在开头
{% block block_name %} 类似的还有:{% block js %}{% block css %}等
{% endblock %}

4.模板导入
{% include 'tag.html' %} 可以导入多个,不要写在开头


5.自定义simple_tag(任意参数,随意加空格,不能作if条件)

a. app下创建templatetags目录
b. 任意py文件,写一个func
c. 创建template对象register register = template.Library()
d. @register.simple_tag #装饰
def func(a1,a2) #参数可以多个
e. html中{% load xxoo %}
f. {% 函数名 arg1 arg2 %}

6.自定义filter(只能两个参数,不能加空格,能作if条件)

@register.filter
def kelvin(a1,a2)
return a1+a2

{{ 'czl'|kelvin:'ls' }} {{ 参数一|函数名:参数二 }} 可以传数字,并接时str()即可

优点:能放在if里面作条件
{% if 'czl'|kelvin:'ls' %}

{% endif %}

7.自定义分页
浏览器默认防御XSS攻击,所有的字符串直接显示,不作html格式编译,需要管道符声明一下
后台:mark_safe(page_str)
前端:{{ page_str|safe }}

all_count = len(LIST)
每页10个,总多少页:count,y = divmod(all_count,10)
if y:
count += 1

page_list = []
for i in range(1,1+count): #一共多少页
if i == current_page:
temp = '<a class="page active" href="/user_list/?p=%s">%s</a>'%(i,i)
else:
temp = '<a class="page" href="/user_list/?p=%s">%s</a>'%(i,i)
page_list.append(temp)

page_str = ''.join(page_list) 将列表拼接出来
page_str = mark_safe(page_str) 转化为安全的了

然后在前端即可:{{ page_str }}即可显示代码


函数化自定义功能(上一页、下一页、跳转)

用到的几个函数:int()

prev ='<a class="page" href="javascript:void(0)">上一页</a>' 这是不跳转,不跳转还能用#表示

next = '<a class="page" href="/user_list/?p=%s">下一页</a>' % (current_page + 1)


jump = """
<input type="text" /><a onclick='jumpto(this);'>GO</a> this表示当前点击的标签

<script>
function jumpto(ths){
var val = ths.previousSibling.value; 获取点击的<a>的上一个标签text
location.href = "/user_list/?p=" + val; 串接url
}
</script> #这个script还能放在html中,定义好功能
"""

Django内置分页
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger

有两个类Paginator,字段
# per_page: 每页显示条目数量
# count: 数据总个数
# num_pages:总页数
# page_range:总页数的索引范围,如: (1,10),(1,200)
# page: page对象
以及Page类
# has_next 是否有下一页
# next_page_number 下一页页码
# has_previous 是否有上一页
# previous_page_number 上一页页码
# object_list 分页之后的数据列表
# number 当前页
# paginator paginator对象

使用:paginator = Paginator(L, 10) #L是总数据,10是每页个数
posts = paginator.page(current_page)#post数据包含已切片数据,当前页等

return render(request, 'index.html', {'posts': posts})即可

Django内置分页扩展
class CustomPaginator(Paginator): #自定义一个类,继承Paginator类,即可扩展
def __init__(self,current_page,per_pager_num,*args,**kwargs):
self.current_page = int(current_page)
self.per_pager_num = int(per_pager_num)
super(CustomPaginator,self).__init__(*args,**kwargs) #这样初始化设置不会改变,方法不会受到影响

def pager_num_range(self):
#这里能用继承的类中init方法定义的self类字段,num_pages,per_pager_num,current_page三个参数
return range() #返回页码数的范围,前端循环输出每一个页码即可


函数封装
几个要点:
①将用到的变量添加到init中
def __init__(self,current_page,all_count,per_page_count=10,pager_num=7):
这样下面的函数引用时self.就行
②@property,放置在要不加括号的函数前面,使得函数self.total_count引用时不用加括号()
③将能提取的都提取出来,这样就做成一个模板,使用时include传参数即可
分页具体使用:
def user_list(request):
current_page = request.GET.get('p',1) #获取模板使用的参数
current_page = int(current_page) #转换整数
page_obj = paginations.Page(current_page,len(li)) #导入这个文件,传两个参数即可
data=li[page_obj.start:page_obj.end] #不用加括号了
page_str = page_obj.page_str("/user_list/") #传url参数
return render(request,'user_list.html',{'li':data,'page_str':page_str})

8.cookie
客户端浏览器上的一个文件,发送请求时带着(浏览器自动带)

字典形式{'username':'asdf'}

登陆时:
res = redirect('/index/')
res.ser_cookie('username',u) 设置cookie的一个属性为username,值为u

对应index视图函数中
v = request.COOKIES.get('username') 获取这个属性看有没有值
if not v:
return redirect('/login/') #没有cookie就会转到登陆页面
return render(request,'index.html',{'current_user':v})


9.基于cookie实现用户登陆

request.COOKIES
request.COOKIES['user']
request.COOKIES.get('')

response = redirect 返回内容
response = render

response.set_cookie('key','value') 要返回的时候设置cookie,放在响应头里
return response

key, 键
value='', 值
max_age=None, 超时时间
expires=None, 超时时间(IE requires expires, so set it if hasn't been already.)
import datatime #导入模块
current_data = datatime.datetime.utcnow() #当前时间
current_data = current_data + datatiem.timedelta(seconds=5) #延迟

path='/', Cookie生效的路径,/ 表示根路径,特殊的:跟路径的cookie可以被任何url的页面访问
domain=None, Cookie生效的域名
secure=False, https传输
httponly=False 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖)

10.基于cookie实现定制显示数据条数

function changePageSize(ths) {
var v = $(ths).val() #记得传进this
$.cookie('per_page_count',v) jQuery的cookie插件,使得能够这样设置cookie值
location.reload()
}

注:jQuery插件获取cookie值:$.cookie('key')
jQuery插件设置cookie值:$.cookie('key','v',{'path':''})

DOM设置cookie值:document.cookie="username='asdas'"
DOM获取cookie值:document.cookie 返回所有
比较麻烦,还有设置超时时间等参数

后台:
val = request.COOKIES.get('per_page_count') 视图中获取cookie赋值给函数


11.带签名的cookie

obj.set_signed_cookie('','',salt='')
request.get_signed_cookie('username',salt='')

12.CBV和FBV用户认证装饰器
CBV:
#装饰器
def auth(func):
def inner(request,*args,**kwargs): #装饰器需要一个inner函数
v = request.COOKIES.get('username')
if not v:
return redirect('/login/')
return func(request,*args,**kwargs)
return inner

@auth #在要应用的函数前面加上即可

FBV:
from django.utils.decorators import method_decorator

# @method_decorator(auth,name='dispatch') #方法三,name指明应用的函数,不用写dispatch方法
class Order(views.View):

@method_decorator(auth) #方法二,对get和post都加上
def dispatch(self, request, *args, **kwargs):
return super(Order, self).dispatch(request, *args, **kwargs)

# @method_decorator(auth) #方法一,在单个函数前应用
def get(self,request):
v = request.COOKIES.get('username')
# if not v:
# return redirect('/login/')
return render(request, 'index.html', {'user': v})

def post(self, request):
v = request.COOKIES.get('username')
return render(request, 'index.html', {'user': v})


2018/05/01 周二 s22

- Session
- CSRF
- Model操作
- Form验证(ModelForm)
- 中间件
- 缓存
- 信号


内容概要,复制过来的:

1. Session
基于Cookie做用户验证时:敏感信息不适合放在cookie中

a. Session原理
Cookie是保存在用户浏览器端的键值对
Session是保存在服务器端的键值对 {asd:{'':'',}}

步骤: 生成随机字符串,写到用户浏览器cookie,保存到session中,
然后在随机字符串对应的字典中设置相关内容。

获取时,获取当前浏览器的随机字符串,获取字符串对应的信息,进行匹配

b. Cookie和Session对比

依赖于cookie,

c. Session配置(缺少cache)

d. 示例:实现两周自动登陆
request.session['']=''

- request.session.set_expiry(60*10)
- SESSION_SAVE_EVERY_REQUEST = True

PS: cookie中不设置超时时间,则表示关闭浏览器自动清除


- session依赖于cookie
- 服务器session
request.session['k1'] #不存在会报错
request.session.get('k1',None)

request.session['k1'] = 123 #已存在值会覆盖掉,不存在会创建
request.session.setdefault('k1',123) # 存在则不设置

del request.session['k1']
request.session.delete("session_key")
request.session.clear() 先获取该用户随机字符串,再delete,如注销

- 配置文件中设置默认操作(通用配置):
SESSION_COOKIE_NAME = "sessionid" # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认)
SESSION_COOKIE_PATH = "/" # Session的cookie保存的路径(默认)
SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名(默认)
SESSION_COOKIE_SECURE = False # 是否Https传输cookie(默认)
SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http传输(默认)
SESSION_COOKIE_AGE = 1209600 # Session的cookie失效日期(2周)(默认)
SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否关闭浏览器使得Session过期(默认)
SESSION_SAVE_EVERY_REQUEST = False # 是否每次网页请求都保存Session,默认修改之后才保存(默认)
更新超时时间

- 引擎的配置

2. CSRF
a. CSRF原理
get请求时发送一段只能自己反解的字符串,下次post发送数据的时候要带着来,检查请求头,否则不受理

b. 无CSRF时存在隐患
避免其他网站提交数据过来

c. Form提交(CSRF)
{% csrf_token %},会生成隐藏的input标签带着字符串信息
d. Ajax提交(CSRF)
CSRF请求头 X-CSRFToken 全局默认的settings中会更新seetings.py配置,其中csrf有默认设置,字段为
HTTP_X_CSRFTOKEN='',HTTP_是django自动加的前缀,请求头不能包含下划线,
这是非法的,后台拿不到数据,X-CSRFTOKEN,官网推荐为X-CSRFToken

方式一:
$.ajax({
...
headers:{'X-CSRFToken':$.cookie('csrftoken')}
})
方式二:统一配置一次即可,发送前
$.ajaxSetup({
beforeSend:function(xhr,settings){ #xhr为xmlhttprequest对象
xhr.setRequestHeader('X-CSRFtoken',$.cookie('csrftoken')) #设置csrf
}
})

3. Model操作

a. 字段类型 + 参数

b. 连表字段 + 参数

c. Meta

d. SQL操作:
- 基本增删改查
- 进阶操作
- 正反查询
- 其他操作

e. 验证(弱)

4. Form操作
完成:
- 验证用户请求
- 生成HTML
(保留上一次提交的数据)

自定义:
- 类
- 字段(校验)
- 插件(生成HTML)

初始化操作:

a. Form验证用户请求

b. Form生成HTML

c. Form字段详细(自定义字段,Model...) + 插件

d. 自定义验证(钩子以及__all__)

e. 注册示例:
用户名、密码、邮箱、手机号(RegexValidator或RegexField)、性别、爱好、城市

f. 初始化值

5. ModelForm

a. Model+Form功能集合

b. save

c. save + save_m2m

6. 中间件

7. 缓存
5种配置
3种应用:
全局
视图函数
模板

8. 信号
- 内置信号
- 自定义
- 定义信号
- 出发信号
- 信号中注册函数

============= 作业:xxxoo管理 =============
用户验证:session
新URL:Form验证
中间件:IP过滤
信号:记录操作
CSRF:


本周内容开始

一. Django的url和views

URL
namespace等

views:
request.body #封装了post提交的所有数据
request.POST(Request.body) #源码中POST也是从body拿数据,没有request.PUT,只能自己从body提取
request.FILES(Request.body)
request.GET
request.XXX.getlist

request.Meta() #请求头的数据
request.method(POST,GET,PUT)
request.path_info
request.COOKIES

return HttpResponse #支持字节、字符串 如bytes('中国')
return render
return redirect


response = HttpResponse
response[‘name’]='alex' #可以自己写响应头,浏览器的响应头可以看到
response.set_cookie()
return response 放在响应头里,返回cookie

返回的内容: 返回内容 响应头(如cookie)

二.Model 操作

各种操作语句:增删改查

三。模板

继承、tags、simple_tag和filter

四。session

保存在服务器端的键值对(数据库、缓存、内存),依赖于cookie

session = {
asdasdasd:{....}, 每个用户一个字典,保存信息
asdasdasd:{....},
}

原理:生成随机字符串,写到用户浏览器cookie,再保存到session中,在随机字符串对应的字典设置内容。
获取时,获取session键值对即可。
生成session
request.session['username'] = u
request.session['is_login'] = True

获取session
if request.session['is_login']:

===================session的使用=============================

# 获取、设置、删除Session中数据
request.session['k1'] #不存在会报错
request.session.get('k1',None)
request.session['k1'] = 123 #已存在值会覆盖掉,不存在会创建
request.session.setdefault('k1',123) # 存在则不设置
del request.session['k1']

# 所有 键、值、键值对 ,类似字典操作
request.session.keys()
request.session.values()
request.session.items()
request.session.iterkeys()
request.session.itervalues()
request.session.iteritems()


# 用户session的随机字符串
request.session.session_key

# 将所有Session失效日期小于当前日期的数据删除
request.session.clear_expired()

# 检查 用户session的随机字符串 在数据库中是否
request.session.exists("session_key")

# 删除当前用户的所有Session数据
request.session.delete("session_key")
request.session.clear() 先获取该用户随机字符串,再delete,如注销

request.session.set_expiry(value)
* 如果value是个整数,session会在些秒数后失效。
* 如果value是个datatime或timedelta,session就会在这个时间后失效。
* 如果value是0,用户关闭浏览器session就会失效。
* 如果value是None,session会依赖全局session失效策略。


============session的配置=======================

SESSION_ENGINE = 'django.contrib.sessions.backends.db' # 引擎(默认)

SESSION_COOKIE_NAME = "sessionid" # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认)
SESSION_COOKIE_PATH = "/" # Session的cookie保存的路径(默认)
SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名(默认)
SESSION_COOKIE_SECURE = False # 是否Https传输cookie(默认)
SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http传输(默认)
SESSION_COOKIE_AGE = 1209600 # Session的cookie失效日期(2周)(默认)
SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否关闭浏览器使得Session过期(默认)
SESSION_SAVE_EVERY_REQUEST = False # 是否每次请求都保存Session,默认修改之后才保存(默认)
# 在已登录状态访问页面中时,session会一直更新过期时间

能存放session的地方

数据库(默认)
缓存
文件
缓存+数据库
加密cookie

五。CSRF原理

get服务器给一个信息,post时没有这个信息不能提交
放在在别的网站能向其他网站post数据

在设置{% csrf_token %}的时候,post时会生成隐藏的标签,并且cookie会生成相应的字符串,
form表单提交会提交这个数据
这样ajax提交的时候,获取这个值到请求头,发过去即可

$('#btn').click(function () {
$.ajax({
url:'/login/',
type:"POST",
data:{'user':'root','pwd':'123'},
headers:{'X-CSRFtoken':$.cookie('csrftoken')}, #设置请求头的csrftoken值
success:function (args) {
}
})
})


或者统一配置:
$.ajaxSetup({
beforeSend:function (xhr,settings){ #在发送前的设置,xhr是ajax语言的根本
xhr.setRequestHeader('X-CSRFtoken',$.cookie('csrftoken')) #设置csrf
}
})


例外情况:装饰器
@csrf_protect,为当前函数强制设置防跨站请求伪造功能,即便settings中没有设置全局中间件。
@csrf_exempt,取消当前函数防跨站请求伪造功能,即便settings中设置了全局中间件。


对于ajax请求中,统一配置的时候,如果get方式提交,不用设置csrf了的时候,可以判断type的类型,不作settings

function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});

六。中间件

请求---> 中间件request--->url--->views-->
请求<---中间件response<---response <----

如果某一中间件不通过了,从该中间件开始返回response。

应用:黑名单过滤

①from django.utils.deprecation import MiddlewareMixin

②class Row(MiddlewareMixin):
def process_request(self,request):
print('') #如果该处不通过条件,从该处开始返回response

def process_view(self, request, callback, callback_args, callback_kwargs)
print()
callback指url匹配的视图函数,args指参数,kwargs指带name的参数

def process_response(self,request,response):
print('')
return response

def process_exception(self, request, exception):
if isinstance(exception,ValueError):
return HttpResponse()

在settings.py中'文件夹.文件名.Row'写入中间件的路径

process_request(self,request)
process_view(self, request, callback, callback_args, callback_kwargs) #执行完request后从头执行
process_template_response(self,request,response) #如果view函数中返回的对象中包含render方法
process_exception(self, request, exception) 视图函数执行出错时执行,从末尾开始,如果都没有会报错
process_response(self, request, response)

如 class Foo():
def render(self):
return HttpResponse():
def test(request):
return Foo() 就会调用process_template_response方法


有process_view版
请求---> 中间件request(顺序)--->url(路由匹配)--->获取views函数以及参数-->
请求<---中间件response(倒序)<---拿到视图response <----执行中间件view(顺序)<--

有process_exception版
请求---> 中间件request(顺序)--->url(路由匹配)--->获取views函数以及参数-->
请求<---倒序process_exception执行<---视图函数出错<----执行中间件view(顺序)<--

有process_template_response版
请求---> 中间件request(顺序)--->url(路由匹配)--->获取views函数以及参数-->
请求<---中间件response(倒序)<---拿到视图response<--执行process_template_response<--执行中间件view(顺序)<--


七. 缓存

位置:数据库、文件、内存,适合一些不常变动的数据,超时再重新拿一份放缓存

缓存方式:key-value,生成key的方式:
'KEY_PREFIX': '', # 缓存key的前缀(默认空)
'VERSION': 1, # 缓存key的版本(默认1)
'KEY_FUNCTION' 函数名 # 生成key的函数(默认函数会生成为:【前缀:版本:key】)

如生成一个key为p1:1:c1

开发调试(什么都不做)
内存
文件
数据库
Memcache缓存(python-memcached模块)
Memcache缓存(pylibmc模块) #什么模块取决于使用的模块是哪个,多个机器时按(,1)(,2)分配
将key转数字除3,余数为0放第一,1和2放第二个机器

开发调试配置:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.dummy.DummyCache', # 引擎
'TIMEOUT': 300, # 缓存超时时间(默认300,None表示永不过期,0表示立即过期)

'OPTIONS':{
'MAX_ENTRIES': 300, # 最大缓存个数(默认300)
'CULL_FREQUENCY': 3, # 缓存到达最大个数之后,剔除缓存个数的比例,即:1/CULL_FREQUENCY(默认3)
},
'KEY_PREFIX': '', # 缓存key的前缀(默认空)
'VERSION': 1, # 缓存key的版本(默认1)
'KEY_FUNCTION' 函数名 # 生成key的函数(默认函数会生成为:【前缀:版本:key】),可以修改:为-
}
}

应用:三个级别
①视图函数级别缓存
from django.views.decorators.cache import cache_page

@cache_page(60 * 15)
def my_view(request):
ctime = time.time()
return的内容就会先从缓存里面找

②局部缓存,比如商品描述可以缓存,商品个数就不缓存
a. 引入TemplateTag

{% load cache %}

b. 使用缓存

{% cache 5000 缓存key %} #5000是超时时间s,存在缓存位置的key
缓存内容
{% endcache %}

③全站缓存(所有的函数和请求)

MIDDLEWARE = [
'django.middleware.cache.UpdateCacheMiddleware', #这个中间件只有response,不处理request
# 其他中间件...
'django.middleware.cache.FetchFromCacheMiddleware',#这个中间件只有request,走的时候不管事
]

FetchFromCacheMiddleware放在最后,通过其他csrf认证后才给,这个中间件只有request

UpdateCacheMiddleware放在第一,中间的其他中间件可能会response额外内容,这个中间件只有response

流程: 使用中间件,经过一系列的认证等操作,
如果内容在缓存中存在,则使用FetchFromCacheMiddleware获取内容并返回给用户,
当缓存中不存在,视图函数处理返回内容,
同时UpdateCacheMiddleware会将缓存保存至缓存,从而实现全站缓存

缓存顺序:全站的中间件有就直接返回了 顺序:全站>函数>模板

八. 信号

如何在每次保存数据前后作一个操作

方法一:在views函数前加装饰器,但这样只能统一作操作,不能细化

方法二:在save()方法里面加装饰器,违反了不能修改源码的原则

方法三:django为每步操作前后留了钩子(位置),在创建数据前后也一样

原理:执行操作的时候会触发相应的信号,比如post_save,里面注册了好多函数,save时触发信号,顺序执行这些函数
我们只要找到该信号,往里面注册自定义的函数即可。

Model signals
pre_init # django的modal执行其构造方法前,自动触发
post_init # django的modal执行其构造方法后,自动触发
pre_save # django的modal对象保存前,自动触发
post_save # django的modal对象保存后,自动触发
pre_delete # django的modal对象删除前,自动触发
post_delete # django的modal对象删除后,自动触发
m2m_changed # django的modal中使用m2m字段操作第三张表(add,remove,clear)前后,自动触发
class_prepared # 程序启动时,检测已注册的app中modal类,对于每一个类,自动触发
Management signals
pre_migrate # 执行migrate命令前,自动触发
post_migrate # 执行migrate命令后,自动触发
Request/response signals
request_started # 请求到来前,自动触发,在中间件前
request_finished # 请求结束后,自动触发
got_request_exception # 请求异常后,自动触发
Test signals
setting_changed # 使用test测试修改配置文件时,自动触发
template_rendered # 使用test测试渲染模板时,自动触发
Database Wrappers
connection_created # 创建数据库连接时,自动触发


步骤:
①导入from django.db.models.signals import pre_init, post_init等

②自定义函数
def callback(sender, **kwargs): sender:源码发送的触发的所有信号,可以作相关操作
print("xxoo_callback")
print(sender,kwargs)

③xxoo.connect(callback) 加入到内置信号

④有多个时,顺序执行 xxoo.connect(f1) xxoo.connect(f2)

⑤注册.py文件在settings所在文件夹的init.py中import

⑥接下来,在创建数据库对象的时候就能send触发信号,触发callback函数

自定义信号

a. 定义信号
import django.dispatch
pizza_done(信号名) = django.dispatch.Signal(providing_args=["toppings", "size"])

b. 注册信号
def callback(sender, **kwargs):
print("callback")
print(sender,kwargs)
pizza_done.connect(callback)

c. 触发信号
from 路径 import pizza_done
pizza_done.send(sender='seven',toppings=123, size=456) #这样就触发了该信号

最后,自己触发这个自定义信号,执行注册函数,一般设置特定条件下触发该信号

应用:如公司服务器特定条件下发短信,现在改微信的话,只需保持源码不动,改一下callback函数
为微信接口,实现特定条件下触发信号,执行注册函数。

要点:实现了解耦。


九. Form组件认证

功能: ①作输入验证 (字段本身只做验证)(验证没写,继承自Form的baseform的is_valid方法)
②生成输入框的同时,保留输入的内容,
post方式访问时传了post数据,CharField内部的widgts生成html标签。

提交的input框数据可能很多,但Form只验证类中字段名与input框name相同的数据

步骤:类--字段(校验)--插件(生成HTML)


from django import forms 先导入

class FM(forms.Form):
user = forms.CharField(error_messages={'required':'用户名不能为空'})
pwd = forms.CharField(min_length=6,max_length=12,error_messages={'required':'用户名不能为空','min_length':'长度不能小于6'})
email = forms.EmailField(error_messages={'invalid':'格式错误','required':'用户名不能为空'})

//user_type = fields.ChoiceField(choices=models.UserType.objects.values_list('id','caption')) #数据需要更新
user_type = fields.ChoiceField()

def __init__(self,*args,**kwargs):
# super会拷贝所有的静态字段user等,赋值给self.fields
super(FM,self).__init__(*args,**kwargs)
self.fields['user_type'].choices = models.UserType.objects.values_list('id','caption')

#每次调用创建对象时更新,如果不init,在编译FM类时,每个字段已经写到内存,数据已经加载完,调用时只调用编译那会的数据
#这样的话第一次页面加载时,可以不添加choices,在init里写,创建对象时被引用即可

def fm(request):
if request.method == 'GET':
obj = FM() #传一个空对象进去,这时的input框没有值
return render(request,'fm.html',{'obj':obj}) #传值
elif request.method == 'POST':
obj = FM(request.POST) #把post数据传进去,这时的input框有输入的值

r = obj.is_valid() #验证数据是否成功
if r:
# print(obj.cleaned_data) #成功信息
models.UserInf.objects.create(**obj.cleaned_data) #字典加**即可创建,要求Form的字段与models中相同
else:
# print(obj.errors) #ErrorDict对象,继承了dict,<ul><li>user<ul><li>具体错误信息</li>的形式,
#obj.errors.as_json()是一个字典,每个key对应字段,默认是as_ul()
{'key1':[{'message':'ddd','code':'required'},...],'key2':}

return render(request,'fm.html',{'obj':obj}) #Form提交方式,如果Ajax提交需要把obj.errors赋值给data

在HTML中:
<form novalidate> #声明novalidate之后,obj.errors即可正常显示,而且自动显示在errors标签中
<p>{{ obj.user }}{{ obj.errors.user.0 }}</p> #ErrorDict对象,通过字典方式去拿,拿到user对应的错误信息列表的第一个
<p>{{ obj.pwd }}{{ obj.errors.pwd.0 }}</p>
<p>{{ obj.email }}{{ obj.errors.email.0 }}</p>
</form>
自定制包含标签:
{{ obj.as_p }} 生成标签P包含输入框,不建议这样,不能自定制

{{ obj.as_ul }} 生成列表

<table>{{ obj.as_table }}</table> 生成table,需要前后加标签


Form扩展--定制样式:

from django.forms import widgets
from django.forms import fields #以后字段都在这个fields里面了

user = fields.CharField( #用field,不用forms
error_messages={'required':'用户名不能为空'},
widget= widgets.Textarea(attrs={'class':'c1'}) #widgets是插件,包含所有的标签
如果要附加属性,在括号内加即可。不加样式括号不用加
)

Field
required=True, 是否允许为空
widget=None, HTML插件,Field通过__str__\format方法并接字符串来生成html标签,widget修改input的type
label=None, 用于生成Label标签或显示内容 {{ obj.user.label }}
initial=None, 初始值
help_text='', 帮助信息(在标签旁边显示)
error_messages=None, 错误信息 {'required': '不能为空', 'invalid': '格式错误'}
show_hidden_initial=False, 是否在当前插件后面再加一个隐藏的且具有默认值的插件(可用于检验两次输入是否一致)
validators=[], 自定义验证规则,如[RegexValidator(r'^[0-9]+$', '请输入数字')]
localize=False, 是否支持本地化,如本地时间
disabled=False, 是否可以编辑
label_suffix=None Label内容后缀

CharField(Field)
max_length=None, 最大长度
min_length=None, 最小长度
strip=True 是否移除用户输入空白

IntegerField(Field)
max_value=None, 最大值
min_value=None, 最小值

FloatField(IntegerField)
...

DecimalField(IntegerField)
max_value=None, 最大值
min_value=None, 最小值
max_digits=None, 总长度
decimal_places=None, 小数位长度

BaseTemporalField(Field)
input_formats=None 时间格式化

DateField(BaseTemporalField) 格式:2015-09-01
TimeField(BaseTemporalField) 格式:11:12
DateTimeField(BaseTemporalField)格式:2015-09-01 11:12

DurationField(Field) 时间间隔:%d %H:%M:%S.%f
...

RegexField(CharField) #类似CharField+validators
regex, 自定制正则表达式
max_length=None, 最大长度
min_length=None, 最小长度
error_message=None, 忽略,错误信息使用 error_messages={'invalid': '...'}

EmailField(CharField)
...

FileField(Field) #文件的chunks在cleaned_data字典里,字段名f就是字典的key,value是文件对象
allow_empty_file=False 是否允许空文件 #与input type=file一样,只不过多了内置验证

ImageField(FileField)
...
注:需要PIL模块,pip3 install Pillow
以上两个字典使用时,需要注意两点:
- form表单中 enctype="multipart/form-data"
- view函数中 obj = MyForm(request.POST, request.FILES)

URLField(Field)
...


BooleanField(Field)
...

NullBooleanField(BooleanField) #可以为空
...

****ChoiceField(Field) #相当于CharField(widget=widgets.RadioSelect(choices=[(),()]))
...
choices=(), 选项,如:choices = ((0,'上海'),(1,'北京'),),列表[]也可以
required=True, 是否必填
widget=None, 插件,默认select插件
label=None, Label内容
initial=None, 初始值,数字
help_text='', 帮助提示


ModelChoiceField(ChoiceField)
... django.forms.models.ModelChoiceField
queryset, # 查询数据库中的数据 models.User.objects.all(),html会显示object,__str__即可
empty_label="---------", # 默认空显示内容
to_field_name=None, # HTML中value的值对应的字段,比如username,value就会显示数据库中对应的username
limit_choices_to=None # ModelForm中对queryset二次筛选

ModelMultipleChoiceField(ModelChoiceField)
... django.forms.models.ModelMultipleChoiceField



TypedChoiceField(ChoiceField)
coerce = lambda val: val 对选中的值进行一次转换,默认ChoiceField返回字符串,这个可以转换lambda x:int(x)
empty_value= '' 空值的默认值

MultipleChoiceField(ChoiceField)
...
choices=[],
initial=[1,2,3] #多选

TypedMultipleChoiceField(MultipleChoiceField)
coerce = lambda val: val 对选中的每一个值进行一次转换
empty_value= '' 空值的默认值

ComboField(Field) #包含其他field在括号里
fields=() 使用多个验证,如下:即验证最大长度20,又验证邮箱格式
fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),])

MultiValueField(Field)
PS: 抽象类,子类中可以实现聚合多个字典去匹配一个值,要配合MultiWidget使用

SplitDateTimeField(MultiValueField) #使得一个字段可以对应多个input框
input_date_formats=None, 格式列表:['%Y--%m--%d', '%m%d/%Y', '%m/%d/%y']
input_time_formats=None 格式列表:['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']

FilePathField(ChoiceField) 文件选项,目录下文件显示在页面中,把一个路径下所有文件列出来
path, 文件夹路径
match=None, 正则匹配
recursive=False, 递归下面的文件夹
allow_files=True, 允许文件
allow_folders=False, 允许文件夹
required=True,
widget=None,
label=None,
initial=None,
help_text=''

GenericIPAddressField #与models中相同
protocol='both', both,ipv4,ipv6支持的IP格式
unpack_ipv4=False 解析ipv4地址,如果是::ffff:192.0.2.1时候,可解析为192.0.2.1, PS:protocol必须为both才能启用

SlugField(CharField) 数字,字母,下划线,减号(连字符)(只支持)
...

UUIDField(CharField) uuid类型

总结:单个值时,用CharField、IntegerField等,如果不满足,自定义正则表达式,
而选择,不管单选还是多选radio\select\checkbox,都可以用ChoiceField

常用选择插件:
单radio,值为字符串
user = fields.CharField(
initial=2,
widget=widgets.RadioSelect(choices=((1,'上海'),(2,'北京'),)) #CharField本身没有choices,得用widget
)

单radio,值为字符串
user = fields.ChoiceField(
choices=((1, '上海'), (2, '北京'),),
initial=2,
widget=widgets.RadioSelect
)

单select,值为字符串
user = fields.CharField(
initial=2,
widget=widgets.Select(choices=[(1,'上海'),(2,'北京'),])
)

单select,值为字符串
user = fields.ChoiceField(
choices=((1, '上海'), (2, '北京'),),
initial=2,
widget=widgets.Select
)

多选select,值为列表 #多选只能choiceField,因为charField只允许字符串,单选一个是字符串
user = fields.MultipleChoiceField(
choices=((1,'上海'),(2,'北京'),),
initial=[1,],
widget=widgets.SelectMultiple
)


单checkbox
user = fields.CharField(
widget=widgets.CheckboxInput()
)


多选checkbox,值为列表
user = fields.MultipleChoiceField(
initial=[2, ],
choices=((1, '上海'), (2, '北京'),),
widget=widgets.CheckboxSelectMultiple
)

初始化操作:

dic = {
'user':'root',
'pwd':'123123',
'email':'123',
'city':1,
'city2':[1,2],
}
obj = FM(initial=dic) #输入字典,即可实现类似编辑时设置默认值的情况

Form组件扩展:
1.简单扩展
利用Form组件自带的正则扩展
class MyForm(Form):
user = fields.CharField(
validators=[RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头')],
)
或者c = fields.RegexField(regex=r'^[0-9]+$'),只能一个正则表达式

2.基于源码扩展钩子
a.源码_clean_fields()中
for name, field in self.fields.items(): #循环每一个要验证的字段
... #验证表达式,返回值给cleaned_data
if hasattr(self, 'clean_%s' % name): #判断是否有clean_字段的方法
value = getattr(self, 'clean_%s' % name)() #执行clean_字段名()方法,需要返回值
self.cleaned_data[name] = value #赋值

定义的Form类中
def clean_username(self):
v = cleaned_data['username']
if models.UserInfo.objects.filter(username=v).count():
from django.core.exceptions import ValidationError
raise ValidationError('用户名已存在') #会被上一级的try捕捉到
return v
b.源码_clean_form()中
def _clean_form(self):
try:
cleaned_data = self.clean() #执行clean方法
except ValidationError as e: #捕捉异常
self.add_error(None, e) #None对应的ErrorDict的key为__all__,整体错误信息
else:
if cleaned_data is not None:
self.cleaned_data = cleaned_data
自定义clean方法
def clean(self):
value_dict = self.cleaned_data
v1 = value_dict.get('username')
v2 = value_dict.get('user_id')
if v1 == ss and v2 == asd:
raise ValidationError('整体错误信息') #字典key是__all__,但前端不能obj.errors.__all__
return self.cleaned_data #不改变

注:整体错误信息在前端显示为{{ obj.non_field_errors }} 源码中NON_FIELD_ERRORS = __all__

c.源码_post_clean
默认pass,如果自定制需要捕捉异常,否则程序报错


2018/05/03 周四 s24

Model的一大波操作
- 创建数据库表 多对多、一对多、one to one之间的关系以及参数
- 操作数据库表

基本操作:.all .distinct .count

进阶性能相关:select_related #跨表查询时用到,把跨表数据全拿过来,后面.操作的时候不再额外发sql查询,
只能加连表的字段,这是一次查询,放到内存中待用
models.xx.objects.all().select_related(f_key)

prefitch_related #多个表相连时用select_related性能会降低,它则是第一次查询的时候进行多次
查询,先把A表所有数据拿到,再去B表条件查询,id in A 表的f_key,比如
主机和业务线,可能所有的主机才2个业务线。
models.xx.objects.all().prefitch_related(f_key)

- 验证功能:只有一个clean方法预留了钩子

Form的一大波操作
-验证:字段的is_valid方法,通过cleaned_data返回字典,errors返回错误信息
- Form提交:render传一个对象obj,包含cleaned_data字典,errors即ErrorDict
- Ajax提交:验证正确了cleaned_data是一个字典,可以传,但错误信息ErrorDict不是字典,是一个django对象,
不能用python的json.dumps序列化。
- 可以as_json化为字典,再as_json化为字符串HttpResponse给浏览器
- 通过json.dumps定制一个Clis,进行特殊对象的序列化,什么类型取什么值,构造出json对象,返回需要的值

-预留钩子:验证数据是否符合正则表达式的同时,验证其他东西,比如用户名是否已存在。

1.多个字段进行验证时,先每一个进行正则表达式验证 + clean_字段名方法,这个方法能自定制函数,返回data

- 即执行is_valid()方法->errors()方法->full_clean()方法->_clean_fields()方法
这个方法会返回self.cleaned_data[name] = value 或者 self.add_error(name, e)
返回的数据从这个方法产生,而下面两个方法是钩子

from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator

class FInfo(forms.Form):
username = fields.CharField(max_length=5,
validators=[RegexValidator(r'^[0-9]+$', 'Enter a valid extension.', 'invalid')], )
email = fields.EmailField()

def clean_username(self): #格式:clean_字段名方法
#Form中字段中定义的格式匹配完之后,执行此方法进行验证
value = self.cleaned_data['username']
if "666" in value: #自定制操作
raise ValidationError('666已经被玩烂了...', 'invalid')
return value #返回给cleaned_data[name]

2.再整体验证,比如用户名和密码对不对,有clean(__all__)和_post_clean()两个钩子可以定制整体的操作

- full_clean()方法->_clean_form()方法,
- 调用clean()方法,clean方法是个钩子,可以自定制需要的函数,返回cleaned_data
- except ValidationError as e:self.add_error(None, e)

- full_clean()方法->_post_clean()方法,
默认pass,这也是一个钩子



流程:obj=FM(request.post)创建一个Form对象,先if obj.is_valid()触发baseform内部的is_valid()方法,这个方法return会
调用errors方法,errors的return再调用full_clean()方法,创建和赋值全局变量,经过这个方法is_valid()return了值。

full_clean()方法挨个执行
- self._clean_fields() #赋值全局变量self.cleaned_data或者self.add_error,进行每个字段的验证
- self._clean_form() #钩子,自定制的操作可能会重新赋值这两个全局变量
- self._post_clean() #也是钩子,默认pass

总结:is_valid()->每一个字段进行正则+clean_字段名方法 ->clean(__all__)-> _post_clean()


无论Form提交还是Ajax提交,都能验证,只不过ajax提交的话From生成标签的功能就没意义了,而且Form提交的时候还能保留
上一次输入的数据。


一. ModelForm

Model + Form 验证+数据库操作

1.缺点:耦合程度强,依赖于model里面的类,适合小程序,如定制django的admin
优点:model和form都要写一遍字段,modelform直接利用model的字段

2.写法:
class UserModelForm(forms.ModelForm):

class Meta:
model = models.User 通过User定位里面的字段生成标签,label名、错误信息等都是models定义的
fields = '__all__' 展示的字段,all表示全部,还可以是['username'],还能exclude=['']

meta内的每个字段是一行数据,两行数据之间不要逗号

也可以生成html标签,视图函数中验证数据的方法和Form组件一模一样

3.校验功能:(与Form一致)

Form:UserForm --> Form --> BaseForm(is_valid)

ModelForm: UserModelForm --> ModelForm --> BaseModelForm-->BaseForm(is_valid) #与Form相同的方法

if request.method == 'GET':
obj = UserModelForm()
return render(request,'index.html',{'obj':obj})

elif request.method == 'POST':
obj = UserModelForm(request.POST) #错误时,显示上一次已输入值,这个和Form组件一样
print(obj.is_valid()) #is_valid() 要加括号
print(obj.cleaned_data) #返回数据,usertype是一个对象
print(obj.errors)
return render(request,'index.html',{'obj':obj})

4.具体参数:
class Meta:
model, # 对应Model的
fields=None, # 字段
exclude=None, # 排除字段
labels=None, # 提示信息,一个字典
help_texts=None, # 帮助提示信息
widgets=None, # 自定义插件,需要导入forms的widgets as Fwidgets(别名),字典
error_messages=None, # 自定义错误信息,字典嵌套字典形式
error_messages={‘username’:{'required':'',}}
整体错误信息from django.core.exceptions import NON_FIELD_ERRORS
‘__all__’:{ } 定义整体

field_classes=None # 自定义字段类 (也可以自定义字段)
from django.forms import fields as Ffields
field_classes = {'email':Ffields.URLField},fields与Meta中冲突
只能填类,加括号就是对象了,所以URLField没有括号

localized_fields=('ctime',) # 本地化,如:这样显示的是本地东八区的时间
如:
数据库中
2016-12-27 04:10:57
setting中的配置
TIME_ZONE = 'Asia/Shanghai'
USE_TZ = True
则显示:
2016-12-27 12:10:57
5.外键值的显示:
model创建的表中,一对多时
user_type = models.ForeignKey(to='',to_field='')
直接用ModelForm创建标签时,页面选择框显示的是一个个UserType object对象
解决:在model中写__str__

多对多类似,在创建标签时,也是一个个UserGroup object


6.保存(生成html标签其实没省什么功夫,要声明meta,新增数据时最省)
if obj.is_valid():
obj.save() #保存对象和mangTomany


拆开对象和关系
instance = obj.save(False)
instance.save() #仅保存对象,instance指当前model对象
obj.save_m2m #保存manytomany

7.编辑例子:
li = models.UserInfo.objects.all().select_related('user_type')
先取出userinfo表数据,赋值给li
再取出与userinfo关联的外键表的所有数据放到内存,不支持manytomany,括号中写外键字段

{% for row in li %}
<li>{{ row.username }}-{{ row.user_type.caption }}-<a href=''>编辑</a></li>
{% endfor %}

点击编辑跳转新页面:
获取当前id对应的用户信息
信息放到input框中


显示默认输入值

def edit(request,nid):
if request.method == 'GET':
user_obj = models.UserInfo.objects.filter(id=nid).first() #取出信息
mf = UserModelForm(instance=user_obj) #传默认值
return render(request, 'edit.html', {'mf': mf,'nid':nid})

elif request.method == 'POST':
user_obj = models.UserInfo.objects.filter(id=nid).first()
mf = UserModelForm(request.POST,instance=user_obj) #instance用于更新,如果没有,则新增
if mf.is_valid():
mf.save()
else:
print(mf.error.as_json())
return render(request, 'edit.html', {'mf': mf, 'nid': nid})

8.钩子:(和Form一模一样)
继承来源一样,钩子也一样。
is_valid里面包含正则+clean_字段名方法,还有clean()和_post_clean()

def clean_username(self):
old = self.cleaned_data['username'] #拿到通过了定义的正则表达式验证的数据
......
return old #必须返回cleaned_data,否则view拿不到

9.额外定义的字段
还能在class ModelForm自定义指定model外的字段,以Form的field形式,可以用于免登陆等,记录value值后用session和cookie即可
is_rmb = Ffields.CharField(
widget=Fwidgets.CheckboxInput()
)
class Meta:
model =
....

总结: 1.生成标签:class meta定制
2.默认值,传instance
3.额外的标签
4.各种验证 is_valid--先每个字段的正则表达式,clean_字段名方法---clean---_post_clean()各种钩子
5.保存,save,还能拆开,先save(false),获取到对象instance、.save_m2m()

缺点:一键保存看出了耦合


2. Ajax


原生
jQuery #内部调用原生
伪Ajax操作

******XmlHttpRequest对象的主要方法:(原生ajax)

a. void open(String method,String url,Boolen async)
用于创建请求

参数:
method: 请求方式(字符串类型),如:POST、GET、DELETE...
url: 要请求的地址(字符串类型)
async: 是否异步(布尔类型) #异步发,不影响任何效果

b. void send(String body) #对比jQuery的data:
用于发送请求

参数:
body: 要发送的数据(字符串类型)

c. void setRequestHeader(String header,String value)
用于设置请求头

参数:
header: 请求头的key(字符串类型)
vlaue: 请求头的value(字符串类型)

d. String getAllResponseHeaders()
获取所有响应头

返回值:
响应头数据(字符串类型)

e. String getResponseHeader(String header)
获取响应头中指定header的值

参数:
header: 响应头的key(字符串类型)

返回值:
响应头中指定的header对应的值

f. void abort()

终止请求

主要属性,如xhr.xxx来使用,不用加括号
a. Number readyState
状态值(整数)

详细:
0-未初始化,尚未调用open()方法,只创建了xml对象;
1-启动,调用了open()方法,未调用send()方法;
2-发送,已经调用了send()方法,未接收到响应;
3-接收,已经接收到部分响应数据;
4-完成,已经接收到全部响应数据;

b. Function onreadystatechange
当readyState的值改变时自动触发执行其对应的函数(回调函数)

这是一个属性,写在open前也可以,只要在创建xhr之后即可

c. String responseText
服务器返回的数据(字符串类型)

d. XmlDocument responseXML
服务器返回的数据(Xml对象)

e. Number states
状态码(整数),如:200、404... 如return HttpResponse(json.dumps(ret),status=404,reason='NOT FOUND')

f. String statesText
状态文本(字符串),如:OK、NotFound...

示例:

function Ajax1() {
var xhr = getXHR(); #一般创建写法var xhr = new XMLHttpRequest()
xhr.open('POST','/ajax_json/',true);
xhr.onreadystatechange = function(){ #状态码改变时自动执行
if (xhr.readyState == 4){ #状态码
var obj = JSON.parse(xhr.responseText); #转化为json对象,可以理解为字典
console.log(obj);
}
};
xhr.setRequestHeader('k1','v1'); #设置请求头,如csrf
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset-UTF-8');
#POST必须按照格式设置文件解析请求头,GET不用设置直接发
xhr.send('name=root;pwd=123'); #发送数据

浏览器兼容问题:
function getXHR(){
var xhr = null;
if(XMLHttpRequest){
xhr = new XMLHttpRequest();
}else{
xhr = new ActiveXObject("Microsoft.XMLHTTP");
}
return xhr;

}

伪Ajax:

利用iframe框改src时框跳转,但大页面不刷新,实现不刷新发送请求,而普通的form表单会刷新

<input type="text" id="url"/>
<input type="button" value="发送" onclick="ifm_request();"/>
<iframe id="ifm" src="http://www.baidu.com"></iframe> #跳转网页

function ifm_request() {
var url = $('#url').val();
// console.log(url)
$('#ifm').attr('src',url);
}


因此,利用iframe实现伪ajax方法,不依赖任何插件

<form action="/ajax_json/" method="post" target="ifm1"> #target属性让form和iframe建立关系,表单数据通过iframe发送
<iframe id='ifm1' name="ifm1"></iframe> #iframe框
<input type="text" name="username" />
<input type="text" name="email" />
<input type="submit" onclick="submitForm();" value="提交"/>
</form>

写一个js,当点击提交时,获取发送的数据:

function submitForm() {
$('#ifm1').load(function () {
var text = $('#ifm1').contents().find('body').text(); #contents是整个html,找到body标签
var obj = JSON.parse(text); #text是字符串,需要转为字典
})
}

iframe标签包含的是document对象,相当于上下文,或者空间管理,嵌套了html,innerText、children等无法使用
对象里面有html标签-header\body标签,body标签的文本即返回的字符串
.contents拿到html对象


form提交后,要等到服务器返回数据时iframe框才接收到数据,同时触发onload事件,在jQuery为$().load()


==========ajax操作时机=========================

如果发送的是普通的数据、字符串 ------> jQuery,XMLHttpRequest,iframe
如果发送的是文件 ------> iframe,jQuery(FormData),XMLHttpRequest(FormData)

3. 文件上传

-Form上上传
-ajax(3种) #悄悄上传、图片预览

上传标签的样式:定义一个按钮,和input file标签相同大小,file在上层,透明度=0即可

<style>
.upload{
display: inline-block;padding: 10px;
background-color: brown;
position: absolute;
top: 0;
right: 0;
left: 0;
z-index: 90;
}
.file{
display: inline-block;padding: 10px;
opacity: 0;
background-color: brown;
position: absolute;
top: 0;
right: 0;
left: 0;
z-index: 100;
}
</style>

<div style="position: relative;width: 100px;height: 50px;">
<input class="file" type="file" id="fafafa" name="fafafa" />
<a class="upload">上传</a>
</div>

①原生xhr操作
function xhrSubmit() {
//$('#fafafa')[0] #$('')取得的是一个jQuery对象。而innerHTML是一个DOM属性。
var obj = document.getElementById('fafafa').files[0]; #name相同的文件可能有多个,获取第一个

var fd = new FormData(); #依赖这个,Form表单对象
fd.append('username','root'); #添加普通数据
fd.append('fafafa',obj); #添加文件数据也是可以的

var xhr = new XMLHttpRequest();
xhr.open('POST','/upload_file/',true);
xhr.onreadystatechange = function(){
if (xhr.readyState == 4){
var obj = JSON.parse(xhr.responseText);
console.log(obj);
}
};
xhr.send(fd); #发送
}

视图函数中
username = request.POST.get('username')
fafafa = request.FILES.get('fafafa') #获取文件,可chunks()写入,是一个对象,封装了大小,名称,内容等
import os
img_path = os.path.join('static/imgs/',fafafa.name)
print(username,fafafa) #打印的是文件名,__str__()
with open(img_path,'wb') as f:
for item in fafafa.chunks():
f.write(item)
ret = {'status':True,'error':None,'data':img_path} #返回文件路径
return HttpResponse(json.dumps(ret))


②jQuery上传

function jqSubmit() {
var obj = document.getElementById('fafafa').files[0];

var fd = new FormData(); #也依赖这个,低版本浏览器可能不兼容
fd.append('username','root');
fd.append('fafafa',obj);

$.ajax({ #不用xml语句了
url:'/upload_file/',
type:'POST',
data:fd,
processData: false, // tell jQuery not to process the data #上传文件时,声明,内部不用转化字符串拼接
contentType: false, // tell jQuery not to set contentType
success:function (arg,a1,a2) {
console.log(arg);
console.log(a1);
console.log(a2);

}
})
}
视图函数写入chunks的写法一样

③伪ajax

<form action="/upload_file/" method="POST" enctype="multipart/form-data" target="ifm1"> #form声明
<iframe id='ifm1' name="ifm1"></iframe>
<input type="file" name="fafafa"/>
<input type="submit" onclick="ifmSubmit();" value="提交"/>
</form>


用到的script,兼容性高,现网站普遍使用这个,在所有网站都适用

以下代码可作查看
function ifmSubmit() {
$('#ifm1').load(function () {
var text = $('#ifm1').contents().find('body').text(); #文件通过form的post传到view保存了
var obj = JSON.parse(text); #将view返回的字符串反转为字典
console.log(obj);
})
}

4.图片预览功能:

function ifmSubmit() {
$('#ifm1').load(function () {
var text = $('#ifm1').contents().find('body').text();
var obj = JSON.parse(text);
$('#preview').empty(); #先清空
var imgTag = document.createElement('img'); #创建标签
imgTag.src = "/"+obj.data; #路径要以/开头,这是静态路径,添加到src即可查看
$('#preview').append(imgTag); #添加内容

})
}

还能在上传时即可预览上传,不用点提交,绑定一个onchange函数即可

function changeUpload() {
$('#ifm1').load(function () {
var text = $('#ifm1').contents().find('body').text();
var obj = JSON.parse(text);

$('#preview').empty();
var imgTag = document.createElement('img');
imgTag.src = "/"+obj.data;
$('#preview').append(imgTag);

});
$('#fm1').submit(); #先绑定事件,再提交submit,用js替代了按钮
}

4. 图片验证码

原理:字符串放在session中,创建一张图片写入字符串,保存到本地指定文件夹,打开并读取显示

步骤:访问页面login
-创建一个图片并给用户返回
-session存放验证码
POST校验

img标签:
- 静态文件:
src可以是固定的本地静态文件路径,
也可以是url,对应一个view函数,然后返回HttpResponse(open('','rb').read())
- 随机新生成的图片:
因为src写不了固定的路径,只能是views打开内容给url,src=url

# 1. 创建一张图片 #pip3 install Pillow
# 2. 在图片中写入随机字符串 #依赖pillow和字体文件
# obj = object() #特殊的对象,包含图片信息,即已经放到内存中的一个类
# 3. 将图片写入到指定文件
# 4. 打开指定目录文件,读取内容
# 5. HttpResponse(data) #返回图片内容给img标签,能显示,而直接返回给一个页面则乱码
#静态图片在img标签直接写url,django默认打开图片读取内容并返回


stream = BytesIO() #字节io操作,在内存开辟文件空间,读写。不同浏览器请求时开辟的地方不同
验证码图片没必要保存在服务器上,头像则要保存而且命名要不同
img,code = create_validate_code() #返回一个图片对象和验证码
img.save(stream,'PNG') #写上文件句柄和后缀,文件句柄也可以是open的本地文件
request.session['Checkcode'] = code
return HttpResponse(stream.getvalue()) #拿到内存的图片

如果要更新,src = src + ? #?发get请求,加一个问号无所谓

5.KindEditor 文本编辑框

基本使用
<textarea name="content" id="content"></textarea>

<script src="/static/jquery-1.12.4.js"></script>
<script src="/static/plugins/kind-editor/kindeditor-all.js"></script>
<script>
$(function () {
initKindEditor();
});

function initKindEditor() {
var kind = KindEditor.create('#content', {
width: '100%', // 文本框宽度(可以百分比或像素)
height: '300px', // 文本框高度(只能像素)
minWidth: 200, // 最小宽度(数字)
minHeight: 400 // 最小高度(数字)
});
}
</script>

上传文件:
function initKindEditor() {
var kind = KindEditor.create('#content', {
width: '100%', // 文本框宽度(可以百分比或像素)
height: '300px', // 文本框高度(只能像素)
minWidth: 200, // 最小宽度(数字)
minHeight: 400, // 最小高度(数字)
uploadJson: '/kind/upload_img/', #文件提交的url,图片、flash、文件、视频都在提交这个url
extraFileUploadParams: {
'csrfmiddlewaretoken': '{{ csrf_token }}'
},
fileManagerJson: '/kind/file_manager/', #文件管理路径
allowPreviewEmoticons: true, #远程
allowImageUpload: true #是否允许本地上传
});
}

views返回的内容:
dic = {
'error': 0,
'url': '/static/imgs/20130809170025.png',
'message': '错误了...'
}

return HttpResponse(json.dumps(dic))

XSS攻击
输出文章的时候做不了,那就提交的时候过滤掉,写一个白名单

获取输入框内容
kind=KindEditor.html()


2018/05/04 周五 s25

一. 博客系统

1.IntegerField替代ManyToManyField,ForeignKeyField
caption = ((1,'a'),(2,'b'),(3,'c'))
choice_id = models.IntegerField(choices = caption)

减小数据库连表操作,适用于分类不变的,要想变化,得搞一张表

2.组合搜索----
①从数据库拿数据列出文章和搜索条件
<ul>
{% for row in result %}
<li>{{ row.id }}-{{ row.title }}</li>
{% endfor %}
</ul>

通过正则表达式filter数据库
通过url传匹配的id到view函数,url的名字与model中定义的字段名相同,然后直接**kwargs来filter即可:
url(r'^article-(?P<article_type_id>\d+)-(?P<category_id>\d+).html',views.article),

result = models.Article.objects.filter(**condition) #传入字典,等同于where...and...

如果等于0,返回全部(实现全部这个标签功能)

for k,v in kwargs.items():
kwargs[k] = int(v) # 用于和row_id进行比较,所以要转为数字
if v == 0:
pass #表示不加条件,不作筛选,即全部
else:
condition[k] = v #否则加上

②给条件标签<a>加上href,

方式一:
<a href="article-{{ row.id }}-{{ arg_dict.category_id }}.html"> {{ row.caption }}</a>

#arg_dict.category_id,传进url正则匹配到的参数字典,即可获取到另一个条件是哪个值(python的分割、reverse)

<a href="article-0-{{ arg_dict.category_id }}.html">全部</a>

#全部即表示0

方式二:通过反转生成url
<a href="{% url "video2" direction_id=item.id classification_id=kwargs.classification_id level_id=kwargs.level_id %}" class="active">{{ item.name }}</a>


给选中标签加颜色,通过条件判断赋予一个class,然后css样式即可

{% if row.id == arg_dict.article_type_id %} #表示选中

<a class="active" href="article-{{ row.id }}-{{ arg_dict.category_id }}.html"> {{ row.caption }}</a>

{% endif %}

同样,给全部标签也加颜色

{% if arg_dict.article_type_id == 0 %} #表示点击全部时,url中匹配的该行id=0


③用simple_tag简化html标签

-创建app下templatetags目录,创建py
-写函数func实现相同功能
回传标签语言,mark_safe(),生成多个a标签时,ret是个列表,需要化为一个整体,''.join()
-注册
register = template.Library() @register.simple_tag
-html引入{% load xxoo %}
-{% 函数名 arg1 arg2 %} 传入参数,

注:在view函数中对象通过.操作,字典通过[],而HTML中对象和字典都是通过.

④固定的标签可以放在内存中,减少数据库操作次数

type_choice = ( #元组嵌套元组的形式,第一个是id,第二个是caption
(1,'Python'),
(2,'OpenStack'),
(3,'Linux'),
)
article_type_id = models.IntegerField(choices=type_choice) #写法,替代ForeignKey(),名称对应外键字段_id
检索时,与外键相同的外键字段_id去filter即可
注:与ForeignKey不同,这里字段名是什么url中匹配组就是什么,这样models filter时直接传

views.py中
article_type_list = models.Article.type_choice #从数据库中拿数据,type_choice是表的静态字段

# 利用map方法和lambda表达式转换元组为列表嵌套字典
status_list = list(map(lambda x:{'id':x[0],'name':'x[1]'},models.Video.status_choice))

if row[0] == n1: #封装函数中元组的取值方法

新增一个条件:url正则匹配,views列数据,前端html代码增加。其他条件filter等不变

3.多对多组合搜索条件

a.如何构成分类条件字典用于filter

如果:direction_id 0 #方向不影响分类
*列出所有的分类
如果 classification_id = 0: #分类不加入条件
pass
else:
condition['classification_id'] = classification_id

否则:direction_id != 0 #方向影响分类
*列表当前方向下的所有分类
如果 classification_id = 0: #该方向下的所有分类加入条件
获取当前方向下的所有分类 [1,2,3,4]
condition['classification_id__in'] = [1,2,3,4]
else: #该方向下的某个分类加入条件
classification_id != 0
获取当前方向下的所有分类 [1,2,3,4]
classification_id 是否在 [1,2,3,4] : #上一次点击前的classification_id可能这一次不在该方向下
condition['classification_id'] = classification_id #在
else:
condition['classification_id__in'] = [1,2,3,4] #不在

b.代码
direction_id = kwargs.get('direction_id') #获取url传的参数
classification_id = kwargs.get('classification_id')
level_id = kwargs.get('level_id')

direction_list = models.Direction.objects.all() #列出所有的方向

if direction_id == 0:
class_list = models.Classification.objects.all()#方向下的所有分类
if classification_id == 0:
pass #分类是全部,不加入条件
else:
condition['classification_id'] = classification_id
else:
direction_obj = models.Direction.objects.filter(id=direction_id).first()
class_list = direction_obj.classification.all() # 当前方向下的所有分类,用于前端循环列出

vlist = direction_obj.classification.all().values_list('id') #d当前方向下的所有分类id,queryset元组列表
if not vlist:
classification_id_list = [] #vlist分类id可能在该方向下没有
else:
classification_id_list = list(zip(*vlist))[0] #当前方向下的所有分类id,元组形式

if classification_id == 0: #分类是全部,把该方向下的所有分类加入条件
condition['classification_id__in'] = classification_id_list
else:
if classification_id in classification_id_list: #判断分类是否在方向下
condition['classification_id'] = classification_id
else:
#################指定方向:[1,2,3] 分类:5
kwargs['classification_id'] = 0
condition['classification_id__in'] = classification_id_list

if level_id == 0:
pass
else:
condition['level_id'] = level_id

level_list = models.Level.objects.all() #所有的水平

video_list = models.Video.objects.filter(**condition) #filter

c.前端html标签
类似一对多,生成对应的href即可

<a href="{% url "video2" direction_id=0 classification_id=kwargs.classification_id level_id=kwargs.level_id %}" class="active">全部</a>

<a href="{% url "video2" direction_id=item.id classification_id=kwargs.classification_id level_id=kwargs.level_id %}" class="active">{{ item.name }}</a>

二. JSONP跨域请求本质

json是格式,jsonp是一种请求方式

用途:目前请求都在自己写的project里,请求往返发,还没有和外界交互,拿取数据。
往外界发数据时,请求发过去了,但本地浏览器阻止接收。

1.python模块发请求:
目前为止,可以requests库请求get一个网址(浏览器请求的url是指向内部视图函数的,网站是视图函数通过request库
请求的,与浏览器无关,而跳过视图函数就要在html中通过ajax发起请求)
如:
response = requests.get('http://weatherapi.market.xiaomi.com/wtr-v2/weather?cityId=101121301')
response封装了响应头、响应内容、cookie等字节信息,
response.content是字节类型的,
response.text是字符串,需要先response.encoding='utf-8'编码


2.ajax发请求
不通过django的view函数发请求,而是页面通过按钮触发JavaScript直接向外界请求

3.示例:原生ajax的xhr语法请求

function getContent() {
var xhr = new XMLHttpRequest(); #创建对象
xhr.open('GET','http://weatherapi.market.xiaomi.com/wtr-v2/weather?cityId=101121301');
xhr.onreadystatechange = function () {
console.log(xhr.responseText);
}
}

获取失败:浏览器同源策略,不允许到别的网站请求数据返回,阻止ajax(XMLHttpRequest)请求,但有些不阻止

4.巧妙:后门(iframe,a,img标签),js的cdn网址,即script的src属性=...?k1=v1在script标签加载时,即通过get方法发送参数过去

var tag = document.createElement('script');
tag.src = 'http://weatherapi.market.xiaomi.com/wtr-v2/weather?cityId=101121301'; ?表式get方式
document.head.appendChild(tag); #添加src标签时,即对网址发去了请求
document.head.removeChild(tag); #请求完成后去掉多余标签,js已经拿到数据放到内存

如果直接返回字符串'ok',则无变量接受,报错。src属性要给浏览器一个Javascript文件,如alert(111),以及function

5.原理:本质上src就是请求一个js文件,以JavaScript的格式进行解析。

通过JavaScript添加一个src=请求url的script标签,然后删除,即可实现发送请求。
即步骤4代码

远程可以返回一个执行函数(js格式),会调用浏览器上定义好的同名函数
callback(10000)

然后在html上再定义:
function callback(arg) {
alert(arg)
}

这样,在数据交互的时候,遵循JSONP格式,包裹函数名

6.问题点:这个函数可能与自我定义的其他函数名字冲突

7.方法:在url再传一个参数

tag.src = 'http://www.jxntv.cn/data/jmd-jxtv2.html?callback=list&_=1454376870403'; #callback是一般命名规则

服务器端:通过 func = request.GET.get('callback')
content = “%s(100000)” %(func,)
return HttpResponse(content)

html上,通过function list(arg){}即可对返回的内容进一步操作

应用:比如电视台给的一个url,list('节目列表')返回,本地html上function list(arg){
console.log(arg) #即可打印节目列表
}

总结:只能发get请求,$.ajax method虽然是post,但也是将data附在url上转化为get方式进行请求


8.jQuery已经封装好功能:(方法一样,加src标签然后删除)

$.ajax({
url: 'http://www.jxntv.cn/data/jmd-jxtv2.html',
type: 'GET', #JSONP只能GET,post也会内部改为get
dataType: 'jsonp', #以这个方式去发送请求
jsonp: 'callback', #?callback=func通过Ajax发送时不能放在url,得设置这两个
jsonpCallback: 'list' #对应url的写法,以callback=list的方式去请求
})

9.跨站资源共享CORS:
远程服务器端设置一个响应头指定哪个url或者域名进行访问,就能通过浏览器的同源策略阻止

obj = HttpResponse('sss')
obj['Access-Control-Allow-Origin']='*'

存在浏览器兼容性问题

三. XSS过滤

文本编辑框返回safe过的html标签内容,在里面写script的话会造成alert等(html解析时从上到下解析,遇到script执行)
(直接alert的话< >会被转义,不是尖括号,所以不是script标签了)
解决:过滤掉特殊标签

re模块\beatifulsoup4模块

from bs4 import BeautifulSoup
soup = BeautifulSoup(content, 'html.parser') #内置的解析器
tag = soup.find('script') #找到指定标签
tag.clear() #清除内容,也可以tag.hidden = True

span = soup.find('script')
del span.attrs['style'] #删除span标签的style属性

content = soup.decode() #将对象转化成字符串形式


或者写一个白名单
for tag in soup.find_all(): #找到所有标签
if tag.name in tags:
pass
else:
tag.hidden = True
tag.clear()
continue #跳出当前for循环,下面的属性筛选代码执行不到

如果标签包含属性class id等
tags = {
'p': ['class'],
'strong': ['id',]
}

input_attrs = tag.attrs # 标签的所有属性,一个字典{'class': 'c1', 'id': 'i1'}
valid_attrs = tags[tag.name] # 获取白名单中的属性字典,['class']
for k in list(input_attrs.keys()): # for k,v in xxx.items()行不通,原因在于迭代过程就del了本身元素
#.keys()是迭代器,list()相当于不对原字典作迭代了
if k in valid_attrs:
pass
else:
del tag.attrs[k] # 删除不在名单中的属性


问题:创建这个过滤方法的对象,如果多个访问,则会创建多个对象,而白名单是写死的,内存只存在一个即可,不然浪费内存

解决:单例模式

class Foo(object):
instance = None #静态字段,只属于类,创建多个类示例时只创建一个

def __init__(self):
self.name = 'alex' #name已经写死

@classmethod #装饰器,类方法
def get_instance(cls):
if Foo.instance: #如果已经创建,不为空
return Foo.instance
else:
Foo.instance = Foo()#如果为空,新建一个
return Foo.instance

def process(self):
return '123'

obj1 = Foo.get_instance() #两个获取到的一样
obj2 = Foo.get_instance() #如果是obj.Foo(),则不一样

进阶:(创建对象时不换方式)

class Foo(object):
instance = None

def __init__(self): #后执行
self.name = 'alex'

def __new__(cls, *args, **kwargs):
if Foo.instance:
return Foo.instance
else:
Foo.instance = object.__new__(cls, *args, **kwargs) #创建对象的本质方法,不写默认是这个创建
return Foo.instance

obj1 = Foo() #创建时会先执行new方法

四.博客系统

-博客表结构:
- 文章信息:
标题、简介
阅读数:read_count=models.IntegerField(default=0) #四个字段用来存数量,具体的信息在下个表维护
评论数:comment_count=models.IntegerField(default=0) #每当有人评论时+1
点赞数:up_count=models.IntegerField(default=0)
踩数:down_count=models.IntegerField(default=0)

- 赞或踩:放在同一个表中维护,用于检索用户是否赞过或者踩过
文章:ForeignKey(to='Article')
赞或踩用户:ForeignKey(to='UserInfo')
是否赞:models.BooleanField(verbose_name='是否赞')

- 楼层相互评论:
评论内容:(content)
评论的文章:ForeignKey(to='Article')
评论者:ForeignKey(to='UserInfo')
回复评论:reply=models.ForeignKey(verbose_name='回复评论',to='self',related_name='back',null=True)
self指外键是自己的nid,只能从自己已有的nid中拿,null=True时指不回复,仅评论文章

-功能:原子性事务操作(不成功回滚)
from django.db import transaction
with transaction.atomic():
obj = models.Article.objects.create(**form.cleaned_data) #文章信息
models.ArticleDetail.objects.create(content=content, article=obj) #具体内容
tag_list = []
for tag_id in tags:
tag_id = int(tag_id) #转为数字
tag_list.append(models.Article2Tag(article_id=obj.nid, tag_id=tag_id))
models.Article2Tag.objects.bulk_create(tag_list) #bulk_create是批量创建对象,减少SQL查询次数
-文章时间分类:
先把c_time格式成年月,再进行分组(sqllite)
date_list = models.Article.objects.raw(
'select nid, count(nid) as num,strftime("%Y-%m",create_time) as ctime from repository_article
group by strftime("%Y-%m",create_time)')
后面的strftime是把create_time格式为年-月,再group by
前面的strftime是已经分组后,将要显示的列名称
注:mysql是date_format(create_time,"%Y-%m")
然后列出文章:
article_list = models.Article.objects.filter(blog=blog).extra(
where=['strftime("%%Y-%%m",create_time)=%s'], params=[val, ]).all()
#字符串格式化的时候要想输出%,前面得加一个%,这样交给数据库解析语句时就不是占位符
-文章点赞:
js发送数据:文章id,赞or踩,session获取登陆的用户id
文章点赞个数自+1:F或者Q

五、瀑布流

思路:将图片放在固定数量的div下,div宽度一定,高度变化。难点在于将图片从数据库取出循环放在几个div中。


方式一:自定义filter

{% for row in img_list %}
{% if forloop.counter|函数名:1 %}
<div>
{% elif forloop.counter|函数名:2 %}

{% elif forloop.counter|函数名:3 %}

{% elif forloop.counter|函数名:0 %}
{% endif %}
{% endif %}

方式二:JavaScript

$(function () {
initImg();
});
NID = 0; #全局变量,监听滚轮修改值,再次获取数据
function initImg() {
$.ajax({
url: '/get_imgs.html',
type: 'GET',
data:{nid:NID}, #传参数,用于从数据库取哪一部分的数据filter(id__gt=nid)
dataType: 'JSON',
success:function (arg) {
var img_list = arg.data;
$.each(img_list,function (index,v) { #对img_list进行循环,index是循环自带索引,v是img_list的每一个值
var eqv = index % 4; #当前循环的索引除以4的余数
var tag = document.createElement('img');
tag.src = '/'+ v.src;
$('#container').children().eq(eqv).append(tag); #拿到第几个孩子
})

}

})
}

改进一:监听滚轮,滑到底部再发请求取数据

$(function () {
initImg();
});
NID = 0; #全局变量,监听滚轮修改值,再次获取数据
LASTPOSITION = 3 #这个值依据4个div和每次取7张图片来定,第一次循环完index=2
function initImg() {
$.ajax({
url: '/get_imgs.html',
type: 'GET',
data:{nid:NID}, #传参数,用于从数据库取哪一部分的数据filter(id__gt=nid)
dataType: 'JSON',
success:function (arg) {
var img_list = arg.data;
$.each(img_list,function (index,v) { #对img_list进行循环,index是循环自带索引,v是img_list的每一个值
var eqv = (index+LASTPOSITION+1) % 4; #当前循环的索引除以4的余数
var tag = document.createElement('img');
tag.src = '/'+ v.src;
$('#container').children().eq(eqv).append(tag); #拿到第几个孩子
if(index+1==img_list.length){ #最后一个图片时
NID = v.id; #把最后图片的id赋值给NID,下次再从这开始取
LASTPOSITION = eqv;
}
})
}
})
}

function scrollEvent(){
$(window).scroll(function () {
// 什么时候到达最底部
var scrollTop = $(window).scrollTop(); #滚动条可滚动到顶的高度
var winHeight = $(window).height(); #浏览器窗口高度
var docHeight = $(document).height(); #文档html的body高度
if (scrollTop + winHeight == docHeight) {
initImg();
}
})
}

改进二:全局变量可能存在冲突问题

$(function () {
var obj = new ScrollImg(); #创建对象
obj.fetchImg(); #this代指obj
obj.scrollEvent();

});
function ScrollImg() { #函数嵌套函数,相当于定义了一个类
this.NID = 0; #this代指obj,相当于类的self
this.LASTPOSITION = 3;
this.fetchImg = function () {
var that = this; #通过对象调用方法,this还是代指obj
$.ajax({
url: '/get_imgs.html',
type: 'GET',
data: {nid: that.NID},
dataType: 'JSON',
success: function (arg) {
var img_list = arg.data;
$.each(img_list, function (index, v) {
var eqv = (index + that.LASTPOSITION + 1) % 4; #that代指obj
console.log(eqv);
var tag = document.createElement('img');
tag.src = '/' + v.src;
$('#container').children().eq(eqv).append(tag);
if (index + 1 == img_list.length) {
that.LASTPOSITION = eqv;
//that.NID = v.id;
}
})
}
})
};
this.scrollEvent = function () {
var that = this;
$(window).scroll(function () {
var scrollTop = $(window).scrollTop();
var winHeight = $(window).height();
var docHeight = $(document).height();
if (scrollTop + winHeight == docHeight) {
that.fetchImg();
}
})
}
}

六、报障系统

1.对于choice=((),()),obj.status获取的是数字,obj.get_status_display是文本内容



2018/07/21 day71 权限管理


1.python中 列表、字典 都是引用类型


















猜你喜欢

转载自www.cnblogs.com/czlong/p/9382209.html