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‘: [‘borui@qq.c
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 被阉割的那部分视频+代码
数据结构 + 算法