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中 列表、字典 都是引用类型