首页 > 技术文章 > django基础

jiangzongyou 2019-12-13 16:10 原文

http协议
8种请求方式:get post head put delete options trace connect
get请求的参数(键值对)会放在请求体的第一行的url栏里(路径里)
post请求的参数(键值对)会放在请求体的最后
-input标签必须要有name,因为数据的传递必须要有键值对
状态码:
1XX Informational(信息性状态码) 接受的请求正在处理
2XX Success(成功状态码) 请求正常处理完毕
3XX Redirection(重定向状态码) 需要进行附加操作以完成请求
4XX Client Error(客户端错误状态码) 服务器无法处理请求
5XX Server Error(服务器错误状态码) 服务器处理请求出错
url
协议,域名,路径,(?分割线),参数
请求
请求行 "请求方式 URL路径 HTTP/1.1\r\n"
请求头 k1:v1\r\n
k2:v2\r\n
\r\n
请求数据(请求体) ---> get请求没有请求体
相应
相应行 "HTTP/1.1 状态码 状态描述\r\n"
响应头 k1:v1\r\n
k2:v2\r\n
\r\n
响应数据 --->服务器返回的html文本
浏览器发送接收过程
1.在浏览器里输入url,回车,发送get请求
2.服务器收到请求,获取url路径,根据路径不同,返回不同的相应数据
3.浏览器接受响应,双方断开连接(短链接,有几秒延迟)
4.浏览器从响应体中获取数据,进行解析渲染
web框架的功能:
1.socket收发消息 -wsgiref(线下测试) uwsgi(线上部署)
2.根据不同的路径返回不同的内容
3.返回动态页面(字符串的替换) -jinja2
框架的分类:
django 实现功能 2,3
flask(轻量级) 实现功能 2
tornado(轻量级) 实现功能 1,2,3
-轻量级框架内部封装的模块少一些
框架的安装
1.pip install django==1.11.26
1.pip install django==1.11.26 -i https://pypi.tuna.tsinghua.edu.cn/simple/
2.pycharm下载,选中版本为1.11.26
创建运行项目:
1.命令行
创建项目: django-admin startproject 项目名称
-找到一个文件夹,打开终端
-用命令行创建项目是没有TEMPLATES文件(pycharm有)的
-而且也没有这句配置 'DIRS': [os.path.join(BASE_DIR, 'templates')
启动项目: python36 manage.py runserver 127.0.0.1:8000
-在manage.py的目录下打开cmd
-127.0.0.1:8000 此时,仅限这种方式访问
-127.0.0.1:8000 可以不写,默认就是这个ip和端口
-也可以只写端口,eg,runserver 8000,默认就是127这个ip
-可设置为 0.0.0.0:80,以本机ip地址为django服务器地址
-这个时候
-127.0.0.1 可以访问
-127.0.0.1:80 可以访问
-192.168.101.177 可以访问 前提是 ALLOWED_HOSTS = ['*']
-192.168.101.177:80 可以访问 前提是 ALLOWED_HOSTS = ['*']
-在浏览器中直接输入ip地址,浏览器会默认给你加上:80端口,一般服务器地址都是80端口
-ALLOWED_HOSTS = ['*'],设置后才能让其他人通过交换机访问到自己
验证项目运行,在浏览器中输入:
http://127.0.0.1:8000/
2.pycharm
创建: 新建项目-django项目-改项目名称
-django是一个单独的项目,最好单独放出来,也可以放在这里
-社区版无django项目
启动项目: file -> open -> 选中项目的根目录 -> 确认
day2
获取post请求
MIDDLEWARE下csrf注释掉'django.middleware.csrf.CsrfViewMiddleware',才可获取post请求
写函数
def login(request):
request.method -> 请求方式 GET POST
request.GET -> 拿到请求行中的url中携带的参数,不是GET请求提交的参数
request.POST -> 拿到post请求的请求数据,即form表单提交的数据,{},request.POST['key']
request.POST.get('key') 这种方式取不会报错
返回值
from django.shortcuts import HttpResponse, render, redirect
return HttpResponse('字符串')
return render(request,'完整的html文件名')
return redirect('/重定向的地址/') 左边的/代表根目录 响应头 Location:'地址'
静态文件的配置
-常用于在html文件中引入静态的css和js文件
-查找顺序是按照添加路径的先后,也就是 2 -> 0 -> 1
STATIC_URL = '/static/' # 别名
STATICFILES_DIRS = [
os.path.join(BASE_DIR,'static2'),
os.path.join(BASE_DIR,'static0'),
os.path.join(BASE_DIR,'static1'),
]
创建app
-方式1,在项目根目录的cmd中输入,python36 manage.py startapp app名称
-方式2,在pycharm中,tools -> run manage.py task -> startapp app名称
-可以同时创建多个app,注册多个app,用来隔离不同功能模块的代码
-各个文件的作用
-admin Django后台管理工具
-model 与ORM相关
-views 写函数
注册app
在settings中,INSTALLED_APPS下,加上app.apps.AppConfig
配置数据库
-先自己创建一个django用的数据库
-在settings中,更改数据库配置
# import pymysql 这两句话最好写在这个文件下的init文件中,习惯
# pymysql.install_as_MySQLdb()
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'day53',
'HOST': '127.0.0.1',
'PORT': 3306,
'USER': 'root',
'PASSWORD': '',
}
}
-创建表结构
-在model文件中写入
class User(models.Model):
username = models.CharField(max_length=32) # username varchar(32)
password = models.CharField(max_length=32) # username password(32)
-在根目录的路径下打开cmd
-第一步,让django知道我们做了哪些改变 python36 manage.py makemigrations
-第二步,让django把这些变化同步到数据库中 python36 manage.py migrate
-在pycharm中添加和mysql的链接
ORM Object Relational Mapping(对象-关系-映射)
- 面向对象与数据库之间的对应关系
- 类 -> 表
- 对象 -> 行
- 属性 -> 字段
-from app import models

obj = models.User.objects.all() # 返回一个QuerySet'对象列表',每一个元素对象,就是一行
obj = models.User.objects.get(字段名1=前端返回的信息,字段名2=...) # 找匹配的一个对象,找不到或者找到多个均报错
obj = models.User.objects.filter(字段名1=前端返回的信息,字段名2=...) # 获取满足条件的所有对象列表,没有就返回空对象列表

obj = models.User.objects.create(字段名1='xxx',字段名2=...) # 插入数据,返回这个对象
obj = models.User(字段名1='xxx',字段名2=...) # 或者直接实例化,然后再save也可以插入行
obj.save()

obj = models.User.objects.get(pk=1)
obj.delete()
obj_list = models.User.objects.get(pk=1)
obj_list.delete()

obj = models.User.objects.get(pk=1)
obj.name = 'new_name' 在内存中修改对象的属性
obj.save() 提交到数据库中

day3
django的命令
下载安装 pip install django==1.11.26
创建项目 django-admin startproject 项目名称
启动 cd到项目根目录下,python manage.py runserver # 127.0.0.1:8000
创建app python manage.py startapp app名称
数据库迁移
-python manage.py makemigrations # 记录models.py的变更记录
-python manage.py migrate # 迁移 将变更记录同步到数据库中
django的配置
静态文件
STATIC_URL = '/static/'
STATICFILES_DIRS=[
os.path.join(BASE_DIR,'static2'),
os.path.join(BASE_DIR,'static'),
os.path.join(BASE_DIR,'static1'),
]
数据库
看以前的笔记
INSTALLED_APPS
'app01'
'app01.apps.App01Config' 推荐这种写法
中间件
# 'django.middleware.csrf.CsrfViewMiddleware' 注释掉才可以接收post请求
TEMPLATES
'DIRS': [os.path.join(BASE_DIR, 'templates')]
使用mysql
创建 -> 配置 -> 更改为pymysql -> 在model中创建表(会设置主键) -> 迁移
day4
外键的设置
# 外键的语法,设置级联删除: on_delete=models.CASCADE,SET(value),SET_DEFAULT,SET_NULL
# 级联删除在1.11及之前的版本是默认的,2.0之后必须写出来
# django会自动给外键加上_id后缀,也就是说,在数据库中,是pid_id
# 'book_manager',推荐使用字符串的方式写参考表,此时是用的反射来找book_manager这个类,
# 若使用不加引号的book_manager,那么当book_manager这个表在最后的话,其它指向这个表的程序就找不到book_manager,因为是从上往下执行的
# 一队多时,把外键加到多里,这样查询出来就是一个对象
pid = models.ForeignKey('book_manager',on_delete=models.CASCADE)
外键的使用
{% for book in books %}
<td>{{ forloop.counter }}</td>
<td>{{ book.id }}</td>
<td>{{ book.name }}</td>
<td>{{ book.pid }}</td> pid并不是book表的字段,而是被关联的表的对象,也就是一行
<td>{{ book.pid.name }}</td> 进行了两次查询,先查pid,再查pid.name
<td>{{ book.pid_id }}</td> pid_id才是真正的book表的字段名
{% endfor %}
else语法
{% if 条件1 %}
xxx
{% elseif 条件2 %}
xxx
{% elseif 条件3 %}
xxx
{% else %}
xxx
{% endif %}
外键的新增
这两种都可以
obj = models.Book.objects.create(name = new_book,pid=models.book_manager.objects.get(pid = pid_id))
models.Book.objects.create(name = new_book,pid_id=pid_id)
day5
多对多的表的建立
class Author(models.Model):
name = models.CharField(max_length=32)
books = models.ManyToManyField('Book')
# 以上代码,django执行的流程:
创建author表,包含id,name字段
创建author_books表,包含id,author_id(自动外键关联Author表的id),book_id(自动外键关联Book表的id)字段
# 默认自带的就有级联删除的设置
# 这里的Authors.books = app01.Book.None,其类型是ManyRelatedManager,即关系管理对象
# Author.books.all() = 所有匹配该作者的书的对象列表
# 在html里的写法是 {{ Author.books.all }} 不需要加括号
多对多的表的使用 getlist('form的name属性') set()
# 把新增的作者写进数据库
new_author_name = request.POST.get('new_author_name')
author_obj = models.Author.objects.create(name = new_author_name)
# 如果传过来的书籍的id是多选,那么需要用getlist(),否则就只能识别到传过来的列表的最后一项
book_pk = request.POST.getlist('book_pk')
# 把这个作者的书全部一起写到数据库,set会删掉之前的,重新写
# set是直接对数据库操作的,不需要save
# 要理解这个set,其实就是对第三张表进行操作,更改当前Author_id对应的所有的Book_id
author_obj.books.set(book_pk)
设置多对多的3种方法
方法一: 见上
-django会帮我们生成第3张表,不会再Author中生成其它字段
方法二:
class AuthorBook(models.Model):
author = models.ForeignKey(Author,on_delete = models.CASCADE)
book = models.ForeignKey(Author,on_delete = models.CASCADE)
date = models.DateField()
特点:
可以增加多的列,从而存储更多的信息
缺点:
当需要修改该作者所有的书籍的时候,不方便,要一行行的修改
方法三: 联合方法一和方法二
class Author(models.Model):
name = models.CharField(max_length=32)
books = models.ManyToManyField('Book',through='AuthorBook')
class AuthorBook(models.Model):
author = models.ForeignKey(Author,on_delete = models.CASCADE)
book = models.ForeignKey(Book,on_delete = models.CASCADE)
date = models.DateField(now())
特点:
django检查到through,就不会主动生成第三张表
又可以往第3张表里增加字段
缺点:
不可以使用Author.books.set(),因为django不知道date字段怎么填
方法四: 方法3的特殊情况,了解,当有两个外键关联一张表的时候的多对多如何创建
class Author(models.Model):
name = models.CharField(max_length=32)
books = models.ManyToManyField('Book',through='AuthorBook',though_field=['author','book'])
class AuthorBook(models.Model):
author = models.ForeignKey(Author,related_name='a',on_delete = models.CASCADE)
book = models.ForeignKey(Book,on_delete = models.CASCADE)
tuijianren = models.ForeignKey(Author,related_name='b',on_delete = models.CASCADE)
date = models.DateField(now())
day6
图书馆项目流程总结(重要)
已经有数据库了,怎么自动生成model
-python36 manage.py inspectdb > models.py
如果手动在数据库里更改了字段
-需要在models里面增加修改或删除对应的字段,就是说models和mysql要对应起来才行
MVC,MTV
MVC:Model View Controller 软件工程中的一种软件架构模式
MTV: Django的框架模式
操作数据库 HTML 业务逻辑
MVC model view controller
MTV model templates urls+views
模板的使用
{{ }} 变量 (模板可接收多种类型的变量)(模板可直接使用{{ request }}变量)
1.用法
{{ list.索引 }} 不支持倒序索引,即不能识别list.-1
{{ dict.key }} 取对应的value,还支持dict.keys,dict.values,dict.items,
{{ obj.属性 }}, {{ obj.方法 }}
'.'支持所有不带参数的方法,用的时候去掉括号()
但不支持带参数的方法,例如{{ dict.get('key') }}
对于实例对象,通常自己重写__str__,让返回的内容好看些
2.优先级
使用'.'时,存在优先级: 在字典中的key -> 对象的属性或方法 -> 数字索引
eg,若view函数中存在dic = {'keys':'xxx','name':12},
当在html中使用dic.keys时,取到的是xxx,而不是dict_keys(['keys', 'name'])
3.过滤器,filter
作用:改变显示结果
语法:{{ 变量名|filter_name }} 或 {{ 变量名|filter_name:参数 }}
注意冒号:前后无空格,有空格就报错
内置过滤器:
{{ 变量|default:xxx }}
当'变量'在views中并没有定义或者是空时,页面返回default的值,'xxx'可以是int,string,或者是其它变量
setting里的TEMPLATES里的OPTIONS里加上'string_if_invalid' : '变量不存在', 作用与default一样
{{ filesize|filesizeformat }} >>> 1.0 KB 设置filesize=1025,返回其格式化大小
{{ 变量|add:xxx }}
可做算术运算(优先级>)字符串拼接(add:前后必须都是str类型),列表拼接(前后必须都是lst类型)
{{ 字符串|lower }} 小写
{{ 字符串|upper }} 大写
{{ 字符串|title }} 首字母大写
{{ 变量|length }} 返回变量的长度,如 value=['a', 'b', 'c', 'd']的话,就显示4
{{ 变量|slice:"-2:0:-1" }} 切片,与列表切片的使用方法一样
{{ 变量|first }} 取第一个
{{ 变量|last }} 取最后一个
{{ 变量|join:'|' }} 分割
{{ 'longstr'|truncatechars:5 }} >>> lo... 按字符截断 最后三个.也要算在长度里面
{{ 'long str'|truncatewords:1 }} >>> long... 按单词截断 最后三个.不算在长度里面 只认英文 不认中文
{{ time|date:'Y-m-d H:i:s' }} 格式化时间
设置 {'time':datetime.datetime.now()}
输出 2019-11-10 14:29:58,记得是i和s,不是M和S
date的另一种设置,在settings里面修改下面配置:
USE_L10N = False
DATETIME_FORMAT = 'Y-m-d H:i:s'
DATE_FORMAT = 'Y-m-d'
TIME_FORMAT = 'H:i:s'
这样之后,只要django检查到参数time是时间格式的,就会自动把其格式转换成对应的时间格式
{{ time }} >>> 2019-11-10 14:34:57
{{ 变量|safe }} 告诉django不需要转义,比如:变量 = "<a href='#'>点我</a>"
{{ 变量|add:'2'|safe }} safe常见的就是这种多个过滤器一起使用的情况
可以去设置这个过滤器函数的 @register.filter(is_safe=True) 来达到默认safe的效果
或者过滤器函数源文件里, from django.utils.safestring import mark_safe,然后 return mark_safe(....)
4.自定义过滤器filter
1.在app文件夹内创建一个名为'templatetags'的python包,包名不可修改
-文件夹也可以,但官方文档说最好用包
-用了包之后,pycharm就可以加载这个包,在输入与包内内容相关的变量的时候,就可以智能补全,使用文件夹就没法智能提醒和补全
2.在这个包内新建一个my_tags.py文件,文件名可以任意
3.在这个文件中写入以下内容,register不能是其它名字,函数名可以任取
from django import template
register = template.Library()
@register.filter(name='haha') # haha就是add_xx函数的别名,使用的时候,要使用haha()
def add_xx(value,arg): # 最少一个参数(value),最多两个参数
return '{}-{}'.format(value,arg)
4.在html文件中
{% load my_tags %}
{{ 'we'|haha:'sweet' }}
>>> we-sweet
5.如果自定义filter不管用,记得去检查app有咩有注册
day7 {% %} 标签
1.for标签
{% for i in list %}
...{{ forloop }}
{% endfor %}
forloop内置变量
forloop.counter 当前循环的索引值(从1开始)
forloop.counter0 当前循环的索引值(从0开始)
forloop.revcounter 当前循环的倒序索引值(到1结束)
forloop.revcounter0 当前循环的倒序索引值(到0结束)
forloop.first 当前循环是不是第一次循环(布尔值)
forloop.last 当前循环是不是最后一次循环(布尔值)
forloop.parentloop 本层循环的外层循环,如果没有外层循环,那么parentloop:{}就是空字典
举例 {# 需求:对行列均处于偶数位置的数据进行标红 #} 要会使用forloop.parentloop
hobbies = [
['唱','跳','rap','篮球','卖艺'],
['唱','跳','rap','篮球','卖艺'],
['唱','跳','rap','篮球','卖艺'],
]
return render(...'hobbies': hobbies)
<table border="1">
<tbody>
{% for hobby in hobbies %}
<tr>
{% for hobby_in in hobby %}
{% if forloop.counter|divisibleby:2 and forloop.parentloop.counter|divisibleby:2 %}
<td style="color: red">{{ hobby_in }}</td>
{% else %}
<td>{{ hobby_in }}</td>
{% endif %}
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
for...empty标签 去看图书馆里项目的/book_list/
<ul>
{% for user in user_list %}
<li>{{ user.name }}</li>
{% empty %}
<li>空空如也</li> # 如果循环拿到的是空,那么就显示空空如也
{% endfor %}
</ul>
2.if标签
{% if user_list %}
用户人数:{{ user_list|length }}
{% elif black_list %}
黑名单数:{{ black_list|length }}
{% else %}
没有用户
{% endif %}
注: elif和else都不是必须项
if语句支持 and 、or、==、>、<、!=、<=、>=、in、not in、is、is not判断
不支持连续判断:
python中 10>5>1, 10>5 and 5>1,True and True,True
模板中和js中,10>5>1,True>1,Flase,即不支持连续判断,而且也不支持 if 2 + 2 == 4,因为不识别'+-*/'
3.with标签 中间变量 改名字 仅仅在with标签内才生效
{% with 变量名=新变量名 %} # 等号前后不能有空格
{{ 新变量名 }}
{% endwith %}
{# ... #}注释标签
{# ... #} 这是django的注释语法,里面的内容不会翻译,也不会放在最终的html文档中
<!-- {{ string }} --> 这时html的注释,里面的变量会被翻译,会被放在html文档中,但不会显示在网页上
4.csrf_token标签
csrf: 跨站请求伪造
form表单里面写上{% csrf_token %},就可以发送post请求,同时防止跨站伪造,且不用在settings里面注释csrf了
原理: django在html里加了一个隐藏的input标签,其value是是给服务器做验证用的
<input type="hidden" name="csrfmiddlewaretoken" value="nwSD71CUEPor07CRbX7L2tkGZFGAPDOSWZSf1qANLlkngeMrn5EfBorazxYQsxKf">
5.母版和继承
母版: base.html
一个普通的html,其提取了多个页面的公共部分,不同的地方用{% block v %}{% endblock %}标记
在head最后加上{% block cs %}{% endblock %},给需要继承的页面留出空间,方便加载其自己的css样式
在body最后加上{% block js %}{% endblock %},给需要继承的页面留出空间,方便加载其自己的js样式
继承:
引入母版{% extends 'base.html' %},必须放在body的首行,如果不加引号,会被当成变量
使用{% block v %}xxx{% endblock %}来修改母版中的内容,要修改的内容xxx必须写在block块中,否则不显示
6.组件 navbar.html
把一小段html代码单独存在html里,方便引用 例如,把图书管理系统的导航栏单独拿出来
引用方法 {% include 'navbar.html' %}
组件是写死的,里面不能定义block块
7.静态文件,常用在head中,可动态的去检索settings里面的STATIC_URL = '/static/',然后做拼接
settings.py中
STATIC_URL = '/static9/' # 9只是为了方便说明问题
STATICFILES_DIRS = [
os.path.join(BASE_DIR,'static2'),
os.path.join(BASE_DIR,'static0'),
os.path.join(BASE_DIR,'static1'),
]
html.py中
{% load static %} # 常放在html标签的第一行,以前废弃掉的写法是{% load staticfiles %}
{% get_static_prefix %} >>> /static9/
<link href="{% static '/css/bootstrap.css' %}">
>>> 自动拼接成 '/static9/css/bootstrap.css'
>>> 虽然我实际的文件夹的名字叫'static',但这个请求还是能够访问到这个css文件
>>> 查找顺序是按照添加路径的先后,也就是 2 -> 0 -> 1
8.自定义方法 filter(见上) simple_tag inclusion_tag
流程:见上
simple_tag 可以传入任意数量的参数,比filter好用
@register.simple_tag
def push(value,*args,**kwargs):
return '{}-{}-{}'.format(value,'#'.join(args),'$'.join(kwargs.values()))
在html中使用时:
{% load my_tags %}
{% push lst '1' '2' k1='3' k2='4' k3='5' %}
# simple_tag对参数类型不限制 lst是render传过来列表
inclusion_tag('组件.html'),可以传入任意数量的参数,弥补了组件无法使用block的不足
@register.inclusion_tag('组件.html')
def page(num):
return {'num' : range(1,num+1)}
在html中使用时:
{% load my_tags %}
{% page 2}
调用流程: 2传给page函数,page函数return给组件html,
组件html拿到num变量,并渲染其自身的{{num}},
渲染完之后的组件html被返回给调用page的那个位置
day 8 views视图
FBV与CBV
function base view 在views里面写函数
class base view 在views里面写类
设置
from django.views import View
class AddPublisher(View):
# http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
# 这是View源码里的请求方式列表,下面所有的函数名都必须来自于列表
def get(self,request,*args,**kwargs):
return ...
def post(self,request,*args,**kwargs):
return ...
使用
url(r'^add_publisher/', views.AddPublisher.as_view())
流程
as_view() -> view函数 -> 实例化AddPublisher, self = cls(**initkwargs),self.request = request
-> 执行self.dispatch(request,*args,**kwargs)
-> 判断请求方式是否被允许 http_method_names = ['get',...]
-> 如果允许,通过反射拿到类中对应的请求方式的方法 -> handler
如果不允许 -> self.http_method_not_allowed -> handler
-> handler(request,*args,**kwargs)
-> 返回响应
FBV与CBV加装饰器
FBV,直接加上就行,去看图书管理项目,views里面的book_manager函数,加了@timer
CBV,
首先导入模块
from django.utils.decorators import method_decorator
然后自己写一个装饰器
def timer(func):
def inner(request,*args,**kwargs):
start = time.time()
ret = func(request,*args,**kwargs)
end = time.time()
print('消耗时间: {}'.format(end - start))
return ret
return inner
其次是4种加法:
方法1:直接加到方法上
@method_decorator(timer)
def get(....)
方法2:加到类上
@method_decorator(timer,name='post')
@method_decorator(timer,name='get')
class AddPublisher(View):
方法3:加到dispatch上,等于是给所有的自定义函数都加上了timer
@method_decorator(timer,name='dispatch')
class AddPublisher(View):
方法4:重写dispatch,并加到其上面,作用与方法3一样
@method_decorator(timer)
def dispatch(self, request, *args, **kwargs):
ret = super().dispatch(request, *args, **kwargs)
return ret
注:也可以直接在类的方法上加@timer
print(args) --> (slef对象,<WSGIRquest:...>即request)
使用method_decorator时
print(args) --> (<WSGIRquest:...>即request)
可见,使用method_decorator之后,装饰器内的inner的第一个参数就是request,这样导致在使用时不易弄混参数位置,不易出错
request对象的属性和方法
属性
request.method 请求方式
request.GET url上的参数 ?name=jay&pwd=123
request.POST POST请求提交的数据
request.path_info URL的路径,不包括ip和端口,不包含参数 eg./home/
request.body 请求体 b''
request.scheme 协议 http https
request.META 字典,全大写,包含所有的HTTP请求头
request.COOKIES
request.session 会话
request.FILES 上传文件
views里的设置,基本固定的写法,记住f1.chunks()就是一点点的从客户端取,避免撑爆内存
def upload(request):
if request.method == 'POST':
# 获取文件
print(request.FILES)
f1 = request.FILES.get('f1')
# 保存文件
with open('e:/'+f1.name,'wb') as f:
for i in f1.chunks():
f.write(i)
return render(request,'upload.html')
html里的设置,注意enctype
<form action="" method="post" enctype="multipart/form-data">
{% csrf_token %}
<input type="file" name="f1">
<button>上传</button>
</form>
方法
request.get_host() 获取主机ip和端口
request.get_full_path() 全路径,包含参数 例如 "/music/bands/the_beatles/?print=true"
request.is_ajax() 如果请求是通过XMLHttpRequest 发起的,则返回True
response对象的属性和方法 (常用的3种:render,redirect,HttpResponse)
from django.shortcuts import render,redirect,HttpResponse
HttpResponse('字符串')
render(request,'html全称',{'key': value}) 最后还是继承的HttpResponse
redirect('/路径/') 重定向,最后还是继承的HttpResponse
JsonResponse 返回给浏览器的是Content-Type: application/json,这样浏览器会自动反序列化
from django.http.response import JsonResponse
import json
def json_data(request):
data={'name':'alex','age': 18}
lst=[1,2,3]
return JsonResponse(data)
return JsonResponse(lst,safe=false) JsonResponse默认只能传递dic,要传递list,需要设置参数
return HttpResponse(json.dumps(data)) Content-Type: text/html; 浏览器不会自动反序列化,所以可以自己封装
day 9 路由系统
路由: 一种请求与一个函数之间的映射关系
url配置 应该是^articles,而不是 ^/articles,因为django会自动在域名后面加上一个'/' 'r' 是可选的但是建议加上
1.0版本
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^articles/$', views.函数1), 只能匹配一个地址称为静态路由
# 如果一个地址能同时匹配函数2和3,那么按从上到下的顺序匹配
url(r'^articles/', views.函数2), 可以匹配多个地址称为动态路由
url(r'^articles/[0-9]{4}/', views.函数3), 可以匹配多个地址称为动态路由
...]
2.0版本
from django.urls import path,re_path
urlpatterns = [
path(r'^articles/$', views.函数名),...]
分组
url(r'^articles/([0-9]{4})/(\d{2})', views.函数)
函数此时需要设置3个位置参数,def 函数(request,year,mouth) year=[0-9]{4} mouth=\d{2}
这样就可以不使用request.GET.get()去拿url中的参数了,直接就可以在函数中使用year变量
命名分组
url(r'^articles/(?P<year>[0-9]{4})/(?P<mouth>\d{2})', views.函数)
与分组不同的是,此时year和mouth是关键字参数,不再是位置参数,且定义函数时year和mouth不可更改写法
函数默认值 当不同的url都指向一个函数的时候,需要注意函数的参数个数问题
urlpatterns = [
url(r'^blog/$', views.page),
url(r'^blog/page(?P<num>[0-9]+)/$', views.page),
]
# views.py中,可以为num指定默认值
def page(request, num="1"):
pass
路由分发 include
根路由: settings里面 ROOT_URLCONF = 'orm_test.urls'
与settings同级的urls.py中
from django.conf.urls import url,include
from django.contrib import admin
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^app01/', include('app01.urls')), # 可以包含其他的URLconfs文件
url(r'^org/', include(('apps.organizations.urls','organizations'),namespace='org')), # 另一种用法
]
app01文件夹内的urls.py中
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^book_manager/', views.book_manager),
....
]
views函数的额外参数(了解)
url(r'^blog/(?P<year>[0-9]{4})/$', views.year_archive, {'year': 2019,'foo': 'bar'}),
2019的优先级比url里面的year的高,且是int类型的
URL反向解析 在views或template或其它需要使用URL的地方,通过特定语法,反向获取URL
name称为:URL的命名
url(r'^reverseURL/$', views.reverseURL,name='static'), 静态的
url(r'^reverseURL/(\d*)/$', views.reverseURL,name='dynamic'), 动态的命名
url(r'^reverseURL/(?P<year>\d{4})/(?P<month>\d{2})/$', views.reverseURL,name='dynamic_group'), 动态的命名分组

views里面
from django import reverse
reverse('static') >>> /reverseURL/
reverse('dynamic',args=(2222,)) >>> /reverseURL/2222/ 注意元祖的逗号不能少 额外的,def reverseURL(request,year):
reverse('dynamic_group',kwargs={'year':5555,'month':12}) >>> /reverseURL/5555/12/ 额外的,def reverseURL(request,year,month):

template里的html里
{% url 'static' %} >>> /reverseURL/
{% url 'dynamic' 2019 %} >>> /reverseURL/2019/
{% url 'dynamic_group' 2019 12 %} >>> /reverseURL/2019/12/
{% url 'dynamic_group' month=12 year=6666 %} >>> /reverseURL/6666/12/
命名空间
urls里
urlpatterns = [
url(r'^app01/', include('app01.urls')),
url(r'^app02/', include('app02.urls')), # 可以包含其他的URLconfs文件
]
app01.urls里 urlpatterns = [url(r'^index/',views.index,name='index'),]
app02.urls里 urlpatterns = [url(r'^index/',views.index,name='index'),]
两者的index.html里都是 {% url 'index' %}
那么当在地址栏里输入127.0.0.1:8002/app01/index/时,返回的是 /app02/index/,因为下面的name覆盖了上面的
要解决这个问题,就要如下设置
url(r'^app01/', include('app01.urls',namespace='app01')),
url(r'^app02/', include('app02.urls',namespace='app02')),
且在使用的要加上'app01:',例如,在html中使用方法: {% url 'app01:index' %},在views中使用方法reverse('app01:index')
删除出版社,书籍,作者三合一
day10 (app.model配置)
orm对应关系
类 -> 表
对象 -> 数据行
属性 -> 字段
ORM字段
AutoField(primary_key=True) 设置主键固定搭配,必须加primary_key参数
CharField(max_length=32) 默认是varchar,必须加max_length参数
IntegerField() 整数,默认int(11)
FloatField() 浮点型,默认double,(注原mysql中float(255,30)是单精度,duble(255,30)是双精度)
DecimalField() decimal类型,默认是double(65,30)双精度,可自行修改小数位数
max_digits 总长度
decimal_places 小数位长度
TextField() 存长文本
date = models.DateTimeField(auto_now=True)
存储格式为YYYY-MM-DD HH:MM:ss.uuuuuu uu是毫秒,中国是东八区,所以可看到存储的时间比实际时间慢了8小时
通常会设置其一:
auto_now=True 新增,修改对象时自动添加当前时间。
auto_now_add=True 新增对象时自动添加当前时间。
注:DateField只有YYYY-MM-DD
ORM字段的参数
null=True 默认是False,即默认不能为null
db_column='name' 修改数据库中字段名为name
default 默认值,和auto_now_add,和auto_now 三者只能选其一
primary_key=True 设置主键主键,默认为False
db_index=True 建立索引,默认为False
unique=True 唯一,默认为False
admin表的参数
blank=True admin登录后的表单里,可以输入''空字符串,blank=False则为必填项
在cmd中,输入python36 manage.py createsuperuser
设置用户名,默认是administrator,后面可以改
email可以不输,密码必须是字母数字的组合root1234,最少8位
习惯上使用联合,blank=True, null=True
verbose_name admin登录后的表单填写页面显示的字段名称,不再是原有的字段名了
-def __init__(self, verbose_name=None...) 通常字段的第一个参数就是这个,所以可以以位置传参的方式放在第一个位置
editable Admin表是否可以编辑,默认为True
help_text Admin中该字段的提示信息,默认为空help_text=''
choices(常用) Admin表选择框,用不变动的数据放在内存中从而避免跨表操作
-eg, gf = models.IntegerField(choices=[(0, '肌肉'),(1, '大表姐'),],default=1)
-0和1是存储在mysq中的,肌肉和大表姐是显示在网页上的
class Meta: 类中类
db_table = "table_name" # 数据库中表名称, 不再是'app名称+下划线+类名'
verbose_name = '个人信息' # admin中显示的,内层结构表名称
verbose_name_plural = '所有用户信息' # admin中显示的,外层结构表名称

# 联合索引
index_together = [
("pub_date", "deadline"), # 应为两个存在的字段
]

# 联合唯一索引
unique_together = (("driver", "restaurant"),) # 应为两个存在的字段
必知必会13条ORM查询对象语句 (单表查询)
首先创建python脚本,方便查询django的对象,设置如下:
import os,django
os.environ.setdefault('DJANGO_SETTINGS_MODULE','orm_practice.settings')
django.setup()
from app01 import models
在models表里的Person类里重写__str__方法,让结果更易分辨
def __str__(self):
return '{}-{}-{}'.format(self.pk,self.name,self.age)
总结如下(前提是在models里面添加一个Person表,并设置pid主键,name,age,tall,birth(DateTimeField类型),等字段)
返回queryset的有
ret = models.Person.objects.all() # 返回QuerySet
filter(age=57,tall=170,) exclude(age=57,tall=170,) # 可传参,exclude是filter的补集,
values('name','pk',) values_list('name','pk',) # 可传参,且参数顺序可自行决定,values返回字典{name:'peiqi',...},values_list返回集合('peiqi',...)
order_by('-age','tall',) # 默认是升序,加上符号-就是降序,先按age降序,再按tall升序(目的就是如果存在相同的age,那么就按tall升序排列)
reverse() # 只能对order_by之后的对象进行reverse操作
distinct() # 只有当筛选出来的两个对象的数据完全一致的时候才可以使用去重,不支持 按字段去重
返回数字的:
count() 统计当前QuerrySet里面的对象个数
返回具体对象的
get(name='marry') # 必须填参数,查询到多个或没有,就报错
first() last() # first和last取不到就返回none
返回布尔值的
exists() # 当使用if语句时,使用if ...filter(age=11).exists()的效率要高于if ...filter(ag=11)
单表的双下划线查询 (单表查询)
脚本设置同上
ret = models.Person.objects.filter(pk__gt=100) # greater than 大于
ret = models.Person.objects.filter(pk__lt=100) # less than 小于
ret = models.Person.objects.filter(pk__gte=100) # greater than equal 大于等于
ret = models.Person.objects.filter(pk__lte=100) # 小于等于
ret = models.Person.objects.filter(pk__in=[100,103]) # 原生sql 中的 in
ret = models.Person.objects.exclude(pk__in=[100,103]) # 原生sql 中的 not in

ret = models.Person.objects.filter(pk__lte=103,pk__gte=100) # 小于等于,并且大于等于
ret = models.Person.objects.filter(pk__range=[1,3]) # 左闭右闭区间,且包括1-3之间的小数
ret = models.Person.objects.filter(name__contains = 'b') # 原生sql中的 like 包含,区分大小写
ret = models.Person.objects.filter(name__icontains = 'B') # 不区分大小写
ret = models.Person.objects.filter(name__startswith = 'b') # 以..开头,区分大小写
ret = models.Person.objects.filter(name__istartswith = 'B') # 以..开头,不区分大小写
ret = models.Person.objects.filter(name__endswith = 'X') # 以..结束,区分大小写
ret = models.Person.objects.filter(name__iendswith = 'X') # 不敏感处理,不区分大小写
ret = models.Person.objects.filter(age__isnull=True) # 是否为null

针对DateTimeField字段类型的数据
ret = models.Person.objects.filter(birth__year='2019') # 查年份
ret = models.Person.objects.filter(birth__month='01') # 查月份
ret = models.Person.objects.filter(birth__contains='2019-01-24') # 查日期的方法
外键的查询 (多表查询)
举例,现有以下两个表
class Publisher(models.Model): # 一
name = models.CharField(max_length=32)
def __str__(self):
return self.name
class Book(models.Model): # 多
title = models.CharField(max_length=32)
pub = models.ForeignKey(Publisher, on_delete=models.CASCADE)
def __str__(self):
return self.title
需求:查询红楼梦的出版社 (多 -> 1) (也可以用在多对多上,用法完全一样)
基于对象的方式 (正向查询)
ret = models.Book.objects.get(title='红楼梦').pub 直接就可以拿到所关联对象
基于字段的方式 (反向查询)
ret = models.Publisher.objects.filter(book__title='红楼梦')
注:如果设置了pub = models.ForeignKey(...,related_name='books',...),那么就要用books__title(book__title是默认写法)
注:related_query_name='xxx'会覆盖掉related_name=的设置(了解)
related_name 主要用于反向的对象查询的设置
related_query_name 主要用于反向的字段查询的设置,且优先级高于related_name
需求:查询机械出版社的所有的书 (1 -> 多) (也可以用在多对多上,用法完全一样)
基于对象的方式 (正向查询)
ret = models.Publisher.objects.get(name='机械出版社').book_set.all()
分析:
pub_obj = models.Publisher.objects.get(name='机械出版社')
pub_obj.book_set # RelatedManager 关系管理对象(小写的类名_set),注意区分和上面的'.pub'的区别
pub_obj.book_set.all() # 通过关系管理对象拿到机械出版社的所有的书
注:可以更改book_set的写法
pub = models.ForeignKey(...,related_name='books',...)
这个时候,写法就变成pub_obj.books.all()
基于字段的方式 (反向查询)
ret = models.Book.objects.filter(pub__name='机械出版社')
# pub__name,表示通过pub,跨到其所关联的表,拿到name对应的数据,然后返回book表中匹配name的那些对象
# 这里若写publisher__name,是错误的
day11
多对多查询与操作 首先创建python脚本,方便查询django的对象,设置见上,表的参数见下:
class Publisher(models.Model): # 一
name = models.CharField(max_length=32)
def __str__(self):
return self.name
class Book(models.Model): # 多
title = models.CharField(max_length=32)
pub = models.ForeignKey(Publisher, on_delete=models.CASCADE)
def __str__(self):
return self.title
class Author(modesl.Model):
name = models.CharField(max_length=32)
books = models.ManyToManyField(Book,)
def __str__(self):
return self.name
基于对象的方式
正向查询(1 -> 多)
需求: 拿到作者id为1写的所有的书
author_obj = models.Author.objects.get(pk=1)
author_obj.books # 关系管理对象
author_obj.books.all()
反向查询(1 -> 多)
需求: 拿到红楼梦的所有作者
book_obj = models.Book.objects.filter(title='红楼梦').first()
book_obj.author_set # 关系管理对象
book_obj.author_set.all()
注:可以更改author_set的写法
books = models.ManyToManyField(Book,related_name='authors')
pub = models.ForeignKey(...,related_name='books',...)
这个时候,写法就变成book_obj.authors.all()
基于字段的方式
正向查询(1 -> 多)(查红楼梦的所有作者)
ret = models.Author.objects.filter(books__title='红楼梦') # 注意是双下划线,且要用filter
print(ret) # 拿到了
反向查询(1 -> 多)(查jay作者写的所有书)
ret = models.Book.objects.filter(author__name='jay')
print(ret) # 拿到了
注:可设置books里的related_name='authors'来更改author__name的写法为authors__name
注:related_query_name='xxx'会覆盖掉related_name=的设置(了解)
关系管理对象的方法 RelatedManager
多对多时 (借用上面的多对多的例子)
author_obj = models.Author.objects.get(pk=1)
author_obj.books # 关系管理对象
author_obj.books.all() #拿到作者(id=1)写的所有的书,QuerySet对象列表
author_obj.books.set([1,2]) # set([id,id]),在第三张表中,设置作者(id=1)写的所有的书的id为1和2
author_obj.books.set(models.Book.objects.filter(pk__in=[1,2,3])) # set([对象,对象]),在第三张表中,设置作者(id=1)写的所有的书为[对象,对象]对应的这些书
author_obj.books.add([2,3]) # add([id,id]),在第三张表中,给作者(id=1)添加关联关系(书的id为2和3)
author_obj.books.add(*models.Book.objects.filter(pk__in=[2,3])) # add([对象,对象]),在第三张表中,给作者(id=1)添加关联关系(所关联的书的对象为pk__in=[2,3]),需要在对象列表前加*,来打散列表
author_obj.books.remove([4,5]) # 原理与add一样,remove是删除关联对象
author_obj.books.remove(*models.Book.objects.filter(pk__in=[4,5])) # 原理与add一样
author_obj.books.clear() # 在第3张表中,清空作者(id=1)关联的所有的书的id
author_obj.books.create(title='西游记',pub_id=1) # 首先在Book表里新建一个西游记对象,设置pub_id=1,然后在第三张表中,给作者(id=1)添加与西游记的关联关系
create也可以反向用
book_obj = models.Book.objects.get(pk=1)
book_obj.author_set.create(name='金庸') # 先拿到book对象(id=1),在反向的author_set拿到关系管理对象,然后在author表里新建一个金融对象,然后再在第三张表里给book(id=1)关联上金融的id
外键 (1对多) (借用上面的多对多的例子)
pub_obj = models.Publisher.objects.get(pk=1)
pub_obj.book_set.all() # 通过反向的book_set拿到关系管理对象,然后根据书的信息,展示出版社(id=1)出版的所有的书
pub_obj.book_set.set(models.Book.objects.filter(pk__in=[1,2,3])) # 不能再用set([id,id])的方法,只能用set([对象,对象]),自己的理解:book表里有id和title和pub_id字段,
pub_id是关联的字段,可不用管,django会自行添加这个字段
即使在这里指定了要关联的book的id字段,但是title字段被忽略,程序并不认可这种只指定一个字段的方法,所有只能用book的对象,其包含了id和title两个字段
pub_obj.book_set.add(*models.Book.objects.filter(pk__in=[4,5])) # 原理与set一样,需要加*来打散列表
remove()和clear(),需要设置Book的pub字段的参数 'null=True',可想而知,如果不能为null,那就只能为空字符串(这当然是不可能的),或删除这一行(这也不可能)
pub_obj.book_set.remove(*models.Book.objects.filter(pk__in=[4,5])) # 原理与set一样,需要加*来打散列表
pub_obj.book_set.clear() # 在book表中,清空出版社(id=1)与book的所有的对应关系,留下null
pub_obj.book_set.create(title='用python煮饭') # 在book表中,新增'用python煮饭'对象,然后给其加上pub_id=1的关联关系
聚合与分组 (682集,习题,或者orm_test项目)
前期准备,在models里面创建book表,并手动添加一些数据
class Publisher(models.Model): # 一
name = models.CharField(max_length=32)
def __str__(self):
return self.name
class Book(models.Model): # 多
title = models.CharField(max_length=32)
price = models.DecimalField(max_digits=6,decimal_places=2)
pub = models.ForeignKey(Publisher, on_delete=models.CASCADE)
def __str__(self):
return self.title
class Author(modesl.Model):
name = models.CharField(max_length=32)
books = models.ManyToManyField(Book,)
def __str__(self):
return self.name
准备python脚本,663行,
需要额外导入模块 from django.db.models import Avg, Sum, Max, Min, Count
聚合: aggregate()是QuerySet的一个终止子句
需求: 求id大于3的所有书的最高价格,与平均价格
ret = models.Book.objects.filter(pk__gt=3).aggregate(Max('price'),avg=Avg('price'))
print(ret) >>> {'avg': 100.0,'max': Decimal('190.00')} 说白了就是对结果进行筛选,且返回字典,key是django自己生成的,你也可以自己重命名
分组:
需求:统计每一本书的作者个数
ret = models.Book.objects.annotate(Count('author')) # 返回QuerySet,每一个元素都是Book对象,annotate就是分组的体现
ret = models.Book.objects.annotate(Count('author')).values()
# 在第三张表中,按照Book进行分组,然后统计每一组的作者个数
# 返回每一个Book对象的所有字段与对应的数据,末尾会自动添加一个字段'author__count'这就是聚合的体现,注意是双下划线
ret = models.Book.objects.annotate(count=Count('author')).values() # 把最后一个字段'author__count'重命名为'count'
需求:统计每个出版社最便宜的书的价格
方式一 (1 -> 多)
ret = models.Publisher.objects.annotate(Min('book__price')).values()
# 反向分组,在book表中,(在设置了外键的这个表中),对publisher进行分组,然后对每一组使用Min聚合函数
# 返回每一个Publisher对象的所有字段与对应的数据,末尾会自动添加一个字段'book__price__min',注意是双下划线
方式二 (多 -> 1)
ret = models.Book.objects.values('pub_id').annotate(min=Min('price'))
# 使用pub_id和pub效果一样
# 正向分组,在book表中,按照values给的条件进行分组,然后再按照annotate的条件聚合
# 返回一个列表,列表里的每一个元素都是一个字典(这就是分组聚合的结果),里面的字段只有'pub_id'和'min'
# ret = models.Book.objects.values('pub_id').annotate(min=Min('price')).value('pub_id') 这种写法就是说去拿字典里的pub_id字段,一般不这么用
需求:统计不止一个作者的书 (多 -> 多)
ret = models.Book.objects.annotate(num=Count('author')).filter(num__gt=1)
# 返回的是QuerySet
# 在第三张表中,先对书进行分组,然后再用Count聚合函数求出每个组的作者的数量,再进行筛选
需求:统计每个作者出的书的总价格 (多 -> 多)
ret = models.Author.objects.annotate(Sum('books_price')).values()
# 在第三张表中,先对作者进行分组,然后再用Sum聚合函数求出每个组的书的总价,最后用values拿到每个对象具体的key和value
F和Q
F
官网地址: https://docs.djangoproject.com/en/1.11/ref/models/expressions/
An F() object represents the value of a model field or annotated column
作用:操作数据表中的某列值,F()允许Django在未实际链接数据的情况下具有对数据库字段的值的引用
不用获取对象放在内存中再对字段进行操作,直接执行原生产sql语句操作。
前期准备,手动添加数据
class Book(models.Model):
title = models.CharField(max_length=32)
price = models.DecimalField(max_digits=6,decimal_places=2)
kucun = models.IntegerField()
sale = models.IntegerField()
def __str__(self):
return self.title
需要额外导入模块 from django.db.models import F,Q
需求:查询销量大于库存的书
ret = models.Book.objects.filter(sale__gt=F('kucun'))
# F('kucun')可以理解成拿到了库存这个字段的信息
需求:拿到所有的书,同时将销量改成2倍再加上10
ret = models.Book.objects.all().update(sale=F('sale')*2+10)
# update(sale=100)功能上等于ret.sale=100,ret.save()
# 不同在于save()会更新所有字段,update只会更新指定的字段,且update可直接对QuerySet进行修改
Q
作用:对对象进行复杂查询,并支持&(and),|(or),~(not)操作符。
需求:拿到id>3,或者id<2的,并且价格>50的书
ret = models.Book.objects.filter(Q(Q(pk__gt=3) | Q(pk__lt=2)) & Q(price__gt=50))
与或非 & | ~
事务
准备一个简单的表
class Publisher(models.Model):
name = models.CharField(max_length=32)
from django.db import transaction
try:
with transcation.atomic():
# 进行一系列orm操作,如果中途出错,那么会自动进行回滚
models.Publisher.objects.create(name='xxx')
int("ss")
models.Publisher.objects.create(name='xxx2')
except Exception as e:
print(e)
day12
cookie
定义: 保存在浏览器本地上的一组组的键值对
特点:
1.由服务器让浏览器进行设置
2.浏览器保存在浏览器本地
3.下次访问自动携带
为什么要有cookie?
1.登录
2.由于http是无状态的,cookie可以让浏览器保存状态
设置:
ret = redirect('/index/')
方式一: ret.set_cookie('login','1') >> Response Headers里面是Set-Cookie: login=1; Path=/
方式二: ret.set_signed_cookie('login','1','s21') >> s21是加密用的salt
方式三: ret['Set-Cookie']='login=1; expires=Tue; Max-Age=10; Path=/'
return ret
获取:
request.COOKIES >> 获取所有的cookie,{}
request.COOKIES.get('login') >> 获取login的value
request.get_signed_cookie('login',salt='s21',default='')
>> 获取加密的login的value,注意设置default,否则默认是取不到cookie就报错
cookie的参数
ret.set_cookie(self, key, value='', max_age=None, expires=None, path='/',
domain=None, secure=False, httponly=False)
key, 键
value='', 值
max_age=None, 超时时间
expires=None, 超时时间(IE requires expires, so set it if hasn't been already.)
path='/', Cookie生效的路径,/ 表示根路径,特殊的:根路径的cookie可以被任何url的页面访问
domain=None, Cookie生效的域名
secure=False, 默认是http传输,如果改成True,那么使用http传输的时候就不会主动携带cookie,需要设置成https
httponly=False 若设置成True,cookie就只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖)
删除: (常用于注销登录)
ret = redirect('/login/')
ret.delete_cookie('login')
>> self.set_cookie(key, max_age=0, path=path, domain=domain,expires='Thu, 01-Jan-1970 00:00:00 GMT')
>> 其实就把时间设置成0了
return ret
session
定义: 保存在服务器本地上的一组组的键值对
为什么要有session?
1.cookie保存在浏览器本地,有安全隐患
2.网络传输有大小限制,cookie数量不可能无限制
环境配置
首先设置db.sqlite3数据库,这时django默认的数据库,下载驱动
执行 python36 manage.py migrate
自动生成一系列表,session相关数据就存放在django_session表中
django_session表的字段
session_key 即cookie中sessionid的值,发送给服务器的
session_data 一串加密的数据
expire_date 数据生成的时间,主要用来计算过期时间 (sqlite3数据库中,时间数据不可修改)
操作
request.session['login'] = 1 # 设置,在cookie的地方设置
ret = request.session.get('login','no session data') # 获取,no session data是get函数的default值
request.COOKIES >> {'sessionid': 'hqcy9u3haecjzeuzxybac0psvsm2q3v6'} 随机的字符串是django生成的,比喻成钥匙,箱子是数据库中的session_data字段
request.session >> <django.contrib.sessions.backends.db.SessionStore object at 0x000002A925676978>
ret,type(ret) >> 1, int session是可以设置成int类型的,别忘了
session必须依赖cookie,以用来存放session的key
session的方法
del request.session['login'] # 删除session中的login
request.session.keys() # 获取键值对
request.session.values()
request.session.items()
request.session.session_key >> 就是当前生成的那个随机字符串
request.session.clear_expired() # 将所有Session失效日期小于当前日期的数据删除
request.session.exists("session_key") # 检查会话session的key在数据库中是否存在
request.session.delete() # 删除当前会话的数据库中的Session数据,但并不会去删除cookie
request.session.flush() # 删除当前会话的数据库中的Session数据,并且设置sessionid的max_age=0,也就是删除sessionid这个cookie
request.session.set_expiry(value)
* 如果value是个整数,session会在些秒数后失效。
* 如果value是个datatime或timedelta,session就会在这个时间后失效。
* 如果value是0,用户关闭浏览器session就会失效。
* 如果value是None,session会依赖全局session失效策略。
如果不进行任何设置,session会一直保留2周,即使重启浏览器也会被保留,当然,每一次重新生成session时,都会重新写入最新的时间,再重新计算2周
session的配置
from django.conf import global_settings # 默认配置都在这里面
5种引擎
1. 数据库Session
SESSION_ENGINE = 'django.contrib.sessions.backends.db' # 引擎(默认)

2. 缓存Session
SESSION_ENGINE = 'django.contrib.sessions.backends.cache' # 引擎
SESSION_CACHE_ALIAS = 'default' # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置

3. 文件Session
SESSION_ENGINE = 'django.contrib.sessions.backends.file' # 引擎
SESSION_FILE_PATH = None # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir()

4. 缓存+数据库
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db' # 引擎

5. 加密Cookie Session
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies' # 引擎

其他公用设置项:
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,默认修改之后才保存(默认)
day13 csrf,ajax
csrf装饰器
配置
from django.views.decorators.csrf import csrf_exempt,csrf_protect,ensure_csrf_cookie
from django.utils.decorators import method_decorator

-csrf_exempt
@method_decorator(csrf_exempt,name='dispatch') # csrf_exempt,排除,不需要csrf认证,但只能加到dispatch函数上!
class Login(View):
...

-csrf_protect
# 'django.middleware.csrf.CsrfViewMiddleware', 把csrf中间件注释掉
class Login(View):
@method_decorator(ensure_csrf_cookie)
def get(...):

@method_decorator(csrf_protect)
def post(...): # 虽然全局都不验证csrf,但这里的post需要验证
...
# csrf_protect需要结合ensure_csrf_cookie才能使用
# 在全局都不需要csrf的情况下,要想让某一个view函数通过csrf验证,必须加上{% csrf_token %}标签,同时还得加上这个装饰器
{% csrf_token %}标签,会在网页中生成name=csrfmiddlewaretoken value=64位密码 的隐藏标签
ensure_csrf_cookie装饰器,会自动生成csrftoken的cookie
只有当以上两者同时存在,才能够通过csrf校验
csrf校验流程:
获取cookie中的csrftoken的值,把其放入到request.META请求头中,CSRF_COOKIE: csrftoken的cookie
检查是否需要csrf校验,若不需要校验则直接跳过
从post请求中获取csrfmiddlewaretoken的值
-如果不是post请求,或者没有csrfmiddlewaretoken
-就去,request.META.get(settings.CSRF_HEADER_NAME, '') // 'HTTP_X_CSRFTOKEN' // X_CSRFTOKEN头
比较上面2个值,比较成功就算通过csrf校验
# 另外,'django.middleware.csrf.CsrfViewMiddleware',中间件打开的情况下,也会自己生成csrftoken的cookie
# 另外,cookie + 手动设置请求头中的'X-csrftoken'字段,也可以通过校验,这种使用频率不高

ajax
定义: 使用js的技术发送请求和接收响应
特点: 异步,局部刷新,传输数据量小
实例: 去看about_ajax项目,里面还有验证异步的操作
写法:
(# 'django.middleware.csrf.CsrfViewMiddleware', 把csrf中间件注释掉)
常规写法:
注: 若button在form表单内,那么请求会发送到form指定的位置,而不会发到ajax指定的地方,所以要么不放在form内,要么设置button的type="button"
$('#b1').click(function () {
// 发ajax请求
$.ajax({
url: '/calc1/',
type: 'post',
data: {
a: $("[name='i1']").val(),
b: $("[name='i2']").val(),
},
success: function (res) {
$("[name='i3']").val(res)
}
error: function (error) {
console.log(error)
}
})
})

带json格式的字符串:
序列化
data: {
name: 'jay',
age: '18',
hobby_list: ['唱','跳'],
hobby_dic: {'1':'唱','2':'跳'},
store_lst: JSON.stringify(['唱','跳']),
store_dic: JSON.stringify({'1':'唱','2':'跳'}),
},
views视图函数内反序列化的写法,注意在xml中list和dict类型的数据的传递格式
print(request.POST)
>>> <QueryDict: {'name': ['jay'], 'age': ['18'], 'hobby_list[]': ['唱', '跳'], 'hobby_dic[1]': ['唱'], 'hobby_dic[2]': ['跳'], 'store_lst': ['["唱","跳"]'], 'store_dic': ['{"1":"唱","2":"跳"}']}>
print(request.POST.get('hobby_list[]') >>> '跳' 只能取到列表的最后一个元素
print(request.POST.getlist('hobby_list[]') >>> ['唱', '跳'] 也可以取到list,但是太麻烦了
print(json.loads(request.POST.get('store_lst'))) >>> ['唱', '跳'] 利用json的反序列化,拿列表更方便,下面的字典同理
print(json.loads(request.POST.get('store_dic'))) >>> {'1': '唱', '2': '跳'}
ajax传文件:
(# 'django.middleware.csrf.CsrfViewMiddleware', 把csrf中间件注释掉)
<input type="file" id="i1">
<button id="b1">上传</button>
<script src="/static/jquery.js"></script>
<script>
$('#b1').click(function () {
var formobj = new FormData();
formobj.append('file', document.getElementById('i1').files[0]);
// formobj.append('file',$('#b1')[0].files[0])
formobj.append('name', 'jay');
$.ajax({
url: '/upload/',
type: 'post',
data: formobj,
processData: false, // 默认是Ture,默认是enctype="application/x-www-form-urlencoded"
contentType: false, // 默认是Ture,即默认请求头里的Content-Type: "application/x-www-form-urlencoded"
})
})
</script>

views视图函数内:
def upload(request):
print(request.POST) >>> <QueryDict: {'name': ['jay']}>
print(request.FILES) >>> <MultiValueDict: {'file': [<InMemoryUploadedFile: 老男孩18期python全栈课程表.bmp (image/bmp)>]}>
return render(request,'upload.html')
ajax通过csrf校验:
配置:
'django.middleware.csrf.CsrfViewMiddleware', csrf中间件打开
在页面中使用{% csrf_token %}标签,不要放在form标签内,以确保可以生成csrfmiddlewaretoken
方法1:
在data中手动加上csrfmiddlewaretoken
data: {
'csrfmiddlewaretoken': $('[name="csrfmiddlewaretoken"]').val(),
a: $("[name='i1']").val(),
b: $("[name='i2']").val(),
},
方法2:
加请求头
headers:{
'x-csrftoken':$('[name="csrfmiddlewaretoken"]').val(),
},
方法3:
模板内导入ajax_setup.js文件,源码去看about_ajax项目,这种方式可以给全局都加上x-csrftoken的请求头,原理同方式2
day14
ajax+sweetalert完成图书馆里系统的删除,去看book_manage.html的{% block js %}中的代码
着重下面例子中的js的用法
{% block js %}
<script src="/static/sweetalert.min.js"></script>
<script>
$('.btn-danger').click(function () {
swal({
title: "Are you sure?",
text: "Once deleted, you will not be able to recover this imaginary file!",
icon: "warning",
buttons: true,
dangerMode: true,
})
.then((willDelete) => {
if (willDelete) {
$.ajax({
url: '/del_publisher/' + $(this).attr('del_id') + '/',
success: (res) => { // => 代替函数功能,且不改变内部的$(this)的指向
console.log(res)
if (res.status === 0) { // 连等
$(this).parent().parent().remove() // 找父标签,删除标签的语法
}
}
});
swal("Poof! Your imaginary file has been deleted!", {
icon: "success",
});
} else {
swal("Your imaginary file is safe!");
}
});
})
</script>
{% endblock %}
form组件 (去看"form组件"项目)
初识form
reg.html
<form action="" method="post" novalidate> # novalidate, 不让前端验证
{% csrf_token %}
{{ form_obj.as_p }} 一次性生成所有的input标签,不可单独修改
{{ form_obj.errors }} 显示出所有的错误
{{ form_obj.username }} 只生成该字段对应的标签,
{{ form_obj.name.label }} name字段的label属性
{{ form_obj.name.id_for_label }} name字段的input框的id
{{ form_obj.name.errors }} name字段的所有错误,是一个列表
{{ form_obj.name.errors.0 }} name字段的第一个错误
<button>提交</button>
</form>

app01.views
class RegForm(forms.Form): 需要定义一个类
name = forms.CharField(label='用户名',min_length=6,)
pwd = forms.CharField(label='密码',min_length=6,widget=forms.PasswordInput)
# widget插件,让密码以密文的形式呈现
def reg(request):
form_obj = RegForm()
print(form_obj) # 可见,form_obj只是一串html标签
if request.method == 'POST':
print(request.POST)
form_obj = RegForm(data=request.POST)
print(form_obj) # 可见,在执行data=request.POST这一步的时候,form组件就已经判断了是否有错误了,并进行了封装
if form_obj.is_valid(): # 进行数据校验,并且会把error封装进对象中,返回true或false
# 往数据库插入数据
return HttpResponse('注册成功')
print(form_obj)
return render(request,'reg.html',{'form_obj':form_obj})
常用字段与参数
Field
required=True, 是否允许为空
widget=None, HTML插件
label=None, 用于生成Label标签或显示内容
initial=None, 初始值
help_text='', 帮助信息(在标签旁边显示)
error_messages=None, 错误信息 {'required': '不能为空', 'invalid': '格式错误'}
validators=[], 自定义验证规则
localize=False, 是否支持本地化
disabled=False, 是否可以编辑
label_suffix=None Label内容后缀

CharField(Field)
max_length=None, 最大长度
min_length=None, 最小长度
strip=True 是否移除用户输入空白
MultipleChoiceField(Field)
initial=[1,2,...]
ChoiceField(Field)
...
choices=(), 选项,如:choices = ((0,'上海'),(1,'北京'),)
required=True, 是否必填
widget=None, 插件,默认select插件
label=None, Label内容
initial=None, 初始值
help_text='', 帮助提示
校验规则
内置校验
required = True
min_length =
max_length =
自定义校验 validators字段
方式一: 自定义函数
方式二: django内置的类,eg,validators=[RegexValidator(r'^1[3-9]\d{9}$','手机号格式不正确')]
方式三: 局部钩子
方式四: 全局钩子
内置校验
phone=forms.CharField(label='手机号',validators=[RegexValidator(r'^1[3-9]\d{9}$','手机号格式不正确')])
is_valid()校验流程 别忘记写括号()
-> 执行full_clean()方法(定义错误字典,定义存放清洗数据的字典)
-> 执行_clean_field方法
-> 循环所有字段并获取值
-> self.cleaned_data[name]=value -> 内置校验 -> 自定义函数校验 ->
通过校验:self.cleaned_data[name]=value -> 局部钩子校验
-> 通过:self.cleaned_data[name]=value,
-> 不通过:self.errors添加当前字段的错误,并且删除self.cleaned_data中当前字段的值
不通过:self.errors添加当前字段的错误,
-> 全局钩子
day15 中间件,django的请求生命周期
中间件是处理django的请求和响应的框架级别的钩子,本质上是一个类
总结:
执行顺序:view之前的顺序执行,view之后的倒序执行
中间件可定义5个方法:
process_request(self,request)
process_view(self, request, view_func, view_args, view_kwargs)
process_template_response(self,request,response)
process_exception(self, request, exception)
process_response(self, request, response)
process_request(self,request)
4个特征:
执行时间:view视图函数之前
参数:request (中间件和view中的request是同一个)
执行顺序:按注册的顺序执行
返回值:如果是None,则走正常流程;如果是HttpResponse,那么后面中间件的process_request和view函数均不执行,
直接执行当前中间件的process_response,再倒序执行之前中间件的process_response
process_response(self, request, response)
4个特征:
执行时间:view视图函数之后
参数:response (返回给浏览器的响应对象,可以来自于view,也可以来自于process_view)
执行顺序:按注册的倒序执行
返回值:
如果是None,则走完所有的中间件就停止;
如果是response,则走正常流程
process_view(self, request, view_func, view_args, view_kwargs)
4个特征:
执行时间:process_request之后,view视图函数之前
参数:
view_func 视图函数
view_args 视图函数的位置参数,分组
view_kwargs 视图函数的关键字参数,命名分组
response (返回给浏览器的响应对象,可以来自于view,也可以来自于process_view)
执行顺序:按注册的顺序执行
返回值:
如果是None,走正常流程
如果是HttpResponse,后面的view,中间件的process_view都不执行,直接执行当前中间件的process_response,再倒序执行之前中间件的process_response
process_exception(self, request, exception)
4个特征:
执行时间:view视图函数有错才执行;如果没有错误,就不执行
参数:
exception 错误对象
执行顺序:按注册的顺序,倒序执行.
返回值:
如果是None,交给下一个中间件去处理异常,都不处理那就django来处理
如果是HttpResponse,后面中间件的process_exception都不执行,直接执行当前中间件的process_response,再倒序执行之前中间件的process_response
process_template_response(self,request,response) 用的比较少
4个特征:
执行时间:view视图函数返回templateResponse对象时(from django.template.response import TemplateResponse,用法与render一样)
参数:
...
执行顺序:按注册的顺序,倒序执行.
返回值:
要求必须返回response
若返回None,则执行完当前process_template_response后,就执行倒序执行之前中间件的process_response
功能:可以动态的修改template模板的内容

推荐阅读