文章目录
Django 简介
Django是一个由Django Software Foundation开发和维护的高级Python Web开发框架;
Django一个显著特点是其大而全的设计哲学。
- 因此,Django提供的许多功能都是内置的,包括对象关系映射(ORM)层、模板引擎、表单处理、对象验证、URL路由、会话管理、对象序列化等;
- 这意味着开发者不需要从零开始构建这些功能,从而可以更快地构建出功能丰富的Web应用程序。
- 此外,Django还包含了一个自动管理的后台界面,使得开发人员可以轻松地管理内容;
- 这个后台界面通常用于处理数据库记录,如创建、编辑和删除记录等。
Django高度强调遵循MVC设计模式;
- 在Django中称之为MTV,是MVC的变种;
- Model(模型)
- Template(模板)
- View(视图)
这种设计模式使得代码的组织更加清晰,也更容易进行单元测试。
层次 | 职责 |
---|---|
数据层(M) |
处理与数据相关的所有事务,如存取、验证、行为和数据关系 |
模板层(T) |
处理与表现相关的决定:如何在页面或其他类型文档中进行显示 |
视图层(V) |
模型与模板之间的桥梁,负责处理用户请求,获取数据,选择模板进行渲染后返回 |
Django 优点
- 高效快速:Django提供了许多内置的功能和库,可以快速构建复杂的Web应用程序。
- 安全稳定:Django提供了许多安全性功能,如CSRF保护、XSS保护、SQL注入保护等,可以保证Web应用程序的安全性和稳定性。
- 可扩展性:Django提供了许多可扩展的功能和库,如第三方插件、中间件、信号等,可以根据需要扩展和定制Web应用程序。
- 易于维护:Django提供了许多内置的功能和库,可以减少代码量和重复工作,使得Web应用程序易于维护和升级。
- 社区活跃:Django拥有庞大的社区和生态系统,提供了许多第三方插件和库,可以快速解决各种问题和需求。
Django 应用场景
- 大型Web应用程序:Django适用于大型Web应用程序的开发,可以快速构建复杂的Web应用程序。
- CMS系统:Django提供了许多内置的功能和库,如ORM、模板引擎、管理后台等,适用于CMS系统的开发。
- 社交网络:Django提供了许多内置的功能和库,如认证、权限、信号等,适用于社交网络的开发。
- 电子商务:Django提供了许多内置的功能和库,如ORM、表单处理、支付等,适用于电子商务的开发。
Django 快速启动
1.安装
安装Django命令如下:
pip install django
2.创建项目
创建Django项目命令如下:
# 创建项目需要使用命令行, 会自动创建一个同名文件夹
django-admin startproject django_introduction
# ------------------- #
# 会在执行命令的当前目录中 创建 web项目目录
django_introduction #项目名称django_introduction
├── manage.py # 项目管理文件单入口文件,一般不需要修改
└── django_introduction # 和项目同名的目录/存放于项目相关的配置文件等
├── __init__.py # 包初始化文件
├── settings.py # 项目的配置文件
├── urls.py # 项目的根路由文件
└── wsgi.py # 通用网关接口服务文件/后期上线部署到专业的HTTP服务器时需要用到
Django 项目结构
3.创建应用
创建App应用
# 在Django项目中使用manage.py命令创建
python manage.py startapp my_app
# 此时Django项目的结构就会发生变化
django_introduction #项目名称django_introduction
├── manage.py # 项目管理文件单入口文件,一般不需要修改
├── django_introduction # 和项目同名的目录/存放于项目相关的配置文件等
│ ├── __init__.py # 包初始化文件
│ ├── settings.py # 项目的配置文件
│ ├── urls.py # 项目的根路由文件
│ └── wsgi.py # 通用网关接口服务文件/后期上线部署到专业的HTTP服务器时需要用
│ └── db.sqlite3 # Django框架默认使用数据库文件
└── my_app
├── __init__.py # 包初始化文件
├── admin.py # django框架自带后台模块配置文件
├── apps.py # App应用启动类
├── migrations # 数据库变更记录
│ └── __init__.py
├── models.py # 模型文件
├── tests.py # 测试文件
└── views.py # 视图函数文件
4.注册应用
5.编写视图函数
6.启动
启动服务命令
# 在manage.py所在目录 执行runserver命令
python manage.py runserver
7.访问
访问服务
Django 常用命令
django-admin.py和manage.py都是Django框架中用于执行各种任务的命令行工具;
django-admin.py:
- 这是一个全局可用的命令行工具,位于Django安装的
bin
目录下。- 它提供了许多与Django项目和应用无关的功能,如启动项目、创建应用、运行shell等。
- 你可以在任何地方使用它,而不仅仅是在特定的Django项目内。
manage.py:
- 这是一个项目特定的命令行工具,位于每个Django项目的根目录下。
- 它是
django-admin.py
的一个轻量级封装,专门为当前项目提供方便的命令行接口。manage.py
提供的功能与django-admin.py
相似,但更侧重于当前项目的任务,如数据库迁移、创建模型、运行测试等。两者之间的主要区别在于
manage.py
是项目特定的,而django-admin.py
是全局可用的。这意味着
manage.py
只能用于当前项目,而django-admin.py
可以用于任何Django项目。
1.创建项目
# 在当前目录下创建名为my_django_project的Django项目
django-admin.py startproject my_django_project
2.创建应用
# 在当前Django项目下创建名为my_app的应用
# django-admin命令创建
django-admin startapp my_app
# manage.py命令创建
python manage.py startapp my_app
# 应用创建成功后,会在Django项目中新增app目录结构
├ ...
├── myapp / # 应用目录
│ ├── __init__.py
│ ├── admin.py # 应用管理后台配置
│ ├── apps.py # 应用配置
│ ├── migrations / # 应用数据库迁移脚本目录
│ ├── models.py # 应用数据库模型配置
│ ├── tests.py # 应用测试脚本
│ └── views.py # 应用接口编写
│ ...
3.启动Django项目
# 在项目manage.py文件目录下
# 方式一
python manage.py runserver #不指定端口时默认是8000端口
# 方式二
python manage.py runserver 127.0.0.1:8000 #指定IP和端口
# 方式三
python manage.py runserver 8001 #指定端口
4.检查模型变化
# 在项目manage.py文件目录下
# 不指定应用时检查所有应用
python manage.py makemigrations
# 指定应用
python manage.py makemigrations [app_name]
5.修改用户密码
# 在项目manage.py文件目录下
python manage.py changepassword username
Django 配置文件
Django项目中settings.py配置文件内容分析如下:
DEBUG:
- 用途:指定是否开启调试模式。
- 含义:如果为
True
,Django 将显示详细的错误页面,包括堆栈跟踪信息。在生产环境中,应将其设置为False
以避免敏感信息泄露。ALLOWED_HOSTS:
- 用途:定义哪些主机或域名可以访问此 Django 网站。
- 含义:出于安全考虑,Django 默认只允许本地主机(
localhost
和127.0.0.1
)访问。如果要在生产环境中使用其他主机或域名,需要在此列表中添加。INSTALLED_APPS:
- 用途:定义项目所使用的所有应用列表。
- 含义:Django 项目的每个应用都需要在此列表中注册,以便 Django 能够识别和应用它们。
MIDDLEWARE:
- 用途:定义 Django 中间件类的列表。
- 含义:中间件是 Django 的一个轻量级、底层的“插件”系统,用于全局地修改 Django 的输入或输出。
ROOT_URLCONF:
- 用途:指定项目的 URL 声明,即“目录”。
- 含义:它告诉 Django 项目“URL 声明”在哪里。默认值是
'<项目名>.urls'
。TEMPLATES:
- 用途:定义 Django 使用的模板设置。
- 含义:这个设置包含多个字典,每个字典代表一个模板引擎的配置。它包含模板的加载器、上下文处理器、调试选项等。
DATABASES:
- 用途:配置数据库连接。
- 含义:Django 支持多种数据库,这里可以配置默认的数据库连接信息,如引擎类型(例如
'django.db.backends.sqlite3'
)、名称、用户、密码、主机、端口等。TIME_ZONE:
- 用途:设置项目的默认时区。
- 含义:Django 会使用此设置来处理日期和时间。
LANGUAGE_CODE:
- 用途:设置项目的默认语言。
- 含义:Django 会使用此设置来处理字符串的国际化和本地化。
STATIC_URL:
- 用途:定义静态文件(如 CSS、JavaScript、图片)的 URL。
- 含义:这个设置告诉 Django 在构建静态文件的 URL 时应该使用哪个前缀。
STATICFILES_DIRS:
- 用途:指定静态文件的目录。
- 含义:Django 默认会在一些特定的目录中查找静态文件,但这个设置允许你添加额外的目录。
MEDIA_URL 和 MEDIA_ROOT:
- 用途:配置媒体文件的存储和访问。
- 含义:
MEDIA_URL
是媒体文件的 URL 前缀,而MEDIA_ROOT
是实际存储媒体文件的文件系统路径。
Django 路由系统
当用户通过浏览器向Django网站发出请求时,请求会首先到达Django的WSGI服务器(如Gunicorn、uWSGI等),然后由WSGI服务器将请求传递给Django的路由分发系统。
Django的路由系统遵循了典型的MVC(模型-视图-控制器)设计模式,其中URL路由分发系统充当了控制器的角色,由此形成了MTV模式。
这个系统确保每个进入的HTTP请求都能被正确地映射到对应的视图函数或类上,以便进行进一步的处理。
概括来说,Django的路由流程如下
- 请求接收:用户的浏览器发出HTTP请求,这个请求首先由WSGI服务器(如Gunicorn、uWSGI等)接收。
- 路由分发:WSGI服务器将请求传递给Django的路由分发系统。这个系统基于
urls.py
文件中定义的URL模式来匹配请求,并将请求分发到相应的视图函数或类。- 视图处理:视图函数或类负责处理请求,可能涉及到从模型中获取数据、执行业务逻辑等操作。
- 模板渲染:处理完请求后,视图将必要的数据传递给模板。模板使用这些数据来生成最终的HTML响应。
- 响应返回:Django将生成的HTML响应发送回用户的浏览器,用户看到最终的网页内容。
综上所述,Django的路由流程可以概括为:
浏览器发出请求 → WSGI服务器接收请求 → Django路由分发系统 → 视图处理请求 → 模型提供数据 → 模板渲染数据 → HTML响应返回给浏览器。
静态路由
静态路由是明确且固定的。
在Django的
urls.py
文件中的urlpatterns
列表中,使用path
函数定义一个路由时;如果路径是一个固定的字符串,那么这个路由就是静态的。例如,
'staticmode/urls'
就是一个静态路由。用户只有在浏览器中输入这个确切的路径时,才会匹配到
views.static_mode_url
视图函数。
示例代码
path('', views.index),
path('index/', views.index),
path('st/static_url/', views.static_url)
from django.urls import path
from . import views
urlpatterns = [
# 访问根目录时,触发视图函数
path('', views.index),
# 访问index时,触发视图函数
path('index/', views.index),
# 访问a/b/c/时,触发视图函数
path('a/b/c/', views.index),
]
动态路由
在Django中,动态路由允许你创建更加灵活和可复用的URL模式。
这意味着你可以在URL中插入动态的部分,这些部分通常是由用户输入的,如ID、用户名等。
Django将捕获这些动态部分并将其作为参数传递给视图函数。动态路由使用尖括号
<>
来定义,并在尖括号内指定参数的类型。
Django支持多种类型的路由参数,每种类型都对应一种特定的数据类型。
以下是Django支持的所有路由参数类型及其示例代码:
示例代码
from django.urls import path
from . import views
urlpatterns = [
# 字符串(str):匹配任何非空字符串,但不包含斜杠(/)
path('your_path/<str:slug>/', views.your_view),
# 整数(int):匹配正整数
path('your_path/<int:id>/', views.your_view),
# 无符号字符串(slug):匹配由字母、数字、下划线和短划线组成的字符串
path('your_path/<slug:username>/', views.your_view),
# UUID(uuid):匹配一个UUID字符串
path('your_path/<uuid:uuid>/', views.your_view),
# 路径(path):匹配任何字符串,包含斜杠
path('your_path/<path:path>/', views.your_view),
# 正则表达式(regex):使用正则表达式来匹配URL。
# 这是Django 3.1之前版本中常用的方法,但在3.1及以后的版本中推荐使用上述的类型
re_path(r'^your_path/(?P<year>\d{4})/', views.your_view),
]
Django 视图函数
在Django框架中,视图函数(View Function)是MVC(Model-View-Controller)设计模式中的一部分,负责处理用户请求并返回响应;
视图函数通常定义在Django应用的
views.py
文件中,它接收一个HttpRequest对象作为参数,并返回一个HttpResponse对象;视图函数的工作流程如下:
- 接收请求(Request):当用户通过浏览器发送一个HTTP请求(如GET、POST等)到Django服务器时,Django的URL分派器(URLconf)会根据请求的URL路径找到对应的视图函数。
- 处理业务逻辑:视图函数接收HttpRequest对象作为参数,这个对象包含了用户请求的所有信息,如请求方法、路径、参数、头部信息等。在视图函数中,开发者编写处理该请求的业务逻辑代码,这通常包括从数据库中查询数据、处理数据、执行其他业务操作等。
- 返回响应(Response):处理完业务逻辑后,视图函数需要返回一个HttpResponse对象。这个对象包含了要发送给用户的响应内容,通常是HTML页面、JSON数据、重定向等。Django的模板系统可以帮助开发者构建HTML响应内容。
request对象常用属性
path
- 用途:表示请求的路径信息,不包括域名。
- 含义:例如,对于请求
http://example.com/my_app/
,path
将是'/my_app/'
。这是用来区分不同URL请求的关键信息。method
- 用途:表示请求的方法(HTTP动词)。
- 含义:例如,
'GET'
,'POST'
,'PUT'
,'DELETE'
等。这告诉服务器如何处理该请求。encoding
- 用途:表示提交的数据的编码方式。
- 含义:这通常用于解码表单或JSON数据。如果未指定,则使用
DEFAULT_CHARSET
设置的值,默认为'utf-8'
。GET
- 用途:包含所有通过GET方法提交的参数。
- 含义:这些参数通常附加在URL后面,并通过问号(
?
)分隔。例如,在URLhttp://example.com/search/?q=Django
中,GET
将包含{'q': 'Django'}
。POST
- 用途:包含所有通过POST方法提交的参数。
- 含义:这些参数通常用于提交表单数据,不会显示在URL中。
POST
请求通常用于提交敏感或大量数据。body
- 用途:表示原始的请求体,即发送到服务器的原始数据。
- 含义:这通常用于非表单编码的请求,如JSON、XML等。
content_type
- 用途:表示请求体的MIME类型。
- 含义:这告诉服务器如何解析请求体中的数据。例如,
'application/json'
表示请求体包含JSON数据。content_params
- 用途:包含
content_type
中的参数。- 含义:这些参数用于进一步描述请求体的内容。例如,在
'text/html; charset=utf-8'
中,content_params
将包含{'charset': 'utf-8'}
。FILES
- 用途:包含所有上传的文件。
- 含义:当用户在表单中选择文件上传时,这些文件可以通过
FILES
属性访问。每个文件都是一个UploadedFile
对象,可以进行进一步处理。COOKIES
- 用途:包含所有的Cookie。
- 含义:Cookie是由服务器发送到客户端的小段数据,并在随后的请求中由客户端发送回服务器。这通常用于跟踪用户会话或存储用户偏好。
session
- 用途:表示当前的会话。
- 含义:会话是一种在客户端和服务器之间保持状态的方法。你可以将对象保存到会话中,并在随后的请求中检索它们。这对于实现用户登录、购物车等功能非常有用。
user
- 用途:代表当前登录的用户。
- 含义:如果用户已登录,
user
将是一个User
对象,包含用户的详细信息。如果用户未登录,user
将是一个AnonymousUser
的实例,它提供了一些有限的功能。
request对象常用方法
is_ajax()
用途:判断请求是否是通过XMLHttpRequest(通常是AJAX)发起的。
含义:如果请求是由JavaScript框架(如jQuery)发送的异步请求,这个方法将返回
True
。这有助于在视图中区分AJAX请求和常规HTTP请求,从而可以返回不同的响应内容或格式。is_secure()
用途:判断请求是否是通过HTTPS协议发送的。
含义:如果请求使用了安全套接字层(SSL)或传输层安全性(TLS)加密,则该方法返回
True
。这有助于确定是否可以在请求中安全地传输敏感信息。get_full_path()
用途:返回请求的完整路径,包括查询参数(GET参数)。
含义:这个方法拼接了请求的路径(
path
属性)和查询字符串(如果有的话),形成一个完整的URL路径。它常用于构建重定向或反向URL查找。get_host()
用途:返回请求的主机名和端口号(如果非标准端口)。
含义:这个方法返回请求的域名或IP地址以及端口号(如果端口不是HTTP的默认端口80或HTTPS的默认端口443)。它常用于构建绝对URL或确定请求的来源。
get_method()
用途:返回请求使用的HTTP方法(如GET、POST等)。
含义:这个方法返回请求对象的
method
属性,它表示客户端发送请求时使用的HTTP方法。build_absolute_uri()
用途:构建请求的完整绝对URI。
含义:这个方法结合了请求的协议(HTTP或HTTPS)、主机名、端口和路径,生成一个完整的、可以直接访问的URI。
response对象常用属性
响应对象主要有三种形式:HttpResponse()、render()、redirect();
HttpResponse():
- 返回文本,参数为字符串,字符串中写文本内容。
- 如果参数为字符串里含有 html 标签,也可以渲染。
render():
- 返回文本
- 第一个参数为 request
- 第二个参数为字符串(页面名称)
- 第三个参数为字典(可选参数,向页面传递的参数:键为页面参数名,值为views参数名)。
redirect():
- 重定向,跳转新页面。参数为字符串,字符串中填写页面路径。
- 一般用于 form 表单提交后,跳转到新页面。
其实,render 和 redirect 是在 HttpResponse 的基础上进行的封装;
content
- 用途:表示响应体的内容。
- 含义:这是一个字节字符串,通常用于存储要发送给客户端的原始数据。
charset- 用途:指定响应内容的字符编码。
- 含义:这通常是一个字符串,例如
'utf-8'
,它告诉客户端如何解码响应内容。status_code
- 用途:表示HTTP响应的状态码。
- 含义:这是一个整数,例如200表示成功,404表示未找到等。它告诉客户端请求的处理结果。
reason_phrase
- 用途:提供关于状态码的简短描述。
- 含义:这通常是一个字符串,例如
'OK'
或'Not Found'
,它提供了对状态码的额外说明。content_type
- 用途:指定响应的MIME类型。
- 含义:这通常是一个字符串,如
'text/html; charset=utf-8'
,它告诉客户端响应内容的类型和编码方式。streaming
- 用途:表示响应是否应该被流式传输。
- 含义:这是一个布尔值,如果为
True
,则响应内容将被流式传输给客户端,这在处理大量数据时非常有用。
response对象常用方法
set_cookie()
- 用途:在响应中设置一个cookie。
- 含义:这个方法允许你在发送给客户端的响应中设置一个cookie,以便后续请求可以使用它。参数包括cookie的名称、值、过期时间等。
delete_cookie()
- 用途:从响应中删除一个cookie。
- 含义:这个方法允许你删除之前设置的cookie。你需要提供cookie的名称以及可选的路径和域名。
add_post_render_callback()
- 用途:添加一个后渲染回调函数。
- 含义:这个方法允许你添加一个回调函数,该函数将在响应内容被发送给客户端之前被调用。这可以用于修改响应内容或执行其他必要的操作。
set_signed_cookie()
- 用途:在响应中设置一个签名的cookie。
- 含义:这个方法与
set_cookie
类似,但它还使用了一个密钥(salt)来对cookie的值进行签名,以增加安全性。
Django 表单处理
Django表单处理是Django框架中用于处理用户提交的表单数据的一种机制;
它提供了一种方便、安全且可重用的方式来处理表单数据,包括验证、过滤和渲染表单。
Django表单系统主要依赖于
django.forms
模块。
定义表单类
在应用下新建
forms.py
文件中定义一个表单类,该类继承自django.forms.Form
;在表单类中,可以定义表单的字段、字段的验证规则等。
from django import forms
class RegistrationForm(forms.Form):
# 长度必须在4到20个字符之间
username = forms.CharField(min_length=4, max_length=20, label='账号')
# 长度必须在8到20个字符之间,并使用PasswordInput控件渲染,这样在前端显示时密码会被遮盖。
password = forms.CharField(min_length=8, max_length=20, widget=forms.PasswordInput, label='密码')
# 自动校验电子邮箱格式
email = forms.EmailField(label='邮箱')
在视图类中调用表单类
from forms import RegistrationForm
def register(request):
if request.method == 'POST':
form = RegistrationForm(request.POST)
if form.is_valid():
username = form.cleaned_data['username']
email = form.cleaned_data['email']
password = form.cleaned_data['password']
print("username:", type(username), username)
print("email:", type(email), email)
print("password:", type(password), password)
# 处理表单数据
return HttpResponse('注册成功')
else:
form = RegistrationForm()
return render(request, 'register.html', {'form': form})
编写HTML代码
<!-- register.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="提交" style="font-size: 16px">
</form>
</body>
</html>
页面效果
Django 静态文件
在Django中,静态文件指的是那些不会在服务器端执行,而是直接提供给客户端浏览器下载的文件,如CSS样式表、JavaScript脚本、图片、字体文件等。这些文件对于网站的前端显示和用户交互至关重要。
配置静态文件
在项目的settings.py文件中配置STATIC_URL和STATICFILES_DIRS属性
STATIC_URL是你的静态文件的服务URL,默认情况下,它是’/static/';
STATICFILES_DIRS是一个包含所有静态文件目录的路径列表,默认情况下,它包含你的每个应用目录下的static文件夹;
# settings.py
# 静态文件存取的URL地址
STATIC_URL = '/static/'
# 静态文件本地存取的地址
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'),
]
在项目根目录下创建static目录,或根据app应用分别创建静态文件目录;
修改视图函数
将
my_app
应用中的views.py
文件进行修改,代码如下:
from django.shortcuts import render, HttpResponse
# Create your views here.
def index(request):
return HttpResponse("Hello World")
def user_list(request):
# 1.去当前app应用下的templates目录下寻找user_list.html(底层:根据app应用注册的顺序,逐一去他们的templates目录中寻找)
# 2.根据app的注册模块,在每个app应用下的templates目录中寻找(默认,setting.py中TEMPLATES的DIRS不需要配置)
return render(request, "user_list.html")
def user_add(request):
return render(request, "user_add.html")
挂载静态文件
my_app/templates/user_list.html文件代码如下:
{# 加载静态文件目录URL地址 #}
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>用户列表</title>
{# 利用static占位符拼接静态文件 #}
<link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1/css/bootstrap.css' %}">
<script src="{% static 'js/jquery-3.5.1/jquery-3.5.1.min.js' %}"></script>
<script src="{% static 'plugins/bootstrap-3.4.1/js/bootstrap.min.js' %}"></script>
</head>
<body>
<h1>用户列表html</h1>
<p>
<label>
<input type="text" class="btn btn-primary" value="新建"/>
</label>
</p>
<img src="{% static 'img/with_me.gif' %}" alt="关注我一下~">
</body>
</html>
页面效果
访问http://127.0.0.1:8000/user/list/ 效果如下
Django 模板语言
Django 的模板系统是一个强大且灵活的工具,用于动态生成 HTML 页面。
Django 的模板系统使用了一种特殊的模板语法,允许开发者在 HTML 中插入动态内容,这些内容通常来自于视图(views)中处理的数据。
以下是一些 Django 模板的基本组成部分和概念:
- 变量:模板中的变量使用双大括号
{{ variable_name }}
表示。当模板被渲染时,这些变量会被替换为它们在视图函数中传递的值。- 标签:标签是 Django 模板中的控制结构,它们用
{% tag %}
的形式表示。标签可以执行循环、条件语句、包含其他模板文件等操作。- 过滤器:过滤器用于修改变量的显示方式。它们可以在变量后面使用管道符
|
来应用。例如,{{ value|upper }}
会将变量value
转换为大写。- 注释:模板中的注释使用
{# this is a comment #}
的形式表示。- 模板继承:Django 模板支持继承,这意味着你可以定义一个基础模板(base template),然后在其他模板中继承它。这样,你可以在一个地方定义整个网站的布局和样式,并在需要时覆盖或添加特定页面的内容。
- 自定义模板标签和过滤器:Django 还允许你创建自定义的模板标签和过滤器,以满足特定需求。
工作流程
- 用户请求:用户在浏览器的地址栏中输入URL地址并回车,发送HTTP请求(通常是GET请求)到Django服务器。
- URL路由:Django服务器接收到请求后,根据
urls.py
(URLconf)中的配置和请求的URL去匹配对应的视图函数(view function)。- 视图处理:匹配到的视图函数负责处理具体的业务逻辑。这可能包括查询数据库、处理数据、执行其他后端任务等。视图函数通常会返回一个包含模板路径和上下文数据(context data)的字典。
- 模板渲染:Django服务器根据视图函数返回的模板路径加载对应的模板文件。同时,服务器将视图函数返回的上下文数据填充到模板中。模板系统使用这些上下文数据来动态生成HTML页面。
- HTTP响应:Django服务器将渲染后的HTML页面作为HTTP响应返回给浏览器。这个响应包含了生成的HTML内容以及其他可能的HTTP头部信息。
- 页面展示:浏览器接收到HTTP响应后,解析其中的HTML内容,并将其展示给用户。这通常包括解析HTML标签、CSS样式和JavaScript脚本,以及渲染页面上的动态内容。
注意:当前显示的页面 = 模板 + 数据
模板分为两部分:
- 静态页面:主要包括了CSS,HTML,JS,图片
- 动态填充:主要是通过模板语言去动态的产生一些页面上的内容
模板标签
标签 | 描述 |
---|---|
autoescape |
控制自动转义是否应用于变量。通常用于控制整个模板或模板某部分的HTML转义 |
block |
通常用于定义可以在多个地方重用的模板块 |
comment |
添加不会渲染到最终HTML中的注释 |
csrf_token |
在表单中包含一个跨站请求伪造令牌,用于验证表单提交来自合法站点 |
cycle |
在每次迭代时,输出循环中的下一个值。通常用于为列表中的项交替显示不同的样式 |
debug |
在调试模式下输出有用的调试信息。通常仅在开发环境中使用 |
extends |
指示当前模板继承自哪个父模板 |
filter |
应用一个或多个过滤器到变量上,然后输出。过滤器可以更改变量的输出 |
firstof |
输出给定变量列表中的第一个非空变量 |
for |
开始一个for循环,遍历一个序列(如列表或元组)中的每个元素 |
if |
根据条件判断输出不同的内容 |
ifchanged |
仅当值与前一个迭代中的值不同时,才输出内容。通常用于在循环中显示已更改的内容 |
include |
插入另一个模板的内容。这通常用于重用模板片段 |
load |
加载一个自定义模板标签库,以便在模板中使用该库中的标签 |
lorem |
输出一段随机的Lorem Ipsum文本 |
now |
输出当前的日期和时间。可以格式化输出 |
regroup |
通常用于按一个属性对对象列表进行分组 |
resetcycle |
重置cycle标签的计数器 |
spaceless |
移除HTML标签之间的空白和换行符,以减小输出HTML的大小 |
templatetag |
输出一个模板标签。这通常用于在模板文档中显示标签的用法 |
url |
将路径转换为绝对URL。通常用于生成指向视图函数的URL |
verbatim |
关闭模板引擎的自动转义,使得在块内的内容原样输出 |
widthratio |
根据给定的值和最大值计算宽度比率,通常用于按比例分配宽度 |
with |
在模板中定义一个或多个变量,这些变量在with块内有效 |
循环变量
属性 | 描述 |
---|---|
forloop.counter |
当前循环的计数器,从1开始 |
forloop.counter0 |
当前循环的计数器,从0开始 |
forloop.first |
如果循环在第一次迭代中,则为True |
forloop.last |
如果循环在最后一次迭代中,则为True |
forloop.parentloop |
对于嵌套循环,引用外部循环的forloop 对象 |
forloop.revcounter |
当前循环从末尾开始计数的次数,从1开始 |
forloop.revcounter0 |
当前循环从末尾开始计数的次数,从0开始 |
示例代码
1.在
django_introduction/urls.py
文件添加路由
路径参考上述Django 快速启动
中的创建应用
章节,最后的项目结构
path('template/', views.template)
2.在
my_app/views.py
文件添加视图函数
路径参考上述Django 快速启动
中的创建应用
章节,最后的项目结构
def template(request):
# 模拟通过业务逻辑...得出的结果数据
# 1.变量
name = "孙尚香"
# 2.列表
jobs = ["外卖员", "快递员", "出租车司机", "保安"]
# 3.字典
user_info = {"name": "郭嘉", "job": "保安", "salary": 3500}
# 4.列表嵌套字典
users = [
{"name": "刘备", "job": "外卖员", "salary": 3500},
{"name": "关羽", "job": "快递员", "salary": 2800},
{"name": "张飞", "job": "出租车司机", "salary": 5000}
]
return render(
request=request,
template_name="template.html",
context={"name": name, "jobs": jobs, "user_info": user_info, "users": users}
)
3.在
my_app/templates/目录下添加template.html
文件,代码如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>模板语法学习</title>
</head>
<body>
<h1>模板语法学习</h1>
<hr/>
<h1>引用变量</h1>
<p>姓名:{{ name }}</p>
{# 获取整个容器内的数据 #}
<p>职业:{{ jobs }}</p>
<hr/>
<h1>引用列表</h1>
{# 通过下标获取列表内的数据,但需要注意,获取下标元素略有不同 #}
<p>职业1:{{ jobs.0 }}</p>
<p>职业2:{{ jobs.1 }}</p>
<p>职业3:{{ jobs.2 }}</p>
<p>职业4:{{ jobs.3 }}</p>
<hr/>
<h1>引用循环</h1>
{# for 循环 #}
{% for job in jobs %}
{# forloop.counter 的值是一个整数,表示循环的次数。这个属性的值从 1 开始 #}
<p>职业 {{ forloop.counter }}:{{ job }}</p>
{% endfor %}
<hr/>
<h1>引用字典</h1>
<p>用户姓名:{{ user_info.name }}</p>
<p>用户职业:{{ user_info.job }}</p>
<p>用户薪资:{{ user_info.salary }}</p>
<p>字典中所有的键:{{ user_info.keys }}</p>
<p>字典中所有的值:{{ user_info.values }}</p>
<p>字典中所有的键值对:{{ user_info.items }}</p>
<hr/>
<h1>遍历字典</h1>
{% for key, value in user_info.items %}
<p>{{ key }} -> {{ value }}</p>
{% endfor %}
<hr/>
<h1>操作列表嵌套字典</h1>
<p>嵌套数据:{{ users }}</p>
<p>取出一个:{{ users.0 }}</p>
<p>取出子项:{{ users.0.name }}</p>
{% for user in users %}
<p>姓名:{{ user.name }},职业:{{ user.job }},薪资:{{ user.salary }}</p>
{% endfor %}
<hr/>
<h1>条件判断</h1>
{% if name == "赵子龙" %}
<p>我是赵子龙</p>
{% elif name == "黄忠" %}
<p>我是黄忠</p>
{% else %}
<p>我不是赵子龙,也不是黄忠,我是{{ name }}</p>
{% endif %}
<hr/>
</body>
</html>
页面效果
过滤器
为模版过滤器提供参数的方式是:过滤器后加个冒号,再紧跟参数,中间不能有空格
关键字 | 描述 |
---|---|
add |
在给定的值上加上一个数值或字符串 |
addslashes |
在字符串中的每个引号前添加一个反斜杠,用于转义字符串中的引号 |
capfirst |
将字符串的第一个字符转换为大写 |
center |
将字符串居中对齐在指定的宽度内,使用空格填充 |
ljust |
使用空格将字符串左对齐到指定的宽度 |
rjust |
标记此文本是安全的,不应进行 HTML 转义 |
cut |
从字符串中移除指定的字符或短语 |
title |
返回所有单词首字母大写 |
lower |
将字符串转换为小写 |
upper |
将字符串转换为大写 |
first |
返回序列的第一个项目(对于字符串,返回第一个字符) |
last |
返回序列的最后一个项目(对于字符串,返回最后一个字符) |
default |
如果变量值为False,则返回指定的默认值 |
default_if_none |
如果变量值为None,则返回指定的默认值 |
dictsort |
根据给定的键对字典进行排序,并返回排序后的列表 |
dictsortreversed |
根据给定的键对字典进行反向排序,并返回排序后的列表 |
divisibleby |
如果该值可以被指定的数字整除,则返回 True ,否则返回 False |
floatformat |
将浮点数四舍五入到指定的小数位数 |
get_digit |
返回数字的指定位置的数字 |
join |
使用指定的分隔符将序列中的项目连接成字符串 |
length |
返回序列中的项目数或字符串中的字符数 |
length_is |
如果序列的长度或字符串的字符数等于指定的值,返回True |
linenumbers |
在每行文本前添加行号 |
random |
从序列中随机选择一个项目 |
safe |
标记字符串为安全的,不会对其进行HTML转义 |
safeseq |
标记序列的每个项目为安全的,不会对其进行HTML转义 |
slice |
将字符串切片并转换为小写字母组成的单词 |
stringformat |
使用指定的格式字符串格式化字符串 |
date |
以指定格式返回日期 |
time |
以指定格式返回数据 |
timesince |
返回曾经的时间差 |
timeuntil |
返回未来时间差 |
truncatechars |
将字符串缩短为指定数量的字符,而不考虑任何 HTML 标记的长度 |
truncatechars_html |
将字符串缩短为指定数量的单词 |
truncatewords |
将字符串缩短为指定数量的单词,而不考虑任何 HTML 标记 |
truncatewords_html |
将对象的项目返回为无序列的 HTML 列表 |
示例代码
1.在
django_introduction/urls.py
文件添加路由
路径参考上述Django 快速启动
中的创建应用
章节,最后的项目结构
path('filter_key/', views.filter_key)
2.在
my_app/views.py
文件添加视图函数
路径参考上述Django 快速启动
中的创建应用
章节,最后的项目结构
def filter_key(request):
name = "zhang san"
age = 18
hello = '你好,勇士,我是"python"'
jobs = ["外卖员", "快递员", "出租车司机", "保安"]
boolean = False
current_date = datetime.date.today()
current_time = time.time()
return render(request=request, template_name="filter_key.html",
context={
"name": name,
"age": age,
"jobs": jobs,
"hello": hello,
"boolean": boolean,
"current_date": current_date,
"current_time": current_time
})
3.在
my_app/templates/目录下添加filter_key.html
文件,代码如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>过滤器</title>
</head>
<body>
<hr/>
<h1>使用add关键字</h1>
<p>age:{{ age }}</p>
<p>age+5:{{ age|add:"5" }}</p>
<hr/>
<h1>使用addslashes关键字</h1>
<p>hello:{{ hello }} </p>
<p>转义引号:{{ hello|addslashes }}</p>
<hr/>
<h1>使用capfirst关键字</h1>
<p>name:{{ name }} </p>
<p>大写第一个字母:{{ name|capfirst }} </p>
<hr/>
<h1>使用title关键字</h1>
<p>name:{{ name }} </p>
<p>大写每个单词首字母:{{ name|title }} </p>
<hr/>
<h1>使用upper关键字</h1>
<p>name:{{ name }} </p>
<p>大写所有字母:{{ name|upper }} </p>
<hr/>
<h1>使用cut关键字</h1>
<p>name:{{ name }} </p>
<p>剔除指定字符:{{ name|cut:"a" }} </p>
<hr/>
<h1>使用first关键字</h1>
<p>jobs:{{ jobs }} </p>
<p>获取第一个子项:{{ jobs|first }} </p>
<hr/>
<h1>使用last关键字</h1>
<p>jobs:{{ jobs }} </p>
<p>获取最后一个子项:{{ jobs|last }} </p>
<hr/>
<h1>使用default关键字</h1>
<p>boolean:{{ boolean }} </p>
<p>boolean的值是false就给一个默认值:{{ boolean|default:"默认值" }} </p>
<hr/>
<h1>使用length关键字</h1>
<p>jobs:{{ jobs }} </p>
<p>jobs长度:{{ jobs|length }} </p>
<p>jobs长度是4吗:{{ jobs|length_is:"4" }} </p>
<hr/>
<h1>使用join关键字</h1>
<p>name:{{ jobs }} </p>
<p>拼接列表元素:{{ jobs|join:", " }} </p>
<hr/>
<h1>使用random关键字</h1>
<p>jobs:{{ jobs }} </p>
<p>随机一个子项:{{ jobs|random }} </p>
<hr/>
<h1>使用date关键字</h1>
<p>current_date:{{ current_date }} </p>
<p>格式化日期:{{ current_date|date:"Y-m-d" }} </p>
<hr/>
<h1>使用slice关键字</h1>
<p>name:{{ name }} </p>
<p>切片:{{ name|slice:"3:6"}} </p>
<p>切片并转大写:{{ name|slice:"3:6"|upper}} </p>
<hr/>
<h1>使用center关键字</h1>
<p>name:{{ name }} </p>
<p>name:{{ name|center:"20" }} </p>
<p>name:{{ name|ljust:"20" }} </p>
<p>name:{{ name|rjust:"20" }} </p>
</body>
</html>
页面效果
模板继承
在 Django 模板系统中,模板继承允许你定义一个基础模板;
其中包含网站中所有页面都共有的元素(如页眉、页脚、导航栏等);
然后其他模板可以继承这个基础模板,并添加或覆盖特定的内容块。
1.创建基础模块
创建一个基础模板,通常命名为
base.html
。这个模板将包含整个网站通用的元素。
<!-- base.html -->
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}默认标题{% endblock %}</title>
</head>
<body>
<header>
<h1>父模板内容-页眉</h1>
<!-- 其他页眉内容 -->
</header>
<div id="content">
<!-- 这里的内容将被子模板覆盖 -->
{% block content %}
<h1>父模板内容-正文</h1>
{% endblock %}
</div>
<footer>
<h1>父模板内容-页脚</h1>
<!-- 页脚内容 -->
</footer>
</body>
</html>
2.创建子模板
子模板继承基础模板,并且可以添加或覆盖特定的块。
要继承一个模板,使用
{% extends %}
标签。
<!-- child.html -->
{% extends "base.html" %}
{% block title %}关于模板继承{% endblock %}
{% block content %}
<h2>子模板内容-正文1</h2>
<h2>子模板内容-正文2</h2>
<h2>子模板内容-正文3</h2>
{% endblock %}
3.在
django_introduction/urls.py
文件添加路由
路径参考上述Django 快速启动
中的创建应用
章节,最后的项目结构
path('extends/', views.extends)
4.在
my_app/views.py
文件添加视图函数
路径参考上述Django 快速启动
中的创建应用
章节,最后的项目结构
def extends(requests):
return render(request=requests, template_name="child.html")
5.页面效果
自定义标签和过滤器
自定义标签必须在app应用中的创建一个名叫
templatetags
的目录;另外,templatetags 文件夹名字不能修改,这是django规定的。
在
templatetags
下添加一个python
文件,根据需求进行编写标签代码逻辑。
import datetime
from django import template
# 将注册类实例化为register对象
# register = template.Library() 创建一个全局register变量,它是用来注册你自定义标签和过滤器的,只有向系统注册过的tags,系统才认得你。
# register 不能做任何修改,一旦修改,该包就无法引用
register = template.Library()
# 使用装饰器注册自定义标签
@register.simple_tag
def hello():
return "hello world"
# 使用装饰器注册自定义标签
@register.simple_tag
def curr_date(args): # args可传参数,根据实际需求而定
return datetime.datetime.now().strftime(args)
@register.simple_tag
def items(args):
items = ["Python", "Java", "Go", "C++"]
if not args or args > len(items):
return items
else:
return items[args]
# 使用装饰器注册自定义过滤器
@register.filter
def date_filter(value):
if value == "zhang san":
return "张三"
else:
return "世界上人人都是张三,或者世界上没有张三"
在my_app/templates/filter_key.html文件中添加如下代码:
{# 加载自定义标签所在的文件名,由于templatetags的文件名是固定的,django可以直接找到过自定义标签文件所在的位置 #}
{% load custom_tags %}
<hr/>
<h1>自定义标签</h1>
<p>hello:{% hello %}</p>
<p>当前日期:{% curr_date "%Y-%m-%d" %}</p>
<p>当前日期时间:{% curr_date "%Y-%m-%d %H:%M:%S" %}</p>
<p>获取指定列表元素:{% items 1 %}</p>
<p>获取全部列表元素:{% items None %}</p>
<p>自定义过滤器:{{ name|add:"flag"|date_filter }}</p>
页面效果
Django 数据模型
Django中的数据模型指的是一种定义数据源的数据,它包含要存储数据的一些属性和行为。
通常,每个模型对应数据库中的一张表;
每个模型都是django.db.models.Model的一个Python子类;
模型的每个属性都表示为数据库中的一个字段。
Django数据模型的用途和含义如下:
- **用途:**数据模型主要用于数据的存储、查询、更新和删除等操作,通过模型可以更方便地管理数据,并避免出现数据不一致的情况。此外,Django还通过模型来管理数据库迁移,当应用程序的模型发生变化时,Django可以自动检测出这些变化,并生成迁移文件,以便将这些变化应用到数据库中3。
- **含义:**数据模型是MVC框架中重要的一部分,主要负责程序中用于处理数据逻辑的部分。数据模型还包含储存数据的字段和字段限制,是数据的唯一的、权威的信息源。
ORM 简介说明
Django框架中的ORM(Object-Relational Mapping,对象关系映射)是一个非常重要的组件;
它提供了一种将Python类映射到数据库表的方式,使得开发者可以使用Python代码来操作数据库,而无需直接编写SQL语句。
ORM的优点:
- 简化数据库操作:ORM允许开发者使用Python对象的方式来操作数据库,如创建、查询、更新和删除记录。这样,开发者可以专注于业务逻辑,而无需过多关注数据库操作的细节。
- 提高开发效率:ORM可以自动生成并执行SQL语句,从而减少了手动编写和调试SQL语句的时间。
- 提高可维护性:ORM将数据库操作封装在对象中,使得代码更加清晰、易于维护和升级。
- 提高可移植性:ORM可以屏蔽不同数据库之间的差异,使得应用程序更加容易移植到不同的数据库平台上。
- 数据库无关性:ORM使得代码与具体的数据库实现解耦,这意味着在不更改Python代码的情况下,可以更换数据库引擎。
- 数据验证和安全性:ORM通常提供数据验证和清理功能,有助于防止无效或恶意的数据进入数据库。
ORM框架可以帮助我们做两件事情:
- 创建、修改、删除数据库中的表(无需手动编写SQL语句)【无法新建数据库】
- 操作数据表中的数据(无需手动编写SQL语句)
ORM 内置的Meta选项
属性 | 说明 |
---|---|
db_table |
指定模型使用的数据库表名 |
db_table_comment |
为数据库表添加注释(从Django 4.2开始支持) |
ordering |
指定模型的默认排序字段 |
verbose_name |
定义模型的单数显示名称 |
verbose_name_plural |
定义模型的复数显示名称 |
db_tablespace |
指定数据库表使用的表空间 |
app_label |
指定模型所属的应用名称,通常用于多应用项目 |
default_manager_name |
定义模型默认管理器的名称 |
base_manager_name |
定义模型基础管理器的属性名,默认为objects |
default_related_name |
定义外键关联默认的反向关联名称 |
get_latest_by |
指定last() 和earliest() 方法用于获取最近记录的字段 |
managed |
指定Django的migrate 命令是否处理该表的创建和变更 |
order_with_respect_to |
指定一个外键,用于确定模型的排序顺序 |
permissions |
为模型定义额外的权限 |
default_permissions |
定义模型默认权限的列表 |
required_db_features |
指定模型需要的数据库特性列表 |
required_db_vendor |
指定模型支持的数据库类型 |
indexes |
为模型字段定义数据库索引 |
unique_together |
定义模型字段的组合唯一性约束 |
constraints |
为模型定义数据库约束 |
abstract |
指定模型为抽象基类,不创建数据库表 |
proxy |
指定模型为代理模型,使用父类的数据库表 |
from django.db import models
# 创建一个简单的模型
class Blog(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
date_posted = models.DateTimeField(auto_now_add=True)
class Meta:
# 指定数据库表名
db_table = 'custom_blog_table'
# 添加表注释
db_table_comment = 'This is a table for blog entries'
# 指定默认排序方式
ordering = ['-date_posted']
# 定义模型的显示名称
verbose_name = 'blog entry'
verbose_name_plural = 'blog entries'
# 指定数据库表空间
# db_tablespace = 'my_tablespace'
# 定义默认管理器名称
default_manager_name = 'blog_entries'
# 定义默认相关名称,用于反向关联
default_related_name = 'related_blogs'
# 指定获取最新对象的字段
get_latest_by = 'date_posted'
# 是否由Django管理(例如,是否创建、变更表)
managed = True
# 根据外键排序
# order_with_respect_to = 'some_field'
# 定义额外的权限
permissions = (
('can_publish_blog', 'Can publish blog entries'),
)
# 定义默认权限
default_permissions = ('add', 'change', 'delete')
# 指定数据库特征
# required_db_features = {'supports_transactions': True}
# 指定支持的数据库类型
# required_db_vendor = 'postgresql'
# 创建索引
indexes = [
models.Index(fields=['title']),
models.Index(fields=['date_posted']),
]
# 定义组合唯一性约束
unique_together = (('title', 'date_posted'),)
# 添加其他约束
# constraints = [
# models.UniqueConstraint(fields=['title'], name='unique_title'),
# ]
# 创建一个抽象基类模型
class AbstractBase(models.Model):
class Meta:
abstract = True
# 抽象模型不创建表
# 创建一个代理模型,继承自另一个模型,但使用相同的表
class ProxyBlog(Blog):
class Meta:
proxy = True
# 代理模型不创建新表,而是使用父类的表
请注意,并不是所有的
Meta
选项都需要在每个模型中都进行设置,应该根据具体需求来选择性地使用这些选项。
ORM 内置的字段类型
类名 | 说明 |
---|---|
AutoField |
自增的整数字段,通常用于主键 |
BigAutoField |
大整型的自增字段,用于需要更大范围的主键 |
SmallAutoField |
小整型的自增字段,用于需要较小范围的主键 |
BinaryField |
用于存储二进制数据的字段 |
BooleanField |
用于存储布尔值(True或False)的字段 |
CharField |
用于存储字符串的字段,max_length 参数是必需的,用于指定最大字符数 |
TimeField |
用于存储时间的字段,不包含日期信息 |
DateField |
用于存储日期的字段,不包含时间信息 |
DateTimeField |
用于存储日期和时间的字段 |
DecimalField |
用于存储时间间隔或时长的字段 |
EmailField |
用于存储电子邮件地址的字段,实际上是 CharField 的一个变种,带有默认的验证 |
FileField |
用于存储文件路径的字段,文件通常保存在文件系统上 |
FilePathField |
用于存储文件路径的字段,通常用于模型定义所在目录中的文件。 |
FloatField |
用于存储浮点数的字段 |
IntegerField |
用于存储整数的字段 |
BigIntegerField |
用于存储大整数的字段 |
JSONField |
用于存储JSON数据的字段,需要Django的 django.contrib.postgres 应用 |
PositiveBigIntegerField |
用于存储正的大整数的字段 |
PositiveIntegerField |
用于存储正整数的字段 |
PositiveSmallIntegerField |
用于存储正的小整数的字段 |
GenericIPAddressField |
用于存储IPv4或IPv6地址的字段 |
SlugField |
用于存储简短字符串(slug)的字段,通常用于URL路径 |
TextField |
用于存储长文本的字段 |
URLField |
用于存储URL的字段,实际上是 CharField 的一个变种,带有默认的验证 |
UUIDField |
用于存储UUID的字段 |
ForeignKey |
用于创建一对一或多对一的关系,on_delete 参数是必需的,用于指定当关联对象被删除时应采取的操作 |
ManyToManyField |
用于创建多对多的关系 |
OneToOneField |
用于创建一对一的关系,on_delete 参数也是必需的。 |
from django.db import models
# 创建一个简单的模型,展示不同字段类型的使用
class MyModel(models.Model):
# 自增字段
auto_field = models.AutoField(primary_key=True)
# 大整型自增字段
big_auto_field = models.BigAutoField()
# 小整型自增字段
small_auto_field = models.SmallAutoField()
# 二进制字段
binary_field = models.BinaryField()
# 真假值字段
boolean_field = models.BooleanField(default=False)
# 字符串字段,max_length参数指定最大字符数
char_field = models.CharField(max_length=100)
# 时间字段
time_field = models.TimeField()
# 日期字段
date_field = models.DateField()
# 日期时间字段
datetime_field = models.DateTimeField(auto_now_add=True)
# 数字字段,max_digits和decimal_places参数指定总位数和小数位数
decimal_field = models.DecimalField(max_digits=10, decimal_places=2)
# 耗时字段
duration_field = models.DurationField()
# 电子邮箱地址字段
email_field = models.EmailField()
# 文件上传字段
file_field = models.FileField(upload_to='uploads/')
# 文件路径字段,用于指定文件系统中的文件路径
file_path_field = models.FilePathField(path='/path/to/files/')
# 浮点数字段
float_field = models.FloatField()
# 整数字段
integer_field = models.IntegerField()
# 大整数字段
big_integer_field = models.BigIntegerField()
# JSON文本字段
json_field = models.JSONField()
# 大非负整数字段
positive_big_integer_field = models.PositiveBigIntegerField()
# 非负整数字段
positive_integer_field = models.PositiveIntegerField()
# 小非负整数字段(注意:此字段名应为PositiveSmallIntegerField,您的原始列表中有一个拼写错误)
positive_small_integer_field = models.PositiveSmallIntegerField()
# 通用IP地址字段
ip_address_field = models.GenericIPAddressField()
# Slug代表字段,通常用于URL的一部分
slug_field = models.SlugField()
# 文本字段
text_field = models.TextField()
# URL字段,会进行格式验证
url_field = models.URLField()
# UUID字段
uuid_field = models.UUIDField()
# 外键连接字段,关联另一个模型,on_delete参数指定关联对象被删除时的行为
related_model = models.ForeignKey('AnotherModel', on_delete=models.CASCADE)
# 多对多关系字段,关联另一个模型
many_to_many_field = models.ManyToManyField('AnotherModel')
# 一对一关系字段,关联另一个模型
one_to_one_field = models.OneToOneField('AnotherModel', on_delete=models.CASCADE)
# 另一个模型,用于展示多对多和一对一关系
class AnotherModel(models.Model):
name = models.CharField(max_length=100)
ORM 内置的字段参数
参数 | 说明 |
---|---|
verbose_name |
用于管理界面中字段的显示名称 |
null |
指定该字段是否允许为NULL 。对于数据库而言,这意味着该字段在数据库表中可以为空。默认为False ,表示字段必须有值 |
blank |
在表单验证中,blank 表示该字段是否可以为空。这与null 不同,null 是数据库层面的,而blank 是表单验证层面的。默认为False ,表示字段在表单提交时必须有值。 |
choices |
一个包含二元组的列表,用于限制字段的可能值。这些二元组通常用于显示和存储值之间的映射 |
default |
指定字段的默认值,当创建对象时未指定该字段的值时,将使用此默认值 |
help_text |
用于管理界面或表单字段旁边的帮助文本,以解释字段的用途或格式 |
primary_key |
指定该字段是否作为模型的主键。每个模型只能有一个主键字段。默认为False |
unique |
如果为True ,则该字段在整个表中必须是唯一的。这确保了字段值的唯一性 |
related_name |
在ForeignKey 或OneToOneField 中,related_name 定义了反向关系的名称。例如,如果A 模型有一个指向B 模型的外键,B 模型可以通过A 模型的related_name 属性访问A 模型的相关实例。 |
related_query_name |
用于定义当使用select_related 或prefetch_related 时,在关联查询中使用的名称。这影响查询集上的属性和方法名称。 |
on_delete |
在ForeignKey 中,on_delete 定义了当关联的对象被删除时应该采取的行为。例如,CASCADE 表示当关联对象被删除时,也删除当前对象;SET_NULL 表示将当前字段设置为NULL |
through |
仅适用于ManyToManyField 字段,用于指定用于创建多对多关系的中间表。默认情况下,Django会为这个关系创建一个自动生成的中间表,但你可以使用through 选项来指定一个自定义的中间表 |
db_column |
指定数据库中的列名,如果未指定,Django将使用字段名称 |
db_index |
如果为True ,则在该字段上创建数据库索引 |
from django.db import models
# 创建一个简单的模型,展示字段选项的使用
class Blog(models.Model):
# verbose_name:字段的展示名称
title = models.CharField(max_length=200, verbose_name='博客标题')
# null:是否允许为空,默认为False
# blank:是否允许在表单验证时为空,默认为False
content = models.TextField(null=True, blank=True, verbose_name='博客内容')
# choices:字段的可选值列表
STATUS_CHOICES = (
('draft', '草稿'),
('published', '已发布'),
)
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='draft', verbose_name='状态')
# default:字段的默认值
created_at = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
# help_text:字段的帮助文本
description = models.CharField(max_length=255, help_text='简要描述博客', verbose_name='描述')
# primary_key:是否为主键字段
# 如果不显式指定,Django会自动创建一个名为'id'的AutoField作为主键
# unique:是否要求字段值唯一
slug = models.SlugField(unique=True, verbose_name='链接标识符')
class Meta:
verbose_name = '博客'
verbose_name_plural = '博客列表'
# 另一个模型,与Blog模型存在外键关联
class Comment(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE, related_name='comments', verbose_name='所属博客')
# null:是否允许为空
# blank:是否允许在表单验证时为空
# choices:字段的可选值列表
# default:字段的默认值
# help_text:字段的帮助文本
name = models.CharField(max_length=100, null=False, blank=False, verbose_name='评论者姓名')
email = models.EmailField(max_length=254, blank=True, verbose_name='评论者邮箱')
content = models.TextField(verbose_name='评论内容')
created_at = models.DateTimeField(auto_now_add=True, verbose_name='评论时间')
class Meta:
verbose_name = '评论'
verbose_name_plural = '评论列表'
# 使用示例
# 创建一篇博客
blog = Blog(title='我的第一篇博客', status='draft')
blog.save()
# 在该博客下创建一个评论
comment = Comment(blog=blog, name='张三', content='这是一篇很好的博客!')
comment.save()
ORM 数据模型的关系
一对一关系(OneToOneField):
- 一对一关系是指一个模型实例只能关联一个另一个模型实例,而另一个模型实例也只能关联一个该模型实例。
- 例如,一个人只能有一个身份证号码,一个身份证号码也只能对应一个人。
一对多关系(ForeignKey):
- 一对多关系是指一个模型实例可以关联多个另一个模型实例,但另一个模型实例只能关联一个该模型实例。
- 例如,一个作者可以写多篇文章,但一篇文章只能有一个作者。
多对多关系(ManyToManyField):
- 多对多关系是指一个模型实例可以关联多个另一个模型实例,而另一个模型实例也可以关联多个该模型实例。
- 例如,一个学生可以选修多门课程,一门课程也可以被多个学生选修。
ORM 一对一关系的增删改查
定义两个模型:
Person(人)
和IDCard(身份证)
。每个人有一个身份证,每个身份证也只能属于一个人。
from django.db import models
# 定义Person模型
class Person(models.Model):
name = models.CharField(max_length=100)
idcard = models.OneToOneField('IDCard', on_delete=models.CASCADE, related_name='person')
def __str__(self):
return self.name
# 定义IDCard模型
class IDCard(models.Model):
number = models.CharField(max_length=20, unique=True) # 假设身份证号码是唯一的
person = models.OneToOneField(Person, on_delete=models.CASCADE, related_name='idcard')
def __str__(self):
return self.number
增加关系:使用OneToOneField字段的create()方法或save()方法增加关系
# 创建Person实例
person = Person.objects.create(name='John')
# 创建IDCard实例,并将其与Person实例关联
id_card = IDCard.objects.create(number='123456789', person=person)
# 或者使用Person实例直接创建并关联IDCard
person.idcard = IDCard.objects.create(number='123456789')
person.save()
删除关系:使用OneToOneField字段的delete()方法删除关系
# 删除IDCard实例,这将同时删除与Person实例的关联关系
id_card.delete()
# 或者删除Person实例,这也会删除与IDCard实例的关联关系
person.delete()
修改关系:使用OneToOneField字段的save()方法修改关系
# 创建一个新的Person实例
new_person = Person.objects.create(name='Tom')
# 将原有的IDCard实例关联到新的Person实例
id_card.person = new_person
id_card.save()
# 或者直接通过Person实例修改关联
new_person.idcard = id_card
new_person.save()
查询关系:使用OneToOneField字段进行查询及反向查询
# 通过Person实例查询关联的IDCard实例
person = Person.objects.get(name='John')
id_card = person.idcard
# 通过IDCard实例反向查询关联的Person实例
id_card = IDCard.objects.get(number='123456789')
person = id_card.person
ORM 一对多关系的增删改查
定义两个模型:
Author(作者)
和Article(文章)
。每个作者有多篇文章,每篇文章只属于一个作者。
from django.db import models
# 定义Author模型
class Author(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
# 定义Article模型
class Article(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='articles')
def __str__(self):
return self.title
增加关系:使用ForeignKey字段的create()方法或add()方法增加关系
author = Author.objects.create(name='John')
article = Article.objects.create(title='Hello', content='World', author=author)
删除关系:使用ForeignKey字段的delete()方法或set()方法删除关系
# 注意:这将删除整篇文章,而不仅仅是关系。
# 如果你只想删除关系,而不删除文章本身,应该设置author为None。
article.author = None
article.save()
# 或者使用clear()方法删除所有关联
author.articles.clear()
# 如果你真的想删除文章,你可以这样做:
article.delete()
修改关系:使用ForeignKey字段的set()方法修改关系
# 创建一个新的作者
new_author = Author.objects.create(name='Tom')
# 将文章关联到新的作者
article.author = new_author
article.save()
查询关系:使用ForeignKey字段的反向查询方法查询关系
# 获取一个作者的所有文章
author = Author.objects.get(name='John')
articles = author.articles.all()
for article in articles:
print(article.title)
ORM 多对多关系的增删改查
定义两个模型:
Student(学生)
和Course(课程)
。每个学生可以选修多门课程,每门课程也可以被多个学生选修。
from django.db import models
# 定义Student模型
class Student(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
# 定义Course模型
class Course(models.Model):
name = models.CharField(max_length=100)
students = models.ManyToManyField(Student, related_name='courses')
def __str__(self):
return self.name
增加关系:使用ManyToManyField字段的create()方法或add()方法增加关系
# 创建Student实例
student = Student.objects.create(name='John')
# 创建Course实例
course = Course.objects.create(name='Math')
# 将Course实例关联到Student实例中
student.courses.add(course)
# 或者使用create方法直接创建关联
student.courses.create(name='English')
删除关系:使用ManyToManyField字段的remove()方法或clear()方法删除关系
# 获取已经关联的Student和Course实例
student = Student.objects.get(name='John')
course = Course.objects.get(name='Math')
# 删除Student实例与Course实例之间的关系
student.courses.remove(course)
# 清除Student实例与所有课程的关系
student.courses.clear()
修改关系:使用ManyToManyField字段的set()方法修改关系
# 获取已经关联的Student实例
student = Student.objects.get(name='John')
# 创建一个新的Course实例
new_course = Course.objects.create(name='History')
# 将Student实例的courses属性修改为只包含新的Course实例
student.courses.set([new_course])
# 或者你可以设置多个课程
student.courses.set([
Course.objects.create(name='Science'),
Course.objects.create(name='Physics'),
])
查询关系:使用ManyToManyField字段的反向查询方法查询关系
# 首先查询了一个Course实例
course = Course.objects.get(name='Math')
# 使用student_set属性查询了与该Course实例相关联的所有Student实例
students = course.student_set.all()
for student in students:
print(student.name)
# 反向查询也可以直接从Student实例开始
student = Student.objects.get(name='John')
courses = student.courses.all()
for course in courses:
print(course.name)
ORM 查询函数总览
all():查询所有的结果集,返回模型对应的所有对象
result = MyModel.objects.all()
values(*field):查询所有结果,支持指定字段,返回一个包含字典的QuerySet,每个字典代表一个对象,键是字段名,值是字段值。
result = MyModel.objects.values('field1', 'field2')
values_list(*field):与values()相似,返回元组序列,返回一个包含元组的QuerySet,每个元组代表一个对象,元素是字段值
result = MyModel.objects.values_list('field1', 'field2')
filter(**kwargs): 筛选符合条件的结果,基于一个或多个条件筛选对象
result = MyModel.objects.filter(field1='value1')
get(**kwargs):筛选结果,返回结果有且只有一个,根据条件获取单个对象。如果有多个对象满足条件或没有对象满足条件,会抛出异常。
try:
result = MyModel.objects.get(field1='value1')
except MyModel.DoesNotExist:
# 处理对象不存在的情况
except MyModel.MultipleObjectsReturned:
# 处理返回多个对象的情况
exclude(**kwargs):返回与条件不匹配的对象
result = MyModel.objects.exclude(field1='value1')
order_by(*field): 排序
result = MyModel.objects.order_by('field1') # 升序
result = MyModel.objects.order_by('-field1') # 降序
distinct(): 获取去重数据
result = MyModel.objects.values('field1', 'field2').distinct()
count(): 返回记录数(数据类型为数字,非queryset)
result = MyModel.objects.filter(field1='value1').count()
exists(): 判断查询结果是否有数据,如果查询结果包含至少一个对象,则返回True,否则返回False。
result = MyModel.objects.filter(field1='value1').exists()
aggregate**(): 使用聚合函数,对QuerySet中的对象应用聚合函数,并返回单一的结果
from django.db.models import Avg
result = MyModel.objects.aggregate(Avg('field1'))
from django.db.models import * # 引入聚合函数
res = models.User.objects.aggregate(Avg('id'))# 平均值
res = models.User.objects.aggregate(Count('id'))# 计数
res = models.User.objects.aggregate(Count('id',distinct=True))# 去重后的计数
res = models.User.objects.aggregate(Max('id'))# 最大值
res = models.User.objects.aggregate(Min('id'))# 最小值
res = models.User.objects.aggregate(Sum('id'))# 求和
res = models.User.objects.aggregate(Variance('id'))# 方差
res = models.User.objects.aggregate(StdDev('id'))# 标准方差
并集
result1 = MyModel.objects.filter(name='张三')
result2 = MyModel.objects.filter(name='李四')
result3 = (result1 | result2).values()
print(result3)
交集
result1 = MyModel.objects.all()
result2 = MyModel.objects.filter(name='李四')
result3 = (result1 & result2).values()
print(result3)
差集
raw(): 接收一个原始的SQL查询
result = MyModel.objects.raw('select * from student')
示例代码
from django.db import models
from django.db.models import Avg, Sum
# 构建模型
class User(models.Model):
name = models.CharField(max_length=100)
age = models.IntegerField()
email = models.EmailField(unique=True)
def __str__(self):
return self.name
# 示例代码
# 查询所有用户
all_users = User.objects.all()
# 查询指定字段
users_with_name_age = User.objects.values('name', 'age')
# 查询指定字段并排序
sorted_users = User.objects.order_by('-age').values('name', 'age')
# 查询唯一的名字和年龄组合
unique_users = User.objects.values('name', 'age').distinct()
# 查询名字为张三的用户数量
count_zhangsan = User.objects.filter(name='张三').count()
# 查询是否存在名字为李四的用户
exists_lisi = User.objects.filter(name='李四').exists()
# 使用聚合函数计算平均年龄
average_age = User.objects.aggregate(Avg('age'))
# 并集和交集查询
zhangsan_users = User.objects.filter(name='张三')
lisi_users = User.objects.filter(name='李四')
# 并集
union_users = zhangsan_users | lisi_users
# 交集
intersection_users = zhangsan_users & lisi_users
# 原始SQL查询
raw_users = User.objects.raw('SELECT * FROM app_user WHERE age > 25')
# 将QuerySet转换为列表
list_of_users = list(User.objects.all())
ORM Filter 函数详解
__exact
:精确等于
# 查找 name 字段等于 'John' 的对象
MyModel.objects.filter(name__exact='John')
__ne
或__neq
:不等于
# 查找 name 字段不等于 'John' 的对象
MyModel.objects.filter(name__ne='John')
__gt
:大于
# 查找 age 字段大于 30 的对象
MyModel.objects.filter(age__gt=30)
__gte
:大于或等于
# 查找 age 字段大于或等于 30 的对象
MyModel.objects.filter(age__gte=30)
__lt
:小于
# 查找 age 字段小于 20 的对象
MyModel.objects.filter(age__lt=20)
__lte
:小于或等于
# 查找 age 字段小于或等于 20 的对象
MyModel.objects.filter(age__lte=20)
模糊查询(包含)
__contains
:大小写敏感的包含。__icontains
:大小写不敏感的包含。
# 查找 name 字段包含 'Doe' 的对象
MyModel.objects.filter(name__contains='Doe')
# 查找 name 字段包含 'Doe' 的对象,不区分大小写
MyModel.objects.filter(name__icontains='Doe')
以…开始
__startswith
:大小写敏感的以…开始。__istartswith
:大小写不敏感的以…开始。
# 查找 name 字段以 'John' 开始的对象
MyModel.objects.filter(name__startswith='John')
# 查找 name 字段以 'John' 开始的对象,不区分大小写
MyModel.objects.filter(name__istartswith='John')
以…结束
__endswith
:大小写敏感的以…结束。__iendswith
:大小写不敏感的以…结束。
# 查找 name 字段以 'son' 结束的对象
MyModel.objects.filter(name__endswith='son')
# 查找 name 字段以 'son' 结束的对象,不区分大小写
MyModel.objects.filter(name__iendswith='son')
__range
:在某个范围内(包含两端)
# 查找 age 字段在 20 到 30 之间的对象
MyModel.objects.filter(age__range=(20, 30))
__isnull
:判断字段是否为空
# 查找 name 字段为空的对象
MyModel.objects.filter(name__isnull=True)
# 查找 name 字段不为空的对象
MyModel.objects.filter(name__isnull=False)
__and
:逻辑与,通常省略,多个条件默认使用AND连接
# 查找 age 大于 30 或者 name 为 'John' 的对象
MyModel.objects.filter(age__gt=30, name__exact='John') # 默认AND
__or
:逻辑或
# Django ORM 不直接支持 `__or`,但可以通过 `Q` 对象来实现
from django.db.models import Q
MyModel.objects.filter(Q(age__gt=30) | Q(name__exact='John')) # 使用 `|` 表示 OR
在Django ORM中,
非
操作可以通过在查询条件前加上__ne
或__exact
的反义来实现。另外,还可以使用
exclude()
方法来排除满足特定条件的对象。
# 查询所有标题不等于'Django'的书籍
books = Book.objects.filter(title__ne='Django')
# 或者使用exclude方法
books = Book.objects.exclude(title='Django')
__in
:用于检查字段值是否在一个给定的列表中
# 查询ID在[1, 2, 3]中的书籍
books = Book.objects.filter(id__in=[1, 2, 3])
__year
、__month
、__day
:用于日期字段的年、月、日提取
# 查询2020年出版的书籍
books = Book.objects.filter(published_date__year=2020)
__isnull
:用于检查字段是否为NULL
# 查询没有封面图片的书籍(假设cover_image字段为ImageField)
books = Book.objects.filter(cover_image__isnull=True)
__regex
、__iregex
:用于正则表达式匹配,其中__iregex
是不区分大小写的
# 查询标题以'The'开头的书籍
books = Book.objects.filter(title__iregex=r'^The')
ORM 模型使用流程
1.手动创建数据库
使用mysql自带或其他可视化工具执行SQL命令
create database django_example;
2.django连接数据库
修改Django项目中的setting.py文件内DATABASE属性
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # 数据库引擎,这里选择的是mysql
'NAME': 'django_example', # 数据库名称
'USER': 'root', # 数据库用户名
'PASSWORD': 'root', # 数据库密码
'HOST': '10.211.55.3', # 数据库地址
'PORT': '3306', # 数据库端口
}
}
##################################################
# 请注意,在Django的较新版本中,也支持使用mysqlclient模块。
# 然而,由于Windows系统可能存在的兼容性问题,可能会在安装该模块时遇到一些困难。
# 如果遇到安装问题,建议访问pypi.org并下载与你的环境相匹配的预编译wheel包进行尝试。
# 作为替代方案,你也可以选择使用pymysql模块。
# 在使用pymysql时,你需要在Django项目的__init__.py文件中添加以下代码
# 以确保与Django的数据库后端兼容:
import pymysql
pymysql.install_as_MySQLdb()
# 这样,Django就能正确地识别并使用pymysql作为MySQL的数据库后端了。
3.定义模型
在my_app应用的models.py文件中定义数据模型。这些模型将映射到数据库中的表。
from django.db import models
# Create your models here.
class UserInfo(models.Model):
name = models.CharField(max_length=32)
password = models.CharField(max_length=64)
age = models.IntegerField(default=18)
class Department(models.Model):
title = models.CharField(max_length=16)
4.迁移数据库
命令1:python manage.py makemigrations
- 生成迁移文件。
- 当修改了模型(通常在
models.py
文件中)并希望将这些修改应用到数据库时,需要首先生成迁移文件。- 这些文件位于每个应用目录下的
migrations
文件夹中,并描述了从上一次迁移至今模型所发生的所有变化。命令2:python manage.py migrate
- 将迁移应用到数据库。
- 它会读取所有未应用的迁移文件,并按照依赖关系(即一个迁移依赖于另一个迁移)的顺序应用它们到数据库。
- 这通常意味着它会修改数据库结构以匹配当前模型的状态
示例代码
1.在
django_introduction/urls.py
文件添加路由
路径参考上述Django 快速启动
中的创建应用
章节,最后的项目结构
# 案例:用户管理
path("info/list/", views.info_list),
path("info/add/", views.info_add),
path("info/delete/", views.info_delete),
path("info/update/", views.info_update),
2.在
my_app/views.py
文件添加视图函数并将模型类导入
路径参考上述Django 快速启动
中的创建应用
章节,最后的项目结构
def info_list(request):
# 1.获取数据库中所有的用户数据
# [对象,对象,对象...]
data_list = UserInfo.objects.all()
print(data_list)
print(type(data_list))
return render(request, "info_list.html", context={"data_list": data_list})
def info_add(request):
if request.method == "GET":
return render(request, "info_add.html")
# 获取用户提交的表单数据
user = request.POST.get("user")
pwd = request.POST.get("pwd")
age = request.POST.get("age")
# 添加到数据库
UserInfo.objects.create(name=user, password=pwd, age=age)
return redirect("/info/list/")
def info_delete(request):
nid = request.GET.get("nid")
UserInfo.objects.filter(id=nid).first().delete()
return redirect("/info/list/")
def info_update(request):
if request.method == "GET":
return render(request, "info_update.html")
# 获取用户提交的表单数据
nid = request.POST.get("nid")
user = request.POST.get("user")
pwd = request.POST.get("pwd")
age = request.POST.get("age")
# 添加到数据库
UserInfo.objects.filter(id=nid).update(name=user, password=pwd, age=age)
return redirect("/info/list/")
3.在
my_app/templates/目录下添加info_list.html、info_add.html、info_update.html
文件
<!-- info_list.html文件 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>用户信息列表</title>
</head>
<body>
<h1>用户信息</h1>
<a href="/info/add/">添加用户信息</a>
<a href="/info/update/">修改用户信息</a>
<table border="1">
<thead>
<tr>
<th>ID</th>
<th>姓名</th>
<th>密码</th>
<th>年龄</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for user in data_list %}
<tr>
<td>{{ user.id }}</td>
<td>{{ user.name }}</td>
<td>{{ user.password }}</td>
<td>{{ user.age }}</td>
<td>
<a href="/info/delete?nid={{ user.id }}">删除</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</body>
</html>
<!-- info_add.html文件 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>添加用户</title>
</head>
<body>
<h1>添加用户</h1>
<a href="/info/list/">查看用户信息</a>
<form method="post" action="/info/add/">
{% csrf_token %}
<input type="text" name="user" placeholder="用户名">
<input type="text" name="pwd" placeholder="密码">
<input type="text" name="age" placeholder="年龄">
<input type="submit" value="提交">
</form>
</body>
</html>
<!-- info_update.html文件 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>用户信息修改</title>
</head>
<body>
<h1>用户信息修改</h1>
<a href="/info/list/">查看用户信息</a>
<form method="post" action="/info/update/">
{% csrf_token %}
<input type="text" name="nid" placeholder="ID">
<input type="text" name="user" placeholder="用户名">
<input type="text" name="pwd" placeholder="密码">
<input type="text" name="age" placeholder="年龄">
<input type="submit" value="提交">
</form>
</body>
</html>
页面效果
Django 后台管理
Django的Admin后台是一个自动生成的管理界面,可以让开发者方便地管理网站的数据和用户。
通过Admin后台,开发者可以进行以下操作:创建、编辑和删除数据库中的数据记录;
查看数据记录的详细信息;进行数据筛选、搜索和排序;定制Admin后台的界面和功能;
管理用户和用户权限;导入和导出数据
admin 启动
使用startproject命令创建项目时django admin就默认启用了;
详情见setting.py文件的INSTALLED_APPS属性
其中默认已经配置了以下应用:
- django.contrib.admin
- django.contrib.auth
- django.contrib.contenttypes
- django.contrib.sessions
- django.contrib.messages
另外在TEMPLATES配置中还依赖了两个插件:
- django.contrib.auth.context_processors.auth
- django.contrib.messages.context_processors.messages
admin 创建管理员
使用命令完成创建管理员账号
python manage.py createsuperuser
admin 登录
Django 后台管理登录页面
Django 后台管理登录成功首页
是否发现了后台管理系统中,只有Django自己的数据表?
我们可以在应用下,
my_app/admin.py
文件中完成注册
from django.contrib import admin
from my_app import models
# Register your models here.
admin.site.register(models.UserInfo)
admin.site.register(models.Department)
这时在刷新
后台管理
系统,我们自定义的表就会出现
admin 配置
列表页相关配置
参数 | 描述 |
---|---|
list_display |
这是Admin界面上模型列表页要显示的字段列表。可以包括模型的字段,如id 、str 等,也可以包括方法 |
list_display_links |
在列表页上,哪些字段应该显示为可点击的链接。默认为list_display 中的第一个字段 |
list_editable |
允许在列表页上直接编辑的字段 |
list_filter |
为模型提供的过滤器选项,用户可以通过这些选项来筛选列表中的对象 |
show_full_result_count |
当应用过滤器后,是否显示完整的对象数量,而不是“可能有更多的对象” |
preserve_filters |
当对象列表被更改(例如,通过添加或删除对象)时,是否保留当前的过滤器设置 |
date_hierarchy |
允许用户根据某个日期/时间字段来层次化地浏览对象 |
search_fields |
允许用户搜索的字段列表 |
search_help_text |
当用户搜索时显示的帮助文本 |
paginator |
用于分页的类,通常不需要更改 |
list_max_show_all |
当对象数量小于此值时,全部显示而不进行分页 |
list_per_page |
每页显示的对象数量 |
list_select_related |
优化查询性能,指定哪些字段应该使用select_related() 。 |
sortable_by |
允许用户根据哪些字段对列表进行排序。注意:在Django 3.1及更高版本中,此选项已被ordering 和get_queryset 方法替代。 |
ordering |
默认的排序字段 |
actions |
在列表页顶部或底部显示的自定义操作列表 |
actions_on_top |
控制自定义操作是否显示在列表的顶部 |
actions_on_bottom |
控制自定义操作是否显示在列表的底部 |
actions_selection_counter |
如果启用,自定义操作会显示已选择对象的数量 |
empty_value_display |
当模型字段的值为空时,在Admin界面上显示的默认值 |
详情页相关配置
参数 | 描述 |
---|---|
fields |
在详情页上显示的字段列表,支持嵌套分组 |
exclude |
从详情页中排除的字段列表 |
readonly_fields |
详情页上只读的字段列表 |
fieldsets |
用于对详情页上的字段进行分组的设置 |
filter_horizontal |
用于水平显示多对多关系的字段的过滤控件 |
filter_vertical |
用于垂直显示多对多关系的字段的过滤控件 |
inlines |
在详情页上显示的内联模型列表 |
radio_fields |
将某些字段显示为单选按钮,通常用于具有choices 属性的字段或外键字段 |
autocomplete_fields |
自动完成某些字段的值,提高用户输入的效率 |
prepopulated_fields |
当用户填写某个字段时,自动填充其他字段的值。 |
raw_id_fields |
将某些外键字段显示为原始ID输入,而不是一个选择框。 |
form |
自定义用于渲染详情页的表单类 |
formfield_overrides |
允许覆盖表单字段的默认属性 |
save_as |
是否在详情页上显示“另存为”按钮 |
save_as_continue |
如果启用,“另存为”按钮将保存对象并允许用户继续编辑 |
save_on_top |
是否在详情页的顶部显示保存按钮 |
view_on_site |
如果提供,则显示一个链接,允许用户在站点上查看该对象 |
admin 改造示例
修改应用下
my_app/admin.py
文件,代码如下:
from django.contrib import admin
from my_app import models
# Register your models here.
# 定义一个UserInfoAdmin类来定制UserInfo模型在Admin后台的显示和行为
# @admin.register(models.UserInfo)
class UserInfoAdmin(admin.ModelAdmin):
# list_display指定在列表页显示的字段
list_display = ('id', 'name', 'age', 'password')
# list_display_links指定哪些字段应该显示为可点击的链接
list_display_links = ('name',)
# list_editable指定列表页上可编辑的字段
list_editable = ('age',)
# list_filter允许用户根据哪些字段来筛选列表中的对象
list_filter = ('age',)
# search_fields允许用户搜索的字段列表
search_fields = ('name', 'password')
# ordering指定默认的排序字段
ordering = ('id', )
# ordering = ('-age', 'name')
# fields指定在详情页上显示的字段列表
# fields = ('name', 'password', 'age')
# readonly_fields指定详情页上只读的字段
readonly_fields = ('password',)
# fieldsets用于对详情页上的字段进行分组的设置
fieldsets = (
(None, {'fields': ('name',)}),
('Personal info', {'fields': ('age',)}),
('Authentication', {'fields': ('password',)}),
)
# filter_horizontal和filter_vertical用于显示多对多关系的字段的过滤控件
# 假设UserInfo模型有其他的多对多字段,可以在这里配置
# inlines允许您在详情页上显示内联模型
# 假设UserInfo模型有相关的内联模型,可以在这里配置
# 其他可用的配置...
# 自定义动作,例如删除选中的用户
def delete_selected_users(modeladmin, request, queryset):
queryset.delete()
delete_selected_users.short_description = "Delete selected users"
# 将自定义动作添加到actions列表中
actions = [delete_selected_users]
# 在列表页顶部显示自定义动作
actions_on_top = True
# 在列表页底部显示自定义动作
actions_on_bottom = True
# 在列表页每页允许显示的数量
list_per_page = 10
# 显示选中的对象数量
actions_selection_counter = True
# 当模型字段的值为空时,在Admin界面上显示的默认值
empty_value_display = '-'
# 其他配置...
admin.site.register(models.UserInfo, UserInfoAdmin)
admin.site.register(models.Department)
admin 改造效果
admin 修改中文化显示
修改
Django项目下的setting.py
文件,内容如下:
# 中文
LANGUAGE_CODE = 'zh-Hans'
# 时区
TIME_ZONE = 'Asia/Shanghai'
# 开启国际化
USE_I18N = True
Django Cookie
cookie 的由来
在互联网世界中,HTTP协议扮演着至关重要的角色,但它是无状态的。
这意味着,每次用户的请求都是独立的,服务器不会记住之前的请求或状态。
为了解决这个问题,我们需要一种方法来保持状态,于是Cookie应运而生。
Cookie的主要目的是在客户端和服务器之间存储和传递数据,从而帮助服务器识别并记住用户。
cookie 的原理
Cookie是一种存储在用户浏览器中的数据片段,通常以key-value的形式存在,类似于Python中的字典。
当用户首次访问网站并成功登录时,服务器会创建一个包含用户信息的Cookie,并将其发送给用户的浏览器;
浏览器会保存这些Cookie,并在后续的请求中将其发送回服务器。服务器接收到这个Cookie后,可以从中提取用户的信息,从而识别并记住用户。
cookie 的规范
根据HTTP的Cookie规范,每个Cookie的大小上限为4KB,一个服务器最多可以在客户端浏览器上保存20个Cookie,而一个浏览器最多可以保存300个Cookie;
然而,由于浏览器的竞争和功能的不断扩展,一些现代浏览器可能会超出这些规范,例如允许更大的Cookie或保存更多的Cookie。
但无论如何,浏览器都会确保不会因Cookie而占满用户的硬盘空间。
需要注意的是,不同的浏览器之间是不共享Cookie的。
也就是说,如果用户使用IE浏览器访问一个网站并保存了Cookie,那么当他使用Firefox浏览器访问同一个网站时,IE浏览器保存的Cookie是不会被发送给服务器的。
cookie 的覆盖
当服务器发送重复的Cookie时,新的Cookie会覆盖旧的Cookie;
例如,如果服务器第一次发送的Cookie是
Set-Cookie: a=A
;而第二次发送的是
Set-Cookie: a=AA
,那么客户端只会保留第二个Cookie,即a=AA
。
在浏览器中查看cookie
浏览器中按F12,点Network --> Cookies就能看到
或者右击检查,点Application --> Cookies
Django设置Cookie
在Django视图中,你可以使用
HttpResponse
对象的set_cookie
方法来设置cookie。
set_cookie
方法有几个参数,你可以根据需要设置它们:
key
(必填):cookie的名称。value
(必填):cookie的值。max_age
:cookie的有效期,以秒为单位。如果设置为None
,则cookie会随浏览器关闭而失效;如果设置为0,则删除cookie;如果设置为正数,则cookie在指定的秒数后过期。expires
:cookie的过期日期或时间。通常使用datetime.timedelta
对象来设置一个时间间隔,或者一个datetime.datetime
对象来指定一个具体的过期时间。path
:cookie生效的路径。默认是设置cookie的当前页面路径。如果设置为'/'
,则cookie在整个域名下都有效。domain
:cookie生效的域名。默认是当前域名。secure
:如果设置为True
,则只能通过HTTPS协议传输cookie。httponly
:如果设置为True
,则JavaScript无法访问该cookie,增加了安全性。
from django.http import HttpResponse
def set_cookie_view(request):
response = HttpResponse("Cookie has been set")
# 设置名为'my_cookie',值为'cookie_value'的cookie
response.set_cookie('my_cookie', 'cookie_value')
# 设置cookie的过期时间为1小时后
response.set_cookie('expires_cookie', 'cookie_value', expires=60*60)
return response
Django获取Cookie
在Django视图中,你可以通过
request
对象的COOKIES
属性来获取cookie。
COOKIES
是一个字典,包含了所有客户端发送的cookie。
from django.http import HttpResponse
def get_cookie_view(request):
# 获取名为'my_cookie'的cookie的值
cookie_value = request.COOKIES.get('my_cookie')
return HttpResponse(f"Cookie value is: {cookie_value}")
Django删除Cookie
要删除cookie,你需要设置其过期时间为过去的时间。
这样,浏览器在下一次请求时会忽略这个cookie,从而将其删除。
from django.http import HttpResponse
def delete_cookie_view(request):
response = HttpResponse("Cookie has been deleted")
# 设置名为'my_cookie'的cookie的过期时间为过去的时间,从而删除它
response.set_cookie('my_cookie', '', expires=0)
return response
Django Session
session 的由来
HTTP协议无状态,导致服务器无法识别用户身份。
为弥补这一缺陷,引入了Cookie来存储用户信息,但Cookie的明文存储存在安全隐患。
为解决这一问题,Session机制在服务器端存储会话信息,提供更安全灵活的方式来跟踪和管理用户会话状态,避免敏感信息存储在客户端。
session 的原理
- 当用户首次访问应用时,**服务器会为用户创建一个Session,并为其分配一个唯一的Session ID;**这个Session ID是一个随机生成的字符串,用于标识和跟踪用户的会话状态。
- 为了确保浏览器能够在后续的请求中自动携带这个Session ID,这个ID通常会被存储在一个Cookie中;
- 当浏览器发送请求时,它会自动附带这个包含Session ID的Cookie;服务器通过解析这个Cookie来检索对应的Session数据,从而恢复用户的会话状态;
- 由于Session数据存储在服务器端,因此相比存储在客户端的Cookie来说,Session数据更加安全;
- 服务器可以通过各种安全措施来保护Session数据,比如使用加密存储、设置过期时间、限制访问权限等。
- 此外,Session还支持存储更复杂的数据结构,比如对象、列表等,这对于处理复杂的用户会话状态非常有用。
Django设置Session
Django中默认是开启session的;
Django项目中的
settings.py
文件默认包含了django.contrib.sessions.middleware.SessionMiddleware
中间件;这个中间件就是用来处理
session
的。如果想要关闭session,可以将这个中间件注释掉。Django的session数据默认保存在数据库中,但也可以配置为使用其他存储方式,如缓存、文件等。
def set_session(request):
request.session['username'] = 'kevin'
# 如果需要设置多个,则在这里显示声明多个
# request.session['username1'] = 'kevin1'
# request.session['username2'] = 'kevin2'
# 这个数据保存在了哪里?django_session表中
return HttpResponse('set_Session')
"""
设置session发生的事情:
1. 生成了一个随机字符串
2. 把数据给你保存到了django_session表中
2.1 这个操作是在中间件中操作的
2.2 请求来的时候,是把数据准备好,在缓存中(内存)
2.3 响应走的时候,才真正的执行了create,insert方法
3. 把随机字符串返回给浏览器
"""
Django获取Session
def get_session(request):
# 两种方式
print(request.session.get('username'))
print(request.session['username'])
return HttpResponse('get_Session')
"""
获取session发生的事情:
1. 获取cookie值,名为sessionid的cookie值
2. 拿着这个cookie值去数据库中查询
3. 如果查询到了,把查询到的数据封装到request.session中
"""
Django删除Session
def del_Session(request):
# 删除session
request.session.delete() # (只删数据库)
request.session.flush() # 数据库和cookie都删
return HttpResponse('del_session')
Django中Session常用的方法
# 获取、设置、删除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的key
request.session.session_key
# 将所有Session失效日期小于当前日期的数据删除
request.session.clear_expired()
# 检查会话session的key在数据库中是否存在
request.session.exists("session_key")
# 删除当前会话的所有Session数据(只删数据库)
request.session.delete()
# 删除当前的会话数据并删除会话的Cookie(数据库和cookie都删)。
request.session.flush()
这用于确保前面的会话数据不可以再次被用户的浏览器访问
例如,django.contrib.auth.logout() 函数中就会调用它。
"""重点"""
# 设置会话Session和Cookie的超时时间
request.session.set_expiry(value)
* 如果value是个整数,session会在些秒数后失效。
* 如果value是个datatime或timedelta,session就会在这个时间后失效。
* 如果value是0,用户关闭浏览器session就会失效。
* 如果value是None,session会依赖全局session失效策略。
Django中Session相关的配置
Django提供了多种Session后端存储方式,以满足不同的应用需求
数据库Session:
- 使用Django的数据库作为Session数据的存储后端。
SESSION_ENGINE
设置为'django.contrib.sessions.backends.db'
。缓存Session:
- 使用Django的缓存系统(如Memcached、Redis等)作为Session数据的存储后端。
SESSION_ENGINE
设置为'django.contrib.sessions.backends.cache'
。SESSION_CACHE_ALIAS
指定使用的缓存别名,这通常与您的CACHES
设置中的别名相对应。文件Session:
- 将Session数据存储在文件系统中。
SESSION_ENGINE
设置为'django.contrib.sessions.backends.file'
。SESSION_FILE_PATH
指定Session文件存储的路径。如果未设置,将使用系统的临时目录。缓存+数据库:
- 这是一个双重存储后端,首先尝试从缓存中获取Session数据,如果缓存中不存在,则从数据库中获取。
SESSION_ENGINE
设置为'django.contrib.sessions.backends.cached_db'
。加密Cookie Session:
- 将Session数据存储在加密的Cookie中。这意味着Session数据会随着每个请求和响应在客户端和服务器之间传输。
SESSION_ENGINE
设置为'django.contrib.sessions.backends.signed_cookies'
。一些公用的Session设置项:
SESSION_COOKIE_NAME
:设置存储在浏览器上的Session cookie的名称。SESSION_COOKIE_PATH
:设置Session cookie的路径。SESSION_COOKIE_DOMAIN
:设置Session cookie的域名。SESSION_COOKIE_SECURE
:如果设置为True
,则Session cookie只能通过HTTPS传输。SESSION_COOKIE_HTTPONLY
:如果设置为True
,则Session cookie只能通过HTTP(S)传输,而不能通过JavaScript访问。SESSION_COOKIE_AGE
:设置Session cookie的失效日期。SESSION_EXPIRE_AT_BROWSER_CLOSE
:如果设置为True
,则关闭浏览器时Session会过期。SESSION_SAVE_EVERY_REQUEST
:如果设置为True
,则每次请求都会保存Session,而不仅仅是当Session数据发生更改时。
Django 中间件
中间件的简介
Django的中间件(Middleware)是一个轻量级的、底层的“插件”系统;
用于全局地修改Django的输入(请求)或输出(响应),中间件可以视为一个处理链,每个中间件组件都可以决定是否将请求传递给链中的下一个组件,或者结束请求-响应处理并返回自己的响应。
中间件的本质就是一个Python类,在请求到来和结束后,django会根据自己的规则在合适的时机执行中间件中相应的方法。
中间件的作用
- 全局修改:中间件允许你在视图函数之前或之后执行代码,从而全局地修改Django的输入或输出。
- 异常处理:中间件可以捕获视图函数抛出的异常,并进行处理。
- 身份验证:你可以使用中间件来检查用户是否已经通过身份验证,或者是否拥有访问某个视图的权限。
- 日志记录:中间件可以用于记录请求和响应的详细信息,如IP地址、请求时间等。
- 缓存:某些中间件可以用于缓存请求和响应,以提高应用的性能。
中间件的流程
- 请求阶段:当一个请求来到Django时,它首先会经过所有的中间件,这些中间件按照
MIDDLEWARE
设置中的顺序排列。每个中间件都可以决定是否将请求传递给下一个中间件,或者结束请求并返回响应。- 视图处理:如果请求成功通过了所有的中间件,它会被传递给相应的视图进行处理。
- 响应阶段:视图处理完成后,会返回一个响应对象。这个响应对象会再次经过所有的中间件,但是这次是从最后一个中间件开始,依次向前,直到第一个中间件。每个中间件都可以修改响应对象,或者替换为自己的响应。
七个默认中间件
在django项目的settings模块中,有一个MIDDLEWARE变量,其中每一个元素就是一个中间件;
- SecurityMiddleware:安全中间件,提供了多种安全增强功能,例如设置安全相关的HTTP头部,如
X-Content-Type-Options
,X-XSS-Protection
,Content-Security-Policy
等,以防止某些类型的攻击。- SessionMiddleware:会话中间件,用于处理会话相关的功能。它允许你在整个Django请求/响应周期中访问用户会话数据。会话数据通常存储在数据库或缓存中,并且与用户的cookie相关联。
- CommonMiddleware:站点中间件,提供了URL重定向和规范化功能。例如,如果用户在URL末尾添加了斜杠,它将被重定向到没有斜杠的URL(或反之)。这有助于保持URL的一致性。
- CsrfViewMiddleware:CSRF保护中间件,为Django的表单提供了跨站请求伪造(CSRF)保护。它通过在表单中添加一个隐藏字段来工作,并在表单提交时验证这个字段。这有助于防止攻击者伪造用户请求
- AuthenticationMiddleware:认证中间件,用于处理用户身份验证。它确保每个请求都有一个与之关联的用户(通过
request.user
),并允许你在视图中通过@login_required
装饰器来限制访问权限。- MessageMiddleware:消息中间件,为Django的消息框架提供支持。它允许你在用户会话中存储和检索消息,这些消息可以在不同的请求之间持久存在,并且可以显示给用户。
- XFrameOptionsMiddleware:X-Frame-Options中间件,用于设置
X-Frame-Options
HTTP头部,以防止点击劫持攻击。通过设置这个头部,你可以控制你的网站是否可以被嵌入到其他网站的\<iframe>
或\<frame>
标签中。每一个中间件都有具体的功能,以上这些都是一些路径,底层就是定义的类。想要查看中间件的类,变形成导入文件的类,然后点击类名。
如下图。
中间件中的方法
process_request(self, request)
方法(重要)
- 这个方法在请求到达视图之前被调用
- 请求会经过每一个中间件的
process_request
方法,经过的顺序是按照setting.py
文件的配置顺序;- 如果中间件中没有定义该方法,那么则会跳过执行下一个中间件。
- 它应该返回一个
None
或一个HttpResponse
对象。- 如果返回
None
,则请求会继续传递到下一个中间件或最终的视图函数。- 如果返回一个
HttpResponse
对象,Django将直接返回这个响应,并停止进一步的请求处理。- 这使得
process_request
成为一个进行权限认证、日志记录或其他早期处理的好地方。
process_response(self, request, response)
方法(重要)
- 这个方法在视图生成响应之后,但在响应返回给客户端之前被调用
- 它接收请求对象和一个响应对象作为参数,并应该返回这个响应对象
- 你可以在这里进行响应后的处理,比如修改响应内容、添加日志记录等
process_view(self, request, view_func, view_args, view_kwargs)
方法(了解)
- 这个方法在Django决定调用哪个视图函数之后,但在视图函数被调用之前被调用;
- 它允许你基于视图函数或传递给它的参数来决定是否要进一步处理请求
- 返回
None
以继续处理,或者返回一个HttpResponse
对象以停止进一步的处理
process_exception(self, request, exception)
方法(了解)
- 当视图函数抛出一个异常时,这个方法被调用;
- 它允许你捕获异常并进行适当的处理,比如记录错误、返回自定义的错误页面等;
- 返回
None
以允许异常继续向上冒泡,或者返回一个HttpResponse
对象来停止进一步的异常处理。
process_template_response(self, request, response)
方法(了解)
- 当视图函数返回一个实现了
render()
方法的TemplateResponse
对象时,这个方法被调用;- 它允许你在模板渲染之后但在响应发送给客户端之前修改
TemplateResponse
对象;- 返回修改后的
TemplateResponse
对象;
自定义中间件示例
定义视图
views.py
中定义一个视图函数,并在urls.py
文件中注册;
# views.py 视图函数
def index(request):
print('index函数')
return HttpResponse('index')
# urls.py 路由注册
path('index/', views.index),
定义中间件
自定义中间件的步骤:
- 在项目名下或者应用名下新建一个任意名称的文件夹
- 在这个文件夹下面新建一个py文件
- 在这个py文件中,新建一个类,必须继承MiddlewareMixin
- 在你新建的这个类下面根据需要重写哪几个方法;
- 一定要在配置文件的中间件里面注册你的中间件路径
要自定义中间件,必须定义一个类并继承MiddlewareMixin和实现上述中间件方法中的一个或多个。我这里在Django跟项目下创建一个与
manage.py
文件同级的目录:middlewares
;并在
middlewares
目录中创建了一个同名文件:middlewares.py
;代码如下:
from django.utils.deprecation import MiddlewareMixin
class MyMiddleware1(MiddlewareMixin):
def process_request(self, request):
print('我是第一个中间件的process_request')
def process_response(self, request, response):
print('我是第一个中间件process_response')
return response
class MyMiddleware2(MiddlewareMixin):
def process_request(self, request):
print('我是第二个中间件的process_request')
def process_response(self, request, response):
print('我是第二个中间件的process_response')
return response
注册中间件
在
settings.py
中的MIDDLEWARE
列表中注册自定义的中间件类
中间件效果
针对process_reqeust
- 执行顺序是按照配置文件中注册的顺序,从上往下依次执行
- 视图函数在中间件的process_reqeust函数之后执行
- 如果在process_reqeust里面直接返回HttpResponse,之后的中间件一律不在走了,包括视图函数
针对process_response
- 必须要返回一个HttpResponse(response的底层严格还是HttpResponse)
- 执行顺序:是按照配置文件的注册顺序,从下往上依次执行
Django 用户认证
用户认证的简介
Django 用户认证(Auth)组件是 Django 框架中提供的一个强大的身份验证和权限管理系统;
提供了用户注册、登录、权限管理和会话管理等核心功能;助开发者在项目中轻松地添加用户认证功能,并控制用户的访问权限;
通过 auth模块,开发者可以快速地实现用户认证,而无需从头开始编写这些功能的代码。
用户认证的作用
- 用户管理:Django Auth 组件提供了用户模型(
User
),用于存储用户的基本信息,如用户名、密码、电子邮件等;开发者可以使用这个模型来创建、修改、删除用户。- 身份验证:用户可以通过用户名和密码进行身份验证;Auth 组件提供了相关的视图和函数,用于处理用户登录和注销的逻辑。
- 权限管理:Django Auth 组件支持基于角色的权限管理;开发者可以为用户分配不同的角色,并为这些角色设置相应的权限;这样,就可以控制用户可以访问哪些页面或执行哪些操作。
- 会话管理:Django Auth 组件使用 Django 的会话框架来管理用户的登录状态;一旦用户成功登录,他们的信息将被存储在会话中,以便在后续的请求中识别用户。
- 密码管理:Auth 组件提供了密码加密和哈希的功能,以确保用户密码的安全性。密码不会以明文形式存储在数据库中。
auth_user 数据表
auth_user
表是 Djangoauth
模块默认使用的用户模型表,用于存储用户信息,如用户名、密码、电子邮件等。我们早就见过这张表,只是把它忽略了
- 其实在我们刚创建好django项目之后直接执行数据库迁移命令会自动生成很多表,那么其中一张表就是auth_user表;
基本用处:
- django在启动之后就可以直接访问admin路由,需要输入用户名和密码,数据参考的就是auth_user表,并且还必须是管理员用户才能进入
创建超级用户(管理员)
- 首先要数据库迁移
makemigrations:检索模型
migrate:迁移模型- 创建超级用户命令:
python3 manage.py createsuperuser
auth_user
表结构如下,字段含义请看注释;
User 对象
用户对象是Django认证系统的核心;
在Django的认证框架中有且只有一个用户模型也就是User模型,注意这个User模型是Django自带的;
和这个User模型(自定义)没有任何关系的自定义用户模型是无法使用Django认证系统的功能的;
用户模型主要有下面几个字段:
- username
- password
- first_name
- last_name
authenticate 函数
authenticate
函数的主要目的是通过比较用户提供的凭证与存储在数据库或其他认证源中的信息来验证用户的身份。如果凭证有效,该函数会返回一个User对象,该对象代表已验证的用户;
如果凭证无效或用户不存在,则返回None,代表用户提供的凭证验证失败
这个函数的用途非常广泛,通常在用户尝试登录时调用。
通过
authenticate
函数,可以确保只有有效用户才能访问受保护的资源或执行敏感操作。此外,由于
authenticate
函数还设置了用户认证的后端信息,这使得后续的登录流程(如使用login
函数创建会话)能够正确进行。
from django.contrib.auth import authenticate
from django.shortcuts import redirect
from django.http import HttpResponse
def user_login(request):
# 假设登录表单通过POST方法提交,并且用户名和密码分别存储在'username'和'password'字段中
if request.method == 'POST':
username = request.POST['username']
password = request.POST['password']
# 调用authenticate函数进行用户验证
user = authenticate(request, username=username, password=password)
# 检查认证结果
if user is not None:
# 认证成功,使用login函数登录用户
# login函数会设置request.user为当前登录的用户,并创建或恢复会话
# 这里还可以选择登录后的重定向URL
from django.contrib.auth import login
login(request, user)
# 重定向到登录成功后的页面,例如主页
return redirect('home')
else:
# 认证失败,可以设置一个错误消息
error_message = "Invalid username or password"
return HttpResponse(error_message, status=401)
# 如果不是POST请求,显示登录表单
# 这里通常会渲染一个包含登录表单的模板
return render(request, 'registration/login.html')
登录功能 - login
login
函数是Django提供的用于将用户登录到系统中的方法。当用户通过身份验证(例如,他们提供了有效的用户名和密码)后,
login
函数会将用户ID存储在Django的会话框架中,这样用户就可以在整个站点上进行身份验证,直到会话过期或用户明确注销。
# 校验用户民或密码是否一致
# 关键字:auth.authenticate(username=用户输入的用户名,password=用户输入的密码)
# 需导入模块:
from django.contrib import auth
def login(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
# 那么现在如何校验数据呢?
# 获取表的数据 但是密码是密文的怎么比对呢
# 这时就要用到auth模块
res = auth.authenticate(request,username=username,password=password)
'''
他都干了那些事:
自动查找auth_user表
自动给密码加密后再进行比对
注意事项:
括号内必须同时传入用户名和密码
不能只传用户名
'''
print(res,type(res))
print(res.username)
print(res.password)
return render(request,'login.html')
保存用户登录状态
略微修改一下代码,模拟实现保存用户登录状态
关键字:auth.login(request,用户对象)
# 方法优点:只要执行了auth.login方法,就可以在任何地方通过request.user获取到当前登录的用户对象
def login(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
user_obj = auth.authenticate(request,username=username,password=password)
if user_obj:
# 之前我们保存用户登录状态是不是要通过session来保存。现在可通过auth来直接帮助我们操作session表
# 保存用户登录状态:
auth.login(request,user_obj) # 类似于request_session[key] = user_obj
return redirect('/home/') # 跳转到home页面
return render(request,'login.html')
def home(request):
print(request.user) # 可以拿到当前登录的用户对象
return HttpResponse('home')
判断用户是否登录
如上:那么如果没有登录,直接访问home页面,requeest.user返回的是什么呢?
结果:如果没有登录则返回的是AnonymousUser匿名用户那么如果判断用户是否登录了呢?
关键字:request.user.is_authenticated
返回结果:布尔类型:- 未登录(匿名用户) :返回False - 已登录 :返回True
判断用户是否登录 - 装饰器
上述看到我们匿名用户也可以登录到home页面,这样是不合理的,需要登陆后才能访问其他页面才合理。那么就需要校验用户是否登录
需导入模块:
- from django.contrib.auth.decorators import login_required
关键字:添加装饰器
- 局部配置:@login_required(login_url='/指定未登录跳转页面/') - 全局配置:@login_required # 无需指定跳转路径 需要在settings.py配置文件中配置:LOGIN_URL = '/指定未登录跳转页面/'
局部和全局哪个好呢?
- 全局的好处在于无需重复写代码 但是跳转的页面却很单一
- 局部的好处在于不同的视图函数在用户没有登陆的情况下可以跳转到不同的页面
创建用户
create_user
函数用于创建新的用户实例,并将其添加到数据库中。这个函数会创建一个新的User
对象,设置提供的用户名、密码(会被自动哈希处理以提高安全性)以及其他可选参数(如电子邮件),然后将其保存到数据库中。
# 原理:操作auth_user表写入数据:
# 关键字:User.objects.create(username=username,password=password)
# 需导入模块:
from django.contrib.auth.models import User
def register(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
# 操作auth_user表写入数据(明文密码)
User.objects.create(username=username,password=password)
return render(request,'register.html')
# 综上:写入数据 不能用create 密码没有加密 处理
# 创建普通用户
User.objects.create_user(username=username,password=password)
# 创建超级用户(了解):使用代码创建超级用户 邮箱是必填的 而用命令创建则可以不填
User.objects.create_superuser(username=username,email='123@qq.com',password=password)
修改密码
验证旧密码:
- 用户尝试修改密码时,首先必须提供当前的旧密码。
- 使用
User
模型的check_password
方法来验证用户输入的旧密码与数据库中存储的哈希值是否匹配。- 如果
check_password
返回True
,说明旧密码正确,可以继续修改密码流程。- 如果返回
False
,则表明旧密码错误,修改密码的流程应该终止,并向用户显示错误信息。设置新密码:
- 一旦旧密码验证通过,用户就可以输入他们的新密码。
- 使用
User
模型的set_password
方法来设置新密码。这个方法接受一个未加密的密码作为参数,并自动将其转换为一个安全的加密哈希值。- 设置新密码后,调用
User
模型的save
方法将更改保存到数据库中。
@login_required
def set_password(request):
if request.method == 'POST':
old_password = request.POST.get('old_password')
new_password = request.POST.get('new_password')
confirm_password = request.POST.get('confirm_password')
# 校验两次密码是否一直
if new_password == confirm_password:
# 校验老密码:
is_right = request.user.check_password(old_password) # 返回的是布尔值
if is_right:
# 修改密码
request.user.set_password(new_password) # 修改对象的属性
request.user.save() # 同步到数据库
return redirect('/login/') # 注册完之后跳转到登录页面
return render(request,'setpassword.html',locals())
<form action="" method="post">
{% csrf_token %}
<p>username<input type="text" name="username" disabled value="{{ request.user.username }}"></p>
<p>old_password:<input type="text" name="old_password"></p>
<p>new_password:<input type="text" name="new_password"></p>
<p>confirm_password:<input type="text" name="confirm_password"></p>
<input type="submit">
</form>
注销功能
logout
函数用于注销当前登录的用户,它会清除存储在Django会话框架中的用户ID,这样用户就不再是已认证的状态。该函数接受一个HttpRequest对象,无返回值。当调用该函数时,当前请求的session信息会全部清除。该用户即使没有登录,使用该函数也不会报错。
# 注销则是将session表中的用户数据删除即可。
# 关键字:auth.logout(request)
@login_required # 登录才能注销所以也需要验证是否登录。
def logout(request):
auth.logout(request)
return redirect('/login/')
总结
# 1.比对用户名和密码是否正确
user_obj = auth.authenticate(request,username=username,password=password)
# 括号内必须同时传入用户名和密码
print(user_obj) # 返回用户对象 数据不符合则返回None
print(user_obj.username) # 用户名
print(user_obj.password) # 密文
# 2.保存用户状态
auth.login(request,user_obj) # 类似于request.session[key] = user_obj
# 使用auth.login将用户ID存储在session中,之后可以通过request.user访问该用户对象
# 只要执行了该方法 就可以在任何地方通过request.user获取到当前登陆的用户对象
# 3.判断当前用户是否登陆
request.user.is_authenticated()
# 4.获取当前登陆用户对象
request.user
# 5.校验用户是否登陆装饰器
from django.contrib.auth.decorators import login_required
# 局部配置
@login_required(login_url='/login/')
# 全局配置
LOGIN_URL = '/login/'
1.如果局部和全局都有 该听谁的?
局部 > 全局
2.局部和全局哪个好呢?
全局的好处在于无需重复写代码 但是跳转的页面却很单一
局部的好处在于不同的视图函数在用户没有登陆的情况下可以跳转到不同的页面
# 6.比对原密码
request.user.check_password(old_password)
# 7.修改密码
request.user.set_password(new_password) # 仅仅是在修改对象的属性
request.user.save() # 这一步才是真正的操作数据库
# 8.注销
auth.logout(request)
# 9.注册
# 操作auth_user表写入数据
User.objects.create(username=username,password=password) # 写入数据 不能用create 密码没有加密处理
# 创建普通用户
User.objects.create_user(username=username,password=password)
# 创建超级用户(了解):使用代码创建超级用户 邮箱是必填的 而用命令创建则可以不填
User.objects.create_superuser(username=username,email='123@qq.com',password=password)
扩展auth_user表
# 比如给auth_user表添加一个phone字段,那么该如何扩建呢?
from django.contrib.auth.models import User # auth_user所在的表位置
# 我们先来看一下这张表都有那些字段:
# 所以我们在扩展auth_user表是只需要继承AbstractUser即可
# 举例:
# 需导入模块:
from django.contrib.auth.models import User,AbstractUser
class UserInfo(AbstractUser):
phone = models.BigIntegerField()
# 需要注意:
如果继承了AbstractUser
那么在执行数据库迁移命令的时候auth_user表就不会再创建出来了
而UserInfo表中会出现auth_user所有的字段外加自己扩展的字段
这么做的好处在于我们能够直接点击我们自己的表更加快捷的完成操作以及扩展
- 前提:
1、在继承之前没有执行过数据库迁移的命令
即auth_user表没有被创建出来,如果当前库已经创建了那么就需要重新创建一个库
2、继承的类里面不要覆盖AbstractUser里面的字段名
表里面有的字段不要动,只扩展额外的字段即可
3、需要再配置文件中告诉django你要用UserInfo代替auth_user
AUTH_USER_MODEL = 'app01.UserInfo' # 应用名.表名
如果自己写表替代了auth_user那么
auth模块的功能还是照常使用,参考的表由原来的auth_user变成了UserInfo
# 比如:
- 原先:
from django.contrib.auth.models import User
User.objects.create(username=username,password=password)
- 现在:
from app01 import models
models.UserInfo.objects.create(username=username,password=password)
# 方法不变只是操作的表换成了自己指定的表。
Django 跨站请求伪造
Django框架默认启用了CSRF保护机制,以防止跨站请求伪造攻击。CSRF攻击是一种恶意网站欺骗用户浏览器向另一个网站发送伪造请求的攻击方式。如果Django应用没有启用CSRF保护,那么用户的会话可能会受到攻击者的利用,导致未授权的操作。
Django在编写模板的时候,比如登录页,会出现这样的CSRF提示问题:
我们需要在HTML文件中的form表单中添加
csrf_token
,带着表单的POST请求一同发送到服务器验证即可;
<form method="post" action="/myapp/login/">
<!-- 不加这句会有CSRF跨域问题,django有。Flask没有 -->
{% csrf_token %}
<input type="text" name="user" placeholder="用户名">
<input type="password" name="user" placeholder="密码">
<input type="submit" placeholder="提交">
</form>
通过浏览器F12(开发者工具)查看元素,结果发现网页中隐藏了一个input标签,内容就是csrf_token对应的随机字符串。
CSRF攻击是咋回事
CSRF攻击通常发生在用户已经登录了某个网站(如银行网站)的情况下。攻击者可能会诱导用户在不知情的情况下,通过其浏览器向该网站发送一个伪造的请求,这个请求可能是转账、更改密码等敏感操作。由于浏览器已经保存了用户的登录状态(如cookie),这个伪造请求会被服务器认为是用户本人发出的,从而导致恶意操作被执行。
如何使用Django CSRF保护
在Django中启用CSRF保护非常简单,默认情况下,Django的中间件中已经包括了
django.middleware.csrf.CsrfViewMiddleware
,它会在每个响应中添加一个CSRF令牌,并在处理每个请求时验证这个令牌。
1.表单中的CSRF令牌
如果你使用了Django的表单系统,
django.forms.Form
会自动为每个表单添加一个隐藏字段,其中包含CSRF令牌。在模板中渲染表单时,这个隐藏字段会被包含在内,从而确保每个POST请求都包含有效的CSRF令牌。
view.py
# views.py
from django.shortcuts import render
from .forms import MyForm
def my_view(request):
if request.method == 'POST':
form = MyForm(request.POST)
if form.is_valid():
# 处理表单数据
pass
else:
form = MyForm()
return render(request, 'my_template.html', {'form': form})
# forms.py
from django import forms
class MyForm(forms.Form):
# 定义你的字段
pass
my_template.html
<!-- my_template.html -->
<form method="post">
{% csrf_token %}
{{ form }}
<button type="submit">Submit</button>
</form>
2.Ajax请求中的CSRF令牌
对于Ajax请求,Django提供了一个JavaScript库
django.views.static.js
,它包含了一个函数getCookie
,可以用来获取CSRF令牌,并将其作为请求头的一部分发送。
// 在发送Ajax请求之前
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
const csrftoken = getCookie('csrftoken');
// 在发送Ajax请求时
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});
3.禁用CSRF保护
在某些情况下,你可能需要禁用CSRF保护,例如,如果你正在创建一个不需要登录的公共API。在这种情况下,你可以在视图中使用
csrf_exempt
装饰器
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def my_view(request):
# 处理请求
pass
Token抵御CSRF攻击概述
CSRF令牌是一种防御CSRF攻击的有效机制。每个用户会话都会生成一个唯一的令牌,并将其存储在用户的cookie中。同时,这个令牌也会被添加到每个表单的隐藏字段中。当表单被提交时,服务器会验证提交的令牌是否与存储在用户cookie中的令牌匹配。由于攻击者无法访问用户的cookie,因此他们无法伪造一个有效的令牌来通过验证,从而防止了CSRF攻击。
Django 分页器
在Django中,为了实现分页功能,通常需要考虑以下几个参数:
- 当前第几页 (current_page): 用户当前正在查看的页面。这个信息通常由前端通过GET请求传递给后端。例如,如果用户想看第3页的数据,那么前端可能会向
/data?page=3
这样的URL发送请求。- 总数据量 (total_count): 从数据库中查询出来的总记录数。这个信息通常是通过执行一个数据库查询来获取的,例如使用Django的
count()
方法。- 每页展示的条数 (per_page): 这是一个固定的数值,表示每页应该显示多少条数据。这个值可以根据应用的需求进行设置,您提到的20条是一个常见的选择。
- 总页数 (total_pages): 通过将总数据量除以每页展示的条数来计算得到。如果有余数,则总页数需要向上取整,因为即使最后一页不满一页的数据,也需要一个页面来展示。
QuerySet对象
在Django中,可以使用QuerySet对象的切片功能来实现分页;
切片操作允许指定起始索引和结束索引来获取QuerySet的一个子集。
- 切片索引:QuerySet对象支持索引取值和切片操作,但是不支持负数索引;因此计算
start
和end
时,确保使用正数索引。- 计算
start
和end
:start
和end
的计算公式:start = (current_page - 1) * per_page_num
和end = current_page * per_page_num
。- 边界条件:当
current_page
等于总页数时,end
的值可能会超出数据的实际范围;为了避免这种情况,需要调整end
的计算方式,例如使用min(end, total_count)
,其中total_count
是数据的总数量。- 异常处理:处理
current_page
转换为整数时可能出现的异常,可能还需要处理其他潜在的异常,例如当current_page
小于1或大于总页数时。- QuerySet切片:一旦有了
start
和end
的值,就可以使用它们来对QuerySet进行切片操作,例如queryset[start:end]
。
# 获取用户想访问的页码 ,如果没有,默认展示第一页
current_page = request.GET.get("page",1)
# 由于后端接受到的前端数据是字符串类型所以我们这里做类型转换处理加异常捕获
try:
current_page = int(current_page)
except Exception as e:
current_page = 1
# 还需要定义页面到底展示几条数据
per_page_num = 10 # 一页展示10条数据
# 需要对总数据进行切片操作 需要确定切片起始位置和终止位置
start_page = x?
end_page = x?
"""
下面需要研究start、end分别与current_page、per_page_num参数之间的数据关系
per_page_num = 10
current_page start end
1 0 10
2 10 20
3 20 30
4 30 40
per_page_num = 5
current_page start end
1 0 5
2 5 10
3 10 15
4 15 20
可以很明显的看出规律,因此得出计算公式
start = (current_page - 1) * per_page_num
end = current_page * per_page_num
"""
数据总页数获取
总数据有100条,每页展示10条,总共需要10页;
总数据有101条,每页展示10条,总共需要11页;
"""
总数据量 每页展示的数据 总页数
100 10 10
101 10 11
99 10 10
divmod(100, 10)
"""
# 可以判断元祖的第二个数字是否为0从而确定到底需要多少页来展示数据
book_queryset = models.Book.objects.all()
all_count = book_queryset.count() # 数据总条数
page_count, rem = divmod(all_count, per_page_num)
if rem : # 有余数则总页数加一
page_count += 1
翻页li标签获取
page_html = ''
# for i in range(1, page_count+1):
for i in range(current_page - 5, current_page + 6): # 只展示11个页码数
if i == xxx: # 如果展示页数等于当前页数,因为current_page在小于6时,被限制了,所以要新建一个变量来代替当前页
# 页码高亮
# href="?page=%s":前面为空,就是朝当前地址提交,问号后面是参数
page_html += '<li class="active"><a href="?page=%s">%s</a></li>' % (i, i)
else:
page_html += '<li ><a href="?page=%s">%s</a></li>' % (i, i)
至此分页器大致的功能及思路我们就已经大致清楚了
最后我们只需要利用start和end对总数据进行切片取值再传入前端页面就能够实现分页展示
book_list = book_queryset[start:end]
return render(request, 'index.html', locals())
接下来就剩下渲染前端页面了。
自定义分页器封装
在实际Django项目中,我们经常会遇到需要整合非Django官方提供的第三方工具或库的情况。为了保持项目的结构清晰和代码的可维护性,通常的做法是在项目中新建一个名为
utils
的文件夹。这个文件夹将用于存放各种工具函数、类库封装等。自定义分页器核心思路:
- 需求分析:首先明确分页需求,例如每页显示多少条数据、是否支持跳转至指定页码等。
- 数据结构:设计分页所需的数据结构,如当前页码、总页数、每页数据量等。
- 逻辑处理:编写逻辑来根据请求参数(如当前页码)计算需要展示的数据范围,并从数据源中获取这些数据。
- 界面集成:在前端展示界面集成分页控件,如页码选择器、上一页/下一页按钮等。
- 测试与优化:对分页功能进行全面测试,确保在各种情况下都能正确工作,并根据需要进行性能优化。
class Pagination(object):
def __init__(self, current_page, all_count, per_page_num=2, pager_count=11):
"""
封装分页相关数据
:param current_page: 当前页
:param all_count: 数据库中的数据总条数
:param per_page_num: 每页显示的数据条数
:param pager_count: 最多显示的页码个数
"""
try:
current_page = int(current_page)
except Exception as e:
current_page = 1
if current_page < 1:
current_page = 1
self.current_page = current_page
self.all_count = all_count
self.per_page_num = per_page_num
# 总页码
all_pager, tmp = divmod(all_count, per_page_num)
if tmp:
all_pager += 1
self.all_pager = all_pager
self.pager_count = pager_count
self.pager_count_half = int((pager_count - 1) / 2)
@property # 把方法伪装成属性
def start(self):
return (self.current_page - 1) * self.per_page_num
@property
def end(self):
return self.current_page * self.per_page_num
def page_html(self): # 循环的前端的标签
# 如果总页码 < 11个:
if self.all_pager <= self.pager_count:
pager_start = 1
pager_end = self.all_pager + 1
# 总页码 > 11
else:
# 当前页如果<=页面上最多显示11/2个页码
if self.current_page <= self.pager_count_half:
pager_start = 1
pager_end = self.pager_count + 1
# 当前页大于5
else:
# 页码翻到最后
if (self.current_page + self.pager_count_half) > self.all_pager:
pager_end = self.all_pager + 1
pager_start = self.all_pager - self.pager_count + 1
else:
pager_start = self.current_page - self.pager_count_half
pager_end = self.current_page + self.pager_count_half + 1
page_html_list = []
# 添加前面的nav和ul标签
page_html_list.append('''
<nav aria-label='Page navigation>'
<ul class='pagination'>
''')
first_page = '<li><a href="?page=%s">首页</a></li>' % (1)
page_html_list.append(first_page)
if self.current_page <= 1:
prev_page = '<li class="disabled"><a href="#">上一页</a></li>'
else:
prev_page = '<li><a href="?page=%s">上一页</a></li>' % (self.current_page - 1,)
page_html_list.append(prev_page)
for i in range(pager_start, pager_end):
if i == self.current_page:
temp = '<li class="active"><a href="?page=%s">%s</a></li>' % (i, i,)
else:
temp = '<li><a href="?page=%s">%s</a></li>' % (i, i,)
page_html_list.append(temp)
if self.current_page >= self.all_pager:
next_page = '<li class="disabled"><a href="#">下一页</a></li>'
else:
next_page = '<li><a href="?page=%s">下一页</a></li>' % (self.current_page + 1,)
page_html_list.append(next_page)
last_page = '<li><a href="?page=%s">尾页</a></li>' % (self.all_pager,)
page_html_list.append(last_page)
# 尾部添加标签
page_html_list.append('''
</nav>
</ul>
''')
return ''.join(page_html_list) # 用空字符串拼接
自定义分页器使用
views.py文件内容
def index(request):
from utils.mypage import Pagination # 导入类
# 2.获取参数数据
# current_page, all_count, per_page_num=2, pager_count=11
# 2.1 current_page:当前页,从前端用户点击的数据传到问号后面
current_page = request.GET.get('page', 1)
# 2.2 all_count:总页数,计算数据表中的个数
book_queryset = models.Book.objects.all()
all_count = book_queryset.count()
# 2.3 per_page_num可以在配置文件中设置
'''
# 每页展示多少条数据
PER_PAGE_NUM = 10
'''
# 从配置文件中读取数据
from django.conf import settings
per_page_num = settings.PER_PAGE_NUM
# 1.实例化对象
page_obj = Pagination(current_page, all_count, per_page_num=per_page_num)
# 3.展示所有数据,切片[对象点属性:对象点属性]
book_list = book_queryset[page_obj.start:page_obj.end]
# 4.获取li标签,对象点方法
page_html = page_obj.page_html()
return render(request, 'index.html', locals())
index.html文件内容
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>推导分页的原理</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>
{# 循环1000条数据 #}
{% for book in book_list %}
<p>
{{ book.title }}
</p>
{% endfor %}
<nav aria-label="Page navigation">
<ul class="pagination">
<li>
<a href="#" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
{# 模板中无法使用range函数,不能传参,循环页数需要在后端执行 #}
{# 展示循环后的所有li #}
{{ page_html|safe }}
<li>
<a href="#" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</nav>
</body>
</html>
Django 信号量
信号简介
Django的信号(Signals)是一种观察者模式的实现,它允许特定的发送者(senders)在特定事件发生时,通知一组接收者(receivers)。
这些事件可以是Django框架中的任何重要动作,比如模型的保存、删除,用户登录等。
通过连接(connect)信号和接收者函数,开发者可以在不修改原始代码的情况下,增加新的行为或逻辑。
内置信号的种类
信号名 | 描述 |
---|---|
pre_init |
django的modal执行其构造方法前,自动触发 |
post_init |
django的modal执行其构造方法后,自动触发 |
pre_save |
django的modal对象保存前,自动触发 |
post_save |
django的modal对象保存后,自动触发 |
pre_delete |
django的modal对象删除前,自动触发 |
post_delete |
django的modal对象删除后,自动触发 |
m2m_changed |
django的modal中使用m2m字段操作第三张表(add,remove,clear)前后,自动触发 |
class_prepared |
程序启动时,检测已注册的app中modal类,对于每一个类,自动触发 |
pre_migrate |
执行migrate命令前,自动触发 |
post_migrate |
执行migrate命令后,自动触发 |
request_started |
请求到来前,自动触发 |
request_finished |
请求结束后,自动触发 |
got_request_exception |
请求异常后,自动触发 |
setting_changed |
使用test测试修改配置文件时,自动触发 |
template_rendered |
使用test测试渲染模板时,自动触发 |
connection_created |
创建数据库连接时,自动触发 |
内置信号的使用
- 导入信号:首先,你需要从Django的
django.db.models.signals
模块中导入你想要的信号。- 定义接收器函数:接下来,定义一个函数作为信号的接收器。这个函数将在信号被发送时调用。
- 连接信号和接收器:使用
django.dispatch.receiver
装饰器将接收器函数连接到特定的信号。你需要指定信号的名称和你想要连接的发送者。- 确保接收器加载:为了确保接收器函数在应用启动时加载,你可以在应用的
apps.py
文件中的ready
方法里导入它,或者在应用的__init__.py
文件中导入。- 触发信号:Django会在适当的时机自动触发内置信号。对于自定义信号,你可以手动触发它们。
首先,我们假设有一个名为
myapp
的应用,其中有一个MyModel
模型
# myapp/models.py
from django.db import models
class MyModel(models.Model):
name = models.CharField(max_length=100)
# ... 其他字段
然后,我们定义一个接收器函数,该函数将在
MyModel
实例保存前被调用:
# myapp/signals.py
from django.db.models.signals import pre_save
from django.dispatch import receiver
from .models import MyModel
@receiver(pre_save, sender=MyModel)
def my_model_pre_save_receiver(sender, instance, **kwargs):
# 在这里添加你希望在对象保存前执行的代码
print(f"Saving {instance} instance with name: {instance.name}")
# 例如,你可以在这里修改对象的某个字段
# instance.some_field = some_value
确保接收器加载
# myapp/__init__.py
from .signals import my_model_pre_save_receiver
现在,每当你尝试保存一个
MyModel
实例时,my_model_pre_save_receiver
函数就会被调用,打印出相关信息。请注意,
pre_save
信号是在模型的save()
方法被调用但数据尚未写入数据库时触发的。你可以在这个接收器中执行任何需要在保存前完成的逻辑,比如验证数据、修改数据等。
自定义信号
除了内置信号外,Django还允许开发者创建自定义信号。
自定义信号允许开发者定义自己的事件,并在这些事件发生时通知相应的接收器。
首先,我们需要在应用中定义一个信号。这通常在一个名为
signals.py
的文件中完成
# myapp/signals.py
from django.dispatch import Signal
# 定义一个自定义信号
my_custom_signal = Signal(providing_args=["some_argument"])
# 导入了Signal类,并创建了一个名为my_custom_signal的自定义信号
# providing_args参数是一个列表,它定义了传递给接收器函数的参数。
接下来,我们需要定义一个接收器函数,该函数将在信号被发送时调用
# myapp/receivers.py
from django.dispatch import receiver
from .signals import my_custom_signal
@receiver(my_custom_signal)
def my_custom_signal_receiver(sender, **kwargs):
some_argument = kwargs.get('some_argument')
print(f"Received my custom signal with argument: {some_argument}")
# 使用了@receiver装饰器将my_custom_signal_receiver函数与my_custom_signal信号连接起来。
# 该函数接收sender(发送信号的对象)和**kwargs(一个包含所有传递参数的字典)。
最后,我们需要在某个地方发送这个自定义信号。这可以在应用的任何地方完成,通常是在某个逻辑处理完成后
# myapp/views.py or models.py or anywhere else
from django.http import HttpResponse
from .signals import my_custom_signal
def my_view(request):
# ... some logic here ...
# 发送自定义信号,并传递一个参数
my_custom_signal.send(sender=None, some_argument="Hello, signal!")
return HttpResponse("Signal sent!")
我们调用了
my_custom_signal.send()
方法来发送信号,并传递了some_argument
参数。当这个信号被发送时,所有连接了该信号的接收器函数都会被调用,并接收到传递的参数。请确保在应用的
apps.py
中配置ready
方法,或者在应用的__init__.py
文件中导入接收器,以确保接收器在应用启动时加载:
# myapp/apps.py
from django.apps import AppConfig
class MyAppConfig(AppConfig):
name = 'myapp'
def ready(self):
from .receivers import my_custom_signal_receiver
# 或者
# myapp/__init__.py
from .receivers import my_custom_signal_receiver