Python 大而全Web开发框架Django精通指南


文章目录


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 默认只允许本地主机(localhost127.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_URLMEDIA_ROOT:

  • 用途:配置媒体文件的存储和访问。
  • 含义:MEDIA_URL 是媒体文件的 URL 前缀,而 MEDIA_ROOT 是实际存储媒体文件的文件系统路径。

Django 路由系统

当用户通过浏览器向Django网站发出请求时,请求会首先到达Django的WSGI服务器(如Gunicorn、uWSGI等),然后由WSGI服务器将请求传递给Django的路由分发系统。


Django的路由系统遵循了典型的MVC(模型-视图-控制器)设计模式,其中URL路由分发系统充当了控制器的角色,由此形成了MTV模式。


这个系统确保每个进入的HTTP请求都能被正确地映射到对应的视图函数或类上,以便进行进一步的处理。

概括来说,Django的路由流程如下

  1. 请求接收:用户的浏览器发出HTTP请求,这个请求首先由WSGI服务器(如Gunicorn、uWSGI等)接收。
  2. 路由分发:WSGI服务器将请求传递给Django的路由分发系统。这个系统基于urls.py文件中定义的URL模式来匹配请求,并将请求分发到相应的视图函数或类。
  3. 视图处理:视图函数或类负责处理请求,可能涉及到从模型中获取数据、执行业务逻辑等操作。
  4. 模板渲染:处理完请求后,视图将必要的数据传递给模板。模板使用这些数据来生成最终的HTML响应。
  5. 响应返回: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对象;

视图函数的工作流程如下:

  1. 接收请求(Request):当用户通过浏览器发送一个HTTP请求(如GET、POST等)到Django服务器时,Django的URL分派器(URLconf)会根据请求的URL路径找到对应的视图函数。
  2. 处理业务逻辑:视图函数接收HttpRequest对象作为参数,这个对象包含了用户请求的所有信息,如请求方法、路径、参数、头部信息等。在视图函数中,开发者编写处理该请求的业务逻辑代码,这通常包括从数据库中查询数据、处理数据、执行其他业务操作等。
  3. 返回响应(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 模板的基本组成部分和概念:

  1. 变量:模板中的变量使用双大括号 {{ variable_name }} 表示。当模板被渲染时,这些变量会被替换为它们在视图函数中传递的值。
  2. 标签:标签是 Django 模板中的控制结构,它们用 {% tag %} 的形式表示。标签可以执行循环、条件语句、包含其他模板文件等操作。
  3. 过滤器:过滤器用于修改变量的显示方式。它们可以在变量后面使用管道符 | 来应用。例如,{{ value|upper }} 会将变量 value 转换为大写。
  4. 注释:模板中的注释使用 {# this is a comment #} 的形式表示。
  5. 模板继承:Django 模板支持继承,这意味着你可以定义一个基础模板(base template),然后在其他模板中继承它。这样,你可以在一个地方定义整个网站的布局和样式,并在需要时覆盖或添加特定页面的内容。
  6. 自定义模板标签和过滤器:Django 还允许你创建自定义的模板标签和过滤器,以满足特定需求。

工作流程
  1. 用户请求:用户在浏览器的地址栏中输入URL地址并回车,发送HTTP请求(通常是GET请求)到Django服务器。
  2. URL路由:Django服务器接收到请求后,根据urls.py(URLconf)中的配置和请求的URL去匹配对应的视图函数(view function)。
  3. 视图处理:匹配到的视图函数负责处理具体的业务逻辑。这可能包括查询数据库、处理数据、执行其他后端任务等。视图函数通常会返回一个包含模板路径和上下文数据(context data)的字典。
  4. 模板渲染:Django服务器根据视图函数返回的模板路径加载对应的模板文件。同时,服务器将视图函数返回的上下文数据填充到模板中。模板系统使用这些上下文数据来动态生成HTML页面。
  5. HTTP响应:Django服务器将渲染后的HTML页面作为HTTP响应返回给浏览器。这个响应包含了生成的HTML内容以及其他可能的HTTP头部信息。
  6. 页面展示:浏览器接收到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的优点:
  1. 简化数据库操作:ORM允许开发者使用Python对象的方式来操作数据库,如创建、查询、更新和删除记录。这样,开发者可以专注于业务逻辑,而无需过多关注数据库操作的细节。
  2. 提高开发效率:ORM可以自动生成并执行SQL语句,从而减少了手动编写和调试SQL语句的时间。
  3. 提高可维护性:ORM将数据库操作封装在对象中,使得代码更加清晰、易于维护和升级。
  4. 提高可移植性:ORM可以屏蔽不同数据库之间的差异,使得应用程序更加容易移植到不同的数据库平台上。
  5. 数据库无关性:ORM使得代码与具体的数据库实现解耦,这意味着在不更改Python代码的情况下,可以更换数据库引擎。
  6. 数据验证和安全性: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_nameForeignKeyOneToOneField中,related_name定义了反向关系的名称。例如,如果A模型有一个指向B模型的外键,B模型可以通过A模型的related_name属性访问A模型的相关实例。
related_query_name用于定义当使用select_relatedprefetch_related时,在关联查询中使用的名称。这影响查询集上的属性和方法名称。
on_deleteForeignKey中,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界面上模型列表页要显示的字段列表。可以包括模型的字段,如idstr等,也可以包括方法
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及更高版本中,此选项已被orderingget_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会根据自己的规则在合适的时机执行中间件中相应的方法。


中间件的作用
  1. 全局修改:中间件允许你在视图函数之前或之后执行代码,从而全局地修改Django的输入或输出。
  2. 异常处理:中间件可以捕获视图函数抛出的异常,并进行处理。
  3. 身份验证:你可以使用中间件来检查用户是否已经通过身份验证,或者是否拥有访问某个视图的权限。
  4. 日志记录:中间件可以用于记录请求和响应的详细信息,如IP地址、请求时间等。
  5. 缓存:某些中间件可以用于缓存请求和响应,以提高应用的性能。

中间件的流程
  1. 请求阶段:当一个请求来到Django时,它首先会经过所有的中间件,这些中间件按照MIDDLEWARE设置中的顺序排列。每个中间件都可以决定是否将请求传递给下一个中间件,或者结束请求并返回响应。
  2. 视图处理:如果请求成功通过了所有的中间件,它会被传递给相应的视图进行处理。
  3. 响应阶段:视图处理完成后,会返回一个响应对象。这个响应对象会再次经过所有的中间件,但是这次是从最后一个中间件开始,依次向前,直到第一个中间件。每个中间件都可以修改响应对象,或者替换为自己的响应。

在这里插入图片描述


七个默认中间件

在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),

定义中间件

自定义中间件的步骤:

  1. 在项目名下或者应用名下新建一个任意名称的文件夹
  2. 在这个文件夹下面新建一个py文件
  3. 在这个py文件中,新建一个类,必须继承MiddlewareMixin
  4. 在你新建的这个类下面根据需要重写哪几个方法;
  5. 一定要在配置文件的中间件里面注册你的中间件路径
    要自定义中间件,必须定义一个类并继承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

  1. 执行顺序是按照配置文件中注册的顺序,从上往下依次执行
  2. 视图函数在中间件的process_reqeust函数之后执行
  3. 如果在process_reqeust里面直接返回HttpResponse,之后的中间件一律不在走了,包括视图函数

针对process_response

  1. 必须要返回一个HttpResponse(response的底层严格还是HttpResponse)
  2. 执行顺序:是按照配置文件的注册顺序,从下往上依次执行

在这里插入图片描述


Django 用户认证


用户认证的简介

Django 用户认证(Auth)组件是 Django 框架中提供的一个强大的身份验证和权限管理系统;

提供了用户注册、登录、权限管理和会话管理等核心功能;助开发者在项目中轻松地添加用户认证功能,并控制用户的访问权限

通过 auth模块,开发者可以快速地实现用户认证,而无需从头开始编写这些功能的代码。


用户认证的作用
  1. 用户管理:Django Auth 组件提供了用户模型(User),用于存储用户的基本信息,如用户名、密码、电子邮件等;开发者可以使用这个模型来创建、修改、删除用户。
  2. 身份验证:用户可以通过用户名和密码进行身份验证;Auth 组件提供了相关的视图和函数,用于处理用户登录和注销的逻辑。
  3. 权限管理:Django Auth 组件支持基于角色的权限管理;开发者可以为用户分配不同的角色,并为这些角色设置相应的权限;这样,就可以控制用户可以访问哪些页面或执行哪些操作。
  4. 会话管理:Django Auth 组件使用 Django 的会话框架来管理用户的登录状态;一旦用户成功登录,他们的信息将被存储在会话中,以便在后续的请求中识别用户。
  5. 密码管理:Auth 组件提供了密码加密和哈希的功能,以确保用户密码的安全性。密码不会以明文形式存储在数据库中。

auth_user 数据表

auth_user 表是 Django auth 模块默认使用的用户模型表,用于存储用户信息,如用户名、密码、电子邮件等。

我们早就见过这张表,只是把它忽略了

  • 其实在我们刚创建好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
  • email
  • 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中,为了实现分页功能,通常需要考虑以下几个参数:

  1. 当前第几页 (current_page): 用户当前正在查看的页面。这个信息通常由前端通过GET请求传递给后端。例如,如果用户想看第3页的数据,那么前端可能会向/data?page=3这样的URL发送请求。
  2. 总数据量 (total_count): 从数据库中查询出来的总记录数。这个信息通常是通过执行一个数据库查询来获取的,例如使用Django的count()方法。
  3. 每页展示的条数 (per_page): 这是一个固定的数值,表示每页应该显示多少条数据。这个值可以根据应用的需求进行设置,您提到的20条是一个常见的选择。
  4. 总页数 (total_pages): 通过将总数据量除以每页展示的条数来计算得到。如果有余数,则总页数需要向上取整,因为即使最后一页不满一页的数据,也需要一个页面来展示。

QuerySet对象

在Django中,可以使用QuerySet对象的切片功能来实现分页;

切片操作允许指定起始索引和结束索引来获取QuerySet的一个子集。

  1. 切片索引:QuerySet对象支持索引取值和切片操作,但是不支持负数索引;因此计算startend时,确保使用正数索引。
  2. 计算startendstartend的计算公式:start = (current_page - 1) * per_page_numend = current_page * per_page_num
  3. 边界条件:当current_page等于总页数时,end的值可能会超出数据的实际范围;为了避免这种情况,需要调整end的计算方式,例如使用min(end, total_count),其中total_count是数据的总数量。
  4. 异常处理:处理current_page转换为整数时可能出现的异常,可能还需要处理其他潜在的异常,例如当current_page小于1或大于总页数时。
  5. QuerySet切片:一旦有了startend的值,就可以使用它们来对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的文件夹。这个文件夹将用于存放各种工具函数、类库封装等。

自定义分页器核心思路:

  1. 需求分析:首先明确分页需求,例如每页显示多少条数据、是否支持跳转至指定页码等。
  2. 数据结构:设计分页所需的数据结构,如当前页码、总页数、每页数据量等。
  3. 逻辑处理:编写逻辑来根据请求参数(如当前页码)计算需要展示的数据范围,并从数据源中获取这些数据。
  4. 界面集成:在前端展示界面集成分页控件,如页码选择器、上一页/下一页按钮等。
  5. 测试与优化:对分页功能进行全面测试,确保在各种情况下都能正确工作,并根据需要进行性能优化。

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">&laquo;</span>
            </a>
        </li>
        {# 模板中无法使用range函数,不能传参,循环页数需要在后端执行 #}
        {# 展示循环后的所有li #}
        {{ page_html|safe }}

        <li>
            <a href="#" aria-label="Next">
                <span aria-hidden="true">&raquo;</span>
            </a>
        </li>
    </ul>
</nav>

</body>
</html>


Django 信号量


信号简介

Django的信号(Signals)是一种观察者模式的实现,它允许特定的发送者(senders)在特定事件发生时,通知一组接收者(receivers)。

这些事件可以是Django框架中的任何重要动作,比如模型的保存、删除,用户登录等。

通过连接(connect)信号和接收者函数,开发者可以在不修改原始代码的情况下,增加新的行为或逻辑。


内置信号的种类
信号名描述
pre_initdjango的modal执行其构造方法前,自动触发
post_initdjango的modal执行其构造方法后,自动触发
pre_savedjango的modal对象保存前,自动触发
post_savedjango的modal对象保存后,自动触发
pre_deletedjango的modal对象删除前,自动触发
post_deletedjango的modal对象删除后,自动触发
m2m_changeddjango的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创建数据库连接时,自动触发

内置信号的使用
  1. 导入信号:首先,你需要从Django的django.db.models.signals模块中导入你想要的信号。
  2. 定义接收器函数:接下来,定义一个函数作为信号的接收器。这个函数将在信号被发送时调用。
  3. 连接信号和接收器:使用django.dispatch.receiver装饰器将接收器函数连接到特定的信号。你需要指定信号的名称和你想要连接的发送者。
  4. 确保接收器加载:为了确保接收器函数在应用启动时加载,你可以在应用的apps.py文件中的ready方法里导入它,或者在应用的__init__.py文件中导入。
  5. 触发信号: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
  • 28
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: Django是一个用于开发网络应用程序的开源Web框架,使用Python编写。它提供了一组工具和技术,可以帮助开发人员快速和高效地构建和管理复杂的Web应用程序。Django最初是由资深Web开发人员Adrian Holovaty和Simon Willison于2003年开发的。它采用了一种“约定优于配置”的方法,通过使用严格的约定来简化Web应用程序开发。 ### 回答2: PythonDjango框架是一个高效、灵活、开源的Web应用程序框架。使用Django开发可以节约时间和精力,实现高质量的Web应用程序。Django使用了一种MTV(Model-Template-View)模式,将业务逻辑、视图和模板分离开,使得应用程序的开发和维护更加容易。下面我将对Django开发过程进行介绍。 首先,要使用Django进行开发,需要安装PythonDjangoPython是一种高级编程语言,使用它可以开发各种类型的应用程序。Django使用Python语言进行开发,因此我们必须安装Python的最新版本。接着,我们需要安装Django框架。通常,使用pip命令来进行安装。安装完成后,就可以开始使用Django框架进行开发Django开发过程通常分为以下几个步骤: 1.创建项目:使用django-admin startproject 命令创建Django项目。这将自动生成Django框架的基本项目结构。 2.创建应用程序:使用python manage.py startapp 命令创建Django应用程序。 3.设计模型:在Django中,模型是数据的抽象。我们使用模型来定义应用程序的数据结构。 4.编写视图:视图是业务逻辑的处理。在Django中,视图接受浏览器发送的请求并返回相应的响应。 5.编写模板:模板是应用程序显示数据的方式。Django使用模板渲染引擎来动态地生成HTML。 6.测试应用程序:Django提供了单元测试框架可以轻松地测试应用程序是否按照预期工作。 7.部署应用程序:当应用程序测试完成后,我们可以将其部署到云服务器或本地服务器中。 Django框架还提供了许多有用的功能,例如自动管理表单、认证、安性等。此外,Django框架也提供了许多第三方的插件和库,以扩展和改进应用程序的功能。 总结来说,Django是一种理想的Web应用程序框架,它提供了快速开发Web应用程序的工具和技术。如果你是Python开发者,使用Django将会使你的工作更加容易,同时,你还可以使用它来开发高效、灵活、优雅的Web应用程序。 ### 回答3: Pythonweb框架Django可以被用来开发各种大小和类型的网站和应用程序。它作为一个快速,安和灵活的框架,可以提供高效的开发和维护经验。下面是Django开发的一些详细介绍: 优点: 1. 内置的ORM(对象关系映射)系统能够使得开发人员更加高效地管理数据,而不必编写大量的SQL代码。ORM也可以与多个数据库后端(如:SQLite,MySQL,PostgreSQL等)配合使用。 2. Django的模板系统十分灵活,可以轻松实现网站的多语言支持。 3. Django也提供了强大的缓存系统来加速网站的响应时间。 4. 可以使用Django开发RESTful API,从而通过HTTP协议来实现数据交互。 5. Django拥有丰富的第三方库和扩展,可以帮助开发人员更快速地完成开发工作。 缺点: 1. Django的学习曲线可能较为陡峭,需要一定的时间和经验来掌握其核心概念。 2. Django所具有的内置模块使其不太适合开发简单的、小型的应用程序。 3. 在开发高度定制化的应用程序时,Django可能会遇到约束和限制。 总的来说,Django是一种强大的web框架,能够减少开发人员的工作量,提高开发效率,并且拥有优秀的安性、可扩展性和可维护性。但是在使用之前需要了解其优点和缺点,结合实际开发情况,选择适合自己的工具。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

需要休息的KK.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值