一、视图
Django中的视图主要用来接受Web请求,并做出响应。
视图的本质就是一个Python中的函数
视图的响应分为两大类
1)以Json数据形式返回 (JsonResponse)
2)以网页的形式返回
2.1)重定向到另一个网页 (HttpResponseRedirect)
2.2)错误视图(40X,50X)(HttpResponseNotFound,HttpResponseForbidden, HttpResponseNotAllowed等)
视图响应过程: 浏览器输入 -> django获取信息并去掉ip:端口,剩下路径 -> urls 路由匹配 - > 视图响应 -> 回馈到浏览器
视图参数:
1)一个HttpRequest的实例,一般命名为request
2)通过url正则表达式传递过来的参数
位置:通常在应用下的views.py中定义
错误视图:
1) 404视图 (页面没找到)
2) 400视图 (客户操作错误)
3) 500视图(服务器内部错误)
自定义错误视图
在工程的templates文件夹下创建对应的错误文件
在文件中定义自己的错误样式
注意需要在关闭Debug的情况下,并设置ALLOWED_HOSTS才可以
没有关闭Debug的情况下会在界面中直接显示log
HttpRequest
服务器在接收到Http请求后,会根据报文创建HttpRequest对象
视图中的第一个参数就是HttpRequest对象
Django框架接收到http请求之后会将http请求包装为HttpRequest对象,之后传递给视图。
request常用属性和方法:
属性: path 请求的完整路径
method 请求的方法,常用GET,POST
GET 类似字典的参数,包含了get的所有参数
POST 类似字典的参数,包含了post所有参数
FILES 类似字典的参数,包含了上传的文件
COOKIES 字典,包含了所有COOKIE
session 类似字典,表示会话
META['REMOTE_ADDR']
*encoding 编码方式,常用utf-8
方法: is_ajax() 判断是否是ajax(),通常用在移动端和JS中
get_full_path() 返回包含参数字符串的请求路径.
QueryDict: 类字典的对象
类似字典的数据结构。与字典的区别:可以存在相同的键。
QueryDict中数据获取方式
dict['uname'] 或 dict.get('uname)
获取指定key对应的所有值
dict.getlist('uname')
print(request, type(request))
print(request.method)
print(request.path)
print(request.GET)
print(request.POST)
print(request.COOKIES)
print(request.session)
print(request.FILES)
print(request.META['REMOTE_ADDR'])
print(request.GET.get('name'), request.GET['name'])
print(request.GET.getlist('like'))
HttpResponse
服务器返回给客户端的数据
HttpResponse:
HttpResponse由程序员自己创建:
1)不使用模板,直接调用HttpResponse(),返回HttpResponse对象。
2)调用模板,进行渲染。
使用render
render(request,template_name[,context])
参数:
request 请求体对象
template_name 模板路径
context 字典参数,用来填坑
HttpResponse属性:
content 返回的内容
charset 编码格式
status_code 响应状态码(200,3xx,404,5xx)
HttpResponse方法:
write(xxx) 直接写出文本
flush() 冲刷缓冲区
set_cookie(key,value='xxx',max_age=None) 设置cookie
delete_cookie(key) 删除cookie
HttpResponse子类
HttpResponseRedirect
响应重定向:可以实现服务器内部跳转
return HttpResponseRedict('/grade/2017')
使用的时候推荐使用反向解析
JsonResponse
返回Json数据的请求,通常用在异步请求上
JsonResponse(dict)
返回json数据时,Content-type是application/json
def response(request):
res = HttpResponse('ok')
print(res.charset)
print(res.content)
print(res.status_code)
res.write('hello')
res.flush()
print(res.content)
return res
二. Cookie & Session&Token
Cookie
理论上,一个用户的所有请求操作都应该属于同一个会话,而另一个用户的所有请求操作则应该属于另一个会话,二者不能混淆. 而Web应用程序是使用HTTP协议传输数据的。HTTP协议是无状态的协议。一旦数据交换完毕,客户端与服务器端的连接就会关闭,再次交换数据需要建立新的连接。这就意味着服务器无法从连接上跟踪会话。要跟踪该会话,必须引入一种机制。
Cookie就是这样的一种机制。它可以弥补HTTP协议无状态的不足。在Session出现之前,基本上所有的网站都采用Cookie来跟踪会话。
Cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户状态。服务器还可以根据需要修改Cookie的内容。
cookie本身由服务器生成,通过Response将cookie写到浏览器上,下一次访问,浏览器会根据不同的规则携带cookie过来。
注意:cookie不能跨浏览器,一般不跨域
设置cookie(使用response设置):
response.set_cookie(key,value[,max_age=None,expires=None)]
max_age: 整数 单位为秒,指定cookie过期时间
设置为None:浏览器关闭失效,默认值
expires: 指定过期时间,还支持datetime或timedelta,可以指定一个具体日期时间
expires=datetime.datetime(2019, 1, 1, 2, 3, 4)
或 datetime.datetime.now() + datetime.timedelta(days=10)
注意:max_age和expries两个选一个指定
获取cookie(使用request获取):
request.COOKIES.get('username')
删除cookie(使用response删除):
response.delete_cookie('username')
cookie存储到客户端
优点:
数据存在在客户端,减轻服务器端的压力,提高网站的性能。
缺点:
安全性不高:在客户端机很容易被查看或破解用户会话信息
例如:
def login(request):
if request.method == 'GET':
return render(request, 'login.html')
elif request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
users = UserModel.objects.filter(username=username, password=password)
if users.exists():
res = redirect(reverse('index'))
res.set_cookie('userid', users.first().id, max_age=30)
d = datetime.datetime(2030, 1, 2, 3, 4, 5)
res.set_cookie('userid', users.first().id, expires=d)
return res
return render(request, 'login.html')
def logout(request):
res = redirect(reverse('index'))
res.delete_cookie('userid')
return res
Session
服务器端会话技术,依赖于cookie.
django中启用SESSION
settings中
INSTALLED_APPS: 'django.contrib.sessions'
MIDDLEWARE:'django.contrib.sessions.middleware.SessionMiddleware'
'''
session 结合了cookie的功能与数据库的数据存储功能:
django回调时视图时,将请求对象赋值给request参数, 在request对象中请求设置session,
django就会在数据库表中插入设置的session,而且还会在返回浏览器的时候设置一个
key为sessionid,value为数据库session表的session_key 的cookie
(内部机制,只能设置一个名为sessionid的coocie)
之后客户端再次请求会带着该cookies进行校验等操作
(利用cookie存储session_key,数据库存储session_key,session_data;
这样当浏览器再次请求的时候就可以利用session_key获取存储的数据判断是否同一个客户端等)
'''
基本操作
设置Sessions值(使用request设置)
request.session['user_id'] = user.id
request.session.set_expiry(86400)
获取Sessions值
get(key,default=None) 根据键获取会话的值
username = request.session.get("user_id")
删除Sessions值
session_key = request.session.session_key
del request.session[session_key]
flush() 删除当前的会话数据并删除会话的cookie
clear() 清除所有会话
数据存储到数据库中会进行编码,使用的是Base64
每个HttpRequest对象都有一个session属性,也是一个类字典对象.
def login(request):
if request.method == 'GET':
return render(request, 'login.html')
elif request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
users = UserModel.objects.filter(username=username, password=password)
if users.exists():
request.session['userid'] = users.first().id
request.session.set_expiry(60*60*24)
return redirect(reverse('index'))
return render(request, 'login.html')
def index(request):
userid = request.session.get('userid', 0)
user = UserModel.objects.filter(id=userid).first()
return render(request, 'index.html', {'user': user})
def logout(request):
session_key = request.session.session_key
request.session.delete(session_key)
return redirect(reverse('index'))
Token
Token简介:
常用于用户数量较多和手机端用户
'''*******************
# 1.templates/login.html
注意:
*******************'''
<form action="{% url 'login' %}" method="post">
{% csrf_token %}
<p>用户名:<input type="text" name="username"></p>
<p>密码:<input type="password" name="password"></p>
<p><input type="submit" value="登录"></p>
</form>
'''*******************
# 2.user/models.py
注意:
*******************'''
class UserToken(models.Model):
token = models.CharField(max_length=100, unique=True, null=False, verbose_name='用户令牌/用户的唯一标识')
user = models.OneToOneField(UserModel, verbose_name='关联的用户')
out_time = models.DateTimeField(null=False, verbose_name='过期时间')
'''*******************
# 2.user/views.py
注意:
*******************'''
def login(request):
if request.method == 'GET':
return render(request, 'login.html')
elif request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
user = UserModel.objects.filter(username=username).first()
if user:
if check_password(password, user.password):
res = redirect(reverse('index'))
token = get_token()
out_time = datetime.datetime.utcnow() + datetime.timedelta(days=1)
u_token = UserToken.objects.filter(user_id=user.id).first()
if u_token:
u_token.token = token
u_token.out_time = out_time
else:
user_token = UserToken()
user_token.token = token
user_token.user = user
user_token.out_time = out_time
user_token.save()
res.set_cookie('token', token, max_age=86400)
return res
return render(request, 'login.html')
def get_token():
uid = uuid.uuid4()
return uid.hex
def index(request):
token = request.COOKIES.get('token')
user_token = UserToken.objects.filter(token=token).first()
user = None
if user_token:
if user_token.out_time.timestamp() > datetime.datetime.utcnow().timestamp():
user = user_token.user
return render(request, 'index.html', {'user': user})
def logout(request):
res = redirect(reverse('index'))
res.delete_cookie('token')
token = request.COOKIES.get('token')
UserToken.objects.filter(token=token).delete()
return res
三、csrf表单验证处理
csrf:跨站伪造请求
解决csrf在post请求时产生的403错误:
1.注释settings.py中MIDDLEWARE的csrf相关中间件
2.在form表单中添加{% csrf_token %}
模板设置:
<form action="{% url 'register' %}" method="post">
{% csrf_token %}
<p>用户名:<input type="text" name="username"></p>
<p>密码:<input type="password" name="password"></p>
<p>年龄:<input type="number" name="age"></p>
<p><input type="submit" value="注册"></p>
</form>
四、静态文件与媒体文件
一. 静态文件和媒体文件
媒体文件:用户上传的文件,叫做media
静态文件:存放在服务器的css,js,image等 叫做static
1. 在django中使用静态文件
1)首先确保django.contrib.staticfiles在 INSTALLED_APPS中
2)在settings中定义 STATIC_URL = '/static/'
3)在你app的static目录中存放静态文件,比如:my_app/static/image/example.jpg.
4)如果有别的静态资源文件,不在app下的static目录下,
可以通过STATICFILES_DIRS来指定额外的静态文件搜索目录。
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static"),
...
]
5)在模板中使用load标签去加载静态文件
{% load static %}
<img src="{% static "my_app/example.jpg" %}" />
2. 在django中使用媒体文件
在settings中配置 MEDIA_ROOT
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
如果上传的文件还需使用建议放到静态目录下:
MEDIA_ROOT = os.path.join(BASE_DIR, 'static/uploads')
实例:
'''*******************
# 3、单文件上传
views.py
注意:
request.FILES.get('icon')
*******************'''
def index2(request):
if request.method == 'GET':
return render(request, 'index2.html')
elif request.method == 'POST':
username = request.POST.get('username')
icon = request.FILES.get('icon')
file_name = icon_name() + os.path.splitext(icon.name)[-1]
file_path = os.path.join(MEDIA_ROOT, file_name)
with open(file_path, 'ab') as fp:
for part in icon.chunks():
fp.write(part)
fp.flush()
user = User()
user.username = username
user.icon = '/uploads/' + file_name
user.save()
return HttpResponse('ok')
def icon_name():
ret = uuid.uuid4()
return str(ret)
'''*******************
index2.html
注意:
{% csrf_token %}
enctype="multipart/form-data"
*******************'''
<form action="" method="post" enctype="multipart/form-data">
{% csrf_token %}
<p>用户名:<input type="text" name="username"></p>
<p>头像:<input type="file" name="icon"></p>
<p><input type="submit" value="上传"></p>
</form>
'''*******************
# 4、多文件上传
注意:
request.FILES.getlist('icons') # 返回列表
*******************'''
def index3(request):
if request.method == 'GET':
return render(request, 'index3.html')
elif request.method == 'POST':
username = request.POST.get('username')
icons = request.FILES.getlist('icons')
for icon in icons:
file_name = icon_name() + os.path.splitext(icon.name)[-1]
file_path = os.path.join(MEDIA_ROOT, file_name)
with open(file_path, 'ab') as fp:
for part in icon.chunks():
fp.write(part)
fp.flush()
photo = Photo()
photo.user = User.objects.get(username=username)
photo.icon = '/uploads/' + file_name
photo.save()
return HttpResponse('ok')
'''*******************
index3.html
注意:
{% csrf_token %}
enctype="multipart/form-data"
<input type="file" name="icons" multiple>
*******************'''
<form action="" method="post" enctype="multipart/form-data">
{% csrf_token %}
<p>用户名:<input type="text" name="username"></p>
<p>头像:<input type="file" name="icons" multiple></p>
<p><input type="submit" value="上传"></p>
</form>
五、分页
django提供了分页的工具,存在于django.core中
Paginator : 数据分页工具
Page : 具体的某一页面
导入Paginator:
from django.core.paginator import Paginator
Paginator:
对象创建:
Paginator(数据集,每一页数据数)
属性:
count:对象总数
num_pages:页面总数
page_range: 页码列表,从1开始
方法:
page(整数): 获得一个page对象
常见错误:
InvalidPage:page()传递无效页码
PageNotAnInteger:page()传递的不是整数
Empty:page()传递的值有效,但是没有数据
Page:
对象获得,通过Paginator的page()方法获得
属性:
object_list: 当前页面上所有的数据对象
number: 当前页的页码值
paginator: 当前page关联的Paginator对象
方法:
has_next() :判断是否有下一页
has_previous():判断是否有上一页
has_other_pages():判断是否有上一页或下一页
next_page_number():返回下一页的页码
previous_page_number():返回上一页的页码
len():返回当前页的数据的个数
'''*******************
# 分页的使用
注意:
Paginator --分页对象
Paginator.page(页码) --具体页
*******************'''
def paginate(request, page):
if request.method == 'GET':
page = int(page)
per_page = 3
users = User.objects.all()
p = Paginator(users, per_page)
print(p.count)
print(p.num_pages)
print(p.page_range)
p2 = p.page(page)
print(p2.object_list)
print(p2.number)
print(p2.paginator)
print(p2.has_next())
print(p2.has_previous())
print(p2.has_other_pages())
print(p2.next_page_number())
print(p2.previous_page_number())
print(len(p2))
data = {
'p2': p2,
}
return render(request, 'paginate.html', data)
'''*******************
# paginate.html
结合bootstrap使用
*******************'''
<ul class="pagination">
{
<li>
{% if p2.has_previous %}
<a href="{% url 'paginate' p2.previous_page_number %}" >«</a>
{% else %}
<a href="#" >«</a>
{% endif %}
</li>
{
{% for i in p2.paginator.page_range %}
{% if p2.number == i %}
<li class="active" >
{% else %}
<li>
{% endif %}
<a href="{% url 'paginate' i %}">{{ i }}</a></li>
{% endfor %}
{
<li>
{% if p2.has_next %}
<a href="{% url 'paginate' p2.next_page_number %}" >»</a>
{% else %}
<a href="#" >»</a>
{% endif %}
</li>
</ul>
六、中间件&面向切面编程(AOP)
中间件:是一个轻量级的,底层的插件,可以介入Django的请求和响应过程(面向切面编程)
Django中间件的本质就是一个python类
面向切面编程(Aspect Oriented Programming)简称AOP。AOP的主要实现目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合的隔离效果。
中间件可实现功能
- 统计
- 黑名单
- 白名单
- 反爬
- 界面友好化(捕获异常)
'''*******************
2 切入函数
*******************'''
__init__:
没有参数,服务器响应第一个请求的时候自动调用,用户确定是否启用该中间件
process_request(self,request):
在执行视图前被调用,每个请求上都会调用,不主动进行返回或返回HttpResponse对象
process_view(self,request,view_func,view_args,view_kwargs):
调用视图之前执行,每个请求都会调用,不主动进行返回或返回HttpResponse对象
process_template_response(self,request,response):
在视图刚好执行完后进行调用,每个请求都会调用,不主动进行返回或返回HttpResponse对象
process_response(self,request,response):
所有响应返回浏览器之前调用,每个请求都会调用,不主动进行返回或返回HttpResponse对象
process_exception(self,request,exception):
当视图抛出异常时调用,不主动进行返回或返回HttpResponse对象
'''*******************
3、自定义中间件
*******************'''
自定义中间件流程
1.在工程目录下创建middleware目录
2.目录中创建一个python文件
3.在python文件中导入中间件的基类
from django.utils.deprecation import MiddlewareMixin
4.在类中根据功能需求,创建切入需求类,重写切入点方法
class LearnAOP(MiddlewareMixin):
def process_request(self,request):
print('request的路径',request.GET.path)
5.启用中间件,在settings中进行配置,MIDDLEWARE中添加middleware.文件名.类名
'''*******************
4、自定义组件
middlewares.UserMiddleware.RegisterMiddleware
*******************'''
class RegisterMiddleware(MiddlewareMixin):
def process_request(self, request):
print('RegisterMiddleware => process_request')
ip = request.META['REMOTE_ADDR']
def process_exception(self, request, exception):
print("LoginMiddleware => process_exception")
return redirect('/index/')
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'middlewares.UserMiddleware.RegisterMiddleware',
]