class day19: def __init__(): pass def do_homework(): pass def do_my_project(): pass ‘‘‘Django 回顾‘‘‘ - http请求周期 浏览器(socket客户端) 2.socket.connect(ip,port) 进行连接 3.socket.send("http://www.qq.com/index.html...") url + data 遵循的规则:http协议 请求头 请求体 请求头和请求体使用 ‘\\r\\n\\r\\n‘ 分隔,前面是请求头,后面是请求体 GET请求: "GET /index.html?key=1... Http/1.1\\r\\nhost:www.qq.com\\r\\ncontent-type:application/json\\r\\n\\r\\n" POST请求:"POST /index.html Http/1.1\\r\\nhost:www.qq.com\\r\\ncontent-type:application/json\\r\\n\\r\\nname=alex&pwd=123" 6.获取响应 响应头,响应体 = data.split("\\r\\n\\r\\n") 响应头之间用 ‘\\r\\n‘ 分隔 7.断开连接 nginx(socket服务端) 1.server.run(),监听IP和PORT 4.server.recv() 请求头,请求体 = data.split("\\r\\n\\r\\n") request.POST.get(‘name‘) 即是从 请求体 里取值 5.服务端进行响应: conn.send(‘......‘) 遵循的规则:http协议 响应头 响应体 7.断开连接 总结: a.Http请求中本质都是字符串 b.Http请求是短连接(请求 -> 响应 -> 断开连接) c.请求和响应都有头和体 请求:请求头‘\\r\\n\\r\\n‘请求体 响应:响应头‘\\r\\n\\r\\n‘响应体 由于需要处理繁琐http的解析和封装处理工作,所以web框架应运而生 web框架 - Django socket(wsgiref) django没有自己写socket 解析和封装http请求 django-admin startproject mysite cd mysite python manage.py startapp app01 coding...(*****) python manage.py runserver ip:port ‘‘‘写代码‘‘‘ - 路由系统 /login/ func name=‘f1‘ /login/\\d+/ func name=‘f2‘ /login/(?P<n>\\d+)/ func name=‘f3‘ /login/\\d+/ include(‘app01.urls‘) - 视图函数 def index(request): request.GET request.body 原生的请求体 request.POST 转换后的请求体字典 如果请求头中content-type=urlencode-form... 才将request.body转换成字典 - 可能有值 - 也可能没有值 request.method request.Meta request.GET.get() request.GET.getlist() 前端多选的情况,如多个作者 request.POST.get() request.POST.getlist() return HttpResponse(‘字符串/字节‘) return render(request,"html路径",locals()) return redirect("url") - 模板 for if 继承 filter,simple_tag - Models操作 - 创建表 - models.xxx.objects.create(name="xxxx") - models.xxx.objects.create(**dic) - models.xxx.objects.filter(id__gt=1).delete() - models.xxx.objects.filter(id=1) - models.xxx.objects.exclude(id=5) 取出id不等于5的 - models.xxx.objects.filter(id=1).update(name=‘ddd‘) - models.xxx.objects.filter(id=1).update(**dic) queryset --> [对象,对象...] objs = models.xxx.objects.all() queryset --> [{},{}...] objs = models.xxx.objects.all().values() queryset --> [(),()...] objs = models.xxx.objects.all().values_list() demo1 业务线表 bussiness_unit id name 主机表 serverinfo id host port bs(业务线对象) objs = modesl.serverinfo.objects.all() for row in objs: print(row.id) print(row.host) print(row.port) print(row.bs.name) 外键,拿到业务线的名字 objs = modesl.serverinfo.objects.all().values("id","host","port","bs__name") for row in objs: print(row[‘host‘]) print(row[‘bs__name‘]) demo2 (userinfo 和 bussiness_unit 是多对多关系) 用户表 userinfo id user pwd email mm(ManyToMany) 业务线表 bussiness_unit id name 主机表 id host port bs(业务线对象) 用户业务线关系表 ***** id user_id bs_id obj = models.user.objects.filter(user=‘alex‘).first() obj.mm.add(1) obj.mm.add(11) - 通过用户对象查所负责的所有业务线对象 obj = models.user.objects.filter(user=‘alex‘).first() queryset = obj.mm.all() 拿到alex负责的所有业务线 -> [业务线对象,业务线对象...] for row in queryset: print(row.id) print(row.name) - 通过业务线反查对应有哪些人负责? (***反查*** 表名_set) obj = models.bussiness_unit.objects.filter(name=‘二手车‘).first() queryset = obj.userinfo_set.all() 拿到负责二手车业务线的用户对象 -> [用户对象,用户对象...] for row in queryset: print(row.user) print(row.pwd) 总结: 1.多对多关系建在哪张表上都可以; 2.如果建在userinfo表上,那么通过用户对象查所负责的bs对象列表就直接用 obj.mm.all() 方便了 userinfo对象,但是bs对象反查userinfo就得用 userinfi_set.all() ================================================================== 今日内容: 1.登录 - 密码加密,对密码进行比较 - 用户登录之后才能访问某些页面 2.cookie是什么? - 保存在客户端浏览器上的键值对 {k:v} - cookie依附在请求头或者响应头中 - 浏览器发送请求时会自动携带所访问网站对应的cookie - 应用 - 实现登录 - 投票 - 每页显示10条/20条... - 使用 - 设置 response = redirect(‘/index/‘) response.set_cookie(‘my_cookie‘,md5.encrypt(‘xxx‘)) return response key, value=‘‘, max_age=None, 超时时间:秒数 expires=None, 超时时间:截止日期 path=‘/‘, cookie在哪个url里生效 : 访问指定url时才能读取到cookie, ‘/‘ 表示全部页面都可以 domain=None, 当前域名或者二级域名 secure=False, https httponly=False response = redirect(‘/index/‘) # 设置cookie response.set_cookie(‘my_cookie‘,md5.encrypt(user)) return response # 设置cookie过期时间 import datetime deadline = datetime.datetime.utcnow() + datetime.timedelta(seconds=5) response.set_cookie(‘my_cookie‘,md5.encrypt(user),expires=deadline) response.set_cookie(‘my_cookie‘,md5.encrypt(user),max_age=5) - 获取 ck = request.COOKIES.get(‘my_cookie‘) # 详细代码如下: ‘‘‘ # models.py from django.db import models # Create your models here. class UserInfo(models.Model): username = models.CharField(max_length=32) password = models.CharField(max_length=64) email = models.EmailField(null=True) ‘‘‘ ‘‘‘ from django.shortcuts import render,HttpResponse,redirect from app01 import models from common import md5 # 判断用户是否登录的装饰器(通过cookie判断) def auth(func): def inner(request,*args,**kwargs): ck = request.COOKIES.get(‘my_cookie‘) if not ck: return redirect(‘/login/‘) return func(request,*args,**kwargs) return inner @auth def index(request): user = request.COOKIES.get(‘my_cookie‘) print(request.COOKIES) # return HttpResponse("登录成功") return render(request,‘index.html‘,locals()) def login(request): if "GET" == request.method: return render(request,‘login.html‘) else: user = request.POST.get(‘user‘) pwd = request.POST.get(‘pwd‘) obj = models.UserInfo.objects.filter(username=user,password=md5.encrypt(pwd)).first() if obj: response = redirect(‘/index/‘) # 设置cookie过期时间 # import datetime # deadline = datetime.datetime.utcnow() + datetime.timedelta(seconds=5) # response.set_cookie(‘my_cookie‘,md5.encrypt(user),expires=deadline) # response.set_cookie(‘my_cookie‘,md5.encrypt(user),max_age=5) # 设置cookie response.set_cookie(‘my_cookie‘,user) return response else: return render(request,‘login.html‘,{‘msg‘:"用户名或密码错误"}) ‘‘‘ ‘‘‘ # login.html ... <form action="/login/" method="POST"> {% csrf_token %} <input type="text" name="user"> <input type="text" name="pwd"> <input type="submit" value="提交"><span style="color: red;">{{ msg }}</span> </form> ... # index.html ... <h1>{{ user }}</h1> ... ‘‘‘ ‘‘‘ # md5.py def encrypt(pwd): import hashlib obj = hashlib.md5() obj.update(pwd.encode(‘utf-8‘)) data = obj.hexdigest() return data if __name__ == ‘__main__‘: print(encrypt(‘123‘)) ‘‘‘ 3.session 是保存在服务器端的键值对 {k:v} 依赖cookie Django默认支持Session,并且默认是将Session数据存储在数据库中,即:django_session 表中。 更多参考:http://www.cnblogs.com/wupeiqi/articles/5246483.html 生成随机字符串,并将其当做cookie发送给客户端 服务端设置随机字符串作为key,自己设置一些{}:request.session[‘my_session_key‘] = user - 设置session request.session[‘yyy‘] = user return redirect(‘/index/‘) - 获取session # 装饰器 def auth(func): def inner(request,*args,**kwargs): ck = request.session.get(‘yyy‘) if not ck: return redirect(‘/login/‘) return func(request,*args,**kwargs) return inner - 清空session request.session.clear() http://www.cnblogs.com/wupeiqi/articles/5246483.html 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,默认修改之后才保存(默认) # 详细代码如下: ‘‘‘ from django.shortcuts import render,HttpResponse,redirect from app01 import models from common import md5 # 判断用户是否登录的装饰器(通过session判断) def auth(func): def inner(request,*args,**kwargs): ck = request.session.get(‘my_session_key‘) if not ck: return redirect(‘/login/‘) return func(request,*args,**kwargs) return inner @auth def index(request): user = request.session.get(‘my_session_key‘) return render(request,‘index.html‘,locals()) @auth def order(request): return render(request,‘order.html‘) # 登出view def logout(request): # 用户登出后清空session request.session.clear() return redirect(‘/index/‘) def login(request): if "GET" == request.method: return render(request,‘login.html‘) else: user = request.POST.get(‘user‘) pwd = request.POST.get(‘pwd‘) obj = models.UserInfo.objects.filter(username=user,password=md5.encrypt(pwd)).first() if obj: # 设置session request.session[‘my_session_key‘] = user return redirect(‘/index/‘) else: return render(request,‘login.html‘,{‘msg‘:"用户名或密码错误"}) ‘‘‘ ‘‘‘ # urls.py from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r‘^admin/‘, admin.site.urls), url(r‘^login/‘, views.login), url(r‘^logout/‘, views.logout), url(r‘^index/‘, views.index), url(r‘^order/‘, views.order), ] ‘‘‘ ‘‘‘ # login.html ... <form action="/login/" method="POST"> {% csrf_token %} <input type="text" name="user"> <input type="text" name="pwd"> <input type="submit" value="提交"><span style="color: red;">{{ msg }}</span> </form> ... # index.html ... <h1>{{ user }}</h1> ... # order.html ... <body> <h1>欢迎登录:{{ request.session.my_session_key }}</h1> <a href="/logout/">注销</a> </body> ... ‘‘‘ 4.csrf 跨站请求伪造 <form action="/login/" method="POST"> {% csrf_token %} <input type="text" name="user"> <input type="text" name="pwd"> <input type="submit" value="提交"><span style="color: red;">{{ msg }}</span> </form> {% csrf_token %} 在浏览器里默认就是一个隐藏的input标签,如下所示: <form action="/login/" method="POST"> <input type=‘hidden‘ name=‘csrfmiddlewaretoken‘ value=‘T2Ub1TacecIsEsKJvoUvB3xNSwrEGT0NajwGeO6y58mp1IseYVLL3FBnXtOT3WgW‘ /> <input type="text" name="user"> <input type="text" name="pwd"> <input type="submit" value="提交"><span style="color: red;"></span> </form> 而 {{ csrf_token }} 这个就是这个隐藏标签的value值 跨站请求的漏洞: <form method="POST" action="http://www.icbc.com.cn/icbc/"> <input type="text" name="from" style="display: none;" value="A的卡号"> <input type="text" name="to" style="display: none;" value="黑客的卡号"> <input type="text" name="money" style="display: none;" value="1000000000"> <input type="submit" name="" value="点我"> </form> {% csrf_token %} # 首先不提交 csrf_token 的情况:报错403,CSRF verification failed. Request aborted. <form id="my_form" action="/login/" method="POST"> <input type="text" name="user"> <input type="email" name="email"> <button onclick="ajaxSubmit()">Ajax提交</button> </form> <script src="{% static "js/bootstrap.min.js" %}"></script> <script> function ajaxSubmit() { $.ajax({ url:‘/show/‘, type:‘POST‘, data:{ ‘user‘:$(‘#my_form input[name="user"]‘).val(), ‘email‘:$(‘#my_form input[name="email"]‘).val() }, success:function (data) { console.log(data); } }) } </script> # 注意一点: # 如果form表单不写action,则默认提交到当前页面 ajax提交csrf_token的几种方式: # 方式1 <form id="my_form" action="/show/" method="post"> {% csrf_token %} <input type="text" name="user"> <input type="email" name="email"> <button onclick="ajaxSubmit()">Ajax提交</button> </form> <script src="{% static "js/bootstrap.min.js" %}"></script> <script> function ajaxSubmit() { $.ajax({ url:‘/show/‘, type:‘POST‘, data:{ ‘user‘:$(‘#my_form input[name="user"]‘).val(), ‘email‘:$(‘#my_form input[name="email"]‘).val(), ‘csrfmiddlewaretoken‘:$(‘input[name="csrfmiddlewaretoken"]‘).val() }, success:function (data) { console.log(data) } }) } </script> # 方式2 只能写在模板里 <body> <form id="my_form" action="/show/" method="post"> {% csrf_token %} <input type="text" name="user"> <input type="email" name="email"> <button onclick="ajaxSubmit()">Ajax提交</button> </form> <script src="{% static "js/bootstrap.min.js" %}"></script> <script> function ajaxSubmit() { $.ajaxSetup({ data: {‘csrfmiddlewaretoken‘:‘{{ csrf_token }}‘} }); $.ajax({ url:‘/show/‘, type:‘POST‘, data:{ ‘user‘:$(‘#my_form input[name="user"]‘).val(), ‘email‘:$(‘#my_form input[name="email"]‘).val() }, success:function (data) { {# do something...#} } }) } </script> </body> # 后端得到的数据: 类型:<class ‘django.http.request.QueryDict‘> 数据:<QueryDict: {‘csrfmiddlewaretoken‘: [‘raZNrc77aQn7cr5Wr6gtTgOaTdNWZKF0HmAfN6kqlGzmyrr4Dw7DUcSVQ6ZHcFoQ‘], ‘email‘: [‘[email protected] om‘], ‘user‘: [‘borui‘]}> # 方式3 只能写在模板里 <form id="my_form" action="/show/" method="post"> {% csrf_token %} <input type="text" name="user"> <input type="email" name="email"> <button onclick="ajaxSubmit()">Ajax提交</button> </form> <script src="{% static "js/bootstrap.min.js" %}"></script> <script> function ajaxSubmit() { $.ajax({ url:‘/show/‘, type:‘POST‘, data:{ ‘user‘:$(‘#my_form input[name="user"]‘).val(), ‘email‘:$(‘#my_form input[name="email"]‘).val(), ‘csrfmiddlewaretoken‘:"{{ csrf_token }}" }, success:function (data) { {# do something...#} } }) } </script> 这种方法,循环form表单,把很多input值拿出来组成字典, 然而实际上POST请求最后是需要转换成字符串放到请求体中发给后端的,实际上的字符串如下: ‘csrfmiddlewaretoken=ouyWxV86TJWMttyLwzRkORIcqXjInlDREG9oTPlp4z81PtUTIZIuPNMXnQvtAgmH&user=love&email=love%40qq.com‘ 所以,如果ajax里的data字段如果写成一个字典,那么就需要一个转成字符串的过程; 如果直接写成字符串,也是可以的; $(‘#my_form‘).serialize() 这个方法就可以把form表单里所有的值(包含隐藏的csrf标签)拼接成一个字符串; # 方法4: <form id="my_form" action="/show/" method="post"> {% csrf_token %} <input type="text" name="user"> <input type="email" name="email"> <button onclick="ajaxSubmit()">Ajax提交</button> </form> <script src="{% static "js/bootstrap.min.js" %}"></script> <script> function ajaxSubmit() { $.ajax({ url:‘/show/‘, type:‘POST‘, data:$(‘#my_form‘).serialize(), success:function (data) { console.log(data) } }) } </script> ‘‘‘ 以上4中方法都是把csrf_token放入 请求体 里传递给后端 ‘‘‘ # 方法5 把csrftoken对应的值方到 请求头 里,传递给后端,这样也可以通过csrf验证 首先引入 jquery.cookie.js 插件 然后通过 $.cookie(‘csrftoken‘) 则可以获取到csrftoken对应的值 这种方法,是把csrftoken对应的值放到请求头里,必须按照这个格式:headers:{‘X-CSRFToken‘: $.cookie(‘csrftoken‘)} 这种情况,请求体的内容可以随便写; <form id="my_form" action="/show/" method="post"> {% csrf_token %} <input type="text" name="user"> <input type="email" name="email"> <button onclick="ajaxSubmit()">Ajax提交</button> </form> <script src="{% static "js/jquery-3.2.1.min.js" %}"></script> <script src="{% static "js/jquery.cookie.js" %}"></script> <script src="{% static "js/bootstrap.js" %}"></script> <script> function ajaxSubmit() { $.ajax({ url:‘/show/‘, type:‘POST‘, data:{‘k1‘:‘v1‘,‘k2‘:‘v2‘}, headers:{‘X-CSRFToken‘: $.cookie(‘csrftoken‘)}, success:function (data) { console.log(data) } }) } </script> 总结: 基于ajax提交form表单,发送csrf验证的方式里 最常用的就是 data:$(‘#my_form‘).serialize() 和 headers:{‘X-CSRFToken‘: $.cookie(‘csrftoken‘)} 另外: 关于csrf有两个装饰器:from django.views.decorators.csrf import csrf_exempt,csrf_protect ‘django.middleware.csrf.CsrfViewMiddleware‘, # 开启则表示全站都使用csrf验证,而csrf_exempt这个装饰器则表示哪些view可以不使用csrf # 如果不开启,则表示全站都不使用csrf验证,而csrf_protect这个装饰器则表示哪些view可以使用csrf 5.牛逼的自定义分页 request.path_info ‘‘‘ day20 分界线 ‘‘‘ 上节回顾 1.http请求周期 请求头和请求体使用 ‘\\r\\n\\r\\n‘ 分隔 2.Models操作 单表 [obj,obj,obj...] = models.xxx.objects.all() [{},{},{}...] = models.xxx.objects.values(xx,xx,xx...) [(),(),()...] = models.xxx.objects.values_list(xx,xx,xx...) 一对多 dep 部门表:标题 user 员工表:用户,邮箱,部门id(外键) qs = [obj,obj,obj...] = models.user.objects.all() for row in qs: row.id,row.name,row.email,row.xx.title user 查 dep [{},{},{}...] = models.user.objects.values(‘id‘,‘name‘,‘email‘,‘xx__title‘) [(),(),()...] = models.user.objects.values_list(‘id‘,‘name‘,‘email‘,‘xx__title‘) dep 反查 user : user_set (在dep表里隐含的字段,多对多同样) 多对多 业务线 id name M2M 用户表 id name django给创建第三张表(因为manytomany) 3.自定义分页模块 4.csrf 5.cookie 保存在客户端浏览器上的键值对 6.session 保存在服务器端的键值对,依赖cookie day20今日内容 1. FBV 和 CBV FBV:function basic view /index/ func(request) if "GET" == request.method: ... CBV:class basic view /index/ class(object) - 判断: get请求 就执行类里的get方法 post请求 就执行类里的post方法 form表单只能提交 ‘get‘ 和 ‘post‘ 方法 ajax 则可以提交:‘get‘, ‘post‘, ‘put‘, ‘patch‘, ‘delete‘, ‘head‘, ‘options‘, ‘trace‘ ajax 遵循resful规范 /index/ obj = 类() 找到类之后,执行完 __init__ 就执行 dispatch()方法,自己不写的话就执行父类的 dispatch() ‘‘‘ class B: def f1(self): self.f2() class A(B): def f2(self): print("---> A") obj = A() obj.f1() ---> B.f1(obj) --> obj.f2() ‘‘‘ def dispatch(self, request, *args, **kwargs): # Try to dispatch to the right method; if a method doesn‘t exist, # defer to the error handler. Also defer to the error handler if the # request method isn‘t on the approved list. if request.method.lower() in self.http_method_names: handler = getattr(self, request.method.lower(), self.http_method_not_allowed) else: handler = self.http_method_not_allowed return handler(request, *args, **kwargs) POST obj.post 是通过反射 getattr() GET obj.get 是通过反射 getattr() ‘‘‘执行super,调用父类的dispatch方法‘‘‘ def dispatch(self, request, *args, **kwargs): print(‘before‘) # 执行父类的 dispatch方法 response = super(LoginView,self).dispatch(request,*args, **kwargs) print(‘after‘) return response ‘‘‘或者重写父类的dispatch‘‘‘ def dispatch(self, request, *args, **kwargs): method = request.method.lower() # print(‘+++++++++++++‘, self.http_method_names) if hasattr(LoginView,method): func = getattr(LoginView,method) return func(self, request, *args, **kwargs) else: return HttpResponse(‘err‘) def get(self,request,*args,**kwargs): print(‘------->> get‘) return render(request,‘login.html‘) def post(self,request,*args,**kwargs): print(‘------->> post‘) return HttpResponse(‘ok‘) 注意:django的CBV里装饰器不能直接用,需要使用: from django.utils.decorators import method_decorator FBV -> 函数 CBV -> 类 - dispatch - get/post 应用: 登录验证的几种实现方法: - 继承 - 单继承实现 ‘‘‘ from django.shortcuts import render,redirect,HttpResponse from django.views import View from django.views.decorators.csrf import csrf_exempt,csrf_protect from django.core import serializers from django.utils.decorators import method_decorator # Create your views here. class Login(View): def dispatch(self, request, *args, **kwargs): return super(Login,self).dispatch(request, *args, **kwargs) def get(self, request,*args, **kwargs): return render(request,‘login.html‘) def post(self, request,*args, **kwargs): user = request.POST.get(‘user‘) pwd = request.POST.get(‘pwd‘) if ‘alex‘ == user and ‘123‘ ==pwd: request.session[‘user‘] = user return render(request,‘private.html‘,{‘user‘:user}) return render(request,‘login.html‘,{‘msg‘:‘用户名或密码错误!‘}) class Base(View): def dispatch(self, request, *args, **kwargs): if request.session.get(‘user‘): response = super(Base,self).dispatch(request, *args, **kwargs) return response else: return redirect(‘/login/‘) class Index(Base): def get(self, request,*args, **kwargs): return render(request, ‘index.html‘, {‘user‘: request.session.get(‘user‘)}) ‘‘‘ - 多继承实现 ‘‘‘ class Base(object): def dispatch(self, request, *args, **kwargs): if request.session.get(‘user‘): response = super(Base,self).dispatch(request, *args, **kwargs) return response else: return redirect(‘/login/‘) class Index(Base,View): def get(self, request,*args, **kwargs): return render(request, ‘index.html‘, {‘user‘: request.session.get(‘user‘)}) ‘‘‘ - 装饰器 auth 是装饰器函数 - 加载类上 @method_decorator(auth,name=‘post‘) ‘‘‘ # 装饰器:验证用户是否已经登录 def auth(func): def wrapper(request, *args, **kwargs): if request.session.get(‘user‘): obj = func(request, *args, **kwargs) return obj else: return redirect(‘/login/‘) return wrapper @method_decorator(auth,‘get‘) class Index(View): def dispatch(self, request, *args, **kwargs): return super(Index, self).dispatch(request, *args, **kwargs) def get(self, request, *args, **kwargs): return render(request, ‘index.html‘, {‘user‘: request.session.get(‘user‘)}) ‘‘‘ - 加在dispatch上 @method_decorator(auth) ‘‘‘ # 装饰器:验证用户是否已经登录 def auth(func): def wrapper(request, *args, **kwargs): if request.session.get(‘user‘): obj = func(request, *args, **kwargs) return obj else: return redirect(‘/login/‘) return wrapper class Index(View): @method_decorator(auth) def dispatch(self, request, *args, **kwargs): return super(Index, self).dispatch(request, *args, **kwargs) def get(self, request, *args, **kwargs): return render(request, ‘index.html‘, {‘user‘: request.session.get(‘user‘)}) ‘‘‘ - 加在具体方法上 @method_decorator(auth) ‘‘‘ # 装饰器:验证用户是否已经登录 def auth(func): def wrapper(request, *args, **kwargs): if request.session.get(‘user‘): obj = func(request, *args, **kwargs) return obj else: return redirect(‘/login/‘) return wrapper class Index(View): @method_decorator(auth) def get(self, request, *args, **kwargs): return render(request, ‘index.html‘, {‘user‘: request.session.get(‘user‘)}) ‘‘‘ from django.views.decorators.csrf import csrf_exempt,csrf_protect - csrf_exepmt 加在POST上例外,只能加在 dispatch上有效,官网bug。 2. 序列化 (Json是不能处理 queryset的) - 模板渲染 - ajax - from django.core import serializers - serializers 的局限性: queryset里必须是obj才可以 如果是models.xxx.objects.values(xx,xx,xx) 这样得到的queryset是不能用serializers进行序列化的!!! 只能采用在后台自己拼接好字典等形式,自己搞成json.dumps然后传给ajax - json 序列化 (有局限性,需自定义dumps) 因为json能处理的数据类型有限,比如datetime类型就不能处理,需要自定义dumps函数 方式一: user_list = models.UserInfo.objects.all() data = serializers.serialize("json", user_list) [ {"model": "app01.userinfo", "pk": 1, "fields": {"username": "\\u5174\\u666e", "password": "123123"}}, {"model": "app01.userinfo", "pk": 2, "fields": {"username": "\\u94f6\\u79cb\\u826f", "password": "666"}} ] 方式二: user_list = models.UserInfo.objects.values(‘id‘,‘username‘) user_list = list(user_list) data = json.dumps(user_list) [ {"username": "\\u5174\\u666e", "id": 1}, {"username": "\\u94f6\\u79cb\\u826f", "id": 2} ] 问题:对json.dumps做定制: import json from datetime import date from datetime import datetime class JsonCustomEncoder(json.JSONEncoder): def default(self, field): if isinstance(field, datetime): return field.strftime(‘%Y-%m-%d %H:%M:%S‘) elif isinstance(field, date): return field.strftime(‘%Y-%m-%d‘) else: return json.JSONEncoder.default(self, field) user_list = [ {‘id‘:1,‘name‘:‘alex‘,‘ctime‘: datetime.now()}, {‘id‘:2,‘name‘:‘eric‘,‘ctime‘: datetime.now()} ] data = json.dumps(user_list,cls=JsonCustomEncoder) print(data) ajax扩展: 从后端获得的字符串: JSON.parse() 可以被 dataType: ‘JSON‘ 这一行替换: # 后端代码: ... import json return HttpResponse(json.dumps({‘flag‘:True,‘msg‘:‘ok‘})) ... 示例1:使用JSON.parse()的情况,返回的是 object类型 <form id="my_form" action="/login/" method="post"> {% csrf_token %} <input type="text" name="user"> <input type="password" name="pwd"> <button onclick="ajaxSubmit()">Ajax提交</button> </form> <script src="{% static "js/jquery-3.2.1.min.js" %}"></script> <script src="{% static "js/jquery.cookie.js" %}"></script> <script src="{% static "js/bootstrap.js" %}"></script> <script> function ajaxSubmit() { $.ajax({ url:‘/login/‘, type:‘POST‘, data:{ ‘user‘:$(‘#my_form input[name="user"]‘).val(), ‘pwd‘:$(‘#my_form input[name="pwd"]‘).val() }, headers:{‘X-CSRFToken‘: $.cookie(‘csrftoken‘)}, success:function (data) { var info = JSON.parse(data); console.log(info); console.log(typeof info); } }) } </script> 示例2:使用dataType: ‘JSON‘的情况,返回的是string类型 <form id="my_form" action="/login/" method="post"> {% csrf_token %} <input type="text" name="user"> <input type="password" name="pwd"> <button onclick="ajaxSubmit()">Ajax提交</button> </form> <script src="{% static "js/jquery-3.2.1.min.js" %}"></script> <script src="{% static "js/jquery.cookie.js" %}"></script> <script src="{% static "js/bootstrap.js" %}"></script> <script> function ajaxSubmit() { $.ajax({ url:‘/login/‘, type:‘POST‘, datatype:‘JSON‘, data:{ ‘user‘:$(‘#my_form input[name="user"]‘).val(), ‘pwd‘:$(‘#my_form input[name="pwd"]‘).val() }, headers:{‘X-CSRFToken‘: $.cookie(‘csrftoken‘)}, success:function (info) { console.log(info); console.log(typeof info); } }) } </script> 3. Django的Form组件验证(用户请求的验证 + 生成HTML标签) 示例:用户管理 添加用户页面: - 显示HTML标签 - 提交:数据验证 - 成功之后保存 - 错误的话则提示错误信息 from django.forms import widgets 样式 总结: 1. 创建类 MyForm(本质就是正则表达式的集合),继承Form类 2. 只是生成HTML标签:form = MyForm() 添加页面 3. 带默认值的HTML标签:form = MyForm(initial={‘xx‘:‘xx‘,...}) 编辑页面 4. 提交数据:form = Form(data=request.POST) if form.is_valid(): print(form.cleaned_data) else: print(form.errors) 5. 存在的问题:下拉框有更新时无法实时更新到前端页面 解决办法:重写下 __init__(self,*args,**kwargs) 一对多 多对多 三元运算:v = xxx if xxx else [] 找name等于user的input标签 $(‘#f1 input[name="user"]‘).val(‘xxxxx‘) $(‘#f1 .error‘).remove() input标签后提示错误信息: $.each(arg.msg, function(k,v){ var tag = document.createFlement(‘span‘); tag.innerHTML = v[0]; tar.className = "error" # <span class=‘error‘>v[0]</span> $(‘#f1 input[name="‘ + k + ‘user"]‘).after(tag) }) 定律: 如果用模态对话框进行添加或者修改等操作,则前端提交数据需要用ajax,后端可以用django的form组件 因为ajax不刷新,所以可以利用form组件的验证功能,生成HTML的功能可用可不用 如果用新的页面进行添加或者修改等操作,则用ajax和form表单提交都可以;后端可以用django的form组件 因为form有刷新,生成HTML的功能要使用,不可以自己手写HTML标签,避免之前的数据因为刷新导致丢失; 4. 缓存 5. 中间件 6. 信号(scrapy爬虫框架里有用) 7. Django的 Admin JS跳转页面的方法:location.href = ‘/index.html‘ day20作业: 还是主机管理 1. 登录 + 注册(ajax+form组件) FBV / CBV 都可以 2. 业务线管理(列表、增加、修改 页面跳转) 模态确认删除 单表 3. 主机管理(列表、增加、修改 页面跳转) 模态确认删除 一对多 4. 用户管理(列表、增加、修改 页面跳转) 模态确认删除 多对多 5. 自定义分页 6. Bootstrap 把课上讲的内容搞明白,还有几个之前忘记的点,今天老师写的; 然后开始做作业; 周六上午,整理出遇到的问题; # shell in a box 插件 # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ day21课上笔记 .. 2017-09-17 课前回顾: - Form组件验证 参考:http://www.cnblogs.com/wupeiqi/articles/6144178.html - 先写好类: class LoginForm(Form): user = fields.CharField(...) emial = fields.EmailField(...) ... - 使用之添加用户 - 先实例化一个对象:form = LoginForm() 前端:{{ form.user }} -> <input type=‘text‘ name=‘user‘ /> - 等待用户输入,提交 -添加,POST form = LoginForm(data=request.POST) form.is_valid() form.cleaned_data form.errors """这里需要再练习下!!!""" - 修改用户:先发一个GET请求,/edit_user/user_id obj = models.UserInfo.objects.get(id=user_id) form = LoginForm(initial={‘user‘:obj.user}) {{form.user}} -> <input type=‘text‘ name=‘user‘ value=‘数据库中的用户名‘ /> 等待用户输入内容,提交 - 修改用户:再发一个POST请求,/edit_user/user_id form = LoginForm(data=request.POST) form.is_valid() form.cleaned_data: models.UserInfo.objects.filter(id=user_id).update(**cleaned_data) - 下拉框无法自动刷新的问题: ‘‘‘ dp_id = fields.ChoiceField( required=True, choices = [], ) def __init__(self, *args, **kwargs): # 找到类中的所有静态字段,然后拷贝并且赋值给 self.fields super(UserInfoForm,self).__init__(*args, **kwargs) self.fields[‘dp_id‘].choices = models.Depart.objects.values_list(‘id‘, ‘title‘) ‘‘‘ - FBV & CBV(用到了反射) - 序列化 - Django内置的 serializers - json.dumps(xxx,cls=JsonCustomEncoder) - JsonCustomEncoder 在这个函数里自定义一些规则 day21今日内容 - Form组件进行验证之进阶 ‘‘‘ import re from django.forms import Form from django.forms import fields from django.forms import widgets from app01 import models from django.core.validators import RegexValidator from django.core.exceptions import ValidationError ‘‘‘ - 自定义验证规则方式一:使用RegexValidator对象 ‘‘‘ 自定义验证规则方式一:通过RegexValidator对象 phone = fields.CharField( required=True, validators=[RegexValidator(r‘^[0-9]+$‘, ‘请输入数字‘), RegexValidator(r‘^159[0-9]+$‘, ‘数字必须以159开头‘)], ) ‘‘‘ - 自定义验证规则方式二:自定义验证函数 ‘‘‘ # 自定义验证规则函数,优点是可以有数据库操作 def phone_validate(value): mobile_re = re.compile(r‘^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$‘) if not mobile_re.match(value): raise ValidationError(‘手机号码格式错误‘) if models.UserInfo.objects.filter(phone=value).count(): raise ValidationError(‘手机号码已经存在‘) phone = fields.CharField(validators=[phone_validate,]) ‘‘‘ - 自定义验证规则方式三:在当前类中使用钩子函数,函数名称必须符合 "clean_字段名称" ‘‘‘ phone = fields.CharField() # 钩子方法 def clean_phone(self,): """ 只能取当前字段的值,切勿取其他的值 必须得有返回值 :return: """ # 需要要验证的值,自己写正则进行验证 # 去取用户提交的值 value = self.cleaned_data[‘phone‘] mobile_re = re.compile(r‘^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$‘) if not mobile_re.match(value): raise ValidationError(‘手机号码格式错误‘) if models.UserInfo.objects.filter(phone=value).count(): raise ValidationError(‘手机号码已经存在‘) return value ‘‘‘ - 是否可以共存? 可以 - 顺序是怎样的? # 看源码 1. form.is_valid() 2. self.errors 3. self.full_clean() - self._clean_fields() # 即对 self.fields 进行循环验证,依次验证每一个字段: 先执行自己字段的正则,再执行钩子函数 先执行自己字段的正则,再执行钩子函数 ... - self._clean_form() # 字段都验证完后,再对整个form进行验证: - self.clean() - self.add_error(self, field, error) # 如果有错误则把错误加到add_error里 ‘‘‘ class RegisterForm(Form): name = fields.CharField( widget=widgets.TextInput(attrs={‘class‘: ‘c1‘}) ) email = fields.EmailField( widget=widgets.EmailInput(attrs={‘class‘:‘c1‘}) ) phone = fields.CharField( widget=widgets.Textarea(attrs={‘class‘:‘c1‘}) ) pwd = fields.CharField( widget=widgets.PasswordInput(attrs={‘class‘:‘c1‘}) ) pwd_confirm = fields.CharField( widget=widgets.PasswordInput(attrs={‘class‘: ‘c1‘}) ) # 写在RegisterForm类里作用于 RegisterForm # 以用户注册为例:用户输入密码,再次输入密码,这时候需要对两次密码进行比对 def clean(self): pwd = self.cleaned_data[‘pwd‘] pwd_confirm = self.cleaned_data[‘pwd_confirm‘] if pwd == pwd_confirm: return self.cleaned_data else: from django.core.exceptions import ValidationError self.add_error(‘pwd‘, ValidationError(‘密码输入不一致‘)) self.add_error(‘pwd_confirm‘, ValidationError(‘密码输入不一致‘)) return self.cleaned_data ‘‘‘ - self._post_clean() # 等同于 self._clean_form() ,可忽略 ‘‘‘ # 源代码如下: def is_valid(self): """ Returns True if the form has no errors. Otherwise, False. If errors are being ignored, returns False. """ return self.is_bound and not self.errors @property def errors(self): "Returns an ErrorDict for the data provided for the form" if self._errors is None: self.full_clean() return self._errors def full_clean(self): """ Cleans all of self.data and populates self._errors and self.cleaned_data. """ self._errors = ErrorDict() if not self.is_bound: # Stop further processing. return self.cleaned_data = {} # If the form is permitted to be empty, and none of the form data has # changed from the initial data, short circuit any validation. if self.empty_permitted and not self.has_changed(): return self._clean_fields() self._clean_form() self._post_clean() def _clean_fields(self): for name, field in self.fields.items(): # value_from_datadict() gets the data from the data dictionaries. # Each widget type knows how to retrieve its own data, because some # widgets split data over several HTML fields. if field.disabled: value = self.get_initial_for_field(field, name) else: value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name)) try: if isinstance(field, FileField): initial = self.get_initial_for_field(field, name) value = field.clean(value, initial) else: value = field.clean(value) self.cleaned_data[name] = value if hasattr(self, ‘clean_%s‘ % name): value = getattr(self, ‘clean_%s‘ % name)() self.cleaned_data[name] = value except ValidationError as e: self.add_error(name, e) def _clean_form(self): try: cleaned_data = self.clean() except ValidationError as e: self.add_error(None, e) else: if cleaned_data is not None: self.cleaned_data = cleaned_data def clean(self): """ Hook for doing any extra form-wide cleaning after Field.clean() has been called on every field. Any ValidationError raised by this method will not be associated with a particular field; it will have a special-case association with the field named ‘__all__‘. """ return self.cleaned_data ‘‘‘ # 添加新用户demo ‘‘‘ # models.py class Depart(models.Model): """部门表""" title = models.CharField(max_length=32) # 数据库里就是string类型 class Meta: verbose_name_plural = "部门表" class UserInfo(models.Model): """用户表""" name = models.CharField(max_length=32) email = models.CharField(max_length=32) phone = models.CharField(max_length=32) pwd = models.CharField(max_length=64) dp = models.ForeignKey(to=‘Depart‘,to_field=‘id‘) # forms.py import re from django.forms import Form from django.forms import fields from django.forms import widgets from app01 import models from django.core.validators import RegexValidator from django.core.exceptions import ValidationError class UserInfoForm(Form): name = fields.CharField( required=True, min_length=2, max_length=12, error_messages={‘required‘: ‘用户名不能为空‘}, widget=widgets.TextInput(attrs={‘class‘: ‘form-control‘}) ) # 用户提交数据是字符串 pwd = fields.CharField( required=True, min_length=2, max_length=12, error_messages={‘required‘: ‘密码不能为空‘}, widget=widgets.PasswordInput(attrs={‘class‘: ‘form-control‘}) ) pwd_confirm = fields.CharField( required=True, min_length=2, max_length=12, error_messages={‘required‘: ‘确认密码不能为空‘}, widget=widgets.PasswordInput(attrs={‘class‘: ‘form-control‘}) ) email = fields.EmailField( required=True, error_messages={‘required‘: ‘邮箱不能为空‘, ‘invalid‘: ‘邮箱格式错误‘}, widget=widgets.TextInput(attrs={‘class‘: ‘form-control‘}) ) # 自定义验证规则方式三:在当前类的方法中:clean_字段名称 dp_id = fields.ChoiceField( choices=[], error_messages={‘required‘: ‘请选择归属部门‘}, widget=widgets.Select(attrs={‘class‘: ‘form-control‘}) ) phone = fields.CharField(error_messages={‘required‘: ‘手机号不能为空‘}) # 钩子方法 def clean_phone(self,): """ 只能取当前字段的值,切勿取其他的值 必须得有返回值 :return: """ # 需要要验证的值,自己写正则进行验证 # 去取用户提交的值 value = self.cleaned_data[‘phone‘] mobile_re = re.compile(r‘^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$‘) if not mobile_re.match(value): raise ValidationError(‘手机号码格式错误‘) if models.UserInfo.objects.filter(phone=value).count(): raise ValidationError(‘手机号码已经存在‘) return value def __init__(self, *args, **kwargs): # 找到类中的所有静态字段,然后拷贝并且赋值给 self.fields super(UserInfoForm,self).__init__(*args, **kwargs) # self.fields[‘dp_id‘].chioces = models.Depart.objects.all() self.fields[‘dp_id‘].choices = models.Depart.objects.values_list(‘id‘, ‘title‘) def clean(self): print(self.cleaned_data,‘=====================‘) pwd = self.cleaned_data.get(‘pwd‘) pwd_confirm = self.cleaned_data.get(‘pwd_confirm‘) if pwd == pwd_confirm: return self.cleaned_data else: from django.core.exceptions import ValidationError # self.add_error(‘pwd‘, ValidationError(‘密码输入不一致‘)) self.add_error(‘pwd_confirm‘, ValidationError(‘密码输入不一致‘)) return self.cleaned_data # add_user.html <form method="POST" novalidate> {% csrf_token %} <p> 用户名:{{ form.name }} {{ form.errors.name.0 }} </p> <p> 密码:{{ form.pwd }} {{ form.errors.pwd.0 }} </p> <p> 确认密码:{{ form.pwd_confirm }} {{ form.errors.pwd_confirm.0 }} </p> <p> 邮箱:{{ form.email }} {{ form.errors.email.0 }}</p> <p> 手机:{{ form.phone }} {{ form.errors.phone.0 }}</p> <p> 部门:{{ form.dp_id }} {{ form.errors.dp_id.0 }}</p> <input type="submit" value="提交" /> </form> # views.py def add_user(request): if ‘GET‘ == request.method: form = UserInfoForm() return render(request, ‘add_user.html‘, {‘form‘: form}) else: form = UserInfoForm(data=request.POST) if form.is_valid(): form.cleaned_data.pop(‘pwd_confirm‘) models.UserInfo.objects.create(**form.cleaned_data) return redirect(‘/userinfo/‘) return render(request, ‘add_user.html‘, {‘form‘: form}) ‘‘‘ - 常用插件 参考:http://www.cnblogs.com/wupeiqi/articles/6144178.html from django.forms import widgets # 单选:select # city = fields.ChoiceField( # choices=[(0,"上海"),(1,‘北京‘)], # widget=widgets.Select(attrs={‘class‘: ‘c1‘}) # ) # 多选:select # city = fields.MultipleChoiceField( # choices=[(1,"上海"),(2,‘北京‘)], # widget=widgets.SelectMultiple(attrs={‘class‘: ‘c1‘}) # ) # 单选:checkbox # city = fields.CharField( # widget=widgets.CheckboxInput() # ) # 多选:checkbox # city = fields.MultipleChoiceField( # choices=((1, ‘上海‘), (2, ‘北京‘),), # widget=widgets.CheckboxSelectMultiple # ) # 单选:radio # city = fields.CharField( # initial=2, # widget=widgets.Radioselect(choices=((1,‘上海‘),(2,‘北京‘),)) # ) - 初始化的时候赋值,包括单值和多值 注意:写默认值时,多选的值对应一个列表 - 中间件 - 中间件的执行时机:请求到来和请求返回时执行 - 中间件就是一个类,里面有2个方法(也可以没有): process_request(self,request) 默认不写return,这本质上是 return None process_reponse(self,request,response) 必须要写 return response 另外还有: # 依次处理完所有request之后就进行路由匹配,然后再跳转到开头依次进行process_view函数处理 process_view(self, request, callback, callback_args, callback_kwargs) # 捕获异常,自定义返回页面 process_exception(self,request,exception) # Django中间件处理流程 """ 1. 循环中间件MIDDLEWARE列表里的中间件,依次执行每个中间件类的process_request(self,request) 2. 进行路由匹配,拿到对应的视图函数 3. 再次循环中间件MIDDLEWARE列表里的中间件,再从头依次执行每个中间件类的process_view(self, request, callback, callback_args, callback_kwargs) 4. 执行对应的视图函数 5. 逆序循环MIDDLEWARE列表里的中间件,依次执行每个中间件类的process_exception(self,request,exception) 6. 再次逆序循环MIDDLEWARE列表里的中间件,依次执行每个中间件类的process_template_response(...) 7. 再次逆序循环MIDDLEWARE列表里的中间件,依次执行每个中间件类的process_reponse(self,request,response) """ - 应用: - 记录访问日志 - 判断用户是否登录 # 中间件要继承 MiddlewareMixin类 ‘‘‘ class MiddlewareMixin(object): def __init__(self, get_response=None): self.get_response = get_response super(MiddlewareMixin, self).__init__() def __call__(self, request): response = None if hasattr(self, ‘process_request‘): response = self.process_request(request) if not response: response = self.get_response(request) if hasattr(self, ‘process_response‘): response = self.process_response(request, response) return response ‘‘‘ ‘‘‘ HttpRequest.META 一个标准的Python 字典,包含所有的HTTP 头部。具体的头部信息取决于客户端和服务器,下面是一些示例: CONTENT_LENGTH —— 请求的正文的长度(是一个字符串)。 CONTENT_TYPE —— 请求的正文的MIME 类型。 HTTP_ACCEPT —— 响应可接收的Content-Type。 HTTP_ACCEPT_ENCODING —— 响应可接收的编码。 HTTP_ACCEPT_LANGUAGE —— 响应可接收的语言。 HTTP_HOST —— 客服端发送的HTTP Host 头部。 HTTP_REFERER —— Referring 页面。 HTTP_USER_AGENT —— 客户端的user-agent 字符串。 QUERY_STRING —— 单个字符串形式的查询字符串(未解析过的形式)。 REMOTE_ADDR —— 客户端的IP 地址。 REMOTE_HOST —— 客户端的主机名。 REMOTE_USER —— 服务器认证后的用户。 REQUEST_METHOD —— 一个字符串,例如"GET" 或"POST"。 SERVER_NAME —— 服务器的主机名。 SERVER_PORT —— 服务器的端口(是一个字符串)。 ‘‘‘ # 中间件示例1 ‘‘‘ # settings.py MIDDLEWARE = [ ... ‘middle.middleware.my_middleware1‘, ‘middle.middleware.my_middleware2‘, ] # ..\\middle\\middleware.py from django.shortcuts import render,HttpResponse,redirect from django.utils.deprecation import MiddlewareMixin class my_middleware1(MiddlewareMixin): def process_request(self,request): print(‘my_middleware1.process_request...‘) print(request.path) print(request.path_info) print(request.method) print(request.META[‘REMOTE_ADDR‘]) print(request.META[‘REMOTE_HOST‘]) print(request.META[‘REQUEST_METHOD‘]) print(request.META[‘HTTP_USER_AGENT‘]) print(request.META[‘HTTP_HOST‘]) def process_response(self,request,response): print(‘my_middleware1.process_response...‘) return response class my_middleware2(MiddlewareMixin): def process_request(self,request): print(‘my_middleware2.process_request...‘) def process_response(self,request,response): print(‘my_middleware2.process_response...‘) return response ‘‘‘ # 中间件示例2 : 代替登录验证装饰器 ‘‘‘ class my_middleware2(MiddlewareMixin): def process_request(self,request): if ‘/login/‘ == request.path_info: return None user_info = request.session.get(‘user_info‘) if not user_info: return redirect(‘/login/‘) def process_response(self,request,response): print(‘my_middleware2.process_response...‘) return response ‘‘‘ - Django的缓存 参考:http://www.cnblogs.com/wupeiqi/articles/5246483.html - 配置(默认不支持redis) - 开发调试,相当于没有 - 本机内存中 ‘‘‘ # 此缓存将内容保存至内存的变量中 # 配置: CACHES = { ‘default‘: { ‘BACKEND‘: ‘django.core.cache.backends.locmem.LocMemCache‘, ‘LOCATION‘: ‘unique-snowflake‘, } } ‘‘‘ - 本地文件中 - 数据库中 - Memcached - 使用 - 全局 MIDDLEWARE = [ ‘django.middleware.cache.UpdateCacheMiddleware‘, # 其他中间件... ‘django.middleware.cache.FetchFromCacheMiddleware‘, ] CACHE_MIDDLEWARE_SECONDS = 10 # 设置缓存时间 - 视图函数 from django.views.decorators.cache import cache_page @cache_page(60 * 15) def xxx(request): pass - 局部模板 a. 引入TemplateTag {% load cache %} b. 使用缓存 {% cache 5000 缓存key %} 缓存内容 {% endcache %} - 信号 参考:http://www.cnblogs.com/wupeiqi/articles/5246483.html Django中提供了“信号调度”,用于在框架执行操作时解耦。 通俗来讲,就是一些动作发生的时候,信号允许特定的发送者去提醒一些接受者。 需求:在对数据库做增加操作时,记录操作日志 推荐把信号的注册写到 项目目录下的 __init__()函数里 ‘‘‘ # Django内置信号 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 # 创建数据库连接时,自动触发 ‘‘‘ ‘‘‘ 信号模块引入位置: 在项目文件加下的 __init__.py 文件里引入,并注册相关操作 信号模块引入方式: from django.core.signals import request_finished from django.core.signals import request_started from django.core.signals import got_request_exception from django.db.models.signals import class_prepared from django.db.models.signals import pre_init, post_init from django.db.models.signals import pre_save, post_save from django.db.models.signals import pre_delete, post_delete from django.db.models.signals import m2m_changed from django.db.models.signals import pre_migrate, post_migrate from django.test.signals import setting_changed from django.test.signals import template_rendered from django.db.backends.signals import connection_created ‘‘‘ # 信号示例:旨在对数据库执行插入数据之前和之后触发各自的操作 ‘‘‘ from django.db.models.signals import pre_save, post_save def pre_save_callback(sender, **kwargs): print(">>>>>>>>>>> pre_save") print(sender) print(kwargs) def post_save_callback(sender, **kwargs): print("################# post_save") print(sender) print(kwargs) pre_save.connect(pre_save_callback) post_save.connect(post_save_callback) ‘‘‘ # 执行结果: ‘‘‘ [18/Sep/2017 22:20:32] "POST /add_user/ HTTP/1.1" 200 1268 >>>>>>>>>>> pre_save <class ‘app01.models.UserInfo‘> {‘signal‘: <django.db.models.signals.ModelSignal object at 0x0000000002CE79E8>, ‘instance‘: <UserInfo: UserInfo object>, ‘using‘: ‘default‘, ‘raw‘: False, ‘update_fields‘: None} ################# post_save <class ‘app01.models.UserInfo‘> {‘signal‘: <django.db.models.signals.ModelSignal object at 0x0000000002CE7A90>, ‘instance‘: <UserInfo: UserInfo object>, ‘update_fields‘: None, ‘using‘: ‘default‘, ‘created‘: True, ‘raw‘: False} [18/Sep/2017 22:20:49] "POST /add_user/ HTTP/1.1" 302 0 ‘‘‘ - Admin Django内置的Admin是对于model中对应的数据表进行增删改查提供的组件 # 创建超级用户 ‘‘‘ D:\\soft\\work\\Python_17\\day21\\form_demo>python manage.py createsuperuser Username (leave blank to use ‘liulixin‘): root Email address: Password: Password (again): This password is too short. It must contain at least 8 characters. Password: Password (again): >>>>>>>>>>> pre_save <class ‘django.contrib.auth.models.User‘> {‘signal‘: <django.db.models.signals.ModelSignal object at 0x0000000002CEA7F0>, ‘using‘: ‘default‘, ‘instance‘: <User: root>, ‘ raw‘: False, ‘update_fields‘: None} ################# post_save <class ‘django.contrib.auth.models.User‘> {‘signal‘: <django.db.models.signals.ModelSignal object at 0x0000000002CEA898>, ‘created‘: True, ‘update_fields‘: None, ‘instan ce‘: <User: root>, ‘using‘: ‘default‘, ‘raw‘: False} Superuser created successfully. D:\\soft\\work\\Python_17\\day21\\form_demo> ‘‘‘ 更多admin做好的接口参考:http://www.cnblogs.com/wupeiqi/articles/7444717.html Django提供的对数据库的操作,admin.py 和 models.py 配合 ‘‘‘ class Depart(models.Model): """部门表""" title = models.CharField(max_length=32) # 数据库里就是string类型 def __str__(self): return self.title class Meta: verbose_name_plural = "部门表" class UserInfo(models.Model): """用户表""" name = models.CharField(max_length=32) # email = models.EmailField(max_length=32) email = models.CharField(max_length=32) phone = models.CharField(max_length=32) pwd = models.CharField(max_length=64) dp = models.ForeignKey(to=‘Depart‘,to_field=‘id‘) def __str__(self): return self.name class Meta: verbose_name_plural = "用户表" ‘‘‘ 在admin.py中只需要将Mode中的某个类注册,即可在Admin中实现增删改查的功能,如: ‘‘‘ # admin.py from django.contrib import admin from app01 import models # Register your models here. admin.site.register(models.UserInfo) admin.site.register(models.Depart) ‘‘‘ 但是,这种方式比较简单,如果想要进行更多的定制操作,需要利用ModelAdmin进行操作,如: ‘‘‘ # admin.py from django.contrib import admin from app01 import models # Register your models here. class DepartAdmin(admin.ModelAdmin): list_display = [‘id‘,‘title‘] search_fields = [‘title‘, ] admin.site.register(models.Depart,DepartAdmin) class UserInfoAdmin(admin.ModelAdmin): list_display = [‘id‘,‘name‘,‘email‘,‘phone‘,‘dp‘] search_fields = [‘name‘, ] admin.site.register(models.UserInfo,UserInfoAdmin) ‘‘‘ - ModelForm - BBS项目练习 - 前端知识捡一下 - 各个小功能的设计和实现 用户表 用户名 密码 邮件(可为空) 新闻表 标题 简介 url 新闻类型 发布者 发布时间 新闻图表 点赞个数 点赞时要更新字段,Django的F实现自增1 评论个数 新闻类型表 类型名 点赞记录表 评论记录表 """ day22 """ """ 十一作业:CMDB - 采集资产,就是一个py文件,执行一个命令,通过正则取结果:{...} - API: http://www.qiyi.domain/xxx.html - POST请求,request.post(API,data={...}) - Django程序 - url:/assert.html --> assert - def assert(request): request.POST 写到数据库 - index.html --> index 展示数据 - 动态的左侧菜单:多级评论 """ """ day22 今日内容: - 新闻列表 - 点赞(2次数据库操作,所以有可能存在脏数据) - 事物 - 点赞+1的小动画 - 多级评论 - Python中的引用(指针) - 递归:深度优先搜索 - 上传文件 - 基于FormData对象 - 伪ajax,兼容性更好 - 分页 - request.GET - 添加完/修改完 跳转回原来的页面 """ 今日内容: - 反查 - related_name 用于定义反向关联是的名称 - related_query_name class A: title = models.CharField() class B: xxxx = xxxx fk1 = models.ForeignKey(related_name=‘xxx‘) fk2 = models.ForeignKey(related_query_name=‘xxx‘) fk3 = models.ManyToMany(related_name=‘xxx‘) - ManyToMany 的 througth_fields 字段是有顺序要求的 - 点赞 - 事物 from django.db import transaction with transaction.atomic(): pass - 返回值 - 面向对象 - base + 继承 - json.dumps(对象.__dict__) - +/-动画 - 在ajax操作时候,回调函数中的 $(this) 已经不是原来的 $(this) - css - position: fixed,absolute,relative - setInterval定时器 - setTimeout 只执行一次 - JS的闭包 """ var obj = setInterval(function () { fontSize =+ 5; top -= 5; right -= 5; opacity -= 0.1; tag.style.fontSize = fontSize + "px"; tag.style.top = top; tag.style.right = right; tag.style.opacity = opacity; // 终止定时器 if(opacity <= 0){ clearInterval(obj); tag.remove(); } },100); """ - 字典和列表的结合查找 li = [ {‘user‘:‘xxx‘,‘pwd‘:‘xxx‘,‘id‘:1}, {‘user‘:‘xxx‘,‘pwd‘:‘xxx‘,‘id‘:2}, {‘user‘:‘xxx‘,‘pwd‘:‘xxx‘,‘id‘:3}, {‘user‘:‘xxx‘,‘pwd‘:‘xxx‘,‘id‘:4}, ] dic = {} for item in li: dic[item[‘id‘]] = item """ 练习题: li = [ {‘user‘:‘xxx‘,‘pwd‘:‘xxx‘,‘id‘:1,"children":[],‘parent_id‘:None}, {‘user‘:‘xxx‘,‘pwd‘:‘xxx‘,‘id‘:2,"children":[],‘parent_id‘:None}, {‘user‘:‘xxx‘,‘pwd‘:‘xxx‘,‘id‘:3,"children":[],‘parent_id‘:1}, {‘user‘:‘xxx‘,‘pwd‘:‘xxx‘,‘id‘:4,"children":[],‘parent_id‘:2}, {‘user‘:‘xxx‘,‘pwd‘:‘xxx‘,‘id‘:5,"children":[],‘parent_id‘:1}, {‘user‘:‘xxx‘,‘pwd‘:‘xxx‘,‘id‘:6,"children":[],‘parent_id‘:3}, ] #结果: result = [ {‘user‘:‘xxx‘,‘pwd‘:‘xxx‘,‘id‘:1,"children":[{‘user‘:‘xxx‘,‘pwd‘:‘xxx‘,‘id‘:3,"children":[ {‘user‘:‘xxx‘,‘pwd‘:‘xxx‘,‘id‘:6,"children":[],‘parent_id‘:3},],‘parent_id‘:1},{‘user‘:‘xxx‘,‘pwd‘:‘xxx‘,‘id‘:5,"children":[],‘parent_id‘:1},],‘parent_id‘:None}, {‘user‘:‘xxx‘,‘pwd‘:‘xxx‘,‘id‘:2,"children":[{‘user‘:‘xxx‘,‘pwd‘:‘xxx‘,‘id‘:4,"children":[],‘parent_id‘:2},],‘parent_id‘:None}, ] """ - 多级评论 {{ comment_list|safe }} - 字典,列表 通过引用赋值,一个修改,全部都改; - 递归 和左侧菜单原理是一样的 给评论绑定点击事件 - 发送ajax请求 - 把新闻ID传给后台,查询当前新闻下面的所有评论(models.xxx.objects.values(...)) - 后台渲染HTML,返回给前端 或者 把数据字典返回给前端,让前端来递归生成多级结构 应用:菜单 上传文件并预览 - 基于FormData - 缺点:兼容性不好 - 优点:ajax直接发送 - 伪造发送ajax - iframe标签 天生局部刷新 向其他网址发数据,页面不刷新 - form表单 天生整体刷新 - 如果是普通的POST: - ajax - 伪ajax - 如果是上传文件: - 伪ajax a标签包含input的file标签,同时把input标签的透明度置为0 其他: - 如何通过Python代码发送POST数据? import requests requests.get() requests.post() - 两种发送数据的方式(区别是请求头里的content-type不一样): - data - json -> content-type:application/json (django 不处理这个类型,所以request.POST 是空的,需要去request.body) ‘‘‘ import requests # response = requests.get(‘http://127.0.0.1:8003/asset.html‘) # print(response.text) data_dict = { ‘k1‘:‘v1‘, ‘k2‘:‘v2‘ } # content-type: application/x-www-form-urlencoded # response = requests.post(‘http://127.0.0.1:8003/asset.html‘,data=data_dict) # print(response.text) # 采集资产并且汇报 # content-type: appcation/json response = requests.post(‘http://127.0.0.1:8003/asset.html‘,json=data_dict) print(response.text) ‘‘‘ - 如何保留原来页面的条件 request.GET request.GET.urlencode() from django.http.request import QueryDict """mutable这个值默认是False,是不允许修改的""" obj = QueryDict(mutable=True) obj[‘xxxxxx‘] = request.GET.urlencode() url_param = obj.urlencode() request.GET.get(‘xxxxxx‘) 要点:发送POST请求的时候不要写action,还会提交到当前页面 作业: - 评论用js递归构造实现多级 - 尝试去做CMDB - agent 收集服务器相关数据,发送到API;用json汇报 - server 提供API给agent,接收数据,然后存储,最后页面展示(表的增删改查,要保留原来地址) - 展示的时候加上左侧菜单 - 菜单扩展: - 默认展开 ################################################################################################## """ day23 - 爬虫部分 """ 爬虫相关 - 基本操作 - 概要 - 发送http请求 requests模块 - 提取指定信息 正则 Beautifulsoup模块 - 数据持久化 - Python的2个模块 - requests - Beautifulsoup - Http请求相关知识 - 请求 - 请求头 - cookie - 请求体 - 发送的内容 - 响应 - 响应头 - 浏览器读取 - 响应体 - 看到的内容 - 特殊 - cookie - csrf_token - content-type 用来指定客户端按照哪种格式进行解析 - 性能相关 - 进程 - 线程 - 协程 - 【协程】异步非阻塞:充分利用系统资源 - scrapy框架 - 学习scrapy的规则 - redis&scrapy组件:完成一个简单的分布式爬虫 内容详细 - 基本操作 Python伪造浏览器发送请求 pip3 install requests pip3 install Beautifulsoup4 import requests from bs4 import BeautifulSoup response = requests.get("http://www.baidu.com") response.text -> 网页内容 soup = Beautifulsoup(response.text,‘html.parse‘) # 从上到下第一个 <h3 class=‘t‘> 标签 soup.find(name=‘h3‘,attrs={‘class‘:‘t‘}) # 查找全部 <h3>标签 soup.find_all(name=‘h3‘) ... 模块 requests response = requests.get(url=‘url路径‘) # 解决乱码问题 response.encoding = response.apparent_encoding GET请求: requests.get(url=‘www.baidu.com‘) data = "http GET / ...." requests.get(url=‘www.baidu.com?page=1‘) data = "http GET page=1 ...." requests.get(url=‘www.baidu.com‘,params={‘page‘:1}) POST请求: requests.post(url=‘www.baidu.com‘,data={‘name‘:‘alex‘,‘age‘:18}) # 默认携带请求头类型:application/x-www-form-urlencoded requests.post(url=‘www.baidu.com‘,json={‘name‘:‘alex‘,‘age‘:18}) # 默认携带请求头类型:application/json # POST请求既可以在请求体里传参,又可以在url里传参 requests.post(url=‘www.baidu.com‘,params={‘page‘:1},json={‘name‘:‘alex‘,‘age‘:18}) 补充: django里的 request.POST 里的值是django根据请求体里的数据转换过来的 所以,如果body里的数据格式不对,那么就转换不了,导致request.POST里面没有值 django里的 request.body 里永远有值 django里的 request.POST 可能没有值 BeautifulSoup soup = BeautifulSoup(‘html格式字符串‘,‘html.parser‘) tag = soup.find(name=‘div‘,attrs={...}) tag = soup.find_all(name=‘div‘,attrs={...}) tag.find(‘h3‘).text tag.find(‘h3‘).content tag.find(‘h3‘).get(‘属性名称‘) tag.find(‘h3‘).attrs[‘属性名称‘] 服务器端不能主动给客户端发消息 但是websocket可以 - 【轮询】 http协议,客户端轮询(每秒1次)请求服务端;一次请求,服务端收到后不管有没有新消息都立即返回 - 【长轮询】 http协议,客户端发来请求,服务器把客户端给hang住,直到服务端收到新消息并发送给所有客户端、才断开连接; 客户端收到消息后,再立即发请求到服务端进行下一次hang住。 hang住,有一个超时时间,web微信超时时间是25s 应用:web微信 - 【WebSocket】 不是http协议,建立在tcp之上 一次连接不断开,双工通道,可以互相发送消息 但是浏览器兼容性不太好,以后将会应用的更广泛 浏览器有同源策略 ajax发送跨域请求是接收不到结果的 http://www.cnblogs.com/wupeiqi/articles/6283017.html #!/usr/bin/python # -*- coding:utf-8 -*- import requests requests.request() requests.get(url=‘xxx‘) # 本质上就是: requests.request(method=‘get‘,url=‘xxx‘) import json requests.post(url=‘xxx‘,data={‘name‘:‘alex‘,‘age‘:18}) # content_type: application/x-www-form-urlencoded requests.post(url=‘xxx‘,data="name=alex&age=18") # content_type: application/x-www-form-urlencoded # 不伦不类 requests.post(url=‘xxx‘,data=json.dumps({‘name‘:‘alex‘,‘age‘:18})) # content_type: application/x-www-form-urlencoded # 利用headers参数重写 Content_type requests.post(url=‘xxx‘,data=json.dumps({‘name‘:‘alex‘,‘age‘:18}),headers={‘Content_type‘:‘application/json‘}) # content_type: application/x-www-form-urlencoded requests.post(url=‘xxx‘,json={‘name‘:‘alex‘,‘age‘:18}) # content_type: application/json """ 1.method 2.url 3.params 4.data 5.json 6.headers 7.cookies 8.files 9.auth 10.timeout 11.allow_redirects 12.proxies 13.stream 14.cert =================== session,保存请求相关信息 ================== session = requests.Session() session.get(url=‘xxx‘) session.post(...) """ """ 8.files 用作文件上传 """ file_dict = { ‘f1‘: open(‘readme‘, ‘rb‘) } requests.post(url=‘xxx‘,file=file_dict) # 发送文件,定制文件名 # file_dict = { # ‘f1‘: (‘test.txt‘, open(‘readme‘, ‘rb‘)) # } # requests.request(method=‘POST‘, # url=‘http://127.0.0.1:8000/test/‘, # files=file_dict) # 发送文件,定制文件名 # file_dict = { # ‘f1‘: (‘test.txt‘, "hahsfaksfa9kasdjflaksdjf") # } # requests.request(method=‘POST‘, # url=‘http://127.0.0.1:8000/test/‘, # files=file_dict) """ 9.auth 基本认证 路由器登录 """ from requests.auth import HTTPBasicAuth,HTTPDigestAuth requests.get(‘https://api.github.com/user‘,auth=HTTPBasicAuth(‘gypsying‘,‘password‘)) """ timeout (连接超时,响应超时) """ requests.get(‘http://google.com‘,timeout=3) requests.get(‘http://google.com‘,timeout=(5,1)) """ allow_redirects """ """ proxies 应对IP被封的情况 """ proxyDict = { "http": "61.172.249.96:80", "https": "http://61.185.219.126:3128", } proxies = {‘http://10.20.1.128‘: ‘http://10.10.1.10:5323‘} """ stream """ from contextlib import closing with closing(requests.get(‘xxx‘,stream=True)) as f: for i in f.iter_content(): print(i) requests.put() requests.delete() BeautifulSoup - find() - find_all() - get() - attrs - text soup = BeautifulSoup(‘html格式字符串‘,‘html.parser‘) soup = BeautifulSoup(‘html格式字符串‘,features=‘lxml‘) 第三方,需额外安装,但是速度比‘html.parser‘更快 soup = BeautifulSoup(‘html格式字符串‘,‘html.parser‘) tag = soup.find(attrs={‘class‘:‘c1‘}) tag.name -> 标签名字 tag = soup.find(attrs={‘class‘:‘c1‘}) 等价于: tag = soup.find(class_=‘c1‘) print(tag.attrs) tag.attrs[‘id‘] = 1 del tag.attrs[‘class‘] # attrs 进行增删改查都可以 tag.children 所有孩子 tag.descendants 所有后代 tag.find_all() 包含的所有标签,并且递归了 tag.find_all(recursive=False) 包含的所有标签,不递归 tag.clear() 清空内部元素,保留自己 tag.decompose() 递归删除所有标签,包含自己 res = tag.extract() 相当于字典的pop,其余同decompose() tag = soup.find(class_=‘c1‘) # 对象 tag.decode() # 对象变成字符串 tag.encode() # 对象变成字节 tag.find(‘a‘) # tag = soup.find(‘a‘) # print(tag) # tag = soup.find(name=‘a‘, attrs={‘class‘: ‘sister‘}, recursive=True, text=‘Lacie‘) # tag = soup.find(name=‘a‘, class_=‘sister‘, recursive=True, text=‘Lacie‘) # print(tag) find_all() # tags = soup.find_all(‘a‘) # print(tags) # tags = soup.find_all(‘a‘,limit=1) # print(tags) # tags = soup.find_all(name=‘a‘, attrs={‘class‘: ‘sister‘}, recursive=True, text=‘Lacie‘) # # tags = soup.find(name=‘a‘, class_=‘sister‘, recursive=True, text=‘Lacie‘) # print(tags) # ####### 列表 ####### # v = soup.find_all(name=[‘a‘,‘div‘]) # print(v) # v = soup.find_all(class_=[‘sister0‘, ‘sister‘]) # print(v) # v = soup.find_all(text=[‘Tillie‘]) # print(v, type(v[0])) # v = soup.find_all(id=[‘link1‘,‘link2‘]) # print(v) # v = soup.find_all(href=[‘link1‘,‘link2‘]) # print(v) # ####### 正则 ####### import re # rep = re.compile(‘p‘) # rep = re.compile(‘^p‘) # v = soup.find_all(name=rep) # print(v) # rep = re.compile(‘sister.*‘) # v = soup.find_all(class_=rep) # print(v) # rep = re.compile(‘http://www.oldboy.com/static/.*‘) # v = soup.find_all(href=rep) # print(v) # ####### 方法筛选 ####### # def func(tag): # return tag.has_attr(‘class‘) and tag.has_attr(‘id‘) # v = soup.find_all(name=func) # print(v) # ## get,获取标签属性 # tag = soup.find(‘a‘) # v = tag.get(‘id‘) # print(v) from bs4.element import Tag tag.has_attr() tag.text 等价于 tag.get_text() v = tag.index(tag.find(‘div‘)) tag.text tag.string 也可以获取内容,并扩展了修改内容 tag.string = "xxxx" tag.stripped_strings 相当于join给分割成了list tag.children for item in tag.children: print(item,type(item)) from bs4.element import Tag tag= Tag(name=‘i‘,attrs={‘id‘:‘it‘}) tag.string = "asasasasasasazxzxzx" soup.find(id=‘xxx‘).append(tag) """ 扩展copy模块 """ import copy copy.deepcopy() ... tag.wrap(tag1) tag.unwrap() ++++++++++++++++++++++++++++++++++++ 内容梳理: - 汽车之间新闻爬取示例 - github和抽屉自动登录 以及 登陆后的操作 - requests 和 Beautifulsoup 基本使用 - 轮训和长轮询 - Django 里 content-type问题 request.POST request.body 作业:web微信 1. 二维码显示 2. 长轮询 check_login() :ajax递归 (js递归没有层数限制) 3. 检测是否已经扫码 - 扫码之后201:替换头像 base64:... src="img_path" 或者 src="base64:xxxxxxxx...." - 扫码之后继续轮训,等待用户点击确认 - 点击确认之后,返回200 response.text redirect_url-.... - 获取最近联系人信息 下节课前安装 twsited scrapy框架 ################################################################### day24 Web微信 高性能 scrapy requests.post(data=xxx) -> Form Data requests.post(json=xxx) -> Request Payload HttpResponse() 参数可以是字符串也可以是字节 response.text 字符串 response.content 字节 # 获取最近联系人然后进行初始化 # 获取头像 # 拿联系人列表 response = requests.get(url,cookies=xxx) response.encoding = ‘utf-8‘ print(json.loads(response.text)) Web微信总结 - 头像防盗链 - headers - cookies - 检测请求: - tip | pass_ticket | ... - redirect_url和真实请求的url是否一致 - session 保存关键点的cookies和关键变量值 - qrcode 和 ctime - login_cookie_dict - ticket_dict_cookie - ticket_dict - init_cookie_dict - init_dict - all_cookies = {} - all_cookies.update(...) json序列化的时候是可以加参数的: data = { ‘name‘:‘alex‘, ‘msg‘:‘中文asa‘ } import json print(json.dumps(data)) 按Unicode显示 print(json.dumps(data,ensure_ascii=False)) 按中文显示 json.dumps() 之后是字符串 requests.post() 默认是按照 latin-1 编码,不支持中文 所以改成直接发bytes: requests.post(data=json.dumps(data,ensure_ascii=False).encode(‘utf-8‘)) 发送消息需带上cookies 发送消息 检测是否有新消息到来 接收消息 ######################### 高性能相关 100张图片,下载 使用一个线程完成并发操作 是配合IO多路复用完成的 爬虫: - 简单的爬虫 - requests+bs4+twsited+asyncio - scrapy框架 - 下载页面:twsited - 解析页面:自己的HTML解析 - 可限速 - 去重 - 递归 一层一层的爬,成倍速的增长,还可以限制层数 - 代理 - https - 中间件 ... scrapy - scrapy startproject sp1 - cd sp1 - scrapy genspider baidu baidu.com - scrapy genspider chouti chouti.com - scrapy crawl chouti --nolog - name - allow_dimains - start_urls - parse(self,response) - yield Item 持久化 - yield Request(url,callback) 把url放到调度器队列里 本周作业: 1.Web微信 自己去写,写完跟老师的对比 2.高性能总结文档(源码示例+文字理解) 3.任意找一个网站,用Scrapy去爬 - 煎蛋 - 拉钩 - 知乎 - 抽屉 - ... 不遵循爬虫规范: ROBOTSTXT_OBEY = False 可能会失败:没有带请求头 起始url:自定义start_requests(self): ################################################################################################ day25 内容回顾: - scrapy - 创建project - 创建爬虫 - 编写: - 类 - start_urls = [‘xx‘] - def pasrse(self,response): yield Item 对象 ---> 进入Pipeline做持久化存储 yield Request对象 ---> 进入调度中心,递归... # 面向对象的封装 - 初开始引擎执行的是父类的 start_requests()方法,可自己重写: def start_requests(self): for url in self.start_urls: yield Request(url=url,callback=self.parse) 或者: return [Request(url=url,callback=self.parse).] """要么返回一个可迭代对象,要么返回一个生成器对象!!!""" - Pipeline - process_item - open_spider - close_spider ... - request对象("地址",回调函数) - 执行 - 高性能相关 - 多线程【适用于IO密集型】和多进程【适用于计算密集型】 - GIL锁 锁的是进程,单个进程只能有一个线程被CPU调度,即使这个CPU有多个核 - 尽可能利用线程: - 一个线程,基于协程: - 协程,greenlet - 遇到IO就切换 - 代表就是 Gevent - 一个线程,基于事件循环: - IO多路复用 - Socket,setBlocking(False) - 代表:Twisted 、tornado 、、、 今日内容: - scrapy - Cookie操作 - Pipeline - 中间件 - 扩展(信号。。。) - 自定义命令 - 其他配置文件 - 扩展:scrapy-redis - Tornado 和 Flash 简单实用(轻量级框架) - 学习基本流程和规则 内容详细: - Scrapy - 创建项目: D:\\soft\\work\\Python_17\\day25>scrapy startproject day25spider New Scrapy project ‘day25spider‘, using template directory ‘d:\\\\soft\\\\work\\\\pyth on35\\\\lib\\\\site-packages\\\\scrapy\\\\templates\\\\project‘, created in: D:\\soft\\work\\Python_17\\day25\\day25spider You can start your first spider with: cd day25spider scrapy genspider example example.com D:\\soft\\work\\Python_17\\day25>cd day25spider D:\\soft\\work\\Python_17\\day25\\day25spider>scrapy genspider chouti chouti.com Created spider ‘chouti‘ using template ‘basic‘ in module: day25spider.spiders.chouti D:\\soft\\work\\Python_17\\day25\\day25spider> - 执行 - scrapy crawl chouti - scrapy crawl chouti --nolog # 面向对象的封装 - 初开始引擎执行的是父类的 start_requests()方法,可自己重写: def start_requests(self): for url in self.start_urls: yield Request(url=url,callback=self.parse) 或者: return [Request(url=url,callback=self.parse).] """要么返回一个可迭代对象,要么返回一个生成器对象!!!""" - 因为内部会有 iter() 的过程,代码见: - from scrapy.crawler import Crawler - 里面的 crawl() 方法,如下: def crawl(self, *args, **kwargs): assert not self.crawling, "Crawling already taking place" self.crawling = True try: self.spider = self._create_spider(*args, **kwargs) self.engine = self._create_engine() start_requests = iter(self.spider.start_requests()) yield self.engine.open_spider(self.spider, start_requests) ... - 引擎拿到url之后放入到调度器里 - 下载器去调度器拿url,进行下载,下载完成之后执行 callback() - 筛选器 hxs = HtmlXPathSelector(response) hxs.xpath(‘//div[@id="i1"]‘) hxs.xpath(‘//div[@id="i1"]/text()‘) hxs.xpath(‘//div[@id="i1"]/@href‘) hxs.xpath(‘//div[@id="i1"]/@href‘).extract() hxs.xpath(‘//div[@id="i1"]/@href‘).extract_first() for url in hxs.xpath(‘//div[@id="i1"]/@href‘).extract(): yield Item(name=url) - cookie - 登录抽屉,自动点赞 from scrapy.http.cookies import CookieJar cookie_jar = CookieJar() cookie_jar.extract_cookies(response, response.request) - Pipeline - 可以写5个方法 - procsss_item() - return item - raise DropItem() - 多个pipeline时,跳过下一个pipeline: from scrapy.exceptions import DropItem() if ENV==‘debug‘: raise DropItem() else: return Item - url去重规则 (利用set集合) """ 默认是有去重规则的: def start_requests(self): for url in self.start_urls: yield Request(url=url,callback=self.parse,dont_filter=False) """ - settings.py 里面加上自己写的插件 DUPEFILTER_CLASS = ‘scrapy.dupefilter.RFPDupeFilter‘ # 默认的去重规则 DUPEFILTER_CLASS = ‘xxx.yyy.‘ # 自定义过滤插件 - 模仿RFPDupeFilter写自己的插件 - url 做 类 md5 操作 (fingerprint...) """ class RepeatUrl: def __init__(self): self.visited_url = set() @classmethod def from_settings(cls, settings): """ 初始化时,调用 :param settings: :return: """ return cls() def request_seen(self, request): """ 检测当前请求是否已经被访问过 :param request: :return: True表示已经访问过;False表示未访问过 """ if request.url in self.visited_url: return True self.visited_url.add(request.url) return False def open(self): """ 开始爬去请求时,调用 :return: """ print(‘open replication‘) def close(self, reason): """ 结束爬虫爬取时,调用 :param reason: :return: """ print(‘close replication‘) def log(self, request, spider): """ 记录日志 :param request: :param spider: :return: """ print(‘repeat‘, request.url) """ - 自定义命令 - spiders同级目录新建 commands 目录 - 创建 crawlall.py from scrapy.command import ScrapyCommand class Foo(ScrapyCommand): ... def run(self): ... - 把 commands 注册到 setting.py - 可迭代对象:具有 __iter__() 方法,并且执行后可以返回迭代器 - 迭代器:具有 __next__() 方法 并且逐一向后取值 - 生成器:函数中具有yield关键字 - 具有 __iter__() 返回的还是自己本身 - 具有 __next__() - 下载中间件 - 方法: - process_request() - process_response() - process_exception() - process_request() 应用 - 自定义下载模块,而不是用scrapy自带的下载模块 - 定制请求头/cookie 避免每次请求都带上同样的重复代码 request.headers[‘Content-Type‘] = "application/x-www-form-urlencoded; charset=UTF-8" - 设置代理 - os.environ - 默认的代理规则: from scrapy.downloadermiddlewares.httpproxy import HttpProxyMiddleware from urllib.request import getproxies - HTTPS - 设置代理需注意: - 默认代理是使用的环境变量 os.environ[‘xxxxxx_proxy‘] 程序启动之前,先设置 os.environ[‘xxx_proxy‘] = "xxxxxxxx" - 自定义代理中间件 """ def to_bytes(text, encoding=None, errors=‘strict‘): if isinstance(text, bytes): return text if not isinstance(text, six.string_types): raise TypeError(‘to_bytes must receive a unicode, str or bytes ‘ ‘object, got %s‘ % type(text).__name__) if encoding is None: encoding = ‘utf-8‘ return text.encode(encoding, errors) class ProxyMiddleware(object): def process_request(self, request, spider): PROXIES = [ {‘ip_port‘: ‘111.11.228.75:80‘, ‘user_pass‘: ‘‘}, {‘ip_port‘: ‘120.198.243.22:80‘, ‘user_pass‘: ‘‘}, {‘ip_port‘: ‘111.8.60.9:8123‘, ‘user_pass‘: ‘‘}, {‘ip_port‘: ‘101.71.27.120:80‘, ‘user_pass‘: ‘‘}, {‘ip_port‘: ‘122.96.59.104:80‘, ‘user_pass‘: ‘‘}, {‘ip_port‘: ‘122.224.249.122:8088‘, ‘user_pass‘: ‘‘}, ] proxy = random.choice(PROXIES) if proxy[‘user_pass‘] is not None: request.meta[‘proxy‘] = to_bytes("http://%s" % proxy[‘ip_port‘]) encoded_user_pass = base64.encodestring(to_bytes(proxy[‘user_pass‘])) request.headers[‘Proxy-Authorization‘] = to_bytes(‘Basic ‘ + encoded_user_pass) print "**************ProxyMiddleware have pass************" + proxy[‘ip_port‘] else: print "**************ProxyMiddleware no pass************" + proxy[‘ip_port‘] request.meta[‘proxy‘] = to_bytes("http://%s" % proxy[‘ip_port‘]) """ - 自定义下载中间件 """ class DownMiddleware1(object): def process_request(self, request, spider): """ 请求需要被下载时,经过所有下载器中间件的process_request调用 :param request: :param spider: :return: None,继续后续中间件去下载; Response对象,停止process_request的执行,开始执行process_response Request对象,停止中间件的执行,将Request重新调度器 raise IgnoreRequest异常,停止process_request的执行,开始执行process_exception """ pass def process_response(self, request, response, spider): """ spider处理完成,返回时调用 :param response: :param result: :param spider: :return: Response 对象:转交给其他中间件process_response Request 对象:停止中间件,request会被重新调度下载 raise IgnoreRequest 异常:调用Request.errback """ print(‘response1‘) return response def process_exception(self, request, exception, spider): """ 当下载处理器(download handler)或 process_request() (下载中间件)抛出异常 :param response: :param exception: :param spider: :return: None:继续交给后续中间件处理异常; Response对象:停止后续process_exception方法 Request对象:停止中间件,request将会被重新调用下载 """ return None """ - 爬虫中间件 - 扩展 - scrapy 已经给埋好点了 - from scrapy import signals """ engine_started = object() engine_stopped = object() spider_opened = object() spider_idle = object() spider_closed = object() spider_error = object() request_scheduled = object() request_dropped = object() response_received = object() response_downloaded = object() item_scraped = object() item_dropped = object() """ """ from scrapy import signals class MyExtension(object): def __init__(self, value): self.value = value @classmethod def from_crawler(cls, crawler): val = crawler.settings.getint(‘MMMM‘) ext = cls(val) crawler.signals.connect(ext.spider_opened, signal=signals.spider_opened) crawler.signals.connect(ext.spider_closed, signal=signals.spider_closed) return ext def spider_opened(self, spider): print(‘open‘) def spider_closed(self, spider): print(‘close‘) """ - HTTPS证书 - 默认 DOWNLOADER_HTTPCLIENTFACTORY = "scrapy.core.downloader.webclient.ScrapyHTTPClientFactory" DOWNLOADER_CLIENTCONTEXTFACTORY = "scrapy.core.downloader.contextfactory.ScrapyClientContextFactory" - 自定义HTTPS证书 DOWNLOADER_HTTPCLIENTFACTORY = "scrapy.core.downloader.webclient.ScrapyHTTPClientFactory" DOWNLOADER_CLIENTCONTEXTFACTORY = "step8_king.https.MySSLFactory" """ from scrapy.core.downloader.contextfactory import ScrapyClientContextFactory from twisted.internet.ssl import (optionsForClientTLS, CertificateOptions, PrivateCertificate) class MySSLFactory(ScrapyClientContextFactory): def getCertificateOptions(self): from OpenSSL import crypto v1 = crypto.load_privatekey(crypto.FILETYPE_PEM, open(‘/Users/wupeiqi/client.key.unsecure‘, mode=‘r‘).read()) v2 = crypto.load_certificate(crypto.FILETYPE_PEM, open(‘/Users/wupeiqi/client.pem‘, mode=‘r‘).read()) return CertificateOptions( privateKey=v1, # pKey对象 certificate=v2, # X509对象 verify=False, method=getattr(self, ‘method‘, getattr(self, ‘_ssl_method‘, None)) """ - 设置请求头的方法: - 每次一次 yield Request(headers={...}) - 在下载中间件里分门别类的自定义headers - 在settings.py里粗暴的统一定义好一个headers - 多个爬虫的情况: - 去重规则写在哪? - 深度优先/广度优先 规则写在哪? 调度器 - 其他 : http://www.cnblogs.com/wupeiqi/articles/6229292.html - 分布式爬虫:scrapy-redis组件 - 存储 - 调度器 - 去重规则 # 是调度器触发了去重规则 - 流程 - 连接redis - 指定调度器时,调用了去重规则的 request_seen()方法 - scrapy-redis参考:http://www.cnblogs.com/wupeiqi/articles/6912807.html - Django 自己没有写socket,而是使用的 WSGI协议 (代表是 wsgiref) - Flask Web框架 参考:http://www.cnblogs.com/wupeiqi/articles/7552008.html - Web框架 - 路由 - 视图 - 模板渲染 - Flask 自己也没写socket,而是依赖实现WSGI协议的模块:Werkzeug """ from flask import Flask app = Flask(__name__) @app.route(‘/‘) def hello_world(): return ‘Hello World!‘ if __name__ == ‘__main__‘: app.run() """ - @app.route(‘/‘) 返回了一个函数 func - 执行 func(hello_world) - url两种添加方式: - 方式一 @app.route(‘/‘) def hello_world(): return ‘Hello World!‘ - 方式二 def index(): return ‘Hello World!‘ add_url_rule(‘/index‘,view_func=index) - 路由系统 - 固定 @app.route(‘/xxx/‘) def hello_world(): return ‘Hello World!‘ - 不固定 """ Flask只支持如下几种不固定的url匹配: @app.route(‘/user/<username>‘) @app.route(‘/post/<int:post_id>‘) @app.route(‘/post/<float:post_id>‘) @app.route(‘/post/<path:path>‘) @app.route(‘/login‘, methods=[‘GET‘, ‘POST‘]) """ @app.route(‘/xxx/<int:uid>‘) def hello_world(uid): return ‘Hello World!‘ + str(uid) - Flask本身不支持正则匹配,需要自定制正则规则 - 反向生成url: url_for(‘index‘) @app.route(‘/index.htm‘) def index(): return "首页" - session加密之后放到浏览器的cookie里,server端不保存 - 第三方插件: Flask-Session - obj() 就是调用类的 __call__() 方法 - 蓝图:文件夹的堆放 """ 重要 """ - HTTP method 有多少种?分别是什么? 看django的CBV里面的源码 - Tornado ############################################################################# day26 xxx公司的CRM项目 - 项目概要 - 权限管理 (写成一个公共的组件/app,适用于Django项目) ***** - low的增删改查 *** - 增删改查的组件 (写成公共组件/app) **** 内容回顾: - .all .values .values_list models.xxx.objects.all() --> [obj,obj,obj,...] models.xxx.objects.values(‘id‘,‘name‘) --> [{‘id‘:1,‘name‘:‘alex‘},{‘id‘:2,‘name‘:‘standby‘},...] models.xxx.objects.values_list(‘id‘,‘name‘) --> [(1,‘alex‘),(2,‘standby‘,....)] - 中间件是什么?有什么用? 可以拦截所有的请求和响应,进行一些处理 中间件执行时有序的,从上到下执行,如果当前中间件不允许过,那么就不会再执行后面的中间件 比如说:对用户的请求做下黑名单 process_request() 默认没有返回值,即返回None; 则继续往下执行 但是如果有返回值,则不再继续往下执行,直接返回; 每个中间件就是一个类: class Md1: def process_request(self,request): pass def process_response(self,request,response): return response class Md2: def process_request(self,request): pass def process_response(self,request,response): return response settings.py里配置 MIDDLEWARE = [ "XXXXXX.XXXXX.Md1", "XXXXXX.XXXXX.Md2", ] - ORM创建表 - ForeignKey常用操作 class A(Model): name = models.CharField(...) class B(Model): name = models.CharField(...) fk = models.ForeignKey(A) a. B表中有几列: id name fk_id b. 跨表操作 b_list = models.B.objects.all() for item in b_list: item.id item.name item.fk item.fk_id item.fk.id item.fk.name c. 跨表操作 b_list = models.B.objects.values(‘id‘,‘name‘,‘fk_id‘,‘fk__name‘) for item in b_list: item[‘id‘] item[‘name‘] item[‘fk_id‘] item[‘fk__name‘] d. 跨表操作 b_list = models.B.objects.vlaues_list(‘id‘,‘name‘,‘fk_id‘,‘fk__name‘) for item in b_list: item[0] item[1] item[2] item[3] e. 找A表名称等于"alex"的所有B表中的数据 ***** models.B.objects.filter(fk__name="alex") good models.B.objects.filter(fk.name="alex") 这个是不可以,需要验证!!!!!! 外键跨表关联补充: from django.db import models class Department(models.Model): """ 部门表 """ title = models.CharField(verbose_name="部门名称", max_length=16) """ id title userinfo_set """ class UserInfo(object): """ 员工表 """ username = models.CharField(verbose_name="用户名", max_length=16) password = models.CharField(verbose_name="密码", max_length=16) depart = models.ForeignKey(verbose_name="所属部门", to="Department") """ id username password depart_id """ 正向关联:从 UserInfo 跨到 Department 查询相关数据 from crm import models objs = models.UserInfo.objects.filter(depart__title=‘IT部‘) 反向关联:从 Department 跨到 UserInfo 查询相关数据 from crm import models obj = models.Department.objects.filter(title=‘IT部‘).first() print(obj.userinfo_set) """ # 获取model的字段,还包含FK和choice,但是没有多对多的字段 fields1 = [obj.name for obj in self.model_class._meta.fields] # 只获取获取多对多的字段 fields2 = [obj.name for obj in self.model_class._meta.many_to_many] # 这个是最全的,还包含了反向关联字段 fields3 = [obj.name for obj in self.model_class._meta._get_fields()] """ """ _field = self.model_config.model_class._meta.get_field(某个字段) 多对多: option.field_or_func course 咨询的课程 _field crm.Customer.course type <class ‘django.db.models.fields.related.ManyToManyField‘> _field.rel <ManyToManyRel: crm.customer> type <class ‘django.db.models.fields.reverse_related.ManyToManyRel‘> 多对一: option.field_or_func consultant 课程顾问 _field crm.Customer.consultant type <class ‘django.db.models.fields.related.ForeignKey‘> _field.rel <ManyToOneRel: crm.customer> type <class ‘django.db.models.fields.reverse_related.ManyToOneRel‘> """ - M2M常用操作 class A(Model): name = models.CharField(...) class B(Model): name = models.CharField(...) m = models.ManyToMany(A) a. B表中有几列? id name PS:ManyToMany 会自动生成第三张表,字段m用于间接的对第三张表进行操作 b. 在B表中插入3条数据,A表中插入2条数据 models.A.objects.create(name=‘alex1‘) models.A.objects.create(name=‘alex2‘) models.B.objects.create(name=‘egon1‘) models.B.objects.create(name=‘egon2‘) models.B.objects.create(name=‘egon3‘) c. 让 egon1 和 [alex1,alex2] 创建关系 obj = models.B.objects.filter(‘name‘=‘egon1‘) obj.m.add(*[1,2]) d. 查找和egon1有关系的人 obj = models.B.objects.filter(‘name‘=‘egon1‘) obj.m.all() # 拿到了queryset对象集合,都是A表的对象 - Session是什么? 和cookie有什么区别? - session依赖cookie而存在 - session是存储在服务端的键值对 - cookie是存储在客户端浏览器里的键值对 - 用户第一次来访问主页,服务端生成一个随机字符串通过cookie返回给客户端 同时服务端把这个字符串当做key存储在数据库表里(也可以存在其他地方),值可以为空 - 用户第二次发起登录请求则带上刚才的cookie和用户信息 服务端则把用户信息(比如用户名密码)用某种方式存储在刚才随机字符串对应的值里 这样就表示用户登录过了 示例程序:pro_crm - 员工模块 - 权限:根据 含有正则表达式的url 进行分配的 - 操作:通过配置文件进行定制 - 学生模块 - 问卷 今日内容: - 第一版 - permission权限表(含有正则表达式的url) id url title 1 /userinfo/ 用户列表 2 /userinfo/(\\d+)/delete/ 删除用户 3 /userinfo/(\\d+)/change/ 修改用户 4 /userinfo/add/ 添加用户 - userinfo用户表 id username password 1 alex 123 2 egon 123 3 standby 123 - 用户和权限的关系表 id user_id permission_id 1 1 1 2 1 2 3 2 1 4 3 1 5 3 2 6 3 3 问题:用户分配权限时,很麻烦,不方便后面的维护 - 第二版 - permission权限表(含有正则表达式的url) id url title 1 /userinfo/ 用户列表 2 /userinfo/(\\d+)/delete/ 删除用户 3 /userinfo/(\\d+)/change/ 修改用户 4 /userinfo/add/ 添加用户 - userinfo用户表 id username password 1 alex 123 2 egon 123 3 standby 123 - role 角色表 id title 1 销售员 2 销售经理 3 市场专员 4 市场经理 5 IT 6 CTO 7 CEO - 用户和角色关系表 id user_id role_id 1 1 1 2 2 1 3 2 2 4 3 1 5 3 2 6 3 7 - 角色和权限的关系表 id role_id permission_id 1 1 1 2 2 1 3 2 4 4 6 1 5 6 3 6 6 4 7 7 1 8 7 2 9 7 3 10 7 4 列出standby所具有的所有权限,可能会重复,所以需要去重; 注意:根据用户找权限 - 根据用户找角色 - 根据角色找权限 - 增加权限组表group id title 1 用户组 2 订单组 - permission权限表 增加一个代号code字段、权限组字段以及 是否是菜单字段(涉及到在前端页面展示): - permission权限表(含有正则表达式的url) id url title code group_id is_menu 1 /userinfo/ 用户列表 list 1 true 2 /userinfo/(\\d+)/delete/ 删除用户 del 1 false 3 /userinfo/(\\d+)/change/ 修改用户 edit 1 false 4 /userinfo/add/ 添加用户 add 1 true 5 /order/ 订单列表 list 2 true 6 /order/(\\d+)/delete/ 删除订单 del 2 false 7 /order/(\\d+)/change/ 修改订单 edit 2 false 8 /order/add/ 添加订单 add 2 true - 中午作业 - 填充数据:Django admin - 找到id=1的用户 - 具有的角色 - 具有的权限 - 权限去重 - 自定义配置 Django的所有配置(既包含自定义的也包含Django内置的): from django.conf import settings - 找到id=1的用户 - 具有的角色 - 具有的权限 - 权限去重 - 自动生成权限菜单 - 获取数据 - 创建两级菜单 @register.simple_tag() @register.inclusion_rag("menu_tpl.html") 总结: - 表的设计(很重要) 4个类 6张表 - Django admin 录入数据 - 用户登录处理 - 获取角色 - 获取权限 - 对权限去重 - 然后把权限数据存储到session - 登录中间件 - 白名单 - request.path_info - 从session获取权限,进行验证 - 自动生成菜单 - 跨表取出相关数据,转换字典结构 - 视图中生成好字典返回给前端 - 前端模板渲染 - simple_tag - inclusion_rag - 模板layout.html - 权限app包含哪些内容 - 中间件 - init_permission - models - admin - templatetags 依赖配置文件 PERMISSION_DICT = "permission_dict" URL_FORMAT = "^{0}$" RBAC_LOGIN_URL = "/login/" VALID_URL_LIST = [ "^/login/$", "^/admin.*", ] - 使用rbac权限管理 - 创建CMDB项目 - 把rbac拷贝到项目中 - 在settings.APP中注册 rbac - 在settings中配置相关变量: PERMISSION_DICT = "permission_dict" URL_FORMAT = "^{0}$" RBAC_LOGIN_URL = "/login/" VALID_URL_LIST = [ "^/login/$", "^/admin.*", ] - 开发CMDB - 开启中间件验证 - 利用tmplatetags和模板动态生成菜单 - 在Django admin中操作权限信息 day26作业 - 权限系统补充完整,搞清楚每一行代码 [4天] - 自己创建project,比如主机管理系统 - 主机 - 主机组 - 部门 - 机房 - ... - 使用rbac权限系统 [10天] ######################################################################## day27 - 自己写点东西 - 计划: - 立项 (让项目来驱动复习知识点,把知识点串起来) - 业务 - 数据库设计 - 登录 - cookies - session - Form组件验证 - 列表 - 数据ORM操作 ... 内容回顾: - 权限管理 - 基于用户的权限管理 - 基于角色的权限管理 - 数据库(4个类6张表) - 用户 - 角色 - 权限 - 含有正则表达式的url - code 用来控制是否在前端显示添加、删除、编辑这些 button - is_menu 用来控制一般含有正则的权限不能显示在菜单里 - 权限菜单组 - is_group 用来区分组和菜单 - session - 中间件 - inclusion_tag 今日内容: - 权限【修bug】 - 删除和编辑时 左侧菜单无法默认展开(无法保持展开状态) - 在权限表增加一个字段:访问时,默认被选中的组内权限ID - 学习django admin的用法 自定义admin组件 目标:整合 权限组件 + 自定义admin组件 + crm业务 - 权限【修bug】 - 控制页面是否显示 【增加】【编辑】【删除】 这些button ############# 自定义CURD组件 ################# - 学习 django-admin (解决基本的增删改查) http://www.cnblogs.com/wupeiqi/articles/7444717.html ‘‘‘ from django.contrib import admin from . import models admin.site.register(models.UserInfo) ‘‘‘ - 注册一个类就生成了4个URL: - /admin/app01/userinfo/ - /admin/app01/userinfo/add/ - /admin/app01/userinfo/1/change/ - /admin/app01/userinfo/1/delete/ 结论:循环注册所有的类,每个类增加4个url from django.shortcuts import HttpResponse return HttpResponse("ModelAdmin...") from django.contrib import admin from django.contrib.admin import ModelAdmin class UserInfoModelAdmin(ModelAdmin): list_display [‘name‘,‘pwd‘] # 定义显示列表页面显示哪些列 list_display_links[‘pwd‘] # 定义列表页面哪些列可以点击进入编辑页面 list_filter = [‘ut‘] # 右侧显示所有用户的用户类型,用于快速搜索 list_per_page = 10 # 定义列表页面每页显示几条数据 list_editable = [‘name‘] # 字段编辑 search_fields = [‘name‘] # 模糊搜索框 date_hierarcy save_on_top = True # save按钮在上面显示 def func(self,request,queryset): pass func.short_description = "批量初始化" action = [func,] # 添加一个自定义的action raw_id_fields = [‘ut‘,] # 让外键和多对多以input框的形式显示 fields = [‘name‘] # 定义显示哪些字段 exclude = [‘name‘] # 排除name readonly_fields = [‘name‘] # 只读 @admin.register(models.UserInfo) class UserAdmin(admin.ModelAdmin): filter_vertical = ("m2m字段",) # 或filter_horizontal = ("m2m字段",) ordering = [‘id‘,] ordering = [‘-id‘,] http://www.cnblogs.com/wupeiqi/articles/7444717.html - 重要 - admin原理 - 自定义admin组件 - 启动文件执行顺序 - app01/admin.py - project/urls.py - 什么admin.py先执行呢? from django.utils.module_loading import autodiscover_modules def autodiscover(): autodiscover_modules(‘admin‘, register_to=site) from django.contrib import admin class AdminConfig(SimpleAdminConfig): """The default AppConfig for admin which does autodiscovery.""" def ready(self): super(AdminConfig, self).ready() self.module.autodiscover() - 制作启动文件 - 创建APP - 找到app.py def ready(self): pass - settings.py注册 ‘arya.apps.AryaConfig‘ 程序启动会自动去所有app下找 arya文件并执行 - 单例模式 (基于Python文件导入特性) 多文件下要验证下 - urls.py 里的 include 的本质 (路由分发的本质) - 自动生成 - 自定义 - 根据配置文件的修改,完成CURD定制任务 注意: 在rbac中间件里可以通过如下方式把数据复制给request 权限code request.permission_code_list = xxx 本周任务: - 初级:自己手动写 CURD - 权限管理修bug 【*****】 - Arya补充完整 【*****】 - 制作启动文件 - 生成URL分发 - 增加 list_display 来控制页面的显示 - [‘name‘,‘pwd‘,func,...] - 继承 - 多个class之间的兼容 - 下周会用到: - yield - 面向对象的封装 - __iter__ 的使用 - 创建project,整合rabc和arya - Django Admin ################################################################### day28 内容回顾: - 路由系统 - /index/ def func1(request) - /index/(\\d+)/change def func2(request,arg) - /xxx/ include(‘app01.urls‘) /index/ ( [yyy def func3(request,xxxxxxxx)] none, none ) 反向生成url from django.urls import reverse ‘‘‘ url(r‘^/index/$‘, self.changelist_view, name="x1"), url(r‘^/index/(\\d+)/edit$‘, self.change_view, name="x2"), url(r‘^/index/(?P<uid>\\d+)/edit/$‘, self.change_view, name="x3"), from django.urls import reverse url = reverse(viewname=‘x1‘) --> /index/ url = reverse(viewname=‘x2‘,args=(10,) --> /index/10/edit/ url = reverse(viewname=‘x3‘,kwargs={‘uid‘:9}) --> /index/9/edit 以上三中是基础的三种方式,另外如果url经过分发了: - /index/ ([ xxx/, name=‘x4‘, def func4(...), ],None,None) 这种情况下,反向生成:url = reverse(viewname=‘x4‘) - /index/ ([ xxx/, name=‘x5‘, def func4(...), ],None,‘some_namespace‘) 这种情况下,反向生成:url = reverse(viewname=‘some_namespace:x5‘) ‘‘‘ 封装的思想 ‘‘‘ class Foo(object): def func(self): print(‘方法‘) 方法和函数的区别: # - 如果被对象调用,则self不用传值 # obj = Foo() # obj.func() # - 如果被类 调用,则self需要主动传值 # obj = Foo() # Foo.func(obj) ‘‘‘ 后端循环 + 前端循环 --> 后端使用yield from django.forms import ModelForm from django.forms import widgets ModelForm 生成HTML 默认样式 定制样式 定制错误信息 验证 ModelForm里面的钩子函数:def clean_字段名() ... 和Form的使用是一样的 需要自定义认证规则的时候可以使用钩子函数: def clean_username(): pass search_list = [] search_list.extends(...) model里的Q查询和F查询 多条件 或关系 查询: from django.db.models import Q condition = Q() 可以自己定义模糊/精确查询 F查询和Q查询 ‘username__contains‘ --> like模糊匹配 ‘username‘ --> 完全匹配 批量删除操作: self.model.objects.filter(id__in=item_list).delete() 外键: ‘school__title__contains‘ 前端:xx|safe 后端:mark_safe request = request.GET QueryDict QueryDict.urlencode() data = [ {‘value‘:‘multi_delete‘,‘text‘:"批量删除"} ... ] def func(): pass func.text = "批量删除" func.__name__ func.text 批量删除 pk_list = request.POST.getlist(‘pk‘) --> 能获取到checkbox里勾选的行的obj self.model_class.object.filter(id__in=pk_list).delete() ManyToMany 的字段需要写方法拼接: teacher_list = [] for item in request.POST.get(‘teacher‘): teacher_list.append(item.name) return ‘,‘.join(teacher_list) models.Field gender_chioces = [(1,‘男‘),(2,‘女‘)] row.get_gender_display() # 1 -> 男 即 obj.get_字段_display() - arya - 列定制 - 关键字搜索框 - 分页 - action/批量操作 - 增加、删除、修改 - 基于CRM的业务实现 select udp __iter__() arya 被阉割的那部分视频+代码 数据结构 + 算法