Django学习,主要参考参考菜鸟教程上的教程,
网站地址:https://www.runoob.com/django/django-intro.html
Django概述
Django是一个开放源代码的Web应用框架,由Python写成。采用了MTV的框架模式,即模型(Model),视图(View)和模版(Template)。
层次 | 职责 |
---|---|
模型(Model),即数据存取层 | 处理与数据相关的所有事务:如何存取、如何验证有效性、包含哪些行为以及数据之间的关系等。 |
模板(Template),即表现层 | 处理与表现相关的决定:如何在页面或其他类型文档中进行显示。 |
视图(View),即业务逻辑层 | 存取模型及调取恰当模板的相关逻辑。模型与模板的桥梁。 |
Django 本身基于 MVC 模型,即 Model(模型)+ View(视图)+ Controller(控制器)设计模式,MVC 模式使后续对程序的修改和扩展简化,并且使程序某一部分的重复利用成为可能。
MVC 优势:
分离关注点:
- Model :负责应用程序的数据和业务逻辑。通过将数据和逻辑从用户界面分离出来,使得模型可以独立于用户界面进行测试和修改。
- View :负责显示用户界面,但通常没有直接访问应用程序的数据。这使得可以更容易地更改应用程序的外观而不影响数据处理。
- Controller:处理用户输入、更新模型和调整视图。通过将用户输入和应用程序逻辑分离,可以更容易地更改用户界面的交互方式而不影响数据和业务逻辑。
可维护性:
分离关注点使得每个组件都可以独立开发、测试和维护。这种分离降低了代码的耦合性,使得在一个组件中的修改不太可能导致对其他组件的影响。
可扩展性:
由于每个组件都是相对独立的,因此可以更容易地添加新功能或进行修改,而不会影响应用程序的其他部分。
可重用性:
模型、视图和控制器之间的分离允许在不同的上下文中重用这些组件。例如,可以更换视图以改变应用程序的外观,而不影响其余的结构。
可测试性:
模型、视图和控制器的分离使得单元测试更加容易。可以分别测试每个组件,确保其功能正确,而无需整个应用程序的上下文。
团队协作:
因为MVC模式清晰地定义了各个组件的角色和责任,所以在团队协作中更容易分工协作,不同开发人员可以专注于不同部分的开发。
特点
- ORM(对象关系映射):Django 提供了一个强大的 ORM,允许开发者通过 Python 代码来定义和操作数据库模型,而无需直接使用 SQL。这使得数据库操作更加抽象和易于管理。
- MVC 架构 :Django 遵循 MVC(模型-视图-控制器)的软件设计模式,但它使用了稍微不同的术语。在 Django 中,模型(Model)表示数据结构,视图(View)负责呈现用户界面,而控制器(Controller)的职责被称为视图(View)。
- 模板引擎:Django 使用模板引擎来生成 HTML,这使得前端和后端的代码分离更加容易。Django 的模板语言允许开发者在模板中嵌入动态内容。
- 自动化 admin 界面:Django 自动生成管理后台,使得管理和操作数据库的过程变得非常简单。开发者可以轻松地创建、修改和删除数据库记录,而无需编写自定义的管理界面。
- 表单处理:Django 提供了强大的表单处理工具,使得用户输入的验证和处理变得更加简单。这对于开发 Web 表单和处理用户提交的数据非常有用。
- 安全性:Django 内置了一些安全性功能,例如防止常见的 Web 攻击(如 CSRF 攻击),并提供了方便的用户身份验证和授权系统。
- 可扩展性:Django 的组件是松耦合的,允许开发者使用现有的组件或编写自己的应用程序来扩展框架功能。
- 社区支持:Django 拥有庞大的社区支持,提供了大量的文档、教程和第三方包,使得学习和使用 Django 变得更加容易。
项目环境搭建
使用清华镜像源搭建Django环境
pip install Django -i https://pypi.tuna.tsinghua.edu.cn/simple/
HelloDjango
安装了Django之后,就可以使用Django的管理工具:django-admin
$ django-admin
Type 'django-admin help <subcommand>' for help on a specific subcommand.
Available subcommands:
[django]
check
compilemessages
createcachetable
dbshell
diffsettings
dumpdata
flush
inspectdb
loaddata
makemessages
makemigrations
migrate
runserver
sendtestemail
shell
showmigrations
sqlflush
sqlmigrate
sqlsequencereset
squashmigrations
startapp
startproject
test
testserver
创建一个名称为hello_django
的Django项目,命令如下:
django-admin startproject hello_django
目结构文件含义如下:
- 外层的hello_django目录:是项目的容器,Django不关心它的名字,可以将它重命名为任何我们喜欢的名字
- 里面的hello_django目录:它是一个纯python包。为项目的名称,不能随意重命名
- init.py:一个空文件,表示该目录是一个 Python 包
- asgi.py:异步网关协议接口,能够处理多种通用的协议类型,包括 HTTP,HTTP2和WebSocket,可以看成ASGI是WSGI的扩展
- settings.py :Django 项目的配置文件。包含了项目的默认设置,包括数据库信息,调试标志以及其他一些工作的变量
- urls.py :Django 项目的URL路由声明,负责把URL模式映射到应用程序
- wsgi.py:Web服务器网关接口(Python Web Server Gateway Interface的缩写),Python应用和Web服务器之间的一种接口,可以看成是一种协议、规范。它是基于Http协议的,不支持WebSoket
- manage.py :它是Django的一个非常重要的工具,通过它可以调用 django shell 和数据库等,如:创建app应用程序、创建数据库表、清空数据、启动项目等操作
项目启动
python manage.py runserver 0.0.0.0:8000
0.0.0.0
让其它电脑可连接到开发服务器,8000
为端口号。如果不说明,那么端口号默认为 8000。
正常启动,输出结果如下:
在 pychram 中也可以通过编辑配置的方式,便捷启动
视图和 URL 配置
在先前创建的 hello_django
软件包中新建一个 views.py
文件,并输入代码:
from django.http import HttpResponse
def hello(request):
return HttpResponse("Hello world !")
接着,绑定 URL 与视图函数。修改 urls.py
文件代码如下:
from django.contrib import admin
from django.urls import path
from . import views
urlpatterns = [
path("admin/", admin.site.urls),
path("", views.hello, name="hello"),
]
整个目录结构如下:
$ tree
.
|-- hello_django
| |-- __init__.py
| |-- asgi.py
| |-- settings.py
| |-- urls.py # url 配置
| |-- views.py # 添加的视图文件
| |-- wsgi.py
`-- manage.py
完成后,启动 Django 开发服务器,并在浏览器访问打开浏览器并访问:
也可以修改urlpattern,以RestFul风格调整路由地址
urlpatterns = [ ..., path('hello/', views.hello), ]
此时需要访问 http://127.0.0.1:8000/hello
**注意:**项目中如果代码有改动,服务器会自动监测代码的改动并自动重新载入,所以如果你已经启动了服务器则不需手动重启。
path() 函数
Django path()
可以接收四个参数,分别是两个必选参数:route、view 和两个可选参数:kwargs、name。
语法格式:
path(route, view, kwargs=None, name=None)
- route: 字符串,定义 URL 的路径部分。可以包含变量,例如
<int:my_variable>
,以从 URL 中捕获参数并将其传递给视图函数。 - view: 视图函数,处理与给定路由匹配的请求。可以是一个函数或一个基于类的视图。
- kwargs(可选): 一个字典,包含传递给视图函数的额外关键字参数。
- name(可选): 为 URL 路由指定一个唯一的名称,以便在代码的其他地方引用它。这对于在模板中生成 URL 或在代码中进行重定向等操作非常有用。
Django2. 0中可以使用 re_path() 方法来兼容 1.x 版本中的 url() 方法,一些正则表达式的规则也可以通过 re_path() 来实现 。``
from django.urls import include, re_path
urlpatterns = [
re_path(r'^index/$', views.index, name='index'),
re_path(r'^bio/(?P<username>\w+)/$', views.bio, name='bio'),
re_path(r'^weblog/', include('blog.urls')),
...
]
Django 模板
使用 django.http.HttpResponse()
来输出 “Hello Django!”。该方式将数据与视图混合在一起,不符合 Django 的 MVC 思想。
本章节将详细介绍 Django 模板的应用,模板是一个文本,用于分离文档的表现形式和内容。
Template应用实例
在 HelloDjango 目录底下创建 templates 目录并建立 index.html
文件,整个目录结构如下:
hello_django/
|-- hello_django
| |-- __init__.py
| |-- asgi.py
| |-- settings.py
| |-- urls.py
| |-- views.py
| |-- wsgi.py
|-- manage.py
`-- templates
`-- index.html
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>{{ replace_text }}</h1>
</body>
</html>
模板中,变量使用了双括号。
接下来需要向Django说明模板文件的路径,修改hello_django/settings.py
,修改 TEMPLATES 中的 DIRS 为 [os.path.join(BASE_DIR, ‘templates’)],如下所示:
...
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')], # 修改位置
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
...
再修改 views.py
,增加一个新的对象,用于向模板提交数据:
from django.shortcuts import render
def runoob(request):
context = {'replace_text': 'Hello Django!'}
return render(request, 'index.html', context)
里使用 render 来替代之前使用的 HttpResponse。render 还使用了一个字典 context 作为参数。
context 字典中元素的键值 hello 对应了模板中的变量 {{ replace_text }}。
修改 urls.py
的路由地址:
urlpatterns = [
...
path("", views.index, name="index"),
]
再次访问 http://127.0.0.1:8000,可以看到页面:
Django 模板标签
变量
模板语法(具体用法参考上面小节):
view:{"HTML变量名" : "views变量名"}
HTML:{{变量名}}
列表
可以用 .
索引下标取出对应的元素
views.py
修改如下:
from django.shortcuts import render
def index(request):
views_list = ["1 元素", "2 Python", "3 Django"]
context = {
'replace_text': 'Hello Django!!',
'replace_list': views_list,
}
return render(request, 'index.html', context)
index.html
修改如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>{{ replace_text }}</h1>
<p>{{ replace_list }}</p>
<ul>
<li>{{ replace_list.0 }}</li>
<li>{{ replace_list.1 }}</li>
<li>{{ replace_list.2 }}</li>
</ul>
</body>
</html>
字典
templates 中的 index.html
中,可以用 .键
取出对应的值。
过滤器
模板语法:
{{ 变量名 | 过滤器:可选参数 }}
模板过滤器可以在变量被显示前修改它,过滤器使用管道字符,如下所示:
{{ name|lower }} <!-- name 变量被过滤器 lower 处理后,文档大写转换文本为小写 -->
过滤管道可以被套接 ,既是说,一个过滤器管道的输出又可以作为下一个管道的输入:
{{ my_list|first|upper }} <!--将第一个元素并将其转化为大写-->
有些过滤器有参数。 过滤器的参数跟随冒号之后并且总是以双引号包含。 例如:
如果字符串包含的字符总个数多于指定的字符数量,那么会被截断掉后面的部分。
截断的字符串将以 … 结尾。
{{ bio|truncatewords:"30" }} <!-- 将显示变量 bio 的前30个词 -->
其他过滤器:
addslashes : 添加反斜杠到任何反斜杠、单引号或者双引号前面。
date : 按指定的格式字符串参数格式化 date 或者 datetime 对象,实例:
{{ pub_date|date:"F j, Y" }}
length : 返回变量的长度。
default
default 为变量提供一个默认值。
如果 views 传的变量的布尔值是 false,则使用指定的默认值。
以下值为 false:
0 0.0 False 0j "" [] () set() {} None
length
返回对象的长度,适用于字符串和列表。
字典返回的是键值对的数量,集合返回的是去重后的长度。
index.html
加入代码:
<p>{{ replace_list | length }}</p>
filesizeformat
以更易读的方式显示文件的大小(即’13 KB’, ‘4.1 MB’, '102 bytes’等)。
字典返回的是键值对的数量,集合返回的是去重后的长度。
date
根据给定格式对一个日期变量进行格式化。
格式 Y-m-d H:i:s返回 年-月-日 小时:分钟:秒 的格式时间。
views.py
修改如下:
def index(request):
import datetime
now = datetime.datetime.now()
views_list = ["1 元素", "2 Python", "3 Django"]
context = {
'replace_text': 'Hello Django!!',
'replace_list': views_list,
"time": now,
}
return render(request, 'index.html', context)
index.html
加入代码:
<p>Time: {{ time }} </p>
<p> Date: {{ time | date:"Y-m-d" }} </p>
safe
将字符串标记为安全,不需要转义。
要保证 views.py
传过来的数据绝对安全,才能用 safe。
和后端 views.py
的 mark_safe 效果相同。
Django 会自动对 views.py
传到HTML文件中的标签语法进行转义,令其语义失效。加 safe 过滤器是告诉 Django 该数据是安全的,不必对其进行转义,可以让该数据语义生效。
使用示例(views.py
添加代码):
safe_str = "<a href='https://www.runoob.com/'>点击跳转-菜鸟教程</a>"
context = {
...
"safe_str": safe_str,
}
index.html
加入代码:
{{ safe_str }}<br>
{{ safe_str | safe }}
if/else 标签
基本语法格式如下:
{% if condition %}
... display
{% endif %}
<!-- or -->
{% if condition1 %}
... display 1
{% elif condition2 %}
... display 2
{% else %}
... display 3
{% endif %}
根据条件判断是否输出。if/else 支持嵌套。
{% if %}
标签接受 and , or 或者 not 关键字来对多个变量做判断 ,或者对变量取反( not ),例如:
{% if athlete_list and coach_list %}
athletes 和 coaches 变量都是可用的。
{% endif %}
用法示例(views.py
添加代码):
if_else_num = 88
context = {
...
"if_else_num": if_else_num,
}
index.html
加入代码:
<p>if-else (Whether pass or not?) </p>
{% if if_else_num > 90 and if_else_num <= 100 %}
<h3>Excellent!</h3>
{% elif if_else_num > 60 and if_else_num <= 90 %}
<h3>Good!</h3>
{% else %}
<h3>SO BAD!</h3>
{% endif %}
for 标签
{% for %}
允许我们在一个序列上迭代。
与 Python 的 for 语句的情形类似,循环语法是 for X in Y
,Y 是要迭代的序列而 X 是在每一个特定的循环中使用的变量名称。
每一次循环中,模板系统会渲染在 {% for %}
和 {% endfor %}
之间的所有内容。
例如,对于上文的 replace_list 变量,可以使用下面的代码来显示这个列表:
给标签增加一个 reversed 使得该列表被反向迭代。
遍历字典: 可以直接用字典 .items 方法,用变量的解包分别获取键和值。
{% for i,j in views_dict.items %} {{ i }}---{{ j }} {% endfor %}
<ul>
{% for li_txt in replace_list [reversed] %}
<li>{{ li_txt }}</li>
<p>forloop.counter: {{ forloop.counter }}</p>
<p>forloop.counter0: {{ forloop.counter0 }}</p>
<p>forloop.revcounter: {{ forloop.revcounter }}</p>
<p>forloop.revcounter0: {{ forloop.revcounter0 }}</p>
<p>forloop.first: {{ forloop.first }}</p>
<p>forloop.last: {{ forloop.last }}</p>
<hr>
{% endfor %}
</ul>
在 {% for %}
标签里可以通过 {{forloop}}
变量获取循环序号。
- forloop.counter:顺序获取循环序号,从 1 开始计算
- forloop.counter0:顺序获取循环序号,从 0 开始计算
- forloop.revcounter:倒序获取循环序号,结尾序号为 1
- forloop.revcounter0:倒序获取循环序号,结尾序号为 0
- forloop.first(一般配合if标签使用):第一条数据返回 True,其他数据返回 False
- forloop.last(一般配合if标签使用):最后一条数据返回 True,其他数据返回 False
可选的
{% empty %}
从句:在循环为空的时候执行(即 in 后面的参数布尔值为 False ){% for i in listvar %} {{ forloop.counter0 }} {% empty %} 空空如也~ {% endfor %}
ifequal/ifnotequal 标签
{% ifequal %}
标签比较两个值,当他们相等时,显示在 {% ifequal %}
和 {% endifequal %}
之中所有的值。
下面的例子比较两个模板变量 user 和 currentuser :
{% ifequal user currentuser %}
<h1>Welcome!</h1>
{% endifequal %}
和 {% if %}
类似, {% ifequal %}
支持可选的 {% else %}
标签:
{% ifequal section 'sitenews' %}
<h1>Site News</h1>
{% else %}
<h1>No News Here</h1>
{% endifequal %}
注释和include标签
Django 注释使用 {# #}
。
{% include %}
标签允许在模板中包含其它的模板的内容。
下面这个例子包含了 nav.html
模板:
{% include "nav.html" %}
csrf_token
csrf_token 用于form表单中,作用是跨站请求伪造保护。
如果不用 {% csrf_token %}
标签,在用 form 表单时,要再次跳转页面会报 403 权限错误。
用了{% csrf_token %}
标签,在 form 表单提交数据时,才会成功。
首先,向服务器发送请求,获取登录页面,此时中间件 csrf 会自动生成一个隐藏input标签,该标签里的 value 属性的值是一个随机的字符串,用户获取到登录页面的同时也获取到了这个隐藏的input标签。
然后,等用户需要用到form表单提交数据的时候,会携带这个 input 标签一起提交给中间件 csrf,原因是 form 表单提交数据时,会包括所有的 input 标签,中间件 csrf 接收到数据时,会判断,这个随机字符串是不是第一次它发给用户的那个,如果是,则数据提交成功,如果不是,则返回403权限错误。
自定义标签和过滤器
在应用目录下创建 templatetags 目录(与 templates 目录同级,目录名只能是 templatetags)。
在 templatetags 目录下创建文件,如:
my_tags.py
。from django import template register = template.Library() # register的名字是固定的,不可改变
修改
settings.py
文件的 TEMPLATES 选项配置,添加 libraries 配置:TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", "DIRS": [os.path.join(BASE_DIR, "templates")], # 添加模板根目录 "APP_DIRS": True, "OPTIONS": { ... "libraries":{ # ned added "my_tags": "templatetags.my_tags", } }, }, ]
在
my_tags.py
中自定义过滤器和标签。利用装饰器
@register.filter
自定义过滤器。**注意:**装饰器的参数最多只能有 2 个。
@register.filter def my_filter(v1, v2): return v1 + v2
利用装饰器
@register.simple_tag
自定义标签。@register.simple_tag def my_simple_tag(v1, v2, v3): return v1 + v2 + v3
在使用自定义标签和过滤器前,要在 html 文件
<body>
的最上方中导入该 py 文件。<body> {% load my_tags %} ... </body>
在 HTML 中使用自定义过滤器和自定义标签。
<p> Custom filter : </p> {{ 11 | my_filter:22 }} <p> Custom tag : </p> {% my_simple_tag 11 22 33 %}
语义化标签(在
my_tags.py
文件中导入 mark_safe)在定义标签时,用上
mark_safe()
方法,令标签语义化,相当于 jQuery 中的html()
方法。和前端HTML文件中的过滤器 safe 效果一样。
from django.utils.safestring import mark_safe ... @register.simple_tag def my_html(v1, v2): temp_html = "<input type='text' id='%s' class='%s' />" %(v1, v2) return mark_safe(temp_html)
在HTML中使用该自定义标签,在页面中动态创建标签。
{% my_html "zzz" "xxx" %}
配置静态文件
在项目根目录下创建 statics 目录。
在
settings.py
文件的最下方配置添加以下配置:STATIC_URL = '/static/' # 别名 STATICFILES_DIRS = [ os.path.join(BASE_DIR, "statics"), ]
在 statics 目录下创建 css 目录,js 目录,images 目录,plugins 目录, 分别放 css文件,js文件,图片,插件。
把 bootstrap 框架放入插件目录 plugins。
在 HTML 文件的 head 标签中引入 bootstrap。
注意:此时引用路径中的要用配置文件中的别名 static,而不是目录 statics。
<link rel="stylesheet" href="/static/plugins/bootstrap-5.3.3-dist/css/bootstrap.css">
在模板中使用需要加入
{% load static %}
代码,创建新网页文件show_img.html
,从静态目录中引入图片。<!DOCTYPE html> <html lang="en"> {% load static %} <link rel="stylesheet" href="/static/plugins/bootstrap-5.3.3-dist/css/bootstrap.css"> <head> <meta charset="UTF-8"> <title>show IMG</title> </head> <body> {{name}}<img src="{% static 'images/profile.jpg' %}" alt=profile-img"> </body> </html>
views.py
添加:def show_img(request): name = "profile IMG" return render(request, 'show_img.html', {"name": name})
urls.py
添加:urlpatterns = [ ... path("show_img", views.show_img, name="show_img"), ]
模板继承
模板可以用继承的方式来实现复用,减少冗余内容。
网页的头部和尾部内容一般都是一致的,我们就可以通过模板继承来实现复用。
父模板用于放置可重复利用的内容,子模板继承父模板的内容,并放置自己的内容。
父模板
标签 block…endblock:父模板中的预留区域,该区域留给子模板填充差异性的内容,不同预留区域名字不能相同。
{% block 名称 %} 预留给子模板的区域,可以设置设置默认内容 {% endblock 名称 %}
子模板
子模板使用标签 extends 继承父模板:
{% extends "父模板路径"%}
子模板如果没有设置父模板预留区域的内容,则使用在父模板设置的默认内容,当然也可以都不设置,就为空。
子模板设置父模板预留区域的内容:
{ % block 名称 % } 内容 {% endblock 名称 %}
示例(对上面显示图片的 show_img.html
进行重写):
在 templates 目录中添加
base.html
文件,代码如下:<!DOCTYPE html> <html lang="en"> <link rel="stylesheet" href="/static/plugins/bootstrap-5.3.3-dist/css/bootstrap.css"> <head> <meta charset="UTF-8"> <title>show IMG</title> </head> <body> <h1>{{name}}</h1> {% block mainbody %} <p> original text </p> {% endblock %} </body> </html>
以上代码中,名为 mainbody 的 block 标签是可以被继承者们替换掉的部分。
所有的
{% block %}
标签告诉模板引擎,子模板可以重载这些部分。show_img.html
中继承base.html
,并替换特定 block,show_img.html
修改后的代码如下:不需要把 “
base.html
” 写进views.py
和urls.py
文件中{%extends "base.html" %} <!-- 必须放在第一行 --> {% load static %} {% block mainbody %} <p>extended FILE 'base.html'</p> <img src="{% static 'images/profile.jpg' %}" alt=profile-img"> {% endblock %}
Django 模型
Django 对各种数据库提供了很好的支持,包括:PostgreSQL、MySQL、SQLite、Oracle。
Django 为这些数据库提供了统一的调用API。 我们可以根据自己业务需求选择不同的数据库。
MySQL 是 Web 应用中最常用的数据库。本章节以 Mysql 作为实例进行介绍。
安装 mysql 驱动:pip3 install pymysql
Django ORM
Django 模型使用自带的 ORM(对象关系映射,Object Relational Mapping)。
- 用于实现面向对象编程语言里不同类型系统的数据之间的转换。
- ORM 在业务逻辑层和数据库层之间充当了桥梁的作用。
- ORM 是通过使用描述对象和数据库之间的映射的元数据,将程序中的对象自动持久化到数据库中。
使用 ORM 的好处:
- 提高开发效率。
- 不同数据库可以平滑切换。
使用 ORM 的缺点:
- ORM 代码转换为 SQL 语句时,需要花费一定的时间,执行效率会有所降低。
- 长期写 ORM 代码,会降低编写 SQL 语句的能力。
ORM 解析过程:
- 1、ORM 会将 Python 代码转成为 SQL 语句。
- 2、SQL 语句通过 pymysql 传送到数据库服务端。
- 3、在数据库中执行 SQL 语句并将结果返回。
ORM(Models类)—— 数据库(数据表)
对象实例 —— 一条记录
属性 —— 字段
数据库配置
创建 MySQL 数据库( ORM 无法操作到数据库级别,只能操作到数据表)语法:
create database 数据库名称 default charset=utf8; # 防止编码问题,指定为 utf8
也可以在Navicat 等GUI工具中手动创建。
例如:创建一个名为 hello_django 数据库:
在项目的
settings.py
文件中找到 DATABASES 配置项,修改为:DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'hello_django', # 数据库名称 'HOST': '127.0.0.1', 'PORT': 3306, 'USER': 'root', 'PASSWORD': '******', } }
Python2.x 版本这里添加了中文注释,需要在
settings.py
文件头部添加# -\*- coding: UTF-8 -\*-
。在与
settings.py
同级目录下的__init__.py
中引入模块和进行配置,使用 pymysql 模块连接 mysql 数据库:注意:pymysql 版本需要大于等于 1.4.6
pip install --upgrade pymysql -i https://pypi.tuna.tsinghua.edu.cn/simple/
import pymysql pymysql.install_as_MySQLdb()
定义模型
创建 APP
Django 规定,如果要使用模型,必须要创建一个 app。使用以下命令创建一个 TestModel 的 app:
django-admin startapp TestModel
修改
TestModel/models.py
文件,代码如下:from django.db import models class Test(models.Model): name = models.CharField(max_length=20) date = models.DateField()
以上的类名代表了数据库表名,且继承了models.Model,类里面的字段代表数据表中的字段(name),数据类型则由CharField(相当于varchar)、DateField(相当于datetime), max_length 参数限定长度。
接下来在
settings.py
中找到INSTALLED_APPS这一项,如下:INSTALLED_APPS = ( 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'TestModel', # 添加此项 )
在命令行中运行:
需要运行
python .\manage.py migrate
,否则auth_table不会被创建,/admin/
后台无法登录(base) PS D:\Python\py_files\LearnDjango\hello_django> python .\manage.py migrate Operations to perform: Apply all migrations: TestModel, admin, auth, contenttypes, sessions Running migrations: Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying admin.0001_initial... OK Applying admin.0002_logentry_remove_auto_add... OK Applying admin.0003_logentry_add_action_flag_choices... OK Applying contenttypes.0002_remove_content_type_name... OK Applying auth.0002_alter_permission_name_max_length... OK Applying auth.0003_alter_user_email_max_length... OK Applying auth.0004_alter_user_username_opts... OK Applying auth.0005_alter_user_last_login_null... OK Applying auth.0006_require_contenttypes_0002... OK Applying auth.0007_alter_validators_add_error_messages... OK Applying auth.0008_alter_user_username_max_length... OK Applying auth.0009_alter_user_last_name_max_length... OK Applying auth.0010_alter_group_name_max_length... OK Applying auth.0011_update_proxy_permissions... OK Applying auth.0012_alter_user_first_name_max_length... OK Applying sessions.0001_initial... OK
(base) PS D:\Python\py_files\LearnDjango\hello_django> python manage.py makemigrations TestModel Migrations for 'TestModel': TestModel\migrations\0001_initial.py - Create model Test (base) PS D:\Python\py_files\LearnDjango\hello_django> python manage.py migrate TestModel Operations to perform: Apply all migrations: TestModel Running migrations: Applying TestModel.0001_initial... OK
表名组成结构为:应用名_类名(如:TestModel_test)。
注意:尽管我们没有在 models 给表设置主键,但是 Django 会自动添加一个 id 作为主键。
数据库操作
在 hello_django 目录中添加 testdb.py
文件(下面介绍),并修改 urls.py
:
...
from . import views, testdb
urlpatterns = [
...
path("test_add/", testdb.test_add, name="test_add"),
]
添加数据(写在
testdb.py
中)import time from django.http import HttpResponse from TestModel.models import Test def test_add(request): current_time = time.strftime("%Y-%m-%d", time.localtime()) test1 = Test(name='moonjay', date=current_time) test1.save() # Test.objects.create(name="python",data=current_time) return HttpResponse("<p>数据添加成功!</p>")
获取数据
Django提供了多种方式来获取数据库的内容,如下代码(
testdb.py
中添加)所示:def test_get(request): # 初始化 response = "" response1 = "" # 通过objects这个模型管理器的all()获得所有数据行,相当于SQL中的SELECT * FROM listTest = Test.objects.all() # filter相当于SQL中的WHERE,可设置条件过滤结果 response2 = Test.objects.filter(id=1) # Test.objects.filter(pk=1) # pk == primary key # filter() 方法还可以基于双下划线的模糊查询(exclude 同理) Test.objects.filter(pk__in=[1,3]) # __in 用于读取区间,= 号后面为列表(只能使用'='号) Test.objects.filter(pk__gt=2) # 查询pk大于2的数据 # __gte 大于等于;__lt 小于;__lte 小于等于;__range 在 ... 之间,左闭右闭区间 Test.objects.filter(name__contains="moon") # 查询name中包含“moon”的数据 # __icontains 不区分大小写的包含;__startswith 以指定字符开头;__endswith 以指定字符结尾 Test.objects.filter(pub_date__year=2024) # __year 是 DateField 数据类型的年份 # __month 是DateField 数据类型的月份;__day 是DateField 数据类型的天数 # exclude 用于查询不符合条件的数据。 # 返回的是 QuerySet 类型数据,类似于 list,可用索引下标取出模型类的对象。 Test.objects.exclude(id=1) # 获取单个对象 response3 = Test.objects.get(id=1) # 限制返回的数据 相当于 SQL 中的 OFFSET 0 LIMIT 2; Test.objects.order_by('name')[0:2] Test.objects.order_by('-name') # 降序 加‘-’ Test.objects.order_by('-name').reverse() # 降序后再反转(按名字升序) # 数据排序 Test.objects.order_by("id") Test.objects.count() # 查询所有数据的数量 # 上面的方法可以连锁使用 Test.objects.filter(name="moonjay").order_by("id") Test.objects.first() # 返回所有数据的第一条数据 Test.objects.last() # 返回所有数据的最后一条数据 Test.objects.exists() # return bool值,用于判断查询的结果 QuerySet 列表里是否有数据 # 查询所有的id字段和name字段的数据 tests = Test.objects.values("pk","name") # 返回字典(键是字段,值是数据) tests[0]["name"] # 得到第一条记录的name字段的数据 Test.objects.values_list("pk","name") # 返回元组(元组里放的是查询字段对应的数据) # distinct() 方法用于对数据进行去重。返回的是 QuerySet 类型数据。 # 对模型类的对象去重没有意义,因为每个对象都是一个不一样的存在。 # distinct() 一般是联合 values 或者 values_list 使用。 Test.objects.values_list("name").distinct() # 查询一共有多少不重名的人 # 输出所有数据 for var in listTest: response1 += var.name + " " response = response1 return HttpResponse("<p>" + response + "</p>")
修改
urls.py
:urlpatterns = [ ... path("test_add/", testdb.test_add, name="test_add"), path("test_get/", testdb.test_get, name="test_get"), ]
更新数据
(
testdb.py
中添加)修改数据可以使用save()
或update()
:def test_update(request): # 修改其中一个id=1的name字段,再save,相当于SQL中的UPDATE test1 = Test.objects.get(id=1) test1.name = 'moonjay_py' test1.save() # 另外一种方式 #Test.objects.filter(id=1).update(name='Google') # 修改所有的列 # Test.objects.all().update(name='Google') return HttpResponse("<p>修改成功</p>")
同样地,修改
urls.py
:urlpatterns = [ ... path("test_add/", testdb.test_add, name="test_add"), path("test_get/", testdb.test_get, name="test_get"), path("test_update/", testdb.test_update, name="test_update"), ]
删除数据
(
testdb.py
中添加)调用该对象的delete()
方法:def test_del(request): # 删除id=1的数据 Test.objects.get(id=1).delete() # 对象.delete() # Test.objects.filter(id=1).delete() # 类型数据.delete() # 删除所有数据 # Test.objects.all().delete() return HttpResponse("<p>删除成功</p>")
- Django 删除数据时,会模仿 SQL约束 ON DELETE CASCADE 的行为,也就是删除一个对象时也会删除与它相关联的外键对象。
delete()
方法是 QuerySet 数据类型的方法,但并不适用于 Manager 本身。也就是想要删除所有数据,不能不写 all。
books=models.Book.objects.delete() # 报错 books=models.Book.objects.all().delete() # 删除成功
同样地,修改
urls.py
:urlpatterns = [ ... path("test_add/", testdb.test_add, name="test_add"), path("test_get/", testdb.test_get, name="test_get"), path("test_update/", testdb.test_update, name="test_update"), path("test_del/", testdb.test_del, name="test_del"), ]
Django 表单
使用Django对用户提交的表单数据进行处理。
HTTP 请求
HTTP协议以"请求-回复"的方式工作。客户发送请求时,可以在请求中附加数据。服务器通过解析请求,就可以获得客户传来的数据,并根据URL来提供特定的服务。
GET 方法
在之前的项目中创建一个 form.py 文件,用于接收用户的请求:
from django.http import HttpResponse from django.shortcuts import render # 表单 def search_form(request): return render(request, 'search_form.html') # 接收请求数据 def search(request): request.encoding='utf-8' if 'q' in request.GET and request.GET['q']: message = '你搜索的内容为: ' + request.GET['q'] else: message = '你提交了空表单' return HttpResponse(message)
在 templates 目录中添加
search_form.html
:<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>search form</title> </head> <body> <form action="/search/" method="get"> <!-- 注意这里跳转地址 --> <input type="text" name="q"> <input type="submit" value="搜索"> </form> </body> </html>
urls.py
规则添加路由地址:from django.conf.urls import url from . import views,testdb,form urlpatterns = [ ... url('search-form/', form.search_form), url('search/', form.search), ]
POST 方法
上面我们使用了 GET 方法,视图显示和请求处理分成两个函数处理。
提交数据时更常用 POST 方法,并用一个URL和处理函数,同时显示视图和处理请求。
在 templates 创建
post_form.html
:<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>post form</title> </head> <body> <form action="/post_form/" method="post"> <!-- 路由地址须以'/'结尾 --> {% csrf_token %} <input type="text" name="q"> <input type="submit" value="提交"> </form> <p>{{ rlt }}</p> </body> </html>
在模板的末尾,增加一个 rlt 记号,为表格处理结果预留位置。
表格后面还有一个
{% csrf_token %}
的标签。(csrf,Cross Site Request Forgery)。是 Django 提供的防止伪装提交请求的功能。POST 方法提交的表格,必须有此标签。修改 form.py 文件并使用 post_form 函数来处理 POST 请求:
from django.shortcuts import render from django.views.decorators import csrf def post_form(request): ctx ={} if request.POST: ctx['rlt'] = request.POST['q'] return render(request, "post_form.html", ctx)
urls.py
添加路由:urlpatterns = [ ... url('post_form/', form.post_form), ]
Request 对象
每个视图(如views.py
或者上面提到的form.py
)函数的第一个参数是一个 HttpRequest 对象。
# views.py
from django.http import HttpResponse
def hello(request):
return HttpResponse("Hello world !")
HttpRequest对象包含当前请求URL的一些信息:
属性 | 描述 |
---|---|
path | 请求页面的全路径,不包括域名。例如:"/hello/" 。 |
method | 请求中使用的HTTP方法的字符串表示。全大写表示。例如:if request.method == 'POST': do_something() |
GET | 包含所有HTTP GET参数的类字典对象。参见QueryDict 文档。 |
POST | 包含所有HTTP POST参数的类字典对象。参见QueryDict 文档。服务器收到空的POST请求的情况也是有可能发生的。也就是说,表单form通过HTTP POST方法提交请求,但是表单中可以没有数据。 因此,不能使用语句 if request.POST 来判断是否使用HTTP POST方法;应该使用 if request.method == "POST" (参见本表的method属性)。注意: POST不包括file-upload信息。参见FILES属性。 |
REQUEST | 为了方便,该属性是POST和GET属性的集合体,但是有特殊性,先查找POST属性,然后再查找GET属性。借鉴PHP’s $_REQUEST。 例如,如果 GET = {"name": "john"} 和 POST = {"age": '34'} ,则 REQUEST["name"] 的值是"john", REQUEST["age"] 的值是"34"。强烈建议使用GET and POST,因为这两个属性更加显式化,写出的代码也更易理解。 |
COOKIES | 包含所有cookies的标准Python字典对象。Keys和values都是字符串。 |
FILES | 包含所有上传文件的类字典对象。FILES中的每个Key都是<input type="file" name="" /> 标签中name属性的值。FILES中的每个value 同时也是一个标准Python字典对象,包含下面三个Keys:① filename: 上传文件名,用Python字符串表示 ② content-type: 上传文件的Content type ③ content: 上传文件的原始内容 注意:只有在请求方法是POST,并且请求页面中 <form> 有enctype="multipart/form-data" 属性时FILES才拥有数据。否则,FILES 是一个空字典。 |
META | 包含所有可用HTTP头部信息的字典。 例如: CONTENT_LENGTH CONTENT_TYPE QUERY_STRING: 未解析的原始查询字符串 REMOTE_ADDR: 客户端IP地址 REMOTE_HOST: 客户端主机名 SERVER_NAME: 服务器主机名 SERVER_PORT: 服务器端口 META 中这些头加上前缀 HTTP_ 为Key,冒号(:)后面的为 Value, 例如: HTTP_ACCEPT_ENCODING HTTP_ACCEPT_LANGUAGE HTTP_HOST: 客户发送的HTTP主机头信息 HTTP_REFERER: referring页 HTTP_USER_AGENT: 客户端的user-agent字符串 HTTP_X_BENDER: X-Bender头信息 |
user | 是一个django.contrib.auth.models.User 对象,代表当前登录的用户。如果访问用户当前没有登录,user将被初始化为django.contrib.auth.models.AnonymousUser 的实例。可以通过user的 is_authenticated() 方法来辨别用户是否登录:if request.user.is_authenticated(): # Do something for logged-in users. else: # Do something for anonymous users. 只有激活Django中的AuthenticationMiddleware时该属性才可用 |
session | 唯一可读写的属性,代表当前会话的字典对象。只有激活Django中的session支持时该属性才可用。 |
raw_post_data | 原始HTTP POST数据,未解析过。 高级处理时会有用处。 |
Request对象也有一些有用的方法:
方法 | 描述 |
---|---|
__getitem__(key) |
返回GET/POST的键值,先取POST,后取GET。 如果键不存在抛出 KeyError。 此时可以使用字典语法访问HttpRequest对象。 例如: request["foo"] 等同于先request.POST["foo"] 然后request.GET["foo"] 的操作。 |
has_key() | 检查request.GET or request.POST 中是否包含参数指定的Key。 |
get_full_path() | 返回包含查询字符串的请求路径。例如, “/music/bands/the_beatles/?print=true” |
is_secure() | 如果请求是安全的,返回True,就是说,发出的是HTTPS请求。 |
QueryDict对象
在HttpRequest对象中, GET和POST属性是django.http.QueryDict
类的实例。
QueryDict类似字典的自定义类,用来处理单键对应多值的情况。
QueryDict实现所有标准的词典方法。还包括一些特有的方法:
方法 | 描述 |
---|---|
__getitem__ |
和标准字典的处理有一点不同。如果Key对应多个Value,__getitem__() 返回最后一个value。 |
__setitem__ |
设置参数指定key的value列表(一个Python list)。注意:它只能在一个mutable QueryDict 对象上被调用(就是通过copy()产生的一个QueryDict对象的拷贝) |
get() | 如果key对应多个value,get()返回最后一个value。 |
update() | 参数可以是QueryDict,也可以是标准字典。和标准字典的update方法不同,该方法添加字典 items,而不是替换它们:>>> q = QueryDict('a=1') >>> q = q.copy() # to make it mutable >>> q.update({'a': '2'}) >>> q.getlist('a') ['1', '2'] >>> q['a'] # returns the last ['2'] |
items() | 和标准字典的items()方法有一点不同,该方法使用单值逻辑的__getitem__() :>>> q = QueryDict('a=1&a=2&a=3') >>> q.items() [('a', '3')] |
values() | 和标准字典的values()方法有一点不同,该方法使用单值逻辑的__getitem__() |
此外, QueryDict也有一些方法,如下表:
方法 | 描述 |
---|---|
copy() | 返回对象的拷贝,内部实现是用Python标准库的copy.deepcopy() 。该拷贝是mutable(可更改的)。 |
getlist(key) | 返回和参数key对应的所有值,作为一个Python list返回。如果key不存在,则返回空list。 It’s guaranteed to return a list of some sort. |
setlist(key,list_) | 设置key的值为list_ (unlike __setitem__() ). |
appendlist(key,item) | 添加item到和key关联的内部list. |
setlistdefault(key,list) | 和setdefault有一点不同,它接受list而不是单个value作为参数。 |
lists() | 和items()有一点不同, 它会返回key的所有值,作为一个list, 例如:>>> q = QueryDict('a=1&a=2&a=3') >>> q.lists() [('a', ['1', '2', '3'])] |
urlencode() | 返回一个以查询字符串格式进行格式化后的字符串。 |
Django 视图
一个视图函数,简称视图,是一个简单的 Python 函数,它接受 Web 请求并且返回 Web 响应。
响应可以是一个 HTML 页面、一个 404 错误页面、重定向页面、XML 文档、或者一张图片…
无论视图本身包含什么逻辑,都要返回响应。
代码一般放在项目的 views.py 文件中。也可以新建py文件进行分类管理。
每个视图函数都负责返回一个 HttpResponse 对象,对象中包含生成的响应。
视图层中有两个重要的对象:
- 请求对象(request)
- 响应对象(HttpResponse)
HttpRequest 对象(request)
以下介绍几个常用的 request 属性。
GET
数据类型是 QueryDict,一个类似于字典的对象,包含 HTTP GET 的所有参数。
有相同的键,就把所有的值放到对应的列表里。
取值格式:对象.方法。
get()
:返回字符串,如果该键对应有多个值,取出该键的最后一个值。def get_object(request): name = request.GET.get("name") return HttpResponse('姓名:{}'.format(name))
POST
数据类型是 QueryDict,一个类似于字典的对象,包含 HTTP POST 的所有参数。
常用于 form 表单,form 表单里的标签 name 属性对应参数的键,value 属性对应参数的值。
取值格式: 对象.方法。
get()
:返回字符串,如果该键对应有多个值,取出该键的最后一个值。def post_object(request): name = request.POST.get("name") return HttpResponse('姓名:{}'.format(name))
post 请求返回 403
1. 解决:
导入模块:
from django.views.decorators.csrf import csrf_exempt
在函数前面添加修饰器:
@csrf_exempt
2. 原因:
当采用客户端象 django 的服务器提交 post 请求时,会得到403,权限异常。
因为 django 针对提交的请求,有校验。所以会如此。客户端提交的 post 如果不加这段,会出现 403 error。
@csrf_exempt def post_object(request): name = request.POST.get("name") ...
body
数据类型是二进制字节流,是原生请求体里的参数内容,在 HTTP 中用于 POST,因为 GET 没有请求体。
在 HTTP 中不常用,而在处理非 HTTP 形式的报文时非常有用,例如:二进制图片、XML、Json 等。
def body_object(request): name = request.body print(name) # 输出结果为 b'----...' 格式的二进制编码 return HttpResponse("hello")
path
获取 URL 中的路径部分,数据类型是字符串。
def path_object(request): name = request.path print(name) # /路由名/ return HttpResponse("hello")
method
获取当前请求的方式,数据类型是字符串,且结果为大写。
def method_object(request): name = request.method print(name) # POST return HttpResponse("hello")
HttpResponse 对象
响应对象主要有三种形式:HttpResponse()
、render()
、redirect()
。
HttpResponse()
:返回文本,参数为字符串,字符串中写文本内容。如果参数为字符串里含有 html 标签,也可以渲染。render()
:返回文本,第一个参数为 request,第二个参数为字符串(页面名称),第三个参数为字典(可选参数,向页面传递的参数:键为页面参数名,值为views参数名)。redirect()
:重定向,跳转新页面。参数为字符串,字符串中填写页面路径。一般用于 form 表单提交后,跳转到新页面。
render(底层返回的是 HttpResponse 对象) 和 redirect(底层继承的是 HttpResponse 对象) 是在 HttpResponse 的基础上进行了封装。
Django 路由
路由是根据用户请求的 URL 链接来判断对应的处理程序,并返回处理结果,也就是 URL 与 Django 的视图建立映射关系。
Django 路由在 urls.py
配置,urls.py
中的每一条配置对应相应的处理方法。
Django 不同版本 urls.py 配置有点不一样:
Django1.1.x 版本
url() 方法:普通路径和正则路径均可使用,需要自己手动添加正则首位限制符号。
from django.conf.urls import url # 用 url 需要引入 urlpatterns = [ url(r'^admin/$', admin.site.urls), url(r'^index/$', views.index), # 普通路径 url(r'^articles/([0-9]{4})/$', views.articles), # 正则路径 ]
Django 2.2.x 之后的版本
- path:用于普通路径,不需要自己手动添加正则首位限制符号,底层已经添加。
- re_path:用于正则路径,需要自己手动添加正则首位限制符号。
from django.urls import re_path # 用re_path 需要引入 urlpatterns = [ path('admin/', admin.site.urls), path('index/', views.index), # 普通路径 re_path(r'^articles/([0-9]{4})/$', views.articles), # 正则路径 ]
正则路径中的分组
无名分组
无名分组按位置传参,一一对应。
views 中除了 request,其他形参的数量要与 urls 中的分组数量一致。
urls.py
urlpatterns = [ path('admin/', admin.site.urls), re_path("^index/([0-9]{4})/$", views.index), ]
views.py
from django.shortcuts import HttpResponse def index(request, year): print(year) # 一个形参代表路径中一个分组的内容,按顺序匹配 return HttpResponse('hello')
有名分组
语法:
(?P<组名>正则表达式)
有名分组按关键字传参,与位置顺序无关。
views 中除了 request,其他形参的数量要与 urls 中的分组数量一致, 并且 views 中的形参名称要与 urls 中的组名对应。
urls.py
urlpatterns = [ path('admin/', admin.site.urls), re_path("^index/(?P[0-9]{4})/(?P[0-9]{2})/$", views.index), ]
views.py
from django.shortcuts import HttpResponse def index(request, year, month): print(year,month) # 一个形参代表路径中一个分组的内容,按关键字对应匹配 return HttpResponse('hello')
路由分发(include)
存在问题:Django 项目里多个app目录共用一个 urls 容易造成混淆,后期维护也不方便。
解决:使用路由分发(include),让每个app目录都单独拥有自己的 urls。
步骤:
1、在每个 app 目录里都创建一个 urls.py 文件。
2、在项目名称目录下的 urls 文件里,统一将路径分发给各个 app 目录。
from django.contrib import admin from django.urls import path,include # 从 django.urls 引入 include urlpatterns = [ path('admin/', admin.site.urls), path("app01/", include("app01.urls")), # 普通路由分发 path("app02/", include("app02.urls")), ]
3、在各自 app 目录下,编写
urls.py
文件,进行路径跳转。app01 目录:
from django.urls import path,re_path from app01 import views # 从自己的 app 目录引入 views urlpatterns = [ re_path(r'^login/(?P<m>[0-9]{2})/$', views.index, ), ]
app02 目录:
from django.urls import path,re_path from app02 import views # 从自己的 app 目录引入views urlpatterns = [ re_path("^xxx/(?P[0-9]{4})/$", views.xxx), ]
4、在各自 app 目录下的
views.py
文件中写各自的视图函数。
反向解析
随着功能的增加,路由层的 url 发生变化,就需要去更改对应的视图层和模板层的 url,非常麻烦,不便维护。
这时可以利用反向解析:当路由层 url 发生改变,在视图层和模板层动态反向解析出更改后的 url,免去修改的操作。
反向解析一般用在模板中的超链接及视图中的重定向。
普通路径
在
urls.py
中给路由起别名,name=“路由别名”。path("login1/", views.login, name="login")
在
views.py
中,从django.urls
中引入 reverse,利用 reverse(“路由别名”) 反向解析:return redirect(reverse("login"))
在模板 templates 中的 HTML 文件中,利用 {% url “路由别名” %} 反向解析。
<form action="{% url 'login' %}" method="post">
正则路径(无名分组)
在
urls.py
中给路由起别名,name=“路由别名”。re_path(r"^login/([0-9]{2})/$", views.login, name="login")
在
views.py
中,利用 reverse(“路由别名”,args=(符合正则匹配的参数,)) 反向解析。return redirect(reverse("login",args=(10,)))
在模板 templates 中的 HTML 文件中利用 {% url “路由别名” 符合正则匹配的参数 %} 反向解析。
<form action="{% url 'login' 10 %}" method="post">
正则路径(有名分组)
在
urls.py
中给路由起别名,name=“路由别名”。re_path(r"^login/(?P<year>[0-9]{4})/$", views.login, name="login")
在
views.py
中,利用 reverse(“路由别名”,kwargs={“分组名”:符合正则匹配的参数}) 反向解析。return redirect(reverse("login",kwargs={"year":3333}))
在模板 templates 中的 HTML 文件中,利用 {% url “路由别名” 分组名=符合正则匹配的参数 %} 反向解析。
<form action="{% url 'login' year=3333 %}" method="post">
命名空间
命名空间(Namespace)是表示标识符的可见范围。
一个标识符可在多个命名空间中定义,它在不同命名空间中的含义是互不相干的。
一个新的命名空间中可定义任何标识符,它们不会与任何重复的标识符发生冲突,因为重复的定义都处于其它命名空间中。
存在问题:路由别名 name 没有作用域,Django 在反向解析 URL 时,会在项目全局顺序搜索,当查找到第一个路由别名 name 指定 URL 时,立即返回。当在不同的 app 目录下的urls 中定义相同的路由别名 name 时,可能会导致 URL 反向解析错误。
解决:使用命名空间。
普通路径
定义命名空间(include 里面是一个元组)格式如下:
include(("app名称.urls", "app名称"))
例如,对于上文包含app01和app02的项目:
path("app01/", include(("app01.urls","app01"))) path("app02/", include(("app02.urls","app02")))
在
app01/urls.py
中起相同的路由别名。path("login/", views.login, name="login")
在
views.py
中使用名称空间,语法格式如下:return redirect(reverse("app01:login") # reverse("app名称:路由别名")
在 templates 模板的 HTML 文件中使用名称空间,语法格式如下:
{% url "app名称:路由别名" %}
<form action="{% url 'app01:login' %}" method="post">
注:其它路径同上
Django Admin
Django 提供了基于 web 的管理工具。
django.contrib
是一套庞大的功能集,它是Django基本代码的组成部分。
激活管理工具
通常我们在生成项目时会在 urls.py 中自动设置好,我们只需去掉注释即可。
配置项如下所示(
urls.py
):from django.contrib import admin from django.urls import path urlpatterns = [ path("admin/", admin.site.urls), ... ]
通过命令 python manage.py createsuperuser 来创建超级用户,如下所示:
# python manage.py createsuperuser Username (leave blank to use 'root'): admin Email address: admin@runoob.com Password: Password (again): Superuser created successfully.
为了让 admin 界面管理某个数据模型,我们需要先注册该数据模型到 admin。比如,我们之前在 TestModel 中已经创建了模型 Test 。修改 TestModel/admin.py
:
from django.contrib import admin
from TestModel.models import Test
# Register your models here.
admin.site.register(Test)
刷新后即可看到 Testmodel 数据表(访问 http://localhost:8000/admin )
复杂模型
管理页面的功能强大,完全有能力处理更加复杂的数据模型。
先在 TestModel/models.py
中增加一个更复杂的数据模型:
from django.db import models
# Create your models here.
class Test(models.Model):
name = models.CharField(max_length=20)
class Contact(models.Model):
name = models.CharField(max_length=200)
age = models.IntegerField(default=0)
email = models.EmailField()
def __unicode__(self):
return self.name
class Tag(models.Model):
contact = models.ForeignKey(Contact, on_delete=models.CASCADE,)
name = models.CharField(max_length=50)
def __unicode__(self):
return self.name
Tag 以 Contact 为外部键。一个 Contact 可以对应多个 Tag。
还可以看到许多在之前没有见过的属性类型,比如 IntegerField 用于存储整数。
在 TestModel/admin.py
注册多个模型并显示:
from django.contrib import admin
from TestModel.models import Test,Contact,Tag
# Register your models here.
admin.site.register([Test, Contact, Tag])
再次运行创建表结构命令
$ python manage.py makemigrations TestModel # 让 Django 知道我们在我们的模型有一些变更 $ python manage.py migrate TestModel # 创建表结构
自定义表单
可以自定义管理页面,来取代默认的页面。比如上面 Contacts的"add" 页面。想只显示 name 和 email 部分。修改 TestModel/admin.py
:
from django.contrib import admin
from TestModel.models import Test, Contact, Tag
# Register your models here.
class ContactAdmin(admin.ModelAdmin):
fields = ('name', 'email')
admin.site.register(Contact, ContactAdmin)
admin.site.register([Test, Tag])
![]() |
![]() |
---|
还可以将输入栏分块,每个栏也可以定义自己的格式。
Contact 是 Tag 的外部键,所以有外部参考的关系。而在默认的页面显示中,将两者分离开来,无法体现出两者的从属关系。可以使用内联显示,让 Tag 附加在 Contact 的编辑页面上显示。
修改 TestModel/admin.py
为:
from django.contrib import admin
from TestModel.models import Test,Contact,Tag
# Register your models here.
class TagInline(admin.TabularInline):
model = Tag
class ContactAdmin(admin.ModelAdmin):
inlines = [TagInline] # Inline
fieldsets = (
['Main',{
'fields':('name','email'),
}],
['Advance',{
'classes': ('collapse',),
'fields': ('age',),
}]
)
admin.site.register(Contact, ContactAdmin)
admin.site.register([Test])
列表页的显示
自定义,比如在列表中显示更多的栏目,只需要在 ContactAdmin 中增加 list_display 属性:
...
class ContactAdmin(admin.ModelAdmin):
list_display = ('name','age', 'email') # list
...
...
搜索功能在管理大量记录时非常有用,可以使用 search_fields 为该列表页增加搜索栏:
...
class ContactAdmin(admin.ModelAdmin):
list_display = ('name','age', 'email') # list
search_fields = ('name',)
...
...
Django ORM - 多表实例
表与表之间的关系可分为以下三种:
- 一对一:一个人对应一个身份证号码,数据字段设置 unique。
- 一对多:一个家庭有多个人,一般通过外键来实现。
- 多对多:一个学生有多门课程,一个课程有很多学生,一般通过第三个表来实现关联(涉及范式,拆解为一对多和多对一的关系)。
表结构
- 书籍表 Book:title 、 price 、 pub_date 、 publish(外键,多对一) 、 authors(多对多)
- 出版社表 Publish:name 、 city 、 email
- 作者表 Author:name 、 age 、 au_detail(一对一)
- 作者详情表 AuthorDetail:gender 、 tel 、 addr 、 birthday
创建模型
创建APP
(base) PS D:\Python\py_files\LearnDjango> cd .\hello_django\ (base) PS D:\Python\py_files\LearnDjango\hello_django> django-admin startapp BookModel
在项目中的 models.py
中添加以下类:
class Book(models.Model):
id = models.AutoField(primary_key=True) # id 会自动创建,可以手动写入
title = models.CharField(max_length=32) # 书籍名称
price = models.DecimalField(max_digits=5, decimal_places=2) # 书籍价格
publish = models.ForeignKey("Publish", on_delete=models.CASCADE) # 出版社名称
pub_date = models.DateField() # 出版时间
authors = models.ManyToManyField("Author") # 会自动生成 book_authors 表
class Publish(models.Model):
name = models.CharField(max_length=32)
city = models.CharField(max_length=64)
email = models.EmailField()
class Author(models.Model):
name = models.CharField(max_length=32)
age = models.SmallIntegerField()
au_detail = models.OneToOneField("AuthorDetail", on_delete=models.CASCADE)
class AuthorDetail(models.Model):
gender_choices = (
(0, "女"),
(1, "男"),
(2, "保密"),
)
gender = models.SmallIntegerField(choices=gender_choices)
tel = models.CharField(max_length=32)
addr = models.CharField(max_length=64)
birthday = models.DateField()
说明:
- 1、EmailField 数据类型是邮箱格式,底层继承 CharField,进行了封装,相当于 MySQL 中的 varchar。
- 2、Django1.1 版本不需要联级删除:
on_delete=models.CASCADE
,Django2.2 需要。- 3、一般不需要设置联级更新。
- 4、外键在一对多的多中设置:
models.ForeignKey("关联类名", on_delete=models.CASCADE)
。- 5、
OneToOneField = ForeignKey(...,unique=True)
设置一对一。- 6、若有模型类存在外键,创建数据时,要先创建外键关联的模型类的数据,不然创建包含外键的模型类的数据时,外键的关联模型类的数据会找不到。
settings.py
中找到INSTALLED_APPS这一项,如下:
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'TestModel', # 添加此项
'BookModel', # 添加此项
)
执行以下命令:
$ python manage.py makemigrations BookModel # 让 Django 知道我们在我们的模型有一些变更
Migrations for 'BookModel':
BookModel\migrations\0001_initial.py
- Create model AuthorDetail
- Create model Book
- Create model Publish
- Create model Author
- Add field authors to book
$ python manage.py migrate BookModel # 创建表结构
Operations to perform:
Apply all migrations: BookModel
Running migrations:
Applying BookModel.0001_initial... OK
在 BookModel/admin.py
注册多个模型并显示:
from django.contrib import admin
from BookModel.models import Book, Publish, Author, AuthorDetail
# Register your models here.
admin.site.register([Book, Publish, Author, AuthorDetail])
插入数据
在 MySQL 中执行以下 SQL 插入操作:
insert into bookmodel_publish(name,city,email) values ("华山出版社", "华山", "hs@163.com"), ("明教出版社", "黑木崖", "mj@163.com")
# 先插入 authordetail 表中多数据
insert into bookmodel_authordetail(gender,tel,addr,birthday) values (1,13432335433,"华山","1994-5-23"), (1,13943454554,"黑木崖","1961-8-13"), (0,13878934322,"黑木崖","1996-5-20")
# 再将数据插入 author,这样 author 才能找到 authordetail
insert into bookmodel_author(name,age,au_detail_id) values ("令狐冲",25,1), ("任我行",58,2), ("任盈盈",23,3)
ORM - 添加
一对多(ForeignKey)
传对象 id 的形式(由于传过来的数据一般是 id,所以传对象 id 是常用的)。
一对多中,设置外键属性的类(多的表)中,MySQL 中显示的字段名是:外键属性名_id。
返回值的数据类型是对象,书籍对象。
步骤:
- a. 获取出版社对象的 id
- b. 给书籍的关联出版社字段 pulish_id 传出版社对象的 id
BookModel/views.py
from django.shortcuts import render
from BookModel import models
from django.http import HttpResponse
# Create your views here.
def add_book(request):
# 获取出版社对象
pub_obj = models.Publish.objects.filter(pk=1).first()
# 给书籍的出版社属性publish传出版社对象
book = models.Book.objects.create(title="Python", price=200, pub_date="2024-7-13", publish=pub_obj.id) # 等价于使用 pub_obj.pk
return HttpResponse("<p>" + str(book) + ", 数据添加成功!</p>")
hello_django/urls.py
中添加
from BookModel import views as book_views
urlpatterns = [
...
path("add_book/", book_views.add_book, name="add_book"),
]
多对多(ManyToManyField)
在第三张关系表中新增数据
步骤:
- a. 获取作者(Author)对象
- b. 获取书籍(Book)对象
- c. 向“第三张表”中添加(本例是添加author_id和book_id)
views.py
添加代码:
def add_b2a(request):
# 获取作者对象
chong = models.Author.objects.filter(name="令狐冲").first()
ying = models.Author.objects.filter(name="任盈盈").first()
# 获取书籍对象
book = models.Book.objects.filter(title="冲灵剑法").first()
# 给书籍对象的 authors 属性用 add 方法传作者对象
book.authors.add(chong, ying)
return HttpResponse("<p>成功添加</p>")
按照常规方式(不正确):
需要先在models添加class Book2Author
class Book2Author(models.Model): # 这里变量不要命名为author_id,因为设置ForeignKey会在生成表时默认变为author_id author = models.ForeignKey(Author, on_delete=models.CASCADE) book = models.ForeignKey(Book, on_delete=models.CASCADE)
然后使用如下代码添加:models.Book2Author.objects.create(author_id=chong.pk, book_id=book.pk) models.Book2Author.objects.create(author_id=ying.pk, book_id=book.pk)
hello_django/urls.py
中添加
from BookModel import views as book_views
urlpatterns = [
...
path("add_book2author/", book_views.add_b2a, name="add_b2a"),
]
数据库添加结果:
关联管理器(对象调用)
前提:
- 多对多(双向均有关联管理器)
- 一对多(只有多的那个类的对象有关联管理器,即反向才有)
语法格式:
正向:属性名
反向:小写类名加 _set
注意:一对多只能反向
常用方法(add):用于多对多,把指定的模型对象添加到关联对象集(关系表)中。
注意:add() 在一对多(即外键)中,只能传对象(QuerySet数据类型),不能传 id(
*[id表]
)。
*[ ]
的使用:
# 方式一:传对象
book_obj = models.Book.objects.get(id=10)
author_list = models.Author.objects.filter(id__gt=2)
book_obj.authors.add(*author_list) # 将 id 大于2的作者对象添加到这本书的作者集合中
# 方式二:传对象 id
book_obj.authors.add(*[1,3]) # 将 id=1 和 id=3 的作者对象添加到这本书的作者集合中
return HttpResponse("ok")
反向:小写表名_set【出现bug:‘Author’ object has no attribute ‘book_set’】
def association_set(request):
ying = models.Author.objects.filter(name="任盈盈").first()
book = models.Book.objects.filter(title="冲灵剑法").first()
ying.book_set.add(book) # 影响 book_authors 表
return HttpResponse("ok")
create():创建一个新的对象,并同时将它添加到关联对象集之中。返回新创建的对象。
pub = models.Publish.objects.filter(name="明教出版社").first()
wo = models.Author.objects.filter(name="任我行").first()
book = wo.book_set.create(title="吸星大法", price=300, pub_date="1999-9-19", publish=pub.pk)
# 会影响book表和book2author表
print(book, type(book))
return HttpResponse("ok")
remove():从关联对象集中移除执行的模型对象。
对于 ForeignKey 对象,这个方法仅在 null=True
(可以为空)时存在,无返回值。
author_obj =models.Author.objects.get(id=1)
book_obj = models.Book.objects.get(id=11)
author_obj.book_set.remove(book_obj)
return HttpResponse("ok")
clear():从关联对象集中移除一切对象,删除关联,不会删除对象。
对于 ForeignKey 对象,这个方法仅在 null=True
(可以为空)时存在。无返回值。
# 清空"独孤九剑"关联的所有作者
book = models.Book.objects.filter(title="独孤九剑").first()
book.authors.clear()
ORM - 查询
基于对象的跨表查询。
正向:属性名称
反向:小写类名_set
一对多
查询主键为 1 的书籍的出版社所在的城市(正向)。
BoolModel/views.py
def one2many_find(request):
book = models.Book.objects.filter(pk=1).first()
res = book.publish.city
print(res, type(res))
return HttpResponse(res)
hello_django/urls.py
添加path("find1/", book_views.one2many_find, name="one2many_find"),
查询明教出版社出版的书籍名(反向)。
反向:对象.小写类名_set 可以跳转到关联的表。
pub = models.Publish.objects.filter(name="明教出版社").first()
res = pub.book_set.all()
for i in res:
print(i.title)
return HttpResponse("ok")
pub.book_set.all():取出书籍表的所有书籍对象,在一个 QuerySet 里,遍历取出一个个书籍对象。
一对一
查询令狐冲的电话(正向)
正向:对象.属性 可以跳转到关联的表
BoolModel/views.py
def one2one_find(request):
author = models.Author.objects.filter(name="令狐冲").first()
res = author.au_detail.tel
print(res, type(res))
return HttpResponse(res)
hello_django/urls.py
添加path("find2/", book_views.one2one_find, name="one2one_find"),
查询所有住址在"黑木崖"的作者的姓名(反向)。
一对一的反向,用 对象.小写类名 即可,不用加 _set。
addr = models.AuthorDetail.objects.filter(addr="黑木崖").first()
res = addr.author.name
print(res, type(res))
return HttpResponse("ok")
多对多
"吸星大法"所有作者的名字以及手机号(正向)——book.authors
。
正向:对象.属性可以跳转到关联的表。
作者表里没有作者电话,因此再次通过**对象.属性(i.au_detail)**跳转到关联的表(作者详情表)。
def many2many_find(request):
book = models.Book.objects.filter(title="吸星大法").first()
res = book.authors.all()
for i in res:
print(i.name, i.au_detail.tel)
return HttpResponse("ok")
还需要对
urls.py
进行修改
查询"任我行"出过的所有书籍的名字(反向)。
author = models.Author.objects.filter(name="任我行").first()
res = author.book_set.all()
for i in res:
print(i.title)
return HttpResponse("ok")
基于双下划线的跨表查询
正向:属性名称__跨表的属性名称
反向:小写类名__跨表的属性名称
一对多
查询“华山出版社”出版过的所有书籍的名字与价格。(正向)
BookModel/views.py
def cross_table_find(request):
res = models.Book.objects.filter(publish__name="华山出版社").values_list("title", "price")
return HttpResponse(res)
hello_django/urls.py
添加:path("find4/", book_views.cross_table_find),
反向:(book__title,book__price
)跨表获取数据。
res = models.Publish.objects.filter(name="菜鸟出版社").values_list("book__title","book__price")
return HttpResponse(res)
多对多
查询"任我行"出过的所有书籍的名字。
正向:(authors__name
) 跨表获取数据:
res = models.Book.objects.filter(authors__name="任我行").values_list("title")
反向:(book__title
) 跨表获取数据:
res = models.Author.objects.filter(name="任我行").values_list("book__title")
一对一
查询任我行的手机号。
正向:(au_detail__tel
) 跨表获取数据。
res = models.Author.objects.filter(name="任我行").values_list("au_detail__tel")
反向:(author__name
)跨表获取数据。
res = models.AuthorDetail.objects.filter(author__name="任我行").values_list("tel")
Django ORM – 聚合与分组查询
聚合查询(aggregate)
聚合查询函数是对一组值执行计算,并返回单个值。
Django 使用聚合查询前要先从 django.db.models
引入 Avg、Max、Min、Count、Sum(首字母大写)。
from django.db.models import Avg,Max,Min,Count,Sum
聚合查询返回值的数据类型是字典。
聚合函数 aggregate()
是 QuerySet 的一个终止子句, 生成的一个汇总值,相当于 count()
。
使用 aggregate()
后,数据类型就变为字典,不能再使用 QuerySet 数据类型的一些 API 了。
日期数据类型(DateField)可以用 Max 和 Min。
返回的字典中:键的名称默认是(属性名称加上__聚合函数名
),值是计算出来的聚合值。
如果要自定义返回字典的键的名称,可以起别名:aggregate(别名 = 聚合函数名("属性名称"))
计算所有图书的平均价格:
from django.db.models import Avg, Max, Min, Count, Sum
def use_aggregate(request):
res = models.Book.objects.aggregate(Avg("price"))
# print(res) # {'price__avg': Decimal('250.000000')}
return HttpResponse(res["price__avg"]) # 250.000000
urls.py
添加path("use_aggregate/", book_views.use_aggregate),
计算所有图书的数量、最贵价格和最便宜价格:
res=models.Book.objects.aggregate(c=Count("id"),max=Max("price"),min=Min("price"))
print(res,type(res)
分组查询(annotate)
返回值:
- 分组后,用 values 取值,则返回值是 QuerySet 数据类型里面为一个个字典;
- 分组后,用 values_list 取值,则返回值是 QuerySet 数据类型里面为一个个元组。
MySQL 中的 limit 相当于 ORM 中的 QuerySet 数据类型的切片。
注意:
annotate 里面放聚合函数。
- values 或者 values_list 放在 annotate 前面:values 或者 values_list 是声明以什么字段分组,annotate 执行分组。
- values 或者 values_list 放在annotate后面:annotate 表示直接以当前表的pk执行分组,values 或者 values_list 表示查询哪些字段, 并且要将 annotate 里的聚合函数起别名,在 values 或者 values_list 里写其别名。
准备数据和创建模型
BookModel/models.py
class Emp(models.Model):
name = models.CharField(max_length=32)
age = models.IntegerField()
salary = models.DecimalField(max_digits=8, decimal_places=2)
dep = models.CharField(max_length=32)
province = models.CharField(max_length=32)
class Emps(models.Model):
name = models.CharField(max_length=32)
age = models.IntegerField()
salary = models.DecimalField(max_digits=8, decimal_places=2)
dep = models.ForeignKey("Dep", on_delete=models.CASCADE)
province = models.CharField(max_length=32)
class Dep(models.Model):
title = models.CharField(max_length=32)
在 MySQL 命令行中执行:
INSERT INTO `bookmodel_emp` (`id`, `name`, `age`, `salary`, `dep`, `province`) VALUES ('1', '令狐冲', '24', '6000.00', '销售部', '河南');
INSERT INTO `bookmodel_emp` (`id`, `name`, `age`, `salary`, `dep`, `province`) VALUES ('2', '任盈盈', '18', '8000.00', '关公部', '广东');
INSERT INTO `bookmodel_emp` (`id`, `name`, `age`, `salary`, `dep`, `province`) VALUES ('3', '任我行', '56', '10000.00', '销售部', '广东');
INSERT INTO `bookmodel_emp` (`id`, `name`, `age`, `salary`, `dep`, `province`) VALUES ('4', '岳灵珊', '19', '6000.00', '关公部', '河南');
INSERT INTO `bookmodel_emp` (`id`, `name`, `age`, `salary`, `dep`, `province`) VALUES ('5', '小龙女', '20', '8000.00', '关公部', '河北');
INSERT INTO `bookmodel_dep` (`id`, `title`) VALUES ('1', '销售部');
INSERT INTO `bookmodel_dep` (`id`, `title`) VALUES ('2', '关公部');
INSERT INTO `bookmodel_emps` (`id`, `name`, `age`, `salary`, `province`, `dep_id`) VALUES ('2', '令狐冲', '24', '8000.00', '河南', '1');
INSERT INTO `bookmodel_emps` (`id`, `name`, `age`, `salary`, `province`, `dep_id`) VALUES ('3', '任盈盈', '18', '9000.00', '广东', '2');
INSERT INTO `bookmodel_emps` (`id`, `name`, `age`, `salary`, `province`, `dep_id`) VALUES ('4', '任我行', '57', '10000.00', '广东', '1');
INSERT INTO `bookmodel_emps` (`id`, `name`, `age`, `salary`, `province`, `dep_id`) VALUES ('5', '岳灵珊', '19', '6000.00', '河南', '2');
INSERT INTO `bookmodel_emps` (`id`, `name`, `age`, `salary`, `province`, `dep_id`) VALUES ('6', '小龙女', '20', '8000.00', '河北', '2');
几个比较典型的实例
上面插入的数据暂时没用上
统计每一个出版社的最便宜的书的价格(BookModel/views.py
):
res = models.Publish.objects.values("name").annotate(in_price = Min("book__price"))
print(res)
urls.py
添加path("use_annotate/", book_views.use_annotate),
统计每一本书的作者个数:
res = models.Book.objects.annotate(c = Count("authors__name")).values("title","c")
print(res)
统计每一本以**“冲”**开头的书籍的作者个数:
res = models.Book.objects.filter(title__startswith="冲").annotate(c = Count("authors__name")).values("title","c")
print(res)
统计不止一个作者的图书名称:
res = models.Book.objects.annotate(c = Count("authors__name")).filter(c__gt=0).values("title","c")
print(res)
根据一本图书作者数量的多少对查询集 QuerySet 进行降序排序:
res = models.Book.objects.annotate(c = Count("authors__name")).order_by("-c").values("title","c")
print(res)
查询各个作者出的书的总价格:
res = models.Author.objects.annotate(all = Sum("book__price")).values("name", "all")
print(res)
F() 查询
F()
的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值。
之前构造的过滤器都只是将字段值与某个常量做比较,如果想要对两个字段的值做比较,就需要用到 F()
。
使用前要先导包:
from django.db.models import F
用法:F("字段名称")
F 动态获取对象字段的值,可以进行运算。
Django 支持 F()
对象之间以及 F()
对象和常数之间的加减乘除和取余的操作。
修改操作(update)也可以使用 F()
函数。
下面使用上一小节创建的数据
查询工资大于年龄的人:
from django.db.models import F
def use_f(request):
person = models.Emp.objects.filter(salary__gt=F("age")).values("name", "age")
return HttpResponse(person)
urls.py
添加path("use_f/", book_views.use_f),
将每一本书的价格提高100元:
res = models.Book.objects.update(price=F("price")+100)
print(res)
Q() 查询
使用前要先导包:
from django.db.models import Q
用法:Q(条件判断)
例如:Q(title__startswith="冲")
之前构造的过滤器里的多个条件的关系都是 and,如果需要执行更复杂的查询(例如 or 语句),就可以使用 Q 。
Q 对象可以使用 & | ~ (与 或 非)操作符进行组合。
优先级从高到低:~ > & >|
。
可以混合使用 Q 对象和关键字参数,Q 对象和关键字参数是用"and"拼在一起的(即将逗号看成 and ),但是 Q 对象必须位于所有关键字参数的前面。
查询价格大于 350 或者名称以**“冲”**开头的书籍的名称和价格:
from django.db.models import Q
def use_q(request):
res = models.Book.objects.filter(Q(price__gt=350) | Q(title__startswith="冲")).values("title", "price")
return HttpResponse(res)
urls.py
添加path("use_q/", book_views.use_q),
查询以"法"结尾或者不是 2010 年 10 月份的书籍:
res = models.Book.objects.filter(Q(title__endswith="法") | ~Q(Q(pub_date__year=2010) & Q(pub_date__month=10)))
查询出版日期是 2004 或者 1999 年,并且书名中包含有"大"的书籍。
Q 对象和关键字混合使用,Q 对象要在所有关键字的前面:
res = models.Book.objects.filter(Q(pub_date__year=2004) | Q(pub_date__year=1999), title__contains="大")
Django Form 组件
Django Form 组件用于对页面进行初始化,生成 HTML 标签,此外还可以对用户提交的数据进行校验(显示错误信息)。
报错信息显示顺序:
- 先显示字段属性中的错误信息,然后再显示局部钩子的错误信息。
- 若显示了字段属性的错误信息,就不会显示局部钩子的错误信息。
- 若有全局钩子,则全局钩子是等所有的数据都校验完,才开始进行校验,并且全局钩子的错误信息一定会显示。
使用 Form 组件,需要先导入 forms:from django import forms
简单例子
在 BookModel 目录下创建一个 my_forms
.py:
from django import forms
class EmpForm(forms.Form):
name = forms.CharField(min_length=4, label="姓名", error_messages={"min_length": "你太短了", "required": "该字段不能为空!"})
age = forms.IntegerField(label="年龄")
salary = forms.DecimalField(label="工资")
字段属性:
- label:输入框前面的文本信息。
- error_message:自定义显示的错误信息,属性值是字典, 其中 required 为设置不能为空时显示的错误信息的 key。
BookModel/views.py
from django.shortcuts import render, HttpResponse
from BookModel.My_Forms import EmpForm
from BookModel import models
from django.core.exceptions import ValidationError
# Create your views here.
def add_emp(request):
form = EmpForm()
return render(request, "add_emp.html", {"form": form})
BookModel/urls.py
文件添加规则:path('add_emp/', views.add_emp)
添加文件templates/add_emp.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Add EMP</title>
</head>
<body>
<h3>添加员工</h3>
<!-- 法1 自己写
<form action="" method="post">
<p>姓名:<input type="text" name="name"></p>
<p>年龄:<input type="text" name="age"></p>
<p>工资:<input type="text" name="salary"></p>
<input type="submit">
</form>
-->
<!-- 法2 通过form对象的as_p方法实现 -->
<form action="" method="post" novalidate>
{% csrf_token %}
{{ form.as_p }}
<input type="submit">
</form>
{#法3 手动获取form对象的字段#}
{#<form action="" method="post" novalidate>#}
{# {% csrf_token %}#}
{# <div>#}
{# <label for="id_{{ form.name.name }}">姓名</label>#}
{# {{ form.name }} <span>{{ form.name.errors.0 }}</span>#}
{# </div>#}
{# <div>#}
{# <label for="id_{{ form.age.name }}">年龄</label>#}
{# {{ form.age }} <span>{{ form.age.errors.0 }}</span>#}
{# </div>#}
{# <div>#}
{# <label for="id_salary">工资</label>#}
{# {{ form.salary }} <span>{{ form.salary.errors.0 }}</span>#}
{# </div>#}
{# <input type="submit">#}
{#</form>#}
{#4、用for循环展示所有字段#}
{#<form action="" method="post" novalidate>#}
{# {% csrf_token %}#}
{# {% for field in form %}#}
{# <div>#}
{# <label for="id_{{ field.name }}">{{ field.label }}</label>#}
{# {{ field }} <span>{{ field.errors.0 }}</span>#}
{# </div>#}
{# {% endfor %}#}
{# <input type="submit">#}
{#</form>#}
</body>
</html>
局部钩子和全局钩子
修改BookModel 目录下的 my_forms.py
:
from django import forms
from django.core.exceptions import ValidationError
from BookModel import models
class EmpForm(forms.Form):
name = forms.CharField(min_length=5, label="姓名", error_messages={"required": "该字段不能为空!",
"min_length": "用户名太短。"})
age = forms.IntegerField(label="年龄")
salary = forms.DecimalField(max_digits=5, decimal_places=2, label="工资")
r_salary = forms.DecimalField(max_digits=5, decimal_places=2, label="请再输入工资")
def clean_name(self): # 局部钩子
val = self.cleaned_data.get("name")
if val.isdigit():
raise ValidationError("用户名不能是纯数字")
elif models.Emp.objects.filter(name=val):
raise ValidationError("用户名已存在!")
else:
return val
def clean(self): # 全局钩子 确认两次输入的工资是否一致。
val = self.cleaned_data.get("salary")
r_val = self.cleaned_data.get("r_salary")
if val == r_val:
return self.cleaned_data
else:
raise ValidationError("请确认工资是否一致。")
修改BookModel/views.py
文件代码:
from django.shortcuts import render, HttpResponse, redirect
from BookModel.My_Forms import EmpForm
from BookModel import models
from django.core.exceptions import ValidationError
# Create your views here.
def add_emp(request):
if request.method == "GET":
form = EmpForm() # 初始化form对象
return render(request, "add_emp.html", {"form":form})
else:
form = EmpForm(request.POST) # 将数据传给form对象
if form.is_valid(): # 进行校验
data = form.cleaned_data
data.pop("r_salary")
models.Emp.objects.create(**data)
return redirect("/index/")
else: # 校验失败
clear_errors = form.errors.get("__all__") # 获取全局钩子错误信息
return render(request, "add_emp.html", {"form": form, "clear_errors": clear_errors})
add_emp.html
添加代码:
<form action="" method="post" novalidate>
{% csrf_token %}
<div>
<label for="id_{{ form.name.name }}">姓名</label>
{{ form.name }} <span>{{ form.name.errors.0 }}</span>
</div>
<div>
<label for="id_{{ form.age.name }}">年龄</label>
{{ form.age }} <span>{{ form.age.errors.0 }}</span>
</div>
<div>
<label for="id_salary">工资</label>
{{ form.salary }} <span>{{ form.salary.errors.0 }}{{ clear_errors.0 }}</span>
</div>
<div>
<label for="id_r_salary">请再输入工资</label>
{{ form.r_salary }} <span>{{ form.r_salary.errors.0 }}{{ clear_errors.0 }}</span>
</div>
<input type="submit">
</form>
Django 用户认证(Auth)组件
用户认证(Auth)组件一般用在用户的登录注册上,用于判断当前的用户是否合法,并跳转到登陆成功或失败页面。
Django 用户认证(Auth)组件需要导入 auth 模块:
使用以下代码,按照先编写
views.py
,再加入urls.py
路由的方式,向auth_user
表添加数据
# 认证模块
from django.contrib import auth
# 对应数据库
from django.contrib.auth.models import User
# User.objects.create(username='runbooo',password='123')
# User.objects.create_user(username='runbooo',password='123')
User.objects.create_superuser(username='runboooo',password='123',email='runboo@163.com')
返回值是用户对象。(来自
auth_user
表)
创建用户对象的三种方法:
create():创建一个普通用户,密码是明文的。
create_user():创建一个普通用户,密码是密文的。
create_superuser():创建一个超级用户,密码是密文的,要多传一个邮箱 email 参数。
等价于,通过命令 python manage.py createsuperuser 来创建超级用户
验证用户的用户名和密码使用 authenticate()
方法,从需要 auth_user 表中过滤出用户对象。
使用前要导入:from django.contrib import auth
返回值:如果验证成功,就返回用户对象,反之,返回 None。
views.py
添加:
def login(request):
if request.method == "GET":
return render(request, "login.html")
username = request.POST.get("username")
password = request.POST.get("pwd")
valid_num = request.POST.get("valid_num")
keep_str = request.session.get("keep_str")
if keep_str.upper() == valid_num.upper():
user_obj = auth.authenticate(username=username, password=password)
print(user_obj.username)
给验证成功的用户加 session,将 request.user
赋值为用户对象。登陆使用 login()
方法。
返回值:None。
修改views.py
如下:
def login(request):
if request.method == "GET":
return render(request, "login.html")
username = request.POST.get("username")
password = request.POST.get("pwd")
valid_num = request.POST.get("valid_num")
keep_str = request.session.get("keep_str")
if keep_str.upper() == valid_num.upper():
user_obj = auth.authenticate(username=username, password=password)
print(user_obj.username)
if not user_obj:
return redirect("/login/")
else:
auth.login(request, user_obj)
path = request.GET.get("next") or "/index/"
print(path)
return redirect(path)
else:
return redirect("/login/")
注销用户使用 logout()
方法,需要清空 session 信息,将 request.user
赋值为匿名用户。
views.py
添加:
def logout(request):
ppp = auth.logout(request)
print(ppp) # None
return redirect("/login/")
设置装饰器,给需要登录成功后才能访问的页面统一加装饰器。使用前要导入:from django.contrib.auth.decorators import login_required
from django.contrib.auth.decorators import login_required @login_required
def index(request):
return HttpResponse("index页面。。。")
设置从哪个页面访问,登录成功后就返回哪个页面。
解析:
django 在用户访问页面时,如果用户是未登录的状态,就给用户返回登录页面。
此时,该登录页面的 URL 后面有参数:next=用户访问的页面的 URL。
因此,设置在用户登录成功后重定向的 URL 为 next 参数的值。
但是,若用户一开始就输入登录页面 login,
request.GET.get("next")
就取不到值,所以在后面加 or,可以设置自定义返回的页面。
# 如果直接输入 login、get() 就取不到值,path 可以自定义设置返回的页面
path = request.GET.get("next") or "/index/"
return redirect(path)
Django cookie 与 session
Cookie 是存储在客户端计算机上的文本文件,并保留了各种跟踪信息。
识别返回用户包括三个步骤:
- 服务器脚本向浏览器发送一组 Cookie。例如:姓名、年龄或识别号码等。
- 浏览器将这些信息存储在本地计算机上,以备将来使用。
- 当下一次浏览器向 Web 服务器发送任何请求时,浏览器会把这些 Cookie 信息发送到服务器,服务器将使用这些信息来识别用户。
HTTP 是一种"无状态"协议,这意味着每次客户端检索网页时,客户端打开一个单独的连接到 Web 服务器,服务器会自动不保留之前客户端请求的任何记录。
但是仍然有以下三种方式来维持 Web 客户端和 Web 服务器之间的 session 会话:
Cookies
一个 Web 服务器可以分配一个唯一的 session 会话 ID 作为每个 Web 客户端的 cookie,对于客户端的后续请求可以使用接收到的 cookie 来识别。
在Web开发中,使用 session 来完成会话跟踪,session 底层依赖 Cookie 技术。
Django 中 Cookie 的语法
设置 cookie:
rep.set_cookie(key,value,...)
rep.set_signed_cookie(key,value,salt='加密盐',...)
获取 cookie:
request.COOKIES.get(key)
删除 cookie:
rep = HttpResponse || render || redirect
rep.delete_cookie(key)
创建应用和模型
(base) PS D:\Python\py_files\LearnDjango\hello_django> django-admin startapp CookieModel
修改CookieModel/models.py
:
class UserInfo(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=64)
settings.py
中找到INSTALLED_APPS这一项,如下:
INSTALLED_APPS = [
...
"CookieModel", # new added
]
插入数据库(执行以下命令):
$ python manage.py makemigrations CookieModel # 让 Django 知道我们在我们的模型有一些变更
Migrations for 'CookieModel':
CookieModel\migrations\0001_initial.py
- Create model UserInfo
$ python manage.py migrate CookieModel # 创建表结构
Operations to perform:
Apply all migrations: CookieModel
Running migrations:
Applying CookieModel.0001_initial... OK
测试数据:insert into cookiemodel_userinfo(username, password) values ("moonjay_cookie", "123456")
CookieModel/views.py
:
from django.shortcuts import render, redirect
from CookieModel import models
# Create your views here.
def login(request):
if request.method == "GET":
return render(request, "login.html")
username = request.POST.get("username")
password = request.POST.get("pwd")
user_obj = models.UserInfo.objects.filter(username=username, password=password).first()
print(user_obj.username)
if not user_obj:
return redirect("/login/")
else:
rep = redirect("/index/")
rep.set_cookie("is_login", True)
return rep
def index(request):
print(request.COOKIES.get('is_login'))
status = request.COOKIES.get('is_login') # 收到浏览器的再次请求,判断浏览器携带的cookie是不是登录成功的时候响应的 cookie
if not status:
return redirect('/login/')
return render(request, "index_cookie.html")
def logout(request):
rep = redirect('/login/')
rep.delete_cookie("is_login")
return rep # 点击注销后执行,删除cookie,不再保存用户状态,并弹到登录页面
def order(request):
print(request.COOKIES.get('is_login'))
status = request.COOKIES.get('is_login')
if not status:
return redirect('/login/')
return render(request, "order.html")
hello_django/urls.py
添加:
from CookieModel import views as cookie_views
urlpatterns = [
path("admin/", admin.site.urls), # 前面添加过
...
path("index/", cookie_views.index), # 前面添加过
...
path('login/', cookie_views.login),
path('logout/', cookie_views.logout),
path('order/', cookie_views.order),
]
以下创建三个模板文件:login.html
、index_cookie.html
、order.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login (COOKIES) Page</title>
</head>
<body>
<h3>用户登录</h3>
<form action="" method="post">
{% csrf_token %}
<label>用户名:<input type="text" name="username">
</label>
<label>密码:<input type="password" name="pwd">
</label>
<input type="submit">
</form>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Index Cookie Page</title>
</head>
<body>
<h2>Cookie index 页面...</h2>
<a href="/logout/">注销</a>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Order Page</title>
</head>
<body>
<h2>order 页面。。。</h2>
<a href="/logout/">注销</a>
</body>
</html>
Session
保存在服务端的键值对
服务器在运行时可以为每一个用户的浏览器创建一个其独享的 session 对象,由于 session 为用户浏览器独享,所以用户在访问服务器的 web 资源时,可以把各自的数据放在各自的 session 中,当用户再去访问该服务器中的其它 web 资源时,其它 web 资源再从用户各自的 session 中取出数据为用户服务。
工作原理
a. 浏览器第一次请求获取登录页面 login。
b. 浏览器输入账号密码第二次请求,若输入正确,服务器响应浏览器一个 index 页面和一个键为 sessionid,值为随机字符串的 cookie,即
set_cookie ("sessionid",随机字符串)
。c. 服务器内部在
django_session
表中记录一条数据。django_session
表中有三个字段。- session_key:存的是随机字符串,即响应给浏览器的 cookie 的 sessionid 键对应的值。
- session_data:存的是用户的信息,即多个
request.session["key"]=value
,且是密文。 - expire_date:存的是该条记录的过期时间(默认14天)
d. 浏览器第三次请求其他资源时,携带
cookie:{sessionid:随机字符串}
,服务器从django_session
表中根据该随机字符串取出该用户的数据,供其使用(即保存状态)。
注意:
django_session
表中保存的是浏览器的信息,而不是每一个用户的信息。 因此, 同一浏览器多个用户请求只保存一条记录(后面覆盖前面),多个浏览器请求才保存多条记录。
cookie 弥补了 http 无状态的不足,让服务器知道来的人是"谁",但是 cookie 以文本的形式保存在浏览器端,安全性较差,且最大只支持 4096 字节,所以只通过 cookie 识别不同的用户,然后,在对应的 session 里保存私密的信息以及超过 4096 字节的文本。
session 设置:request.session["key"] = value
执行步骤:
- a. 生成随机字符串
- b. 把随机字符串和设置的键值对保存到
django_session
表的 session_key 和 session_data 里 - c. 设置 cookie: set_cookie(“sessionid”,随机字符串) 响应给浏览器
session 获取:request.session.get('key')
执行步骤:
- a. 从 cookie 中获取 sessionid 键的值,即随机字符串。
- b. 根据随机字符串从
django_session
表过滤出记录。 - c. 取出 session_data 字段的数据。
session 删除,删除整条记录(包括 session_key、session_data、expire_date 三个字段):request.session.flush()
删除 session_data 里的其中一组键值对:del request.session["key"]
执行步骤:
- a. 从 cookie 中获取 sessionid 键的值,即随机字符串
- b. 根据随机字符串从
django_session
表过滤出记录 - c. 删除过滤出来的记录
实例
再CookieModel/views.py
添加代码:
def s_login(request):
if request.method == "GET":
return render(request, "login.html")
username = request.POST.get("username")
password = request.POST.get("pwd")
user_obj = models.UserInfo.objects.filter(username=username, password=password).first()
print(user_obj.username)
if not user_obj:
return redirect("/session_login/")
else:
request.session['is_login'] = True
request.session['user1'] = username
return redirect("/s_index/")
def s_index(request):
status = request.session.get('is_login')
if not status:
return redirect('/session_login/')
return render(request, "s_index.html")
def s_logout(request):
# del request.session["is_login"] # 删除session_data里的一组键值对
request.session.flush() # 删除一条记录包括(session_key session_data expire_date)三个字段
return redirect('/session_login/')
创建路由(hello_django/urls.py
):
from CookieModel import views as cookie_views
urlpatterns = [
...
path('session_login/', cookie_views.s_login),
path('s_index/', cookie_views.s_index),
path('s_logout/', cookie_views.s_logout),
]
s_index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Session Page</title>
</head>
<body>
<h2>session_index 页面。。。{{ request.session.user1 }}</h2>
<a href="/s_logout/">注销</a>
</body>
</html>
Django 中间件
Django 中间件是修改 Django request 或者 response 对象的钩子,可以理解为是介于 HttpRequest 与 HttpResponse 处理之间的一道处理过程。
浏览器从请求到响应的过程中,Django 需要通过很多中间件来处理,可以看如下图所示:
Django 中间件作用:
- 修改请求,即传送到 view 中的 HttpRequest 对象。
- 修改响应,即 view 返回的 HttpResponse 对象。
中间件组件配置在 settings.py
文件的 MIDDLEWARE 选项列表中。
配置中的每个字符串选项都是一个类,也就是一个中间件。
Django 默认的中间件配置:
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",
]
自定义中间件步骤
以APP:BookModel为例,在 BookModel 目录下新建一个 py 文件,名字自定义(middle_ware.py
),并在该 py 文件中导入 MiddlewareMixin:
from django.utils.deprecation import MiddlewareMixin
自定义的中间件类,要继承父类 MiddlewareMixin:
class MW1(MiddlewareMixin):
pass
在 settings.py
中的 MIDDLEWARE 里注册自定义的中间件类:
MIDDLEWARE = [
....
'BookModel.middle_ware.MW1',
]
自定义中间件类的方法
4个方法:
process_request(self,request)
process_view(self, request, view_func, view_args, view_kwargs)
process_exception(self, request, exception)
process_response(self, request, response)
process_request 方法
process_request 方法有一个参数 request,这个 request 和视图函数中的 request 是一样的。
process_request 方法的返回值可以是 None 也可以是 HttpResponse 对象。
- 返回值是 None 的话,按正常流程继续走,交给下一个中间件处理。
- 返回值是 HttpResponse 对象,Django 将不执行后续视图函数之前执行的方法以及视图函数,直接以该中间件为起点,倒序执行中间件,且执行的是视图函数之后执行的方法。
process_request 方法是在视图函数之前执行的。
当配置多个中间件时,会按照 MIDDLEWARE中 的注册顺序,也就是列表的索引值,顺序执行。
不同中间件之间传递的 request 参数都是同一个请求对象。
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import render, HttpResponse
class MW1(MiddlewareMixin):
def process_request(self, request):
print("mw1 process_request 方法。", id(request)) # 在视图之前执行
访问任意页面,控制台输出:
mw1 process_request 方法。 2498897104224
[16/Jul/2024 15:54:40] "GET / HTTP/1.1" 200 1454
...
process_response 方法
process_response 方法有两个参数,一个是 request,一个是 response,request 是请求对象,response 是视图函数返回的 HttpResponse 对象,该方法必须要有返回值,且必须是response。
process_response 方法是在视图函数之后执行的。
当配置多个中间件时,会按照 MIDDLEWARE 中的注册顺序,也就是列表的索引值,倒序执行。
在MW1类中添加方法定义:
def process_response(self, request, response): # 基于请求响应
print("mw1 process_response 方法!", id(request)) # 在视图之后
return response
访问任意页面,控制台输出:
mw1 process_request 方法。 2790043344560
mw1 process_response 方法! 2790043344560
[16/Jul/2024 16:06:03] "GET / HTTP/1.1" 200 1454
从下图看,正常的情况下按照绿色的路线进行执行,假设中间件1有返回值,则按照红色的路线走,直接执行该类下的 process_response
方法返回,后面的其他中间件就不会执行。
process_view 方法
process_view 方法格式如下:process_view(request, view_func, view_args, view_kwargs)
- request 是 HttpRequest 对象。
- view_func 是 Django 即将使用的视图函数。
- view_args 是将传递给视图的位置参数的列表。
- view_kwargs 是将传递给视图的关键字参数的字典。
view_args 和 view_kwargs 都不包含第一个视图参数(request)。
process_view 方法是在视图函数之前,process_request 方法之后执行的。
返回值可以是 None、view_func(request) 或 HttpResponse 对象。
- 返回值是 None 的话,按正常流程继续走,交给下一个中间件处理。
- 返回值是 HttpResponse 对象,Django 将不执行后续视图函数之前执行的方法以及视图函数,直接以该中间件为起点,倒序执行中间件,且执行的是视图函数之后执行的方法。
- 返回值是
view_func(request)
,Django 将不执行后续视图函数之前执行的方法,提前执行视图函数,然后再倒序执行视图函数之后执行的方法。- 当最后一个中间件的 process_request 到达路由关系映射之后,返回到第一个中间件 process_view,然后依次往下,到达视图函数。
在MW1类中添加方法定义:
def process_view(self,request, view_func, view_args, view_kwargs):
print("md1 process_view 方法!") #在视图之前执行 顺序执行
#return view_func(request)
mw1 process_request 方法。 2388860657376
mw1 process_view 方法!
mw1 process_response 方法! 2388860657376
[16/Jul/2024 16:15:08] "GET / HTTP/1.1" 200 1454
process_exception 方法
process_exception 方法:process_exception(request, exception)
- request 是 HttpRequest 对象。
- exception 是视图函数异常产生的 Exception 对象。
process_exception 方法只有在视图函数中出现异常了才执行,按照 settings 的注册倒序执行。
在视图函数之后,在 process_response 方法之前执行。
process_exception 方法的返回值可以是一个 None 也可以是一个 HttpResponse 对象。
- 返回值是 None,页面会报 500 状态码错误,视图函数不会执行。process_exception 方法倒序执行,然后再倒序执行 process_response 方法。
- 返回值是 HttpResponse 对象,页面不会报错,返回状态码为 200。视图函数不执行,该中间件后续的 process_exception 方法也不执行,直接从最后一个中间件的 process_response 方法倒序开始执行。
若是 process_view 方法返回视图函数,提前执行了视图函数,且视图函数报错,则无论 process_exception 方法的返回值是什么,页面都会报错, 且视图函数和 process_exception 方法都不执行。
直接从最后一个中间件的 process_response 方法开始倒序执行(在MW1类中添加方法定义):
def process_exception(self, request, exception): # 引发错误 才会触发这个方法
print("mw1 process_exception 方法!")
# return HttpResponse(exception) # 返回错误信息
访问一个不存在的路由:
mw1 process_request 方法。 2707135433648
mw1 process_response 方法! 2707135433648
Not Found: /404
[16/Jul/2024 16:24:18] "GET /404 HTTP/1.1" 404 5527
Django 视图 - FBV 与 CBV
FBV(function base views) 基于函数的视图,就是在视图里使用函数处理请求。
前面的小节都是使用这种方式
CBV(class base views) 基于类的视图,就是在视图里使用类处理请求。
不同的请求我们可以在类中使用不同方法来处理,这样大大的提高了代码的可读性。
定义的类要继承父类 View,所以需要先引入库:from django.views import View
执行对应请求的方法前会优先执行 dispatch()
方法(在get/post/put...
方法前执行),dispatch()
方法会根据请求的不同调用相应的方法来处理。
其实,在我们前面学到的知识都知道 Django 的 url 是将一个请求分配给可调用的函数的,而不是一个类,那是如何实现基于类的视图的呢?
主要还是通过父类 View 提供的一个静态方法
as_view()
,as_view()
方法是基于类的外部接口, 它返回一个视图函数,调用后请求会传递给dispatch()
方法,dispatch()
方法再根据不同请求来处理不同的方法。
在hello_django/views.py
添加代码:
from django.views import View
class Login(View):
def get(self,request):
return HttpResponse("GET 方法")
def post(self,request):
user = request.POST.get("user")
pwd = request.POST.get("pwd")
if user == "runoob" and pwd == "123456":
return HttpResponse("POST 方法")
else:
return HttpResponse("POST 方法 1")
修改urls.py
文件:
urlpatterns = [
...
path("login/", views.Login.as_view()),
]
Django Nginx+uwsgi 安装配置
在前面的章节中我们使用 python manage.py runserver 来运行服务器。这只适用测试环境中使用。
正式发布的服务,需要一个可以稳定而持续的服务器,比如apache, Nginx, lighttpd等。