• 自学Python第二十二天- Django框架


    自学Python第二十二天- Django框架


    Django 框架是一个基于 python 的重量级的 web 开发框架,现今很多大公司大项目都是使用 Django 框架。

    可以使用 pip install django 来安装 Django 库

    Django官方文档

    创建项目

    可以通过终端和 pycharm 两种方式来创建项目,区别是 pycharm 创建的项目会额外添加了一些模板信息。

    在终端创建项目

    Django 创建项目有点类似于 Scrapy ,在要创建项目的文件夹下,使用终端(命令行)输入命令。django_admin.exe 是在 python 下的 Scripts 下,默认添加到了环境变量中,所以可以直接执行。

    django-admin startproject 项目名称
    
    • 1

    执行完成后,会在当前目录下创建一个项目名称命名的文件夹。这样项目就创建成功了。

    pycharm 创建项目

    在 pycharm 中新建项目时,选择 Django 项目,即可创建 Django 项目了。需要注意的是,使用企业版的 pycharm 。

    默认项目文件

    创建完成的项目目录结构为(learnDjango是项目名称):

    learnDjango
    │  manage.py
    │
    └─ learnDjango
            asgi.py
            settings.py
            urls.py
            wsgi.py
            __init__.py
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • manage.py 负责项目的管理,包括启动项目、创建app、数据管理等
    • asgi.py 异步接收网络请求
    • wsgi.py 同步接收网络请求
    • urls.py URL和处理函数的对应关系
    • settings.py 项目的配置文件

    APP

    一个项目下可以有 N 个独立的 app 来处理不同的功能,每个 app 可以拥有独立的、不同的表结构、函数、HTML模板、CSS等。

    创建 app

    终端里,创建好的项目文件夹下,带参数运行 manage.py 即可创建一个新的 app:

    python manage.py startapp app名称
    
    • 1

    app 的文件结构

    app 的目录结构为(app01是app的名称)

    app01
    │  admin.py
    │  apps.py
    │  models.py
    │  tests.py
    │  views.py
    │  __init__.py
    │
    └─migrations
            __init__.py
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • migrations 记录 Django 的 model 对数据库进行的变更
    • tests.py 进行单元测试
    • admin.py django 默认提供的 admin 后台管理
    • app.py app的启动
    • views.py URL执行的函数(视图函数)
    • models.py 操作数据库

    快速上手

    当创建了项目和app之后,到应用启动还需要注意以下几点:

    • 确保 app 类已经注册

    在 app 目录下的 apps.py 中能看到类名称,通常是 app项目名+Config。将此类名称添加到 settings.py 里的 INSTALLED_APPS 列表中即可。需注意的是添加目录名和文件名,例如 ‘app01.apps.App01Config’

    • 编写 URL 和视图函数的对应关系

    在 urls.py 中编写对应关系。添加到 urlpatterns 列表中,格式为 path('路由路径', 函数名)。例如 path(‘index/’, views.index)。另外注意的是,需要导入 views.py 文件。

    • 编写视图函数

    在 views.py 中编写视图函数。需注意的是,视图函数默认需有形参 request。例如:

    from django.shortcuts import render,HttpResponse
    
    # Create your views here.
    
    def index(request):
        return HttpResponse("初学 Django")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    启动 django 项目

    终端启动

    在终端中输入 python manage.py runserver 来启动 django 服务,使用 ctrl + c 来结束服务

    pycharm 启动

    使用 pycharm 建立的 django 项目可以通过默认启动项直接启动,默认快捷键是 shift + F10

    templates 模板

    在视图函数中使用 render() 方法可以返回 html 页面

    def index(request):
    	return render(request, 'index.html')
    
    • 1
    • 2

    默认情况下 django 会在 app 目录下的 templates 目录下来查找需要使用的 html 页面文件。

    需要注意的是,django 并不是在当前 app 的 templates 目录下寻找,而是根据 app 的注册顺序,查找相应 app 目录下的 templates 目录下查找 html 文件。

    另外,可以在项目 settings.py 文件中的 TEMPLATES 字段下的 ‘DIRS’ 添加参数 [os.path.join(BASE_DIR, 'templates')] ,则优先在项目根目录下的 templates 目录下查找 html 文件。

    静态文件

    在开发过程中,一般将图片、CSS、js 等作为静态文件处理。django 会将静态文件存放在 app 目录下的 static 文件夹下。html 指向静态文件时使用相对路径,例如 src="/static/1.png"。所以一般 static 目录下会创建 img、js、css、plugins 等文件夹存放相应的静态文件。

    不过 django 推荐这样使用静态资源:

    {% load static %}
    
    <link rel="stylesheet" href="{% static 'plugins/bootstrap-5.1.3-dist/css/bootstrap.css' %}">
    
    • 1
    • 2
    • 3

    如果需要更改 static 目录,则在 settings.py 文件里的 STATIC_URL 里更改。

    模板语法

    模板语法本质上是写 HTML 时写一些占位符,由数据对占位符进行替换和处理。

    django 的模板语法是包含在 { } 中的。

    单个数据

    传递单个数据给页面变量

    使用 render() 方法时,可以添加一个字典参数,将需要传递到 html 中变量的变量名和值写入:

    def index(request):
    	text = '初学 Django'
    	return render(request, 'index.html', {"n1": text})
    
    • 1
    • 2
    • 3

    页面变量接收传递的单个数据

    在页面中使用 {{ 变量名 }} 来接送 render() 方法传递的数据。

    <h1> {{ n1 }} h1>
    
    • 1

    列表数据

    列表数据的传递和单个数据是一样的。区别在于接收时,索引不再使用方括号,而是使用点:

    def index(request):
    	text=["Python", "C", "Java", "Basic"]
    	return render(request, 'index.html', {"n1": text})
    
    • 1
    • 2
    • 3
    <h1>计算机编程语言有 {{ n1.0 }},{{ n1.1 }},{{ n1.2 }},{{ n1.3 }} 等h1>
    
    • 1

    循环语句

    循环的语法如下:

    {% for item in n1 %}
    	<span>{{ item }}span>
    {% endfor %}
    
    • 1
    • 2
    • 3

    字典数据

    字典数据的传递是一样的。接收时,使用 变量名.键 来获取字典数据。

    def index(request):
    	text ={"y1": "Python", "y2": "C", "y3": "Java", "y4": "Basic"}
    	return render(request, 'index.html', {"n1": text})
    
    • 1
    • 2
    • 3
    <h1>第一种语言是 {{ n1.y1 }},第二种语言是 {{ n1.y2 }},第三种语言是 {{ n1.y3 }},第四种语言是 {{ n1.y4 }}h1>
    
    • 1

    字典也可以使用循环

    {% for k, v in n1.items %}
    	<li>{{ k }} = {{ v }} li>
    {% endfor %}
    
    • 1
    • 2
    • 3

    嵌套数据

    对于嵌套的数据,例如列表里嵌套的字典,在接收时也可以使用嵌套的方式获取。类似于 python ,只是方括号改成了小数点。

    判断语句

    判断语句语法如下:

    {% if text == "Django老手" %}
    	<h1> 高手高手高高手 h1>
    {% elif text == "初学 Django" %}
    	<h1> 菜鸟一个 h1>
    {% else %}
    	<h1> 无法识别 h1>
    {% endif %}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    其他一些常用方法

    • 日期格式,使用管道符设置格式

    {{ 日期数据 | date:“Y-m-d H:i:s” }}

    {{ bio | truncatewords:“30” }}
    {{ my_list | first | upper }}
    {{ name | lower }}

    请求和响应

    请求

    视图函数中有一个默认形参 request,它是一个对象,封装了用户发送过来的所有请求数据。 request 对象的常用属性和方法有:

    • request.method :请求类型
    if request.method == "GET":
    	return render(request, "login.html")
    
    • 1
    • 2
    • request.GET :通过URL传递的参数
    • request.POST :通过请求体提交的数据
    username = request.POST.get("user")
    password = request.POST.get("pwd")
    
    • 1
    • 2

    响应

    HttpResponse 对象

    使用 HttpResponse 对象可以直接返回响应包,例如 HttpResponse(‘String’) 的响应包返回内容就是字符串。

    render() 方法

    使用 render() 方法通常返回一个 html 页面,也可以使用此方法向页面中传递数据。

    redirect() 方法

    redirect() 方法会重定向到另一个 url ,使用方法为:return redirect("https://www.baidu.com")

    数据库操作

    Django 开发操作数据库更简单,其内部提供了 ORM 框架,可以轻松的访问数据库。Django 官方支持 PostgreSQL、MariaDB、MySQL、Oracle、SQLite,还支持 MS SQL Server 等提供的后端。

    安装数据库的第三方库

    django 数据库是基于其他的第三方库,所以需要先安装。 这里使用 MySQL 数据库。因为 django 对 pymysql 的支持不是很好,据说有错误,所以使用 mysqlclient 库。pip install mysqlclient

    ORM

    ORM 是 django 操作数据库的模块,可以创建、修改、删除数据库中的表(无法创建、删除数据库),还可以操作表中的数据。

    连接到数据库

    ORM 连接数据库需要在 setting.py 中对 DATABASES 列表字段进行设置:

    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql',		# 数据库引擎
            'NAME': 'mytest',			# 数据库名
            'USER': 'root',				# 访问数据库用户名
            'PASSWORD': '123456',		# 访问数据库用户的密码
            'HOST': 'localhost',		# 数据库主机
            'PORT': 3306			# 访问数据库的端口
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    表操作

    创建表

    django 对于表的操作是在 models.py 文件中进行。

    在此文件中,创建一个类,继承 models.Model 类,即可创建表。然后在内的成员中定义表结构。

    class UserInfo(models.Model):
        name = models.CharField(max_length=16, verbose_name='姓名')
        password = models.CharField(max_length=64, verbose_name='密码')
        age = models.IntegerField(verbose_name='年龄')
        account = models.DecimalField(verbose_name='工资账户金额', max_digits=10, decimal_places=2, default=0)  # 精准小数,最大位数10位,小数位2位,默认值为0
        create_time = models.DateTimeField(verbose_name='入职时间')
        depart = models.ForeignKey(to='Department', to_field='id', on_delete=models.CASCADE)  # 外键,关联到 Department 表的 id 字段,级联删除
        gender_choices = ((1, '男'), (2, '女'))
        gender = models.SmallIntegerField(verbose_name='性别', choices=gender_choices)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    数据库中,表名称为app名称 + 下划线 + 小写类名。字段名称就是定义的成员变量,另外会自动添加一个名称为 id 的 bigint 型的自增字段作为主键。

    常用的类型有:

    • CharField : varchar 类型
    • IntegerField : int 类型
    • DecimalField : decimal 类型(精准小数)
    • DateTimeField :日期时间类型
    • ForeignKey :外键类型

    常用的参数有:

    • max_length :最大长度(字符串类型使用)
    • verbose_name :备注名称
    • default :默认值
    • null :是否可以为空
    • blank :同 null,通常一起使用
    • max_digits :数值最大位数
    • decimal_places :小数部分位数
    • to :关联表(外键)
    • to_field :关联列(外键)
    • on_delete :主键删除时处理方式(外键),可以使用值有 models.CASCADE (级联删除) 和 models.SET_NULL (置空)
    • choices :django 的选择值约束
    	# choices 选择约束
        gender_choices = ((1, '男'), (2, '女'))
        gender = models.SmallIntegerField(verbose_name='性别', choices=gender_choices)
    
    • 1
    • 2
    • 3

    删除表或字段

    只需要在 models.py 文件中,将需要删除的表相对的类删除(或注释),即可删除表。同理删除字段就是在类定义中将相应字段定义的成员删除。

    更改表或字段

    更改表或字段和删除表或字段类似,更改 models.py 文件中相应的类或类的成员即可。需要注意的是更改时数据库对数据的约束条件。例如没有设置可为空或具有默认值的字段,添加时要输入默认值等。

    执行数据库结构变更

    在终端执行命令

    python manage.py makemigrations
    
    • 1

    就会读取并处理 models.py,如果第一次则会在 migrations 文件夹下建立索引为 1 的初始化文件 initial.py 。之后每次执行此命令会根据当时的 models.py 和 migrations 下的历史文件,对数据库表进行比较,创建结构变更记录文件。

    然后在终端执行

    python manage.py migrate
    
    • 1

    就会根据 migratrons 下最后的变更文件,对数据库相应表的结构进行变动。

    另外需要注意的是,需要在 setting.py 文件中注册相应的 app。

    数据操作

    新增数据

    在实际使用中,会在视图函数中进行数据操作,所以在 views.py 中需要引入 models.py 文件。

    在视图函数中,可以使用表相应类的 create 方法添加数据

    from app01 import models
    
    # 添加数据,使用 models.表相应的类名称.objects.create(字段=值,字段=值...)
    models.Department.objects.create(title='销售部')
    models.UserInfo.objects.create(name='张三', password='123456', age=19)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    获取(查询)数据

    可以使用 all 方法获取所有数据,得到的是 QuarySet 类型的对象,可以当成数据列表,列表中的每个元素都是一个数据对象可以使用 对象.字段 的方式获取数据。

    data_list = models.UserInfo.objects.all()
    for data in data_list:
    	print(data.id,data.name,data.password,data.age)
    
    • 1
    • 2
    • 3

    也可以使用 filter 方法进行条件筛选,但是筛选出来的数据哪怕只有一条,获取的也是 QuarySet 对象。不过可以使用 first 方法从 QuarySet 中获取第一条数据。

    data = models.UserInfo.objects.filter(id=1).first()
    
    • 1

    对于查询条件,除了等于外还有

    示例说明
    id=12数值字段id的值等于12
    id__gt=12数值字段id的值大于12
    id__gte=12数值字段id的值大于等于12
    id__lt=12数值字段id的值小于12
    id__lte=12数值字段id的值小于等于12
    name__startswith=“张”字符串字段name以 “张” 开始
    name__endswith=“三”字符串字段name以 “三” 结尾
    name__contains=“老”字符串字段name包含了字符串 “老”

    另外可以使用 exclude 方法来排除筛选条件

    data = models.UserInfo.objects.filter(age=19).exclude(id=2).first()
    
    • 1

    查询结果返回其他类型

    使用 filter 方法和 all 方法返回的是一个查询的结果对象,是无法被 json 序列化的,有时候我们需要返回 json 结果,则可以使用 values 方法,参数可以指定返回的列。

    row_dict = models.UserInfo.objects.filter(id=uid).values('id', 'name', 'password', 'age').first()
    
    • 1

    需注意的是,values 方法获取的是个字典列表,所以可以使用 first 方法获取第一个值。另外可以使用 values_list 方法获取元组列表,元组顺序为参数指定的列顺序。

    row_dict = models.UserInfo.objects.filter(id=uid).values_list('id', 'name', 'password', 'age').first()
    
    • 1

    删除数据

    找到了需要处理的数据后,可以使用 delete 方法进行删除

    models.Department.objects.all().delete()
    models.UserInfo.objects.filter(id=3).delete()
    
    • 1
    • 2

    更改数据

    类似于删除数据,可以使用 update 方法对数据进行更新

    models.Department.objects.all().update(password=999)		# 将所有数据的 password 字段值改为 999
    models.UserInfo.objests.filter(id=3).update(name='张三',password='5445',age=20)
    
    • 1
    • 2

    查询分页

    django 可以控制返回查询记录的起始索引和结束索引,有点类似于切片。即,使用 [ 起始记录索引号 : 结束记录索引号 ] 的方式,并且也遵循左闭右开的原则。

    models.UserInfo.objects.all()[0:10]		# 返回查询结果索引为 0 - 9 的记录
    models.UserInfo.objects.filter(age__gte=22)[10:20]		# 返回查询结果索引为 10 - 19 的记录
    
    • 1
    • 2

    这样就可以进行分页了。起始记录索引号的值为 (页码 - 1) * 每页大小 , 结束记录索引号的值为 页码 * 每页大小

    查询后聚合操作

    可以使用 count 方法来返回查询记录总数

    num = models.UserInfo.objects.filter(age__gt=19).count()
    
    • 1

    可以使用 aggregate 方法来进行一些复杂的聚合运算,例如求和

    from django.db.models import Sum
    num = models.UserInfo.objects.all().aggregate(nums=Sum('age'))
    
    • 1
    • 2

    母版的使用

    有些页面内容可以作为母版使用,在母版页面中填充子页面,可以提高代码复用,减少工作量。

    母版页

    在母版页中,需要更改内容(放置子页面)的地方,使用 block 进行标记,即制作好了母版页。

    {% load static %}
    DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>部门列表title>
        <link rel="stylesheet" href="{% static 'plugins/bootstrap-3.3.7-dist/css/bootstrap.min.css' %}">
    head>
    <body>
    <script src="{% static 'js/jquery-3.6.0.min.js' %}">script>
    <script src="{% static 'plugins/bootstrap-3.3.7-dist/js/bootstrap.min.js' %}">script>
    {#导航条#}
    <nav class="navbar navbar-default">
    	...
    nav>
    
    {#内容#}
    <div>
        {% block content %}{% endblock %}
    div>
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    子页面

    在子页面的第一行,写入要继承的母版页,然后将需要填充到母版页的代码同样使用 block 标记即可。

    {% extends 'layout.html' %}
    
    {% block content %}
    {#内容#}
    <div class="container">
        <div class="panel panel-default">
    		...
        div>
    div>
    {% endblock %}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    django组件

    Form 表单

    form 表单的用法和 flask 等其他方法大致一样,不一样的地方在于 django 自带 csrf_token 校验。在视图函数获取表单数据时,会比对 csrf_token 的值,所以在 html 中的form 表单内部需要添加 {% csrf_token %} 用来生成随机值以进行比较。但是使用 form 表单会有很多问题,例如数据校验、错误提示、页面字段需要重写、关联表的数据呈现需要手动写入等等。所以在开发时通常使用 django 提供的组件来实现。

    Form 组件

    django 提供的 form 组件能够自动对数据进行校验,能够提供错误提示,能够自动生成字段。

    使用 form 组件需要在 views.py 中创建一个继承自forms.Form的类,用来处理 form 的内容。

    from django import forms
    class MyForm(forms.Form):
    	# 定义 form 的字段,并使用插件
    	user = form.CharField(widget=forms.Input,label='用户名',required=True)
    	pwd = form.CharField(widget=forms.Input,label='密码',required=True)
    	email = form.CharField(widget=forms.Input)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    然后在视图函数中实例化此类,并传递给前端页面

    def index(request):
    	form = MyForm()
    	return render(request, 'index.html', {'form': form})
    
    • 1
    • 2
    • 3

    然后前端页面使用 form 对象创建表单,而不是使用 等标签创建。form 对象会自动生成插件所标识的 html 标签。

    <form method="post">
    	
    	{ form.user }}
    	{{ form.pwd }}
    	{{ form.email }}
    	-->
    	
    	{% for field in form %}
    		{{ field.label }} : {{ field }}
    	{% endfor %}	
    form>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    field.label 可以获取字段的 verbose_name 名称,如果没有定义则取字段名称。

    ModelForm 组件

    使用 form 组件时,所有字段还是需要手动写一遍(在 views.py 定义的 form 类中),如果觉得麻烦,django 提供了另一个组件: ModelForm 组件

    生成表单

    使用 ModelForm 组件需要在 views.py 中创建一个继承自ModelForm的类,用来处理 form 的内容。

    from django import forms
    class MyForm(forms.ModelForm):
    	# 也可以使用自定义字段
    	# xx = form.CharField()
    	# 需要嵌套类 Meta
    	class Meta:
    		model = UserInfo	# UserInfo 是 models.py 中定义的相应数据表的类
    		fields = ["name", "password", "age"]		# 确定要操作的字段
    		# fields = "__all__"	# 使用全部字段
    		# exclude = ["password"]	# 排除特定字段,选择其他字段
    		# 使用自定义字段
    		# fields = ['name', 'password', 'age', 'xx']
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    传递数据和前端使用上则和 Form 组件类似

    自动生成的标签会丢失样式,所以实际使用中,会在定义要操作的字段时,定义字段的插件及属性,在插件属性中可以添加样式。

    from django import forms
    class MyForm(forms.ModelForm):
    	class Meta:
    		model = UserInfo
    		fields = ["name", "password", "age"]
    		widgets = {
                'name': forms.TextInput(attrs={'class': 'form-control'}),			# 定义字段使用的插件
                'password': forms.TextInput(attrs={'class': 'form-control'})		# 插件的参数中可以添加属性,将样式添加到这里
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    不过这样也字段多的时候也会比较麻烦。通常项目中会在 MyForm 初始化时,通过遍历字段给各字段添加属性的方式添加样式

    from django import forms
    class MyForm(forms.ModelForm):
    	class Meta:
    		model = UserInfo
    		fields = ["name", "password", "age"]
    	
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            for name, field in self.fields.items():
            	# 设置属性
            	if field.widget.attrs:		# 如果字段中有属性,保留原属性
            		field.widget.attrs['class'] = 'form-control'
            		field.widget.attrs['placeholder'] = '请输入' + field.label
            	else:
            		field.widget.attrs = {'class': 'form-control', 'placeholder': '请输入' + field.label}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    提交表单

    ModelForm 组件的表单时,可以将数据返回定义的类来进行校验等操作

    form = MyForm(data=request.POST)		# 创建对象,将提交的表单数据作为参数
    if form.is_valid():		# 使用 is_valid 方法对数据进行有效性校验,如果有效返回 True
    	print(form.cleaned_data)		# 整理数据成一个字典
    	form.save()		# 保存数据
    else:
    	# 如果校验失败,此时错误信息会保存在 form 里,可以将其返回页面,在页面中接收
    	return render(request, 'user_add_ModelForm.html', {'form': form})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    {% for field in form %}
        
        <div class="form-group">
            <label>{{ field.label }}label>
            {{ field }}
            
            <span style="color: red">{{ field.errors.0 }}span>
        div>
    {% endfor %}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    填充数据

    如果需要在表单中填充数据,例如编辑的时候会有默认数据,只需要创建 form 对象时将数据库数据作为参数传给类的 instance 参数即可,其他的完全同生成表单

    def user_edit(request, nid):
        """编辑用户"""
        if request.method == "GET":
            row_obj = models.UserInfo.objects.filter(id=nid).first()
            form = MyForm(instance=row_obj)
            return render(request, 'user_edit.html', {'form': form})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    更新数据

    在提交表单时,使用 form.save() 方法可以添加数据到数据库,但是更新原有数据时必须告诉 form 操作的是哪条数据

    row_obj = models.UserInfo.objects.filter(id=nid).first()
    form = UserModelForm(data=request.POST,instance=row_obj)
    if form.is_valid():  # 检测数据有效性
    	form.save()  # 保存进数据库
    	return redirect('/user/list/')
    else:
    	# 校验失败,显示错误信息
    	return render(request, 'user_add_ModelForm.html', {'form': form})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    保存额外数据

    使用 form.save() 方法保存表单数据到数据库时,实际上保存的是用户提交的表单中的数据。如果有一些其他的数据不在用户提交的保单中,则可以手动添加数据到字段,然后保存

    form = UserModelForm(data=request.POST)
    if form.is_valid():  # 检测数据有效性
    	# 使用 form.instance.字段名 = 值 来手动添加数据,然后保存到数据库
    	form.instance.position = '新入职员工'
    	form.save()  # 保存进数据库
    	return redirect('/user/list/')
    else:
    	# 校验失败,显示错误信息
    	return render(request, 'user_add_ModelForm.html', {'form': form})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    数据校验

    简单校验

    is_valid 方法默认进行空值检测,如果需要更多校验方式,例如字符位数等,需要自定义字段

    from django import forms
    class MyForm(forms.ModelForm):
    	name = forms.Charfield(min_length=3,label="用户名")		# 此字段最小长度为3个字符
    	# name = forms.Charfield(disabled=True,label="用户名")		# 不可用模式,但是提交表单时认为是空
    	password = forms.Charfield(label='密码',required=True)	# 必填校验
    	class Meta:
    		model = models.UserInfo
    		fields = ['name', 'password', 'age']
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    使用正则表达式校验

    在自定义字段时,可以添加参数 validators,使用正则表达式 RegexValidator 类进行校验自定义的字段。

    from django.core.validators import RegexValidator
    class PrettyNumForm(forms.ModelForm):
        mobile = forms.CharField(
            label='手机靓号',
            validators=[RegexValidator(r'^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$','手机号格式错误')]	# 参数2表示校验失败显示内容
        )
        class Meta:
            model = models.PrettyNum
            # fields = "__all__"  # 使用所有字段
            # exclude = ['level']   # 排除某个字段,取其他的字段
            fields = ['mobile','price','level','status']
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            for name, field in self.fields.items():
                if type(field.widget) == django.forms.widgets.TextInput or type(field.widget) == django.forms.widgets.NumberInput:
                    field.widget.attrs = {'class': 'form-control', 'placeholder': '请输入' + field.label}
                else:
                    field.widget.attrs = {'class': 'form-control'}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    使用校验函数校验

    可以在类定义中使用 clean_字段名 方法,将相应的字段数据传入并进行校验

        def clean_mobile(self):		# 校验 mobile 字段
            from django.core.exceptions import ValidationError		
            txt_mobile = self.cleaned_data['mobile']		# 获取 mobile 字段的数据
            if len(txt_mobile) != 11:				# 进行检验
                raise ValidationError("格式错误")		# 没通过检验,抛出一个错误
            else:
                return txt_mobile			# 通过检验,返回值
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    自定义校验,返回错误信息

    可以通过 form.cleaned_data 获取返回的 form 数据字典,所以可以通过 form.cleaned_data[key] 来获取需要的表单数据,来自定义一些数据校验方式。也可以使用 form.cleaned_data.pop(key) 的方式来获取特定键的值,区别在于使用 pop 方法为弹出数据。

    如果自定义校验,或者进行数据核对(例如比较数据库中的信息),则可以主动添加错误信息并返回。

    form = UserModelForm(data=request.POST)
    if form.is_valid():  # 检测数据有效性
    	if models.UserInfo.objects.filter(name=form.cleaned_data['username'], password=form.cleaned_data['password']).exists():
    		# 登录成功,跳转页面
    		return redirect('/user/list/')
    	else:
    		# 用户名和密码对不上
    		form.add_error('password', '用户名或密码错误')  # 主动添加错误信息给 password 字段
    # 校验失败,显示错误信息
    return render(request, 'user_add_ModelForm.html', {'form': form})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    信息提示语言

    通过实例测试可以知道,返回的错误信息是英文的,这是因为 django 在设置上是使用英文 en-us ,可以在 setting.py 中的 LANGUAGE_CODE 设置成中文 zh-hans

    后端传递代码到前端

    有时候,在前端不好确定的代码,可以通过后端生成,然后传递给前端使用。这里以翻页的手动代码进行举例:

    from django.utils.safestring import mark_safe  # 将后端字符串标记为安全,可以传递到前端做为前端的 HTML 代码使用
        # 前端的翻页页码
        page_list = []
    
        # 计算出当前页的前三后三页
        if page >= 5:  # 需要首页
            ele = f'
  • {q}&order={order}&by={by}&page=1">首页
  • '
    page_list.append(ele) for i in range(page - 3, page + 4): if i <= 0 or i > all_page: continue if i == page: ele = f'
  • {q}&order={order}&by={by}&page={i}">{i}(current)
  • '
    else: ele = f'
  • {q}&order={order}&by={by}&page={i}">{i}
  • '
    page_list.append(ele) if page <= all_page - 4: ele = f'
  • {q}&order={order}&by={by}&page={all_page}">尾页
  • '
    page_list.append(ele) page_str = mark_safe(''.join(page_list)) # 使用 mark_safe() 方法将字符串变为前台代码 return render(request,'pn_list.html', page_str)
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    如果不使用 mark_safe 标记,传到前端的数据会以字符串形式呈现。标记后则前端认为字符串是就是前端代码。

    django 使用 cookie 和 session

    创建 session 和 cookie 信息

    在 django 中,使用 request.session[key] = value 能够记录 session 的信息,同时会自动生成验证 session_id 返回到用户浏览器 cookie 中,并且将 session_id 和验证字符串都存储到数据库的 django_session 表中,以便下次使用时自动验证。

    获取并检查 session 和 cookie 信息

    如果是已经记录 session 信息的用户再次访问,则请求信息的 cookie 中会有 session_id 。django 会从数据库中查询是否存在此 session_id,如果存在则能够获取设置的 session[key] 的 value ,如果不存在则不能够获取。所以使用 request.session.get(key) 来获取设置数据就能够自动检查 session 信息。

    session 超时

    可以使用 request.session.set_expiry(sec) 来设置 session 的超时时间,sec 单位为秒。

    注销 session 信息

    使用 request.session.clear() 方法可以清除 session 信息,即进行注销。

    django 的中间件

    django 收到一个请求后,会经过一系列的中间件后达到视图函数,经过视图函数处理后,再经过这些中间件后将数据返回给用户浏览器。

    中间件处理请求的方法是 process_request,处理响应的方法是 process_response。如果请求在通过中间件时, process_request 没有返回值(或返回值为 None),则会继续交给下一步处理;如果有返回值(返回值是 HttpResponse、render、redirect),则会禁止请求往下进行,而将返回值交给本类的 process_response 方法返回请求,直接返回给客户端,所以可以将一些放在视图函数之前或之后的行为写在中间件中。

    中间件的处理顺序按照在 settings.py 中注册的顺序,请求是顺序处理,响应是逆序处理。

    使用中间件

    django 使用的中间件可以在 settings.py 中的 MIDDLEWARE 字段定义。

    自定义中间件

    所有自定义的中间件(类)需继承 django.utils.deprecation.MiddlewareMixin 类。并且要在 settings.py 中添加注册

    from django.utils.deprecation import MiddlewareMixin
    from django.shortcuts import redirect
    
    class AuthMiddleware(MiddlewareMixin):
        """访问验证中间件"""
        def process_request(self,request):
            # 如果请求访问 login 页面则不进行检测
            if request.path_info == '/login/':          # request.path_info 能获取请求URL
                return
            # 1. 读取当前访问用户的 session 信息,如果能读到,说明已经登录过
            username = request.sesssion.get('username')
            if username:
                return
            # 2. 如果没有登录过,
            else:
                return redirect('/login/')
    
        def process_response(self,request,response):
            return response
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    django 使用 AJAX

    django 项目中也可以使用 ajax 技术

    前端

    前端和其他 web 框架一样,需要注意的是,django 接收 POST 请求时,需要 csrf_token 进行验证,而在 ajax 中获取 csrf_token 比较麻烦。所以通常会在后端免除 csrf_token 验证。

    绑定事件方式

    {% extends 'layout.html' %}
    {% block content %}
        <div class="container">
            <h1>任务管理h1>
            <input type="button" class="btn btn-primary" value="点击" onclick="clickMe();"/>
        div>
    {% endblock %}
    {% block js %}
        <script type="text/javascript">
            function clickMe() {
                $.ajax({
                    url: "/test/ajax/",
                    type: "get",
                    data: {
                    	type:'add',
                        n1:123,
                        n2:456
                    },
                    success: function (res) {
                        console.log(res);
                    }
                })
            }
        script>
    {% endblock %}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    以 jQuery 方式

    {% extends 'layout.html' %}
    {% block content %}
        <div class="container">
            <h1>任务管理h1>
            <input type="button" class="btn btn-primary" value="点击" id="btn1" />
        div>
    {% endblock %}
    {% block js %}
        <script type="text/javascript">
            $(function (){
                // 页面框架加载完成后代码自动执行
                bindBtn1Event();        // 绑定事件
            })
            function bindBtn1Event(){
                $("#btn1").click(function (){       // 绑定到 btn1 的 click 事件的函数
                    $.ajax({
                        url: "/task/ajax/",
                        type: "POST",
                        data: {
                            type:'add',
                            n1:123,
                            n2:456
                        },
                        dataType:'JSON',
                        success: function (res) {
                            console.log(res);
                            // console.log(res.res_add)
                            // console.log(res['res_add'])
                        }
                    })
                })
            }
        script>
    {% endblock %}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    后端

    后端部分只要将响应请求 url 绑定视图函数,则可以在视图函数中进行处理

    def task_ajax(request):
        """测试ajax"""
        return HttpResponse('成功')
    
    • 1
    • 2
    • 3

    返回 json 数据

    可以使用 json 的 dumps 方法将字典转为 json 并返回

    import json
    
    def task_ajax(request):
    	res_dict = {'res':'ok', 'data':{'k1': 'v1', 'k2': 'v2'}}
    	res_dict = json.dumps(res_dict)
        return HttpResponse(res_dict)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    也可以直接使用 JsonResponse 返回数据

    from django.http import JsonResponse
    
    def task_ajax(request):
    	res_dict = {'res':'ok', 'data':{'k1': 'v1', 'k2': 'v2'}}
        return JsonResponse(res_dict)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    禁用 csrf 校验检查

    前端发送 post 请求时如果不加载 csrf_token,则后端需要禁用 csrf 校验检查

    from django.views.decorators.csrf import csrf_exempt
    
    @csrf_exempt
    def task_ajax(request):
    	return HttpResponse('成功')
    
    • 1
    • 2
    • 3
    • 4
    • 5

    Ajax 结合 ModelForm

    Ajax 结合 ModelForm 在前端实际上几乎没有改变,要注意的也就是按钮绑定 Ajax 函数不用 csrf_token

    在后端,ModelForm 其实接收到的数据也是 form 的字典格式,包含校验、保存等处理方式没有变化,只是在返回值时有了变化。

    处理重定位信息

    因为 Ajax 是接收处理数据,所以后端返回 重定位 信息是无法跳转的。如果希望跳转,则需要返回一个 json ,ajax 收到了这个特定的 json 数据后使用 js 进行页面跳转。

    处理表单错误信息

    另外返回 ModelForm 错误信息时,form.errors 获取的是一个字典,可以整理成 json 格式返回给 ajax 处理。

    
    <form id="addForm" novalidate>
        <div class="clearfix">
            {% for field in form %}
                
                <div class="col-xs-6">
                    <div class="form-group">
                        <label>{{ field.label }}label>
                        {{ field }}
                        
                        { field.errors.0 }} -->
                        <span style="color: red">span>
                    div>
                div>
            {% endfor %}
            <div class="col-xs-12">
                <button type="button" id="btnAdd" class="btn btn-primary">添加button>
            div>
        div>
    form>
    <script type="text/javascript">
        $(function () {
            // 页面框架加载完成后代码自动执行
            bindBtnAddEvent();
        })
        function bindBtnAddEvent() {
            $("#btnAdd").click(function () {
            	$(".error-msg").empty();        // 清空上一次的错误信息
                $.ajax({
                    url: "/task/add/",
                    type: "POST",
                    data: $("#addForm").serialize(),
                    dataType: 'JSON',
                    success: function (res) {
                        if(res.status){
                            alert("添加成功!");
                        }else{
                            console.log(res);
                            $.each(res.error,function (name,data){      // 循环每一个错误,获取键值
                                // 拼接 id_ 和 name,获取对应文本框的 id
                                // 查找文本框元素的下一个元素(span),使其文本(text)为错误信息
                                $("#id_" + name).next().text(data[0]);
                            })
                        }
                    }
                })
            })
        }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    文件上传

    简单的文件上传

    前端会将文件以 post 请求发送至后端,后端可以使用 request.FILES 来接收。需要注意的是 form 需要 enctype 属性。

    <form method="post" enctype="multipart/form-data">
        {% csrf_token %}
        <input type="text" name="filename">
        <input type="file" name="file">
        <input type="submit" value="提交">
    form>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    file_obj = request.FILES.get('file')  # 获取上传文件的对象
    filename = request.POST.get('filename', file_obj.name)      # 获取设置的文件名,如果没有则为原文件名。
    with open(filename, mode='wb') as f:
        for chunk in file_obj.chunks():  # 将上传文件分块读取
            f.write(chunk)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    使用 Form 组件上传文件

    使用 Form 时可以定义文件字段 FileField ,定义后就能够上传文件了。

    class UpForm(forms.Form):
    	name = forms.CharField(label='姓名')
    	age = forms.IntegerField(label='年龄')
    	img = forms.FileField(label='头像')
    
    def upload_form(request):
    	form = UpForm(data=request.POST, files=request.FILES)
    	if form.is_valid():
    		print(form.cleaned_data)
    		# 文件在 form.cleaned_data 的相应字段(img)内
    		img = form.cleaned_data.get('img')
    		file_path = os.path.join('app01', 'static', 'img', img.name)	# 拼接文件路径
    		with open(file_path,'wb') as f:		# 写入文件
    			for chunk in img.chunks():
    				f.write(chunk)
    
    	else:
    		return render(request, 'upload.html', {'form': form})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    <form method="post" enctype="multipart/form-data" novalidate>
    	.
    	.
    	.
    form>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    文件上传至上传目录 media

    通常使用的静态资源都在 static 文件夹内,如果想要用户上传文件到一个特定文件夹,例如项目根目录下的 media 文件夹,则需要在 urls.py 中进行设置启用。首先引入一些资源,再在 urls.py 文件的 urlpatterns 字段添加:

    from django.views.static import serve
    from django.urls import path, re_path
    from django.conf import settings
    
    re_path(r'^media/(?P.*)$', serve, {'document_root': settings.MEDIA_ROOT}, name='media'),
    
    • 1
    • 2
    • 3
    • 4
    • 5

    然后在 settings.py 中进行配置:

    import os
    
    MEDIA_ROOT = os.path.join(BASE_DIR, 'media')		# 项目根目录下的 media 目录
    MEDIA_URL = '/media/'
    
    • 1
    • 2
    • 3
    • 4

    此时上传文件保存的路径可以写为

    media_file_path = os.path.join('media', image_obj.name)
    
    • 1

    这样将用户上传的文件放在 media 目录下,也可以通过 /media/文件名 的 url 来访问,例如 http://127.0.0.1:8000/media/001.png

    使用 ModelForm 组件上传文件

    ModelForm 组件可以自动上传文件,并存储保存路径到数据库,不用再写相应保存的代码,且能够自动进行重名处理。只是需要设置了上传保存目录。

    需注意的是,保存到数据库中的文件路径也是 media 下的相对路径,并且不包含 media ,使用时候注意添加 media。

    # models.py
    
    class City(models.Model):
    	"""城市"""
    	name = models.CharField(verbose_name='名称', max_length=32)
    	count = models.IntegerField(verbose_name='人口')
    
    	# 本质上数据库存储的是文件路径,也是CharField,可以自动保存数据
    	# upload_to 指的就是上传文件到哪个目录,是 media 目录下的相对路径
    	img = models.FileField(verbose_name='logo', max_length=128, upload_to='city/')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    # views.py
    
    class UpModelForm(forms.ModelForm)
    	class Meta:
    		model = models.City
    		fields = '__all__'
    	
    def upload_modal_form(request):
    	form = UpModelForm(data=request.POST, files=request.FIELS)
    	if form.is_valid():
    		# 保存文件,保存 form 字段信息和文件路径并写入数据库
    		form.save()
    		return HttpResponse('成功')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
  • 相关阅读:
    AVL树高度求解
    Ubuntu22.04 Opencv4.5.1 CPU和GPU编译攻略,Opencv CPU和GPU编译保姆教程 亲自测试。
    Synchronized锁1
    三万字带你了解那些年面过的Java八股文
    java面试宝典2019
    11月新书预告——GNN、深度学习和元宇宙
    使用 Dify 和 MoonShot API 做一个懒人 AI 阅读工具(二):轻量 RAG 应用
    PostgreSQL修炼之道笔记之准备篇(一)
    设计模式复习题
    【STM32CubeMX】STM32H743配置IAP升级
  • 原文地址:https://blog.csdn.net/runsong911/article/details/126011697