Web框架本质
对于所有的Web应用,本质上其实就是一个socket服务端,用户的浏览器其实就是一个socket客户端。
服务器和浏览器都遵循HTTP协议。那么HTTP协议有什么规则呢?
import socket
sk = socket.socket()
sk.bind(("127.0.0.1", 8085))
sk.listen()
while True:
conn, addr = sk.accept()
data = conn.recv(8096)
print(data) # 将浏览器发来的消息打印出来
conn.send(b‘HTTP/1.1 200 OK\r\n\r\n‘)
conn.send(b"OK")
conn.close()
执行上述代码,打印出data值如下:
b‘GET / HTTP/1.1\r\nHost: 127.0.0.1:8085\r\nConnection: keep-alive\r\nCache-Control: max-age=0\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: zh-CN,zh;q=0.9\r\nCookie: csrftoken=72Zj8sygYN8c75db74baSmD2hWLtPIvuufdMMlVQPSv1QwWDErRoFHWokJppPbX1; sessionid=3c4jlkt4hj8hjmoa7vquikxrhz84xt70\r\n\r\n‘
b‘GET /favicon.ico HTTP/1.1\r\nHost: 127.0.0.1:8085\r\nConnection: keep-alive\r\nUser-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36\r\nAccept: image/webp,image/apng,image/*,*/*;q=0.8\r\nReferer: http://127.0.0.1:8085/\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: zh-CN,zh;q=0.9\r\nCookie: csrftoken=72Zj8sygYN8c75db74baSmD2hWLtPIvuufdMMlVQPSv1QwWDErRoFHWokJppPbX1; sessionid=3c4jlkt4hj8hjmoa7vquikxrhz84xt70\r\n\r\n‘
可得每个HTTP请求和响应都遵循相同的格式,一个HTTP包含Header和Body两部分。
静态网站
1 import socket
2
3 def f1(request):
4
5 f = open(‘index.html‘, ‘rb‘)
6 msg = f.read()
7 f.close()
8
9 return msg
10
11 def f2(request):
12 f = open(‘article.html‘, ‘rb‘)
13 msg = f.read()
14 f.close()
15
16 return msg
17
18
19 routers = [
20 (‘/xxx‘, f1),
21 (‘/xxxx‘, f2),
22 ]
23
24 def run():
25 sk = socket.socket()
26 sk.bind((‘127.0.0.1‘, 8085))
27 sk.listen(5)
28
29 while True:
30 conn, addr = sk.accept() # 等待连接
31 # 有人来连接了
32 data = conn.recv(8096)
33 print(data)
34 data = str(data, encoding=‘utf-8‘)
35 headers, bodys = data.split(‘\r\n\r\n‘)
36 temp_list = headers.split(‘\r\n‘)
37 method, url, protocal = temp_list[0].split(‘ ‘)
38 conn.send(b‘HTTP/1.1 200 OK\r\n\r\n‘)
39
40 func_name = None
41
42 for item in routers:
43 if item[0] == url:
44 func_name = item[1]
45 break
46 if func_name:
47 response = func_name(data)
48 else:
49 response = b‘404‘
50
51 conn.send(response)
52
53 conn.close()
54
55
56 if __name__ == ‘__main__‘:
57 run()
1 <!DOCTYPE html>
2 <html lang="zh-CN">
3 <head>
4 <meta charset="UTF-8">
5 <title>guess</title>
6 </head>
7 <body>
8 <h1>用户登录</h1>
9 <form >
10 <p><input type="text" placeholder="用户名"></p>
11 <p><input type="password" placeholder="密码"></p>
12
13 </form>
14 </body>
15 </html>
1 <!DOCTYPE html>
2 <html lang="zh-CN">
3 <head>
4 <meta charset="UTF-8">
5 <title>article</title>
6
7 </head>
8 <body>
9 <table border="1">
10 <thead>
11 <tr>
12 <th>ID</th>
13 <th>用户名</th>
14 <th>邮箱</th>
15 </tr>
16 </thead>
17 <tbody>
18 <tr>
19 <td>1</td>
20 <td>root</td>
21 <td>root@qq.com</td>
22 </tr>
23 </tbody>
24 </table>
25
26 </body>
27 </html>
动态网站
1 import socket
2 import pymysql
3
4 def f1(request):
5 f = open(‘article.html‘, ‘r‘, encoding=‘utf-8‘)
6 msg = f.read()
7 import time
8 ctime = time.time()
9 msg = msg.replace(‘@@sw@@‘, str(ctime))
10 f.close()
11
12 return bytes(msg, encoding="utf-8")
13
14 routers = [
15 (‘/xxx‘, f1),
16 ]
17
18 def run():
19 sk = socket.socket()
20 sk.bind((‘127.0.0.1‘, 8084))
21 sk.listen(5)
22
23 while True:
24 conn, addr = sk.accept() # 等待连接
25 # 有人来连接了
26 data = conn.recv(8096)
27 data = str(data, encoding=‘utf-8‘)
28 headers, bodys = data.split(‘\r\n\r\n‘)
29 temp_list = headers.split(‘\r\n‘)
30 method, url, protocal = temp_list[0].split(‘ ‘)
31 conn.send(b‘HTTP/1.1 200 OK\r\n\r\n‘)
32
33 func_name = None
34
35 for item in routers:
36 if item[0] == url:
37 func_name = item[1]
38 break
39
40 if func_name:
41 response = func_name(data)
42 else:
43 response = b‘404‘
44
45 conn.send(response)
46
47 conn.close()
48
49
50 if __name__ == ‘__main__‘:
51 run()
1 <!DOCTYPE html>
2 <html lang="zh-CN">
3 <head>
4 <meta charset="UTF-8">
5 <title>article</title>
6
7 </head>
8 <body>
9 <table border="1">
10 <thead>
11 <tr>
12 <th>ID</th>
13 <th>用户名</th>
14 <th>邮箱</th>
15 </tr>
16 </thead>
17 <tbody>
18 <tr>
19 <td>1</td>
20 <td>@@sw@@</td>
21 <td>root@qq.com</td>
22 </tr>
23 </tbody>
24 </table>
25
26 </body>
27 </html>
整理:
1. Http无状态,短连接
2. 浏览器 -> socket客户端 网站 -> socket服务端
3. 自己写网站
a. socket服务端
b. 根据URL不同返回不同的内容
路由系统: URL-> 函数
c. 字符串返回给用户。模板引擎渲染
4. Web框架
框架种类:
- a,b,c -> Tornado
- [第三方a],b,c -> wsgiref -> Django
- [第三方a],b,[第三方c] -> flask
Django
创建一个django项目:
1. 终端命令:django-admin startprojec sitename
2. pycharm创建:会有如下目录
sitename
sitename
- settings.py # Django配置文件
- url.py # 路由系统 url -> 函数
- wsgi.py # 用于定义Django用什么socket,wsgiref,uwsgi
pycharm创建Django步骤:
1. 创建project
2. 配置 settings.py
a. 数据库
DATABASES = { ‘default‘: { ‘ENGINE‘: ‘django.db.backends.mysql‘, ‘NAME‘:‘dbname‘, ‘USER‘: ‘root‘, ‘PASSWORD‘: ‘xxx‘, ‘HOST‘: ‘localhost‘, ‘PORT‘: ‘3306‘, } } # 由于Django内部连接MySQL时使用的是MySQLdb模块,而python3中还无此模块,所以需要使用pymysql来代替 # 将下述代码放置与project同名的配置的 __init__.py文件中 import pymysql pymysql.install_as_MySQLdb()
b. 模板路径
TEMPLATE_DIRS = ( os.path.join(BASE_DIR,‘templates‘), )
c. 静态文件(给settings.py最后加上如下代码)
STATICFILES_DIRS = ( os.path.join(BASE_DIR,‘static‘), // 注意有个逗号 )
d. 额外配置(CSRF注释)
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‘, ]
路由系统
URL配置就像Django所支撑网站的目录。它的本质是URL与要为该URL调用的视图函数之间的映射表。
基本格式:
from django.conf.urls import url urlpatterns = [ url(正则表达式, views视图函数,参数,别名), ] 参数说明: 正则表达式:一个正则表达式字符串 views视图函数:一个可调用对象,通常为一个视图函数或一个指定视图函数路径的字符串 参数:可选的要传递给视图函数的默认参数(字典形式) 别名:一个可选的name参数 注意:从Django2.0版本中的路由系统已经替换成下面的写法 from django.urls import path urlpatterns = [ path(‘articles/2003/‘, views.special_case_2003), path(‘articles/<int:year>/‘, views.year_archive),
1. 单一路由系统
re_path(r‘^index/$‘, views.index),
2. 基于正则的路由
re_path(r‘^edit/(\w+)/‘, views.edit) re_path(r‘^add_user/(?P<a1>\w+)/(?P<a2>\w+)/, views.add_user‘),
3. 添加额外的参数
re_path(r‘^add_user/(?P<a1>\w+)/‘, views.add_user, {‘id‘:22}),
4. 为路由映射设置名称
re_path(r‘^index/(\d*)‘, views.index, name=‘n3‘),
设置名称之后,可以在不同地方调用,如:
a. 模板中生成URL {% url ‘n3‘ 25 %}
b. 函数中生成URL reverse(‘n3‘, args=(25,)) 路径: from django.urls import reverse
5. 根据app对路由规则进行分类(路由分发)
urls.py:
urlpatterns=[
re_path(r‘^app01/‘, include(‘app01.urls‘)),
]
app01.urls.py:
urlpatterns=[
path(‘index/‘, views.index),
补充说明
# 是否开启URL访问地址后面不为/跳转至带有/的路径的配置项 APPEND_SLASH=True Django settings.py配置文件中默认没有 APPEND_SLASH 这个参数,但 Django 默认这个参数为 APPEND_SLASH = True。 其作用就是自动在网址结尾加‘/‘。
模板语言
模板语言可以实现数据提示。变量相关的用{{ }}, 逻辑相关的用 {% %}
1. {{ item }} 2. {% for item in user_list %} <a>{{ item}} </a> {{% endfor %}}
def index(request): return render(request,‘index.html‘,{ ‘k1‘:‘v1‘, ‘k2‘:[1,2,3], ‘k3‘:{...} } ) index.html => (模板路径) <h1>{{k1}}</h1> <h1>{{k2.0}}</h1> <h1>{{k2.1}}</h1> {% for item in k2 %} <h4>{{ item }}</h4> {% endfor %}
3. 母版 通常在母版中使用{% block xxx%}来定义块
{% block title %}{% endblock %}
子版
{% extends "base.py" %}
{% block title %}写子板独有的文件{% endblock %}
4. 组件:将常用的页面内容如导航条,页尾信息等组件保存在单独的文件中,然后在需要的地方直接导入即可。
-include -导入小组件 pub.html <div> <h4>漂亮的组件</h4> <div class="title">标题:{{ name }}</div> <div class="content">内容:</div> </div> test.html: <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> </head> <body> {% include ‘pub.html‘ %} {% include ‘pub.html‘ %} {% include ‘pub.html‘ %} {% include ‘pub.html‘ %} </body> </html>
自定义simple_tag
a. 在app中创建templatetags模块
b. 创建任意.py文件,如:xx.py
from django import template register = template.Library() @register.filter def my_upper(value): return value.upper() @register.filter def my_upper(value, arg): return value + arg @register.simple_tag def my_lower(value): return value.lower() @register.simple_tag def my_add(value, a1, a2, a3): return value + a1 + a2 + a3
c. 在使用自定义的simple_tag的html文件中导入创建的xx.py文件名,并使用
<!DOCTYPE html> <html lang="zh-CN"> {% load xx %} <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h2>filter</h2> {{ name|upper }} {{ name|my_upper:‘666‘ }} <h2>tag</h2> {% my_lower ‘ALEX‘ %} {% my_add ‘Alex‘ ‘ is‘ ‘ super‘ ‘ girl‘ %} </body> </html>
视图
一个简单的视图: 下面是一个以HTML文档的形式返回当前日期和时间的视图: from django.http import HttpResponse import datetime def current_datetime(request): now = datetime.datetime.now() html = "<html><body>It is now %s.</body></html>" % now return HttpResponse(html)
CBV和FBV
FBV:就是基于函数的view
views.py # FBV版添加班级 def add_class(request): if request.method == "POST": class_name = request.POST.get("class_name") models.Classes.objects.create(name=class_name) return redirect("/class_list/") return render(request, "add_class.html")
url.py:
path(‘add_class/‘, views.add_class)
CBV基于类的
views.py # CBV版添加班级 from django.views import View class AddClass(View): def get(self, request): return render(request, "add_class.html") def post(self, request): class_name = request.POST.get("class_name") models.Classes.objects.create(name=class_name) return redirect("/class_list/") 使用CBV时候,url.py中也做要修改: path(‘add_class/‘, views.AddClass.as_view()),
给视图加装饰器
1 FBV本身就是一个函数,所以和给普通的函数加装饰器没差别 2 3 4 def wrapper(func): 5 def inner(*args, **kwargs): 6 start_time = time.time() 7 ret = func(*args, **kwargs) 8 end_time = time.time() 9 print("used:", end_time-start_time) 10 return ret 11 return inner 12 13 14 # FBV版添加班级 15 @wrapper 16 def add_class(request): 17 if request.method == "POST": 18 class_name = request.POST.get("class_name") 19 models.Classes.objects.create(name=class_name) 20 return redirect("/class_list/") 21 return render(request, "add_class.html")
1 类中的方法与独立函数不完全相同,因此不能直接将函数装饰器应用于类中的方法 ,我们需要先将其转换为方法装饰器。 2 3 Django中提供了method_decorator装饰器用于将函数装饰器转换为方法装饰器。 4 5 # CBV版添加班级 6 from django.views import View 7 from django.utils.decorators import method_decorator 8 9 class AddClass(View): 10 11 @method_decorator(wrapper) 12 def get(self, request): 13 return render(request, "add_class.html") 14 15 def post(self, request): 16 class_name = request.POST.get("class_name") 17 models.Classes.objects.create(name=class_name) 18 return redirect("/class_list/") 19 复制代码
Request对象和Response对象
Request对象:
当一个页面被请求时,Django就会创建一个包含本次请求原信息的HttpRequest对象。
Django会将这个对象自动传递给响应的视图函数,一般视图函数约定俗成地使用 request 参数承接这个对象
请求相关的常用值:
path_info 返回用户访问url,不包括域名
method 请求中使用的HTTP方法的字符串表示,全大写表示。
GET 包含所有HTTP GET参数的类字典对象
POST 包含所有HTTP POST参数的类字典对象
body 请求体,byte类型 request.POST的数据就是从body里面提取到的
def upload(request): """ 保存上传文件前,数据需要存放在某个位置。默认当上传文件小于2.5M时,django会将上传文件的全部内容读进内存。从内存读取一次,写磁盘一次。 但当上传文件很大时,django会把上传文件写到临时文件中,然后存放到系统临时文件夹中。 :param request: :return: """ if request.method == "POST": # 从请求的FILES中获取上传文件的文件名,file为页面上type=files类型input的name属性值 filename = request.FILES["file"].name # 在项目目录下新建一个文件 with open(filename, "wb") as f: # 从上传的文件对象中一点一点读 for chunk in request.FILES["file"].chunks(): # 写入本地文件 f.write(chunk) return HttpResponse("上传OK")
Response对象 与由Django自动创建的HttpRequest对象相比,HttpResponse对象是我们的职责范围了。我们写的每个视图都需要实例化,填充和返回一个HttpResponse。 HttpResponse类位于django.http模块中。 使用:传递字符串 from django.http import HttpResponse response = HttpResponse("Here‘s the text of the Web page.") response = HttpResponse("Text only, please.", content_type="text/plain")
Django shortcut functions
def render(request, template_name, context=None, content_type=None, status=None, using=None): content = loader.render_to_string(template_name, context, request, using=using) return HttpResponse(content, content_type, status) 参数: request: 用于生成响应的请求对象。 template_name:要使用的模板的完整名称,可选的参数 context:添加到模板上下文的一个字典。默认是一个空字典。如果字典中的某个值是可调用的,视图将在渲染模板之前调用它。 content_type:生成的文档要使用的MIME类型。默认为 DEFAULT_CONTENT_TYPE 设置的值。默认为‘text/html‘ status:响应的状态码。默认为200。 using: 用于加载模板的模板引擎的名称。
def redirect(to, *args, permanent=False, **kwargs): redirect_class = HttpResponsePermanentRedirect if permanent else HttpResponseRedirect return redirect_class(resolve_url(to, *args, **kwargs) 参数可以是: 一个模型:将调用模型的get_absolute_url() 函数 一个视图,可以带有参数:将使用urlresolvers.reverse 来反向解析名称 一个绝对的或相对的URL,将原封不动的作为重定向的位置。 默认返回一个临时的重定向;传递permanent=True 可以返回一个永久的重定向。
中间件
django中的中间件(middleware),在django中,中间件其实就是一个类,在请求到来和结束后,django会根据自己的规则在合适的时机执行中间件中的方法。
在django项目的settings模块中,有一个MIDDLEWARE变量,其中每个元素就是一个中间件。如下图:
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‘, ]
中间件有四种方法,分别是:
process_request(self, request)
process_view(self, request, callback, callback_args, callback_kwargs)
process_template_response(self, request, response)
process_exception(self, request, exception)
process_response(self, request, response) -> 必须要有返回值
用法:
a. 在项目目录下创建一个m1.py文件:
1 m1.py: 2 from django.utils.deprecation import MiddlewareMixin 3 4 class Middle1(MiddlewareMixin): 5 def process_request(self, request): 6 print(‘m1.process_request‘) 7 8 def process_response(self, request, response): 9 print(‘m1.process_response‘) 10 return response 11 12 class Middle2(MiddlewareMixin): 13 def process_request(self, request): 14 print(‘m2.process_request‘) 15 16 def process_view(self, request, callback, callback_args, callback_kwargs): 17 response = callback(request, *callback_args, **callback_kwargs) 18 return response 19 20 def process_response(self, request, response): 21 print(‘m2.process_response‘) 22 return response
b. 在settings.py的MIDDLEWARE中添加:
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‘, ‘m1.Middle1‘, ‘m1.Middle2‘, ]
views.py:
def test(request): print(‘执行视图函数...‘) return HttpResponse(‘..‘)
执行结果是:
m1.process_request m2.process_request m1.process_view 执行视图函数... [04/Feb/2020 10:11:12] "GET /test/ HTTP/1.1" 200 3 m2.process_response m1.process_response
中间件总结:
1. 当类方法中只有process_request, process_response时, 会先依次执行process_request,再执行视图函数,最后再依次执行执行process_response 2. 当某个类方法中有process_request, process_view, process_response时, 会先依次执行process_request,然后再依次执行process_view时,当某个process_view有返回值时直接跳过该process_view以及后续的process_view,直接执行视图函数,最后再依次执行process_response 3. 当存在process_exception时,依次执行process_request,process_view, 视图函数, process_exception, process_response 一旦存在某个阶段存在返回值,则直接跳过该阶段。(注: response必须有返回值,)
ORM
到目前为止,当我们的程序涉及到数据库相关操作时候,我们会一般这么做:
a. 创建数据库设计表结构和字段
b. 使用pymysql来连接数据库
import pymysql # 创建连接 conn = pymysql.connect(host=‘127.0.0.1‘, port=3306, user=‘root‘, passwd=‘123456‘, db=‘t1‘) # 创建游标 cursor = conn.cursor() # 执行SQL,并返回收影响行数 effect_row = cursor.execute("update hosts set host = ‘1.1.1.2‘") # 提交,不然无法保存新建或者修改的数据 conn.commit() # 关闭游标 cursor.close() # 关闭连接 conn.close()
django为使用另外一种新的方式:关系对象映射(ORM)。根据代码中定义的类来自动生成数据库表。类的每个属性对应表中的每个字段。
Django中的ORM
ORM利用pymysql第三方工具连接数据库
Django:
默认:SQLlite
若要用Mysql:需要修改django默认连接的方式
HTTP请求: url -> 视图(模板+数据)D
Django项目使用MySQL数据库
步骤:
1. 创建数据库
2. 在Django项目的setting.py文件中,配置数据库连接信息:
DATABASES = { ‘default‘: { ‘ENGINE‘: ‘django.db.backends.mysql‘, ‘NAME‘:‘s4day70db‘, # 需要手动创建数据库 ‘USER‘: ‘root‘, ‘PASSWORD‘: ‘123456‘, ‘HOST‘: ‘localhost‘, ‘PORT‘: ‘3306‘, } }
3. 在同文件目录下__init__.py中导入如下代码,告诉Django使用pymysql模块连接MySQL数据库
import pymysql pymysql.install_as_MySQLdb()
Model
在Django中model是你数据的单一、明确的信息来源。它包含了你存储的数据的重要字段和行为。通常,一个模型(model)映射到一个数据库表,
基本情况:
每个模型都是一个Python类,它是django.db.models.Model的子类。
模型的每个属性都代表一个数据库字段。
4.创建表(在app01文件下的models.py中)
from django.db import models class UserGroup(models.Model): title = models.CharField(max_length=32) class UserInfo(models.Model): nid = models.BigAutoField(primary_key=True) user = models.CharField(max_length=32) password = models.CharField(max_length=64) age = models.IntegerField(default=1) # ug_id 创造的外键 ug = models.ForeignKey("UserGroup", null=True, on_delete=models.CASCADE)
常用字段 AutoField int自增列,必须填入参数 primary_key=True。当model中如果没有自增列,则自动会创建一个列名为id的列。 ntegerField 一个整数类型,范围在 -2147483648 to 2147483647。 CharField 字符类型,必须提供max_length参数, max_length表示字符长度。 DateField 日期字段,日期格式 YYYY-MM-DD,相当于Python中的datetime.date()实例。 DateTimeField 日期时间字段,格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ],相当于Python中的datetime.datetime()实例
AutoField(Field) - int自增列,必须填入参数 primary_key=True BigAutoField(AutoField) - bigint自增列,必须填入参数 primary_key=True 注:当model中如果没有自增列,则自动会创建一个列名为id的列 from django.db import models class UserInfo(models.Model): # 自动创建一个列名为id的且为自增的整数列 username = models.CharField(max_length=32) class Group(models.Model): # 自定义自增列 nid = models.AutoField(primary_key=True) name = models.CharField(max_length=32) SmallIntegerField(IntegerField): - 小整数 -32768 ~ 32767 PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField) - 正小整数 0 ~ 32767 IntegerField(Field) - 整数列(有符号的) -2147483648 ~ 2147483647 PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField) - 正整数 0 ~ 2147483647 BigIntegerField(IntegerField): - 长整型(有符号的) -9223372036854775808 ~ 9223372036854775807 BooleanField(Field) - 布尔值类型 NullBooleanField(Field): - 可以为空的布尔值 CharField(Field) - 字符类型 - 必须提供max_length参数, max_length表示字符长度 TextField(Field) - 文本类型 EmailField(CharField): - 字符串类型,Django Admin以及ModelForm中提供验证机制 IPAddressField(Field) - 字符串类型,Django Admin以及ModelForm中提供验证 IPV4 机制 GenericIPAddressField(Field) - 字符串类型,Django Admin以及ModelForm中提供验证 Ipv4和Ipv6 - 参数: protocol,用于指定Ipv4或Ipv6, ‘both‘,"ipv4","ipv6" unpack_ipv4, 如果指定为True,则输入::ffff:192.0.2.1时候,可解析为192.0.2.1,开启此功能,需要protocol="both" URLField(CharField) - 字符串类型,Django Admin以及ModelForm中提供验证 URL SlugField(CharField) - 字符串类型,Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号) CommaSeparatedIntegerField(CharField) - 字符串类型,格式必须为逗号分割的数字 UUIDField(Field) - 字符串类型,Django Admin以及ModelForm中提供对UUID格式的验证 FilePathField(Field) - 字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功能 - 参数: path, 文件夹路径 match=None, 正则匹配 recursive=False, 递归下面的文件夹 allow_files=True, 允许文件 allow_folders=False, 允许文件夹 FileField(Field) - 字符串,路径保存在数据库,文件上传到指定目录 - 参数: upload_to = "" 上传文件的保存路径 storage = None 存储组件,默认django.core.files.storage.FileSystemStorage ImageField(FileField) - 字符串,路径保存在数据库,文件上传到指定目录 - 参数: upload_to = "" 上传文件的保存路径 storage = None 存储组件,默认django.core.files.storage.FileSystemStorage width_field=None, 上传图片的高度保存的数据库字段名(字符串) height_field=None 上传图片的宽度保存的数据库字段名(字符串) DateTimeField(DateField) - 日期+时间格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] DateField(DateTimeCheckMixin, Field) - 日期格式 YYYY-MM-DD TimeField(DateTimeCheckMixin, Field) - 时间格式 HH:MM[:ss[.uuuuuu]] DurationField(Field) - 长整数,时间间隔,数据库中按照bigint存储,ORM中获取的值为datetime.timedelta类型 FloatField(Field) - 浮点型 DecimalField(Field) - 10进制小数 - 参数: max_digits,小数总长度 decimal_places,小数位长度 BinaryField(Field) - 二进制类型
null 用于表示某个字段可以为空。 unique 如果设置为unique=True 则该字段在此表中必须是唯一的 。 db_index 如果db_index=True 则代表着为此字段设置数据库索引。 default 为该字段设置默认值。 时间字段独有 DatetimeField、DateField、TimeField这个三个时间字段,都可以设置如下属性。 auto_now_add 配置auto_now_add=True,创建数据记录的时候会把当前时间添加到数据库。 auto_now 配置上auto_now=True,每次更新数据记录的时候会更新该字段
关系字段
使用: ug = models.ForeignKey("UserGroup", null=True, on_delete=models.CASCADE) 分析: 字段参数 to 设置要关联的表 to_field 设置要关联的表的字段 related_name 反向操作时,使用的字段名,用于代替原反向查询时的‘表名_set‘。 例如: class Classes(models.Model): name = models.CharField(max_length=32) class Student(models.Model): name = models.CharField(max_length=32) theclass = models.ForeignKey(to="Classes") 当我们要查询某个班级关联的所有学生(反向查询)时,我们会这么写: models.Classes.objects.first().student_set.all() 当我们在ForeignKey字段中添加了参数 related_name 后, class Student(models.Model): name = models.CharField(max_length=32) theclass = models.ForeignKey(to="Classes", related_name="students") 当我们要查询某个班级关联的所有学生(反向查询)时,我们会这么写: models.Classes.objects.first().students.all() related_query_name 反向查询操作时,使用的连接前缀,用于替换表名。 on_delete 当删除关联表中的数据时,当前表与其关联的行的行为。 on_delete=models.CASCADE 删除关联数据,与之关联也删除
一对一字段。 通常一对一字段用来扩展已有字段 示例: class Author(models.Model): name = models.CharField(max_length=32) info = models.OneToOneField(to=‘AuthorInfo‘) class AuthorInfo(models.Model): phone = models.CharField(max_length=11) email = models.EmailField()
用于表示多对多的关联关系。在数据库中通过第三张表来建立关联关系。 字段 to 设置要关联的表 related_name 同ForeignKey字段。 related_query_name 同ForeignKey字段。 symmetrical 仅用于多对多自关联时,指定内部是否创建反向操作的字段。默认为True。 例如: class Person(models.Model): name = models.CharField(max_length=16) friends = models.ManyToManyField("self") 此时,person对象就没有person_set属性。 class Person(models.Model): name = models.CharField(max_length=16) friends = models.ManyToManyField("self", symmetrical=False) 在使用ManyToManyField字段时,Django将自动生成一张表来管理多对多的关联关系。 但我们也可以手动创建第三张表来管理多对多关系,此时就需要通过through来指定第三张表的表名。 through_fields 设置关联的字段。 db_table 默认创建第三张表时,数据库中表的名称。
5. 注册app
6. 创建数据表
在pycharm终端输入:
python manage.py makemigrations
python manage.py migrate
ORM操作表
<1> all(): 查询所有结果 <2> filter(**kwargs): 它包含了与所给筛选条件相匹配的对象 <3> get(**kwargs): 返回与所给筛选条件相匹配的对象,返回结果有且只有一个,如果符合筛选条件的对象超过一个或者没有都会抛出错误。 <4> exclude(**kwargs): 它包含了与所给筛选条件不匹配的对象 <5> values(*field): 返回一个ValueQuerySet——一个特殊的QuerySet,运行后得到的并不是一系列model的实例化对象,而是一个可迭代的字典序列 <6> values_list(*field): 它与values()非常相似,它返回的是一个元组序列,values返回的是一个字典序列 <7> order_by(*field): 对查询结果排序 <8> reverse(): 对查询结果反向排序,请注意reverse()通常只能在具有已定义顺序的QuerySet上调用(在model类的Meta中指定ordering或调用order_by()方法)。 <9> distinct(): 从返回结果中剔除重复纪录(如果你查询跨越多个表,可能在计算QuerySet时得到重复的结果。此时可以使用distinct(),注意只有在PostgreSQL中支持按字段去重。) <10> count(): 返回数据库中匹配查询(QuerySet)的对象数量。 <11> first(): 返回第一条记录 <12> last(): 返回最后一条记录 <13> exists(): 如果QuerySet包含数据,就返回True,否则返回False 其中: 返回QuerySet对象的方法有 all() filter() exclude() order_by() reverse() distinct() 特殊的QuerySet values() 返回一个可迭代的字典序列 values_list() 返回一个可迭代的元祖序列 返回具体对象的 get() first() last() 返回布尔值的方法有: exists() 返回数字的方法有 count()
from app01 import models # 增 models.UserGroup.objects.create(title=‘销售部‘) models.UserInfo.objects.create(user=‘user‘, password=‘pwd‘, age=18, ug_id=1) # 查 group_list = models.UserGroup.objects.all() group_list = models.UserGroup.objects.filter(id=1).first() group_list = models.UserGroup.objects.filter(id__gt=1) group_list = models.UserGroup.objects.filter(id__lt=1) group_list QuerySet类型(列表) 在ORM中,类代表表,对象表示每一行数据 QuerySet类[obj,obj..] for row in group_list: print(row.id, row.title) # 删 models.UserGroup.objects.filter(id=2).delete() # 改 models.UserGroup.objects.filter(id=2).update(title=‘公关部‘)
数据库获取多个数据的方式:
# 建表 from django.db import models class UserType(models.Model): title = models.CharField(max_length=32) class UserInfo(models.Model): name = models.CharField(max_length=16) age = models.IntegerField() ut = models.ForeignKey(‘UserType‘, on_delete=models.CASCADE) # 添加数据 models.UserType.objects.create(title=‘普通用户‘) models.UserType.objects.create(title=‘黄金会员‘) models.UserType.objects.create(title=‘钻石会员‘) models.UserInfo.objects.create(name=‘小哥‘, age=18, ut_id=1) models.UserInfo.objects.create(name=‘任齐‘, age=17, ut_id=2) models.UserInfo.objects.create(name=‘肖邦‘, age=18, ut_id=2) models.UserInfo.objects.create(name=‘莫邪‘, age=19, ut_id=2)
获取
正向操作:
# 1. [obj,obj,] -> 可跨表 models.UserInfo.objects.all() models.UserInfo.objects.filter(id__gt=1) # 取值 list = models.UserInfo.objects.all() for row in list: print(row.name, row.age, row.ut.title) row.ut.title -> 就是跨表 # 2. [{‘id‘:1, ‘name‘:‘xx‘}, {‘id‘:2, ‘name‘:‘xx‘},...] models.UserInfo.objects.all().values(‘id‘, ‘name‘) models.UserInfo.objects.filter(id__gt=1).values(‘id‘, ‘name‘) # 取值 list= models.UserInfo.objects.all().values(‘id‘, ‘name‘) for row in list: print(row[‘id‘], row[‘name‘]) # 字典型的要想跨表: __ list = models.UserInfo.objects.all().values(‘id‘, ‘name‘, ‘ut__title‘) for row in list: print(row[‘id‘], row[‘name‘], row[‘ut__title‘]) # 3. [(1, ‘xx‘),(2, ‘xxx‘),(3, ‘xxx‘)...] models.UserInfo.objects.all().values_list(‘id‘, ‘name‘) models.UserInfo.objects.filter(id__gt=1).values_list(‘id‘, ‘name‘) # 取值 list = models.UserInfo.objects.all().values_list(‘id‘, ‘name‘) for row in list: print(row[0], row[1]) # 列表型的要想跨表:__ list = models.UserInfo.objects.all().values_list(‘id‘, ‘name‘, ‘ut__title‘) for row in list: print(row[0], row[1], row[2])
反向操作:
1. 小写的表名_set obj = models.UserType.objects.all().first() for row in obj.userinfo_set.all(): print(row.name, row.age) 2. 小写的表名 list= models.UserType.objects.all().values(‘id‘, ‘title, ‘userinfo‘) # userinfo默认取得是id, 若要取userinfo其他字段需要加__ # list= models.UserType.objects.all().values(‘id‘, ‘title, ‘userinfo__name‘) for row in list: print(row[‘id‘], row[‘name‘], row[‘userinfo‘]) 3. 小写的表名 list= models.UserType.objects.all().values_list(‘id‘, ‘title, ‘userinfo‘) # userinfo默认取得是id, 若要取userinfo其他字段需要加__ # list= models.UserGroup.objects.all().values_list(‘id‘, ‘title, ‘userinfo__name‘) for row in list: print(row[0], row[1], row[2]) 注:无论正向还是反向,前面的数据都会取完
多对多创建表
方式1:ManyToManyField 方式2:自己建第三个表 方式3:两者结合 但只能查询所有和清空 m = models.ManyToManyField(‘Boy‘, through=‘Boy‘, through_fields=(‘b‘, ‘g‘)) class Boy(models.Model): name = models.CharField(max_length=32) class Girl(models.Model): nick = models.CharField(max_length=32) m = models.ManyToManyField(‘Boy‘) 相当于建立了=> class Love(models.Model): b = models.ForeignKey(‘Boy‘, on_delete=models.CASCADE) g = models.ForeignKey(‘Girl‘, on_delete=models.CASCADE) 若要求男生女生只能约会一次: class Meta: unique_together = [ (‘b‘, ‘g‘) ] 查询: 1. 查询跟秦汉有关的姑娘 方法1: obj = models.Boy.objects.filter(name="秦汉").first() love_list = obj.love_set.all() for row in love_list: print(row.g.nick) 方法2: love_list = models.Love.objects.filter(b__name="秦汉") for row in love_list: print(row.g.nick) 方法3 love_list = models.Love.objects.filter(b__name="秦汉").values(‘g__nick‘) for item in love_list: print(item[‘g__nick‘]) 方法4 love_list = models.Love.objects.filter(b__name="秦汉").select_related(‘g‘) for obj in love_list: print(obj.g.nick) 使用m = models.ManyToManyField(‘Boy‘) 建立第三表后: # Girl obj = models.Girl.objects.filter(nick="小猫").first() print(obj.id, obj.nick) # 增 obj.m.add(2) obj.m.add(1, 4) obj.m.add(*[3, ]) # 删 obj.m.remove(2) obj.m.remove(*[3, ]) # 改 => 重置 obj.m.set([1, ]) # # 查 boy_list = obj.m.all() # 与小猫有关系的全拿到 for i in boy_list: print(i.name) obj.m.all() 与小猫有关系的全删除 # Boy 反向操作 小写表明_set.all() obj = models.Boy.objects.filter(name="方华").first() print(obj.id, obj.name, obj) v = obj.girl_set.all() print(v)
进阶操作:
# 获取个数 models.Tb1.objects.filter(name=‘seven‘).count() # 大于,小于 models.Tb1.objects.filter(id__gt=1) # 获取id大于1的值 models.Tb1.objects.filter(id__gte=1) # 获取id大于等于1的值 # models.Tb1.objects.filter(id__lt=10) # 获取id小于10的值 # models.Tb1.objects.filter(id__lte=10) # 获取id小于10的值 # models.Tb1.objects.filter(id__lt=10, id__gt=1) # 获取id大于1 且 小于10的值 # in models.Tb1.objects.filter(id__in=[11, 22, 33]) # 获取id等于11、22、33的数据 models.Tb1.objects.exclude(id__in=[11, 22, 33]) # not in # contains models.Tb1.objects.filter(name__contains="ven") models.Tb1.objects.filter(name__icontains="ven") # icontains大小写不敏感 # range models.Tb1.objects.filter(id__range=[1, 2]) # 范围bettwen and # 其他类似 startswith,istartswith, endswith, iendswith, # order by models.Tb1.objects.filter(name=‘seven‘).order_by(‘id‘) # asc models.Tb1.objects.filter(name=‘seven‘).order_by(‘-id‘) # desc # group by from django.db.models import Count, Min, Max, Sum models.Tb1.objects.filter(c1=1).values(‘id‘).annotate(c=Count(‘num‘)) # limit 、offset models.Tb1.objects.all()[10:20] # regex正则匹配,iregex 不区分大小写
# F,Q,extra # F from django.db.models import F models.UserInfo.objects.all().update(age=F(‘age‘)+1) # F 代表数据库原来的值 # Q Q使用有两种方式: models.UserInfo.objects.filter(id=1, name=‘alex‘) ==> condition = { ‘id‘: 1, ‘name‘: ‘root‘ } models.UserInfo.objects.filter(**condition) 1. 对象方式 from django.db.models import Q models.UserInfo.objects.filter(Q(id=1) | Q(name=‘alex‘)) models.UserInfo.objects.filter(Q(id=1) & Q(name=‘alex‘)) 2. 方法方式 q1 = Q() q1.connector = ‘OR‘ q1.children.append((‘id‘, 1)) q1.children.append((‘id‘, 10)) q1.children.append((‘id‘, 9)) q2 = Q() q1.connector = ‘OR‘ q1.children.append((‘c1‘, 1)) q1.children.append((‘c1‘, 10)) q1.children.append((‘c1‘, 9)) con = Q() con.add(q1, ‘AND‘) con.add(q2, ‘AND‘) 简写为: condition = { ‘k1‘: [1, 2, 3, 4], ‘k2‘: [1, ], ‘k3‘: [11, ] } con = Q() for k, v in condition.items(): q = Q() q.connector = ‘OR‘ for i in v: q.children.append(‘id‘, i) con.add(q, ‘AND‘) models.UserInfo.objects.filter(con) # extra ‘‘‘ select id, name, (select count(1) from tb) as n from xb ‘‘‘ v = models.UserInfo.objects.all().extra( select={‘n‘: ‘select count(1) from app01_usertype where id=%s or id=%s‘, ‘m‘: ‘select count(1) from app01_usertype where id=%s or id=%s‘, }, select_params=[1, 2, 3, 4]) for obj in v: print(obj.id, obj.name, obj.n) models.UserInfo.objects.extra( where=[‘id = 1‘, "name=‘alex‘"] ) models.UserInfo.objects.extra( where=["id=1 or id=%s", "name=%s "], params=[1, ‘alex‘] ) models.UserInfo.objects.extra( tables=[‘app01_usertype‘], ) 类似于 select *from app01_userinfo, app01_usertype
a.映射 select select_params=None select 此处 from 表 b.条件 where=None params=None select *from 表 where 此处 c.表 table select *from 表,此处 d.排序 order_by = none select *from 表 order by 此处 例如: models.UserInfo.object.extra( select = {‘newid‘: ‘select count(1) from app01_usertype where id>%s},select_params=[1,], where = [‘age‘>%s], params=[18,], order_by = {‘-age‘}, tables= [‘app01_usertype‘] ) => select app01_userinfo.id, (select count(1) from app01_usertype where id>1)as newid from app01_userinfo,app01_usertype where app01_userinfo.age>18 order by app01_userinfo.age desc
def all(self) # 获取所有的数据对象 def filter(self, *args, **kwargs) # 条件查询 # 条件可以是:参数,字典,Q def exclude(self, *args, **kwargs) # 条件查询 # 条件可以是:参数,字典,Q def select_related(self, *fields) 性能相关:表之间进行join连表操作,一次性获取关联的数据。 总结: 1. select_related主要针一对一和多对一关系进行优化。 2. select_related使用SQL的JOIN语句进行优化,通过减少SQL查询的次数来进行优化、提高性能。 def prefetch_related(self, *lookups) 性能相关:多表连表操作时速度会慢,使用其执行多次SQL查询在Python代码中实现连表操作。 总结: 1. 对于多对多字段(ManyToManyField)和一对多字段,可以使用prefetch_related()来进行优化。 2. prefetch_related()的优化方式是分别查询每个表,然后用Python处理他们之间的关系。 def annotate(self, *args, **kwargs) # 用于实现聚合group by查询 from django.db.models import Count, Avg, Max, Min, Sum v = models.UserInfo.objects.values(‘u_id‘).annotate(uid=Count(‘u_id‘)) # SELECT u_id, COUNT(ui) AS `uid` FROM UserInfo GROUP BY u_id v = models.UserInfo.objects.values(‘u_id‘).annotate(uid=Count(‘u_id‘)).filter(uid__gt=1) # SELECT u_id, COUNT(ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1 v = models.UserInfo.objects.values(‘u_id‘).annotate(uid=Count(‘u_id‘,distinct=True)).filter(uid__gt=1) # SELECT u_id, COUNT( DISTINCT ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1 def distinct(self, *field_names) # 用于distinct去重 models.UserInfo.objects.values(‘nid‘).distinct() # select distinct nid from userinfo 注:只有在PostgreSQL中才能使用distinct进行去重 def order_by(self, *field_names) # 用于排序 models.UserInfo.objects.all().order_by(‘-id‘,‘age‘) def extra(self, select=None, where=None, params=None, tables=None, order_by=None, select_params=None) # 构造额外的查询条件或者映射,如:子查询 Entry.objects.extra(select={‘new_id‘: "select col from sometable where othercol > %s"}, select_params=(1,)) Entry.objects.extra(where=[‘headline=%s‘], params=[‘Lennon‘]) Entry.objects.extra(where=["foo=‘a‘ OR bar = ‘a‘", "baz = ‘a‘"]) Entry.objects.extra(select={‘new_id‘: "select id from tb where id > %s"}, select_params=(1,), order_by=[‘-nid‘]) def reverse(self): # 倒序 models.UserInfo.objects.all().order_by(‘-nid‘).reverse() # 注:如果存在order_by,reverse则是倒序,如果多个排序则一一倒序 def defer(self, *fields): models.UserInfo.objects.defer(‘username‘,‘id‘) 或 models.UserInfo.objects.filter(...).defer(‘username‘,‘id‘) #映射中排除某列数据 def only(self, *fields): #仅取某个表中的数据 models.UserInfo.objects.only(‘username‘,‘id‘) 或 models.UserInfo.objects.filter(...).only(‘username‘,‘id‘) def using(self, alias): 指定使用的数据库,参数为别名(setting中的设置) ################################################## # PUBLIC METHODS THAT RETURN A QUERYSET SUBCLASS # ################################################## def raw(self, raw_query, params=None, translations=None, using=None): # 执行原生SQL models.UserInfo.objects.raw(‘select * from userinfo‘) # 如果SQL是其他表时,必须将名字设置为当前UserInfo对象的主键列名 models.UserInfo.objects.raw(‘select id as nid from 其他表‘) # 为原生SQL设置参数 models.UserInfo.objects.raw(‘select id as nid from userinfo where nid>%s‘, params=[12,]) # 将获取的到列名转换为指定列名 name_map = {‘first‘: ‘first_name‘, ‘last‘: ‘last_name‘, ‘bd‘: ‘birth_date‘, ‘pk‘: ‘id‘} Person.objects.raw(‘SELECT * FROM some_other_table‘, translations=name_map) # 指定数据库 models.UserInfo.objects.raw(‘select * from userinfo‘, using="default") ################### 原生SQL ################### from django.db import connection, connections cursor = connection.cursor() # cursor = connections[‘default‘].cursor() cursor.execute("""SELECT * from auth_user where id = %s""", [1]) row = cursor.fetchone() # fetchall()/fetchmany(..) def values(self, *fields): # 获取每行数据为字典格式 def values_list(self, *fields, **kwargs): # 获取每行数据为元祖 def dates(self, field_name, kind, order=‘ASC‘): # 根据时间进行某一部分进行去重查找并截取指定内容 # kind只能是:"year"(年), "month"(年-月), "day"(年-月-日) # order只能是:"ASC" "DESC" # 并获取转换后的时间 - year : 年-01-01 - month: 年-月-01 - day : 年-月-日 models.DatePlus.objects.dates(‘ctime‘,‘day‘,‘DESC‘) def datetimes(self, field_name, kind, order=‘ASC‘, tzinfo=None): # 根据时间进行某一部分进行去重查找并截取指定内容,将时间转换为指定时区时间 # kind只能是 "year", "month", "day", "hour", "minute", "second" # order只能是:"ASC" "DESC" # tzinfo时区对象 models.DDD.objects.datetimes(‘ctime‘,‘hour‘,tzinfo=pytz.UTC) models.DDD.objects.datetimes(‘ctime‘,‘hour‘,tzinfo=pytz.timezone(‘Asia/Shanghai‘)) """ pip3 install pytz import pytz pytz.all_timezones pytz.timezone(‘Asia/Shanghai’) """ def none(self): # 空QuerySet对象 #################################### # METHODS THAT DO DATABASE QUERIES # #################################### def aggregate(self, *args, **kwargs): # 聚合函数,获取字典类型聚合结果 from django.db.models import Count, Avg, Max, Min, Sum result = models.UserInfo.objects.aggregate(k=Count(‘u_id‘, distinct=True), n=Count(‘nid‘)) ===> {‘k‘: 3, ‘n‘: 4} def count(self): # 获取个数 def get(self, *args, **kwargs): # 获取单个对象 def create(self, **kwargs): # 创建对象 def bulk_create(self, objs, batch_size=None): # 批量插入 # batch_size表示一次插入的个数 objs = [ models.DDD(name=‘r11‘), models.DDD(name=‘r22‘) ] models.DDD.objects.bulk_create(objs, 10) def get_or_create(self, defaults=None, **kwargs): # 如果存在,则获取,否则,创建 # defaults 指定创建时,其他字段的值 obj, created = models.UserInfo.objects.get_or_create(username=‘root1‘, defaults={‘email‘: ‘1111111‘,‘u_id‘: 2, ‘t_id‘: 2}) def update_or_create(self, defaults=None, **kwargs): # 如果存在,则更新,否则,创建 # defaults 指定创建时或更新时的其他字段 obj, created = models.UserInfo.objects.update_or_create(username=‘root1‘, defaults={‘email‘: ‘1111111‘,‘u_id‘: 2, ‘t_id‘: 1}) def first(self): # 获取第一个 def last(self): # 获取最后一个 def in_bulk(self, id_list=None): # 根据主键ID进行查找 id_list = [11,21,31] models.DDD.objects.in_bulk(id_list) def delete(self): # 删除 def update(self, **kwargs): # 更新 def exists(self): # 是否有结果 QuerySet方法大全
跨站请求伪造
引入:XSS攻击
例如用户在网页上传入<script>alert(123)</script>标签,网页进行渲染会出现弹窗。
views.py def test(request): temp = "<a href=‘http://www.baidu.com‘>百度</a>" return render(request, ‘test.html‘, {‘temp‘: temp}) test.html <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> {{ temp|safe }} # 点击会直接出现跳转,不安全。 </body> </html>
避免XSS攻击:
1. 过滤关键字
2. 去掉safe关键字
3. mark_safe
django为用户实现防止请求伪造的功能,通过中间件django.middleware.csrf.CsrfViewMiddleware来完成,发送post/get请求时会带一个随机字符串进行验证。而对于django中设置防跨站请求伪造功能有分全局和局部。
a. 基本应用
若在setting中打开‘django.middleware.csrf.CrsfViewMiddleware’,
在提交form表单时候要在form表单中添加
{% csrf_token %}
Ajax提交数据时候,携带CSRF:
$.ajax({ url: ‘要提交的地址‘, type:‘POST‘, //GET或者POST,提交方式 data:{‘k1‘:‘v1‘, ‘k2‘:‘v2‘}, //提交的数据 success:function(data){ // 当服务端处理完毕后,自动执行的回调函数 // 打他是返回的数据 } })
方式1:值获取到放置在data中
<form method="post" action="/csrf1.html/"> {% csrf_token %} <input id = "user" type="text" name="username"> <input type="submit" value="提交"> <a onclick="submitForm()">Ajax提交</a> </form> <script src="/static/jquery-3.4.1.js"></script> <script> function submitForm() { var csrf = $(‘input[name="csrfmiddlewaretoken"]‘).val(); var user1 = $(‘#user‘).val(); $.ajax({ url: ‘/csrf1.html/‘, type:‘POST‘, data:{‘username‘:user1, ‘csrfmiddlewaretoken‘:csrf}, success:function (arg) { console.log(arg) } }) } </script>
方式2:放在请求头中
<form method="post" action="/csrf1.html/"> <input id="user" type="text" name="user"> <input type="submit" value="提交"> <a onclick="submitForm()">Ajax提交</a> </form> <script src="/static/jquery-3.4.1.js"></script> <script src="/static/jquery.cookie.js"></script> <script> function submitForm() { var token = $.cookie(‘csrftoken‘); var user = $(‘#user‘).val(); $.ajax({ url: ‘/csrf1.html/‘, type: ‘POST‘, headers: {‘X-CSRFToken‘:token}, data:{‘user‘:user, }, success:function (arg) { console.log(arg); } }) } </script>
b. 全站禁用
# ‘django.middleware.csrf.CsrfViewMiddleware‘,
c. 局部禁用
‘django.middleware.csrf.CsrfViewMiddleware‘, from django.views.decorators.csrf import csrf_exempt @csrf_exempt def csrf1(request): if request.method==‘GET‘: return render(request, ‘csrf1.html‘) else: return HttpResponse(‘ok‘)
d. 局部使用
# ‘django.middleware.csrf.CsrfViewMiddleware‘, from django.views.decorators.csrf import csrf_protect @csrf_protect def csrf1(request): if request.method==‘GET‘: return render(request, ‘csrf1.html‘) else: return HttpResponse(‘ok‘)
def wrapper(func): def inner(*args, **kwargs): return func(*args, **kwargs) return inner 1.指定方法加 class Foo(View): @method_decorator(wrapper) def get(self): pass def post(self): pass 2. 类上面加 @method_decorator(wrapper, name=‘ dispatch‘) # dispath是在定义的方法之前先执行的方法,即给所有类中的方法加 class Foo(View): def dispatch(self, request, *args, **kwargs): return xx def get(self): pass def post(self): pass
# 加入装饰器,则不需要验证csrf from django.views.decorators.csrf import csrf_exempt # FBV 应用装饰器 @csrf_exempt def csrf1(request): if request.method == ‘GET‘: return render(request, ‘csrf1.html‘) else: return HttpResponse(‘ok‘)
Cookie
保存在客户端浏览器上的键值对
大家都知道HTTP协议是无状态的。
无状态的意思是每次请求都是独立的,它的执行情况和结果与前面的请求和之后的请求都无直接关系,它不会受前面的请求响应情况直接影响,也不会直接影响后面的请求响应情况。
一句有意思的话来描述就是人生只如初见,对服务器来说,每次的请求都是全新的。
状态可以理解为客户端和服务器在某次会话中产生的数据,那无状态的就以为这些数据不会被保留。会话中产生的数据又是我们需要保存的,也就是说要“保持状态”。因此Cookie就是在这样一个场景下诞生。
什么是Cookie?
Cookie具体指的是一段小信息,它是服务器发送出来存储在浏览器上的一组组键值对,下次访问服务器时浏览器会自动携带这些键值对,以便服务器提取有用信息。
原理:
由服务器产生内容,浏览器收到请求后保存在本地;当浏览器再次访问时,浏览器会自动带上Cookie,这样服务器就能通过Cookie的内容来判断这个是“谁”了。
1. 获取Cookie:
request.COOKIES[‘key‘] request.get_signed_cookie(key, default=RAISE_ERROR, salt=‘‘, max_age=None) 参数: default: 默认值 salt: 加密盐 max_age: 后台控制过期时间
2. 设置Cookie:
rep = HttpResponse(...) 或 rep = render(request, ...) rep.set_cookie(key,value,...) rep.set_signed_cookie(key,value,salt=‘加密盐‘,...) 参数: 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, https传输 httponly=False 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖)
3. 删除Cookie
def logout(request): rep = redirect("/login/") rep.delete_cookie("user") # 删除用户浏览器上之前设置的usercookie值 return rep
def check_login(func): @wraps(func) def inner(request, *args, **kwargs): next_url = request.get_full_path() if request.get_signed_cookie("login", salt="SSS", default=None) == "yes": # 已经登录的用户... return func(request, *args, **kwargs) else: # 没有登录的用户,跳转刚到登录页面 return redirect("/login/?next={}".format(next_url)) return inner def login(request): if request.method == "POST": username = request.POST.get("username") passwd = request.POST.get("password") if username == "xxx" and passwd == "dashabi": next_url = request.GET.get("next") if next_url and next_url != "/logout/": response = redirect(next_url) else: response = redirect("/class_list/") response.set_signed_cookie("login", "yes", salt="SSS") return response return render(request, "login.html")
def login(request): if request.method == ‘GET‘: return render(request, ‘login.html‘) else: user = request.POST.get(‘username‘) pwd = request.POST.get(‘password‘) if user == ‘cui‘ and pwd == ‘123‘: obj = redirect(‘/classes/‘) obj.set_cookie(‘ticket‘, ‘asdadsadasd‘, max_age=600) # cookie 存活600s # obj.set_signed_cookie(‘ticket‘, "123123", salt=‘jjjjjj‘) # 加盐操作,类似于加密操作 return obj else: return render(request, ‘login.html‘, {‘msg‘: ‘用户名或者密码错误‘, }) def classes(request): tk = request.COOKIES.get(‘ticket‘) # tk = request.get_signed_cookie(‘ticket‘, salt=‘jjjjjj‘) if not tk: return redirect(‘/login/‘) class_list = sqlhelper.get_list("select id, title from class", []) return render(request, ‘classes.html‘, {‘class_lst‘: class_list})
Session
- 保存在服务端的数据
- 应用:依赖cookie
- 作用:保持会话(Web网站)
- 好处:敏感信息不会直接给客户端
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,默认修改之后才保存(默认)
def index(request): # 获取、设置、删除Session中数据 request.session[‘k1‘] request.session.get(‘k1‘,None) request.session[‘k1‘] = 123 request.session.setdefault(‘k1‘,123) # 存在则不设置 del request.session[‘k1‘] # 所有 键、值、键值对 request.session.keys() request.session.values() request.session.items() request.session.iterkeys() request.session.itervalues() request.session.iteritems() # 用户session的随机字符串 request.session.session_key # 将所有Session失效日期小于当前日期的数据删除 request.session.clear_expired() # 检查 用户session的随机字符串 在数据库中是否 request.session.exists("session_key") # 删除当前用户的所有Session数据 request.session.delete("session_key") request.session.set_expiry(value) * 如果value是个整数,session会在些秒数后失效。 * 如果value是个datatime或timedelta,session就会在这个时间后失效。 * 如果value是0,用户关闭浏览器session就会失效。 * 如果value是None,session会依赖全局session失效策略。
session存放位置: # 文件中 SESSION_ENGINE = ‘django.contrib.sessions.backends.file‘ # 数据库中 SESSION_ENGINE = ‘django.contrib.sessions.backends.db‘ # 缓存中 SESSION_ENGINE = ‘django.contrib.sessions.backends.cache‘ # 缓存+数据库中 (推荐) SESSION_ENGINE = ‘django.contrib.sessions.backends.cache_db‘ # cookie中 SESSION_ENGINE = ‘django.contrib.sessions.backends.signed_cookies‘
1 def login(request): 2 if request.method == ‘GET‘: 3 return render(request, ‘login.html‘) 4 else: 5 u = request.POST.get(‘user‘) 6 p = request.POST.get(‘pwd‘) 7 obj = models.UserAdmin.objects.filter(username=u, password=p).first() 8 if obj: 9 # 1.生成随机字符串 10 # 2. 通过cookie发生给客户端 11 # 3.服务端保存{随机字符串1:{‘xx‘}} 12 request.session[‘username‘] = obj.username 13 14 return redirect(‘/index/‘) 15 else: 16 return render(request, ‘login.html‘, {‘msg‘: ‘error‘}) 17 18 def index(request): 19 # 1.获取客户端cookie中的随机字符串 20 # 2.去session中查找有没有随机字符串 21 # 3.去session对应的key的value中查看是否有username 22 v = request.session.get(‘username‘) 23 if v: 24 return HttpResponse(‘login successfully: %s ‘%v) 25 else: 26 return redirect(‘/index/‘)
补充:MVC 和 MTV
models(数据库,模型) views(html模板) controllers(业务逻辑处理) -> MVC models(数据库,模型) templates(html模板) views(业务逻辑处理) -> MTV Django -> 默认MTV
分页
Django内置分页:
def index(request): ‘‘‘ 分页 :param request: :return: ‘‘‘ l = [] for i in range(500): l.append(i) current_page = request.GET.get(‘page‘) # user_list = models.UserInfo.objects.all() paginator = Paginator(l, 10) # per_page: 每页显示条目数量 # count: 数据总个数 # num_pages: 总页数 # page_range: 总页数的索引范围,如(1,10),(1,100) # page: page对象 try: posts = paginator.page(current_page) except PageNotAnInteger as e: posts = paginator.page(1) except EmptyPage as e: posts = paginator.page(1) # has_next 是否有下一页 # next_page_number 下一页页码 # has_previous 是否有上一页 # previous_page_number 上一页页码 # object_list 分页之后的数据列表 # number 当前页 # paginator paginator对象 return render(request, ‘num.html‘, {‘posts‘: posts})
1 <!DOCTYPE html> 2 <html lang="zh-CN"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 7 </head> 8 <body> 9 <ul> 10 <h1>用户列表</h1> 11 <ul> 12 {% for row in posts.object_list %} 13 <li>{{row}}</li> 14 {% endfor %} 15 </ul> 16 <div> 17 {% if posts.has_previous %} 18 <a href="/index.html?page={{ posts.previous_page_number }}">上一页</a> 19 {% endif %} 20 21 {% for num in posts.paginator.page_range %} 22 <a href="/index.html?page={{ num }}">{{ num }}</a> 23 {% endfor %} 24 25 {% if posts.has_next %} 26 <a href="/index.html?page={{ posts.next_page_number }}">下一页</a> 27 {% endif %} 28 </div> 29 </ul> 30 31 </body> 32 </html>
自定义分页:
from utils.pager import PageInfo def custom(request): l = [] for i in range(500): l.append(i) all_count = len(l) current_page = request.GET.get(‘page‘) page_info = PageInfo(current_page, all_count, 10, ‘/custom.html‘, 11) list_num = l[page_info.start(): page_info.end()] return render(request, ‘ex.html‘, {‘list_num‘: list_num, ‘page_info‘: page_info})
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.css"> </head> <body> <h1>用户列表</h1> <ul> {% for row in list_num %} <li>{{ row }}</li> {% endfor %} </ul> <nav aria-label="Page navigation"> <ul class="pagination"> {{ page_info.pager|safe}} </ul> </nav> </body> </html>
class PageInfo(object): def __init__(self, current_page, all_count, per_page, base_url, show_page): ‘‘‘ :param current_page: :param all_count: 数据库总行数 :param per_page: 每页显示行数 ‘‘‘ try: self.current_page = int(current_page) except Exception as e: self.current_page = 1 self.per_page = per_page a, b = divmod(all_count, per_page) if b: a = a + 1 self.all_pager = a self.show_page = show_page self.base_url = base_url def start(self): return (self.current_page - 1) * self.per_page def end(self): return self.current_page * self.per_page def pager(self): page_list = [] half = int((self.show_page - 1) / 2) # 如果数据总页数<11 if self.all_pager < self.show_page: bgein = 1 stop = self.all_pager + 1 # 如果数据总页数>11 else: # 如果当前页<=5, 永远显示1,11 if self.current_page <= half: begin = 1 stop = self.show_page + 1 else: if self.current_page + half > self.all_pager: begin = self.all_pager - self.show_page + 1 stop = self.all_pager + 1 else: begin = self.current_page - half stop = self.current_page + half + 1 if self.current_page <= 1: prev = "<li><a href=‘#‘>上一页</a></li>" else: prev = "<li><a href=‘%s?page=%s‘>上一页</a></li>" % (self.base_url, self.current_page - 1) page_list.append(prev) for i in range(begin, stop): if i == self.current_page: temp = "<li class=‘active‘ ><a href=‘%s?page=%s‘>%s</a></li>" % (self.base_url, i, i,) else: temp = "<li><a href=‘%s?page=%s‘>%s</a></li>" % (self.base_url, i, i,) page_list.append(temp) if self.current_page >= self.all_pager: next = "<li><a href=‘#‘>下一页</a></li>" else: next = "<li><a href=‘%s?page=%s‘>下一页</a></li>" % (self.base_url, self.current_page + 1) page_list.append(next) return ‘‘.join(page_list)
***Form验证 **
用户需求:
- 需要对请求数据做验证
- 获取到数据然后进行验证
问题:
-无法记住上次提交的内容,刷新页面数据消失
Django提供了Form组件。
1. 定义规则
from django.forms import Form from django.forms import fields class LoginForm(Form): username = fields.CharField( max_length=18, min_length=6, required=True, error_messages={ ‘required‘: ‘用户名不能为空‘, ‘min_length‘: ‘太短了‘, ‘max_length‘: ‘太长了‘, }
2. 使用
obj = LoginForm(request.POST) v = obj.is_valid() # html标签named的属性 = Form类字段名 # 所有的错误信息 obj.errors # 正确的信息 obj.cleaned_data
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 是否移除用户输入空白 IntegerField(Field) max_value=None, 最大值 min_value=None, 最小值 FloatField(IntegerField) ... DecimalField(IntegerField) max_value=None, 最大值 min_value=None, 最小值 max_digits=None, 总长度 decimal_places=None, 小数位长度 BaseTemporalField(Field) input_formats=None 时间格式化 DateField(BaseTemporalField) 格式:2015-09-01 TimeField(BaseTemporalField) 格式:11:12 DateTimeField(BaseTemporalField)格式:2015-09-01 11:12 DurationField(Field) 时间间隔:%d %H:%M:%S.%f ... RegexField(CharField) regex, 自定制正则表达式 max_length=None, 最大长度 min_length=None, 最小长度 error_message=None, 忽略,错误信息使用 error_messages={‘invalid‘: ‘...‘} EmailField(CharField) ... FileField(Field) allow_empty_file=False 是否允许空文件 ImageField(FileField) ... 注:需要PIL模块,pip3 install Pillow 以上两个字典使用时,需要注意两点: - form表单中 enctype="multipart/form-data" - view函数中 obj = MyForm(request.POST, request.FILES) URLField(Field) ... BooleanField(Field) ... NullBooleanField(BooleanField) ... ChoiceField(Field) ... choices=(), 选项,如:choices = ((0,‘上海‘),(1,‘北京‘),) required=True, 是否必填 widget=None, 插件,默认select插件 label=None, Label内容 initial=None, 初始值 help_text=‘‘, 帮助提示 ModelChoiceField(ChoiceField) ... django.forms.models.ModelChoiceField queryset, # 查询数据库中的数据 empty_label="---------", # 默认空显示内容 to_field_name=None, # HTML中value的值对应的字段 limit_choices_to=None # ModelForm中对queryset二次筛选 ModelMultipleChoiceField(ModelChoiceField) ... django.forms.models.ModelMultipleChoiceField TypedChoiceField(ChoiceField) coerce = lambda val: val 对选中的值进行一次转换 empty_value= ‘‘ 空值的默认值 MultipleChoiceField(ChoiceField) ... TypedMultipleChoiceField(MultipleChoiceField) coerce = lambda val: val 对选中的每一个值进行一次转换 empty_value= ‘‘ 空值的默认值 ComboField(Field) fields=() 使用多个验证,如下:即验证最大长度20,又验证邮箱格式 fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),]) MultiValueField(Field) PS: 抽象类,子类中可以实现聚合多个字典去匹配一个值,要配合MultiWidget使用 SplitDateTimeField(MultiValueField) input_date_formats=None, 格式列表:[‘%Y--%m--%d‘, ‘%m%d/%Y‘, ‘%m/%d/%y‘] input_time_formats=None 格式列表:[‘%H:%M:%S‘, ‘%H:%M:%S.%f‘, ‘%H:%M‘] FilePathField(ChoiceField) 文件选项,目录下文件显示在页面中 path, 文件夹路径 match=None, 正则匹配 recursive=False, 递归下面的文件夹 allow_files=True, 允许文件 allow_folders=False, 允许文件夹 required=True, widget=None, label=None, initial=None, help_text=‘‘ GenericIPAddressField protocol=‘both‘, both,ipv4,ipv6支持的IP格式 unpack_ipv4=False 解析ipv4地址,如果是::ffff:192.0.2.1时候,可解析为192.0.2.1, PS:protocol必须为both才能启用 SlugField(CharField) 数字,字母,下划线,减号(连字符) ... UUIDField(CharField) uuid类型
Form提交表单示例
from django.shortcuts import render, redirect, HttpResponse from django.forms import Form, fields class LoginForm(Form): user = fields.CharField(required=True) pwd = fields.CharField(min_length=18) def login(request): if request.method == ‘GET‘: return render(request, ‘login.html‘) else: obj = LoginForm(request.POST) if obj.is_valid(): print(obj.cleaned_data) # obj.cleaned_data是一个字典 return redirect(‘http://www.baidu.com‘) # obj.errors是一个对象 return render(request, ‘login.html‘, {‘obj‘: obj})
1 <!DOCTYPE html> 2 <html lang="zh-CN"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 7 </head> 8 <body> 9 <h1>用户登录</h1> 10 <form method="post" action="/login/"> 11 {% csrf_token %} 12 <p> 13 <input type="text" name="user" placeholder="姓名">{{ obj.errors.user.0 }} 14 </p> 15 <p> 16 <input type="password" name="pwd" placeholder="密码">{{ obj.errors.pwd.0 }} 17 </p> 18 <input type="submit" value="提交"> 19 </form> 20 </body> 21 </html>
缺点:无法保留用户输入的数据
基于Form-Ajax的提交
import json from django.shortcuts import render, redirect, HttpResponse from django.forms import Form, fields class LoginForm(Form): user = fields.CharField(required=True) pwd = fields.CharField(min_length=18) def ajax_login(request): if request.method ==‘GET‘: return render(request, ‘login.html‘) ret = {‘status‘: True, ‘msg‘: None} obj = LoginForm(request.POST) if obj.is_valid(): print(obj.cleaned_data) else: # print(obj.errors) # obj.errors是一个对象 ret[‘status‘] = False ret[‘msg‘] = obj.errors v = json.dumps(ret) return HttpResponse(v)
1 <!DOCTYPE html> 2 <html lang="zh-CN"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 7 </head> 8 <body> 9 <h1>用户登录</h1> 10 <form id = ‘f1‘ method="post" action="/ajax_login/"> 11 {% csrf_token %} 12 <p> 13 <input type="text" name="user" placeholder="姓名"> 14 </p> 15 <p> 16 <input type="password" name="pwd" placeholder="密码"> 17 </p> 18 <a onclick="submitForm()">提交</a> 19 </form> 20 21 <script src="/static/jquery-3.4.1.js"></script> 22 <script> 23 function submitForm(){ 24 $(‘.c1‘).remove(); 25 $.ajax({ 26 url:‘/ajax_login/‘, 27 type:‘POST‘, 28 data: $(‘#f1‘).serialize(), // 把form表单里所有input框内数据打包: user=xx&pwd=xx&csrf_token=xx 29 dataType:‘JSON‘, 30 success:function (arg) { 31 if(arg.status){ 32 33 } 34 else{ 35 $.each(arg.msg, function (index,value) { 36 var tag = document.createElement(‘span‘); 37 tag.innerHTML = value[0]; 38 tag.className=‘c1‘; 39 $(‘#f1‘).find(‘input[name ="‘+index+‘"]‘).after(tag) 40 }) 41 } 42 } 43 }) 44 } 45 </script> 46 </body> 47 </html>
完美,但还未解决Form提交问题
保留上次输入内容:
1 from django.shortcuts import render, redirect, HttpResponse 2 from django.forms import Form, fields 3 4 class RegisterForm(Form): 5 user = fields.CharField(min_length=8) 6 email = fields.EmailField() 7 password = fields.CharField() 8 phone = fields.RegexField(‘139\d+‘) 9 10 def register(request): 11 if request.method == ‘GET‘: 12 obj = RegisterForm() 13 return render(request, ‘register.html‘, {‘obj‘: obj}) 14 else: 15 obj = RegisterForm(request.POST) 16 if obj.is_valid(): 17 print(obj.cleaned_data) 18 else: 19 print(obj.errors) 20 return render(request, ‘register.html‘, {‘obj‘: obj})
1 <!DOCTYPE html> 2 <html lang="zh-CN"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 7 </head> 8 <body> 9 <form method="post" action="/register/" novalidate> 10 {% csrf_token %} 11 <p>{{ obj.user }}{{ obj.errors.user.0 }}</p> 12 <p>{{ obj.email }}{{ obj.errors.email.0 }}</p> 13 <p>{{ obj.password }}{{ obj.errors.password.0 }}</p> 14 <p>{{ obj.phone }}{{ obj.errors.phone.0 }}</p> 15 <input type="submit" value="提交"> 16 </form> 17 </body> 18 </html>
示例:基于Form 的添加班级和删除班级并进行验证
from django.db import models class Classes(models.Model): title = models.CharField(max_length=32) class Student(models.Model): name = models.CharField(max_length=32) email = models.CharField(max_length=32) age = models.IntegerField(max_length=32) cls = models.ForeignKey(‘Classes‘, on_delete=models.CASCADE) class Teacher(models.Model): tname = models.CharField(max_length=32) c2t = models.ManyToManyField(‘Classes‘)
from django.contrib import admin from django.urls import path, re_path from app01 import views urlpatterns = [ path(‘class_list/‘, views.class_list), path(‘add_class/‘, views. add_class), re_path(‘edit_class/(\d+)/‘, views.edit_class), ]
1 from django.forms import Form, fields 2 from django.shortcuts import render, redirect 3 from app01 import models 4 5 class ClassForm(Form): 6 title = fields.CharField(min_length=5) 7 8 def class_list(request): 9 cls_list = models.Classes.objects.all() 10 return render(request, ‘class_list.html‘, {‘cls_list‘: cls_list}) 11 12 def add_class(request): 13 if request.method == ‘GET‘: 14 obj = ClassForm() 15 return render(request, ‘add_class.html‘, {‘obj‘: obj}) 16 else: 17 obj = ClassForm(request.POST) 18 if obj.is_valid(): 19 # obj.cleaned_data # 字典类型 20 # 数据库创建一条数据 21 # models.Classes.objects.create(title=obj.cleaned_data[‘tt‘] ) 22 models.Classes.objects.create(**obj.cleaned_data) # 怎么回事??? 23 return redirect(‘/class_list/‘) 24 return render(request, ‘add_class.html‘, {‘obj‘: obj}) 25 26 def edit_class(request, nid): 27 if request.method == ‘GET‘: 28 row = models.Classes.objects.filter(id=nid).first() 29 # 让页面显示初始值 30 # obj = ClassForm(data={‘title‘: row.title}) # 进行验证 31 obj = ClassForm(initial={‘title‘: row.title}) 32 return render(request, ‘edit_class.html‘, {‘nid‘: nid, ‘obj‘: obj}) 33 else: 34 obj = ClassForm(request.POST) 35 if obj.is_valid(): 36 models.Classes.objects.filter(id=nid).update(**obj.cleaned_data) 37 return redirect(‘/class_list/‘) 38 return render(request, ‘edit_class.html‘, {‘nid‘: nid, ‘obj‘: obj})
1 <!DOCTYPE html> 2 <html lang="zh-CN"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 7 </head> 8 <body> 9 <h1>班级列表</h1> 10 11 <div> 12 <a href="/add_class/">添加班级</a> 13 </div> 14 15 <ul> 16 {% for row in cls_list %} 17 <li>{{ row.title }} <a href="/edit_class/{{ row.id }}/">编辑</a></li> 18 {% endfor %} 19 </ul> 20 </body> 21 </html>
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>添加班级</h1> <form method="post" action="/add_class/" novalidate> {% csrf_token %} {{ obj.title }}{{ obj.errors.title.0 }} <p><input type="submit" name="提交"></p> </form> </body> </html>
1 <!DOCTYPE html> 2 <html lang="zh-CN"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 7 </head> 8 <body> 9 <h1>编辑班级</h1> 10 <form method="post" action="/edit_class/{{ nid }}/"> 11 {% csrf_token %} 12 {# <input type="text" name="xx" value="{{ row.title }}">#} 13 <p>{{ obj.title }}{{ obj.errors.title.0 }}</p> 14 <input type="submit" value="提交"> 15 16 </form> 17 18 </body> 19 </html>
原文:https://www.cnblogs.com/pythoncui/p/12288495.html