前言

过时的技术,笔记是原来写的。但是由于其中有一些例子,所以想着还是上传一下。

非要用Python写后端,请移步使用Flask或者FastAPI

安装Django

1
pip install Django

有一个项目生成器django-admin.exeScripts中,可以快速创建一个Django项目。

当前生成器的路径为:

1
D:\anaconda\envs\django\Scripts\django-admin.exe

创建Django项目

Django项目中有一个默认的文件和默认的文件夹

使用django-admin创建项目

使用命令创建

1
D:\anaconda\envs\django\Scripts\django-admin.exe startproject mysite

mysite为项目名称,项目结构为:

1
2
3
4
5
6
7
8
mysite
├─ manage.py
└─ mysite
├─ __init__.py
├─ asgi.py
├─ settings.py
├─ urls.py
└─ wsgi.py

使用PyCharm创建项目

有奇怪的问题,创建不了Django项目,可以先创建一个新的env环境,然后在选择环境现有的conda环境,之后删除envs即可。

项目说明

  • 使用命令行创建的项目是标准的

  • Pycharm创建的项目,在标准的基础上多加了点东西

    • 创建了一个名为templates的文件夹,用来放模版文件

    • settings.py中:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      TEMPLATES = [
      {
      "BACKEND": "django.template.backends.django.DjangoTemplates",
      "DIRS": [BASE_DIR / 'templates']
      ,
      "APP_DIRS": True,
      "OPTIONS": {
      "context_processors": [
      "django.template.context_processors.debug",
      "django.template.context_processors.request",
      "django.contrib.auth.context_processors.auth",
      "django.contrib.messages.context_processors.messages",
      ],
      },
      },
      ]

      其中的"DIRS": [BASE_DIR / 'templates']把其中的内容删除即可。

默认项目的文件介绍

项目结构:

1
2
3
4
5
6
7
8
mysite
├─ manage.py 【项目管理,启动项目,创建app,数据管理】
└─ mysite 【跟项目同名】
├─ __init__.py
├─ settings.py 【项目的配置文件】 数据库等操作 常修改
├─ urls.py 【url和函数的对应关系】 常操作
├─ asgi.py 【接受网络请求】异步 不要修改
└─ wsgi.py 【接受网络请求】同步 不要修改

APP

1
2
3
4
5
6
7
8
9
项目
-app 用户管理 【表结构 函数 HTML模版 CSS】
-app 订单管理 【表结构 函数 HTML模版 CSS】
-app 后台管理 【表结构 函数 HTML模版 CSS】
-app 网站 【表结构 函数 HTML模版 CSS】
-app API 【表结构 函数 HTML模版 CSS】
……

一般情况下,项目创建一个app即可。

创建app

1
python manage.py startapp app

app结构

1
2
3
4
5
6
7
8
9
app01
├─ __init__.py
├─ admin.py 固定不动 提供admin后台管理
├─ apps.py 固定不动 app启动类
├─ migrations 固定不动 数据库变更记录
│ └─ __init__.py
├─ models.py 【重要】 对数据库操作
├─ tests.py 固定不动 单元测试
└─ views.py 【重要】函数,与urls.py联动

快速上手

  1. 确保APP已经注册【setting.py】

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    INSTALLED_APPS = [
    # 自己添加的app
    "app01.apps.App01Config",
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    ]
  2. 编写url和视图函数的对应关系 【urls.py】 绑定相应的执行函数

    urls.py

    1
    2
    3
    4
    5
    6
    urlpatterns = [
    # path("admin/", admin.site.urls),
    # www.xxx.com/index/ -> views.index
    path("index/", views.index),
    path("user/add", views.user_add),
    ]
  3. 编写视图函数,接收用户的请求 【views.py】

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    from django.shortcuts import render
    from django.http import HttpResponse


    # Create your views here.


    def index(request):
    # return HttpResponse("Hello, Django!")
    return render(request, "index.html")


    def user_add(request):
    return HttpResponse("添加用户成功!")
  4. 启动Django项目

    • 命令行启动

      1
      python manage.py runserver
    • Pycharm启动

      选中项目文件夹点击运行即可。

      image-20240210121133846

      添加/index查看结果

      image-20240210121206337

真正构建一个网页

加载模版文件

1
2
3
def index(request):
# return HttpResponse("Hello, Django!")
return render(request, "index.html")

关于index.html的查找顺序,先去本项目的根目录中的templates文件夹,如果没有,那就根据注册的app的顺序,依次查找templates文件夹中的HTML文件。

当然如果在setting.py中写入了项目文件的根目录中的templates,那么就会优先去项目文件的根目录中找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
# "DIRS": [BASE_DIR / 'templates']
"DIRS": [os.path.join(BASE_DIR, "templates")]
,
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]

加载静态文件

在开发过程中,一般将:

  • img
  • CSS
  • js
  • plugins

都会当做静态文件处理。静态文件一定要放在static文件夹中。推荐的目录结构:

1
2
3
4
5
static
- css
- img
- js
- plugins

推荐的引入方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{#推荐使用的静态文件引入方法 是一种语言模版#}
{% load static %}

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Hello, Django! 已修改</h1>

<img src="{% static 'img/test.png' %}" alt="index">
</body>
</html>

静态文件的相关的设置,在setting.py

1
STATIC_URL = "/static/"

模版语法

本质上:Django提供的,可以在HTML中写一些占位符,有数据对这些占位符进行替换和处理。

例如:

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>模版语法的学习</h1>
<div>作者:{{ name }}</div>
<div>
<h2>可支持访问列表中的元素</h2>
<p>列表:{{ list }}</p>
<p>列表中的第一个元素:{{ list.0 }}</p>
<p>列表中的第二个元素:{{ list.1 }}</p>
<p>列表中的第三个元素:{{ list.2 }}</p>
</div>

<div>
<h2>支持for循环</h2>
{% for item in list %}
<p>列表中的元素:{{ item }}</p>
{% endfor %}
</div>

<div>
<h2>支持访问字典</h2>
<p>字典:{{ dict }}</p>
<p>字典中的第一个元素:{{ dict.name }}</p>
<p>字典中的第二个元素:{{ dict.age }}</p>
</div>

<div>
<h2>支持for循环</h2>
<ul>
<p>支持获取keys,values,items</p>
{% for key,value in dict.items %}
<li>{{ key }} : {{ value }}</li>
{% endfor %}
</ul>
</div>

<div>
<h2>嵌套的情况</h2>
{% for i in data_list %}
<p>第一层循环:{{ i }}</p>
{% for j,values in i.items %}
<p>第二层循环:{{ j }} : {{ values }}</p>
{% endfor %}
{% endfor %}
</div>

<hr>

<div>
<h2>支持if判断</h2>
{% if name == "gcnanmu" %}
<p>你是{{ name }}</p>
{% elif name == "giao" %}
<p>你是giao哥</p>
{% else %}
<p>你是谁?</p>
{% endif %}
</div>

<div>
<h2>支持过滤器</h2>
<p>原始数据:{{ name }}</p>
<p>转换为大写:{{ name|upper }}</p>
<p>转换为小写:{{ name|lower }}</p>
<p>转换为首字母大写:{{ name|title }}</p>
<p>转换为首字母小写:{{ name|lower }}</p>
</div>
</body>
</html>

views.py中的

1
2
3
4
5
6
7
8
9
10
11
12
def about(request):
name = "GcNanmu"
list = ["gcnanmu", "xiaoming", "xiaohong"]
dict = {"name": "gcnanmu", "age": 18}

data_list = [
{"name": "gcnanmu", "age": 18},
{"name": "xiaoming", "age": 20},
{"name": "xiaohong", "age": 22},
]
# return HttpResponse("关于我们")
return render(request, "about.html", {"name": name, "list": list, "dict": dict, "data_list": data_list})
image-20240210175831264

整个过程:

image-20240210180542093

视图函数的render内部:

  1. 读取含有模板语法的HTML文件

  2. 内部进行渲染(模板语法执行并替换数据)

    最终得到,只包含HTML标签的字符串

  3. 最后才将完成的字符串返回给用户浏览器

也就是说,用户不可能看到占位符中花括号的内容,都是Django渲染完成后才返回,可以在网页源代码中得到证实。

案例:伪联通新闻中心

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[
{
"news_id":1895,
"news_title":"联通5G 智造未来 中国联通带您解锁 2021中国5G+工业互联网大会"
"post_time":"2021-11-19",
},
{
"news_id":1894
"news_title":"服务在升级系列解读之一:下一程,联通服务该如何发力?"
"post_time":"2021-11-19",
},
{
"news_id":1893
"news_title":"中国联通5G赋能百行百业成果巡礼——惠州市仲恺电子信息产业群 5G助力电子信息产业降本增效"
"post_time":"2021-11-18",
}
]

news.html

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>新闻列表</title>
</head>
<body>
<h1>新闻中心</h1>

<h2>总列表</h2>
<ul>
<li>{{ data.0 }}</li>
<li>{{ data.1 }}</li>
<li>{{ data.2 }}</li>
<li>{{ data.count }}</li>
</ul>

<h2>详细信息</h2>
<dl>
{% for i in data %}
<dt>title:{{ i.news_title }}</dt>
<dd>id:{{ i.news_id }} data:{{ i.post_time }}</dd>
{% endfor %}
</dl>
</body>
</html>

views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def news(request):
# return HttpResponse("新闻")
# 1. 定义一些新闻数据 或 从数据库中查询新闻数据
data = [
{
"news_id": 1895,
"news_title": "联通5G 智造未来 中国联通带您解锁 2021中国5G+工业互联网大会",
"post_time": "2021-11-19",
},
{
"news_id": 1894,
"news_title": "服务在升级系列解读之一:下一程,联通服务该如何发力?",
"post_time": "2021-11-19",
},
{
"news_id": 1893,
"news_title": "中国联通5G赋能百行百业成果巡礼——惠州市仲恺电子信息产业群 5G助力电子信息产业降本增效",
"post_time": "2021-11-18",
}
]
return render(request, "news.html",{"data":data})

请求与响应

  1. 请求的方式

  2. 获取请求的数据

    发送请求:

    1
    http://localhost:8000/something/?n1=456&n2=123

    接受请求参数:

    1
    print(f"URL上的参数:{request.GET}")

重定向的原理:

image-20240211151451451

案例:用户登录

image-20240211151451451

这是Django的特殊机制之一,相当于保密机制

1
2
3
4
5
6
7
8
9
10
11
<form action="/login/" method="post">
{% csrf_token %}

<label for="username">用户名:</label>
<input type="text" name="username" id="username" placeholder="用户名">
<br>
<label for="password">密码:</label>
<input type="password" name="password" id="password" placeholder="密码">
<br>
<input type="submit" value="登录">
</form>

{% csrf_token %}一定要加上这个信息,不然就会出现403,这条语句默认的返回值为:<QueryDict: {'csrfmiddlewaretoken': ['gAx9FmZWxIC5KM0vkDONo80C6o3Cosm0J2aWnjHlAq2hb5iAPx9fud8CVJJBcRHi'], 'username': ['gcnanmu'], 'password': ['qwe']}>

可以看到多了一个csrfmiddlewaretoken,这是一个隐藏的输入框带来的,可以理解为一个加密的机制,检验请求是否正常,否则就返回403

image-20240211153537504

数据库操作

Django的解决方案

  • MySQL数据库+pymysql

  • Django开发操作数据库更加简单,内部提供了ORM框架

    image-20240211155805903

    可以使代码非常简洁。

安装第三方模块

1
pip install mysqlclient

可以使用pymysql,但是新版的Django对pymysql支持不太好,因此推荐使用mysqlclient比较好。

ORM框架

ORM可以帮我们做两件事:

  • 创建、修改、删除数据库的表(不用写SQL语句,但无法创建数据库)
  • 操作表中的数据(不用写SQL语句)
  1. 创建数据库

    • 启动mysql服务

      1
      mysql -u root -p
    • 查看当前的数据库

      1
      show database
    • 创建数据库

      1
      create database django
      image-20240212095259904
  2. Django连接数据库

更改settings.py的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
DATABASES = {
"default": {
# 引擎
"ENGINE": "django.db.backends.mysql",
# 数据库的名字
"NAME": "django",
# 账户名
"USER": "root",
# 密码
"PASSWORD": "123456",
# 主机
"HOST": "localhost",
# 端口
"PORT": 3306,
}
}
  1. Django操作表

    • 创建表
    • 删除表
    • 修改表

创建与修改表

创建表,在models.py文件中:

1
2
3
4
5
6
7
8
class UserInfo(models.Model):
# id
# username
name = models.CharField(max_length=32)
# password
password = models.CharField(max_length=64)
# age
age = models.IntegerField()

此时表并没有创建

image-20240212100720097

执行命令

1
2
python manage.py makemigrations
python manage.py migrate

注意你的app需要已经注册!!!!

image-20240212183549476 image-20240212183522287

发现有好多的表,原理是生成表的时候他会遍历整个项目的app,因此有一些默认的app也生成了相应的表

1
2
3
4
5
6
7
8
9
10
INSTALLED_APPS = [
# 自己添加的app
"app01.apps.App01Config",
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
]

如果要创建多张表,那么我们需要创建多个类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 再创建几张表
class Department(models.Model):
# id
id = models.AutoField(primary_key=True)
# 部门名称
name = models.CharField(max_length=32)


class Emp(models.Model):
# id
id = models.AutoField(primary_key=True)
# 员工名字
name = models.CharField(max_length=32)
# 员工年龄
age = models.IntegerField()

然后再执行那两条命令即可。如果想要删除那个表或者表的选项,只需要注释掉相关代码执行命令即可。

但是如果想要往已存在的表中添加新的一行就会出现警告

image-20240212195040231

有三个办法解决:

  1. 在warming中写入一个初始值

  2. 在添加的时候就设定初始值

    1
    size = models.IntegerField(default=2)
  3. 允许空值

    1
    2
    # 允许为空值
    size = models.IntegerField(null=True, blank=True)

以后在开发总想要对表结构进行调整

  • 在models.py文件中操作类即可

  • 命令

    1
    2
    python manage.py makemigrations
    python manage.py migrate

操作表中的数据

案例:用户管理

  1. 展示用户列表

  2. 添加用户

    提交数据要么用表单,要么用Ajax,美化可以考虑Bootstrap

  3. 删除用户

主题:员工管理系统

创建app

新方法:

image-20240214172109281

相当于运行了manage.py,这样运行的好处是会出现提示,而且不用自己写命令:

image-20240214172138486

设计并创建数据库对象

拟设计部门表与员工表

设计时候会出现三个问题:

  1. 部门存储什么值?是存名称?还是ID?

    • ID 数据库泛式,常见的开发都是这样做的。【节省存储开销】
    • 存名称,特别大的公司,查询的次数会非常多,如果只存ID,需要查询多次【可以加速查找】
  2. 存储部门ID需不需要约束?

    部门实际是有个数的限制的,所以需要约束,只能是部门表中已存在的ID

  3. 部门被删除了,员工还存在索引联系,怎么办?

    • 直接删除相关员工信息 【级联删除】
    • 将员工的部门ID设置为NULL 前提是数据允许为空【置空】
  4. 如何设置性别?

    • 使用Boolen
    • 使用SmallIntegerField类型,其中使用choices做约束
    1
    2
    3
    4
    5
    gender_choice = {
    (1, "男"),
    (2, "女"),
    }
    gender = models.SmallIntegerField(verbose_name="性别", choices=gender_choice, default=1)

本次选择ID存储,部门ID采用关联的方法,保证只有ID来显示部门,当部门被删除后采用级联删除。

部门表结果为:

id department
1 销售
2 运维
3 客服

用户表的设计为:

注意:

  • ID是自动生成的
  • depart由于是与部门表关联的,会自动变为depart_id
id name password age account create_time depart_id gender
1 gcnanmu 123456 23 200000 2020/01/01 1 1
2 giao 123456 25 0 2020/01/02 2 2
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
50
51
52
53
54
55
56
57
from django.db import models


# Create your models here.

class DepartMent(models.Model):
"""
部门表
"""
# verbose_name为注解
# BigAutoField 还有一个 AutoField,
# 不过后者是一个 32 位整数,而前者是一个 64 位整数。
id = models.BigAutoField(primary_key=True, verbose_name="ID")
title = models.CharField(max_length=32, verbose_name="部门名称")


class UserInfo(models.Model):
"""
员工表
"""
name = models.CharField(max_length=16, verbose_name="姓名")
password = models.CharField(max_length=32, verbose_name="密码")
age = models.IntegerField(verbose_name="年龄")
# 可能有小数 max_digits为数据总位数,decimal_places为小数位数
account = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="工资", default=0)
create_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
"""
部门存储什么值?是存名称?还是ID?
- ID 数据库泛式,常见的开发都是这样做的。【节省存储开销】
- 存名称,特别大的公司,查询的次数会非常多,如果只存ID,需要查询多次,可以加速查找
存储部门ID需不需要约束
部门实际是有个数的限制的,所以需要约束,只能是部门表中已存在的ID
部门被删除了,员工还存在索引联系,怎么办?
- 直接删除相关员工信息 【级联删除】
- 将员工的部门ID设置为NULL 前提是数据允许为空【置空】
"""
# 这样创建实际是没有约束的
# dapart_id = models.BigIntegerField(verbose_name="部门ID")

# 如何创建约束
# 通过models.ForeignKey 将UserInfo表和DepartMent表关联起来
# 在Django中会自动在列名中加上_id 即生成depart_id
# 如果部门表被删除,员工表还存在索引联系,那么怎么办?
# models.CASCADE 级联删除
# models.SET_NULL 置空
depart = models.ForeignKey(to="DepartMent", to_field="id", on_delete=models.CASCADE, verbose_name="部门ID")
# depart = models.ForeignKey(to="DepartMent", to_field="id",
# on_delete=models.SET_NULL, verbose_name="部门ID",
# null=True, blank=True)
# 可以添加性别 只有两个选项 可以直接使用BooleanField类型
# 也可以使用SmallIntegerField,使用choices作为约束
gender_choice = {
(1, "男"),
(2, "女"),
}
gender = models.SmallIntegerField(verbose_name="性别", choices=gender_choice, default=1)

生成数据库

  1. 创建数据库

    1
    2
    3
    4
    5
    mysql -u root -p 123456

    create database management_sys;
    use management_sys;
    show tables;
  2. 创建表

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    DATABASES = {
    "default": {
    # 引擎
    "ENGINE": "django.db.backends.mysql",
    # 数据库的名字
    "NAME": "management_sys",
    # 账户名
    "USER": "root",
    # 密码
    "PASSWORD": "123456",
    # 主机
    "HOST": "localhost",
    # 端口
    "PORT": 3306,
    }
    }
    image-20240214194429999

​ 生成结果:

image-20240214194542543

静态文件管理

static文件夹

image-20240214194952946

部门管理

需要使用最原始的方式来做。

Django中提供Form与ModelForm组件(方便)

部门列表

image-20240214195519823

传递参数

1
path("depart/<int:nid>/edit", views.depart_edit),

可以将参数放在url中,后续可以直接在函数中获取参数

1
2
3
4
5
6
7
8
9
10
11
def depart_edit(request, nid):
if request.method == "GET":
# 返回值是一个列表
title = models.DepartMent.objects.filter(id=nid).first().title
return render(request, "depart_edit.html", {"title": title})

temp = request.POST.get("department")
# print(temp)
# 多个字段更新 在每个字段后面加上逗号
models.DepartMent.objects.filter(id=nid).update(title=temp, )
return redirect("/depart/list")

模版的继承

每次都要复制一遍导航还是很麻烦的,所以可以使用继承的方法来减少代码的复杂程度

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
50
51
52
53
54
55
56
57
58
59
60
61
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="{% static "plugins/bootstrap-3.4.1/css/bootstrap.min.css" %}">
<style>
.navbar {
{#margin-bottom: 0;#} border-radius: 0;
}
</style>
</head>
<body>
<nav class="navbar navbar-default">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/index/">员工管理系统</a>
</div>

<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li><a href="#">部门管理</a></li>
<li><a href="#">部门管理</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a href="#">登录</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">用户名<span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">个人资料</a></li>
<li><a href="#">我的信息</a></li>
{# <li><a href="#">Something else here</a></li>#}
<li role="separator" class="divider"></li>
<li><a href="#">注销</a></li>
</ul>
</li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>

<div>
{% block content %}{% endblock %}
</div>

<script src="{% static "js/jQuery.js" %}"></script>
<script src="{% static "plugins/bootstrap-3.4.1/js/bootstrap.min.js" %}"></script>

</body>
</html>

{% block content %}{% endblock %}实际为特殊的占位符

此时可以将depart_add.html转化为如下形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{% extends "layout.html" %}
{% load static %}
{% block content %}
<div class="container">
<div class="panel panel-default">
<div class="panel-heading">
<h4>添加部门</h4>
</div>

<div class="panel-body">
<form method="post" action="/depart/add">
{% csrf_token %}
<div class="form-group">
<label for="name">部门名称</label>
<input type="text" class="form-control" id="name" name="department"
placeholder="请输入部门名称">
</div>
<input type="submit" class="btn btn-primary" value="提交">
</form>
</div>
</div>
</div>
{% endblock %}

{% extends "layout.html" %}即为继承模版,必须写在文件的最开头

另外,这样写后续如果对导航进行编辑,就可以实现同步编辑,而不用每个html文件进行修改。

用户列表

创建数据最好直接使用Pycharm进行

要注意日期与性别的转化

新建用户

  • 原始的方式:不采用,麻烦

    × 问题1:如何进行数据的校验(是否为空号)

× 问题2:在页面上需要进行错误提示

× 问题3:如何数据库上有超级多的数据,那么页面上的每一个字段都需要我们自己重新写,很麻烦

× 问题4:如果有一些关联的数据(比如下拉框),需要自己手动取数据进行循环展示在页面中

  • Django组件

    • Form组件(小简便)
    • ModelForm组件(最简便)

原始方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def user_add(request):
if request.method == "GET":
data = {
"gender_choices": models.UserInfo.gender_choice,
"depart_list": models.DepartMent.objects.all()
}

return render(request, "user_add.html", data)

username = request.POST.get("name")
password = request.POST.get("password")
age = request.POST.get("age")
account = request.POST.get("account")
create_time = request.POST.get("create_time")
depart = request.POST.get("depart")
gender = request.POST.get("gender")
print(username, password, age, gender, depart, account, create_time)

models.UserInfo.objects.create(name=username, password=password, gender=gender, depart_id=depart, age=age,
account=account, create_time=create_time)
return redirect("/user/list")

html文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div class="form-group">
<label for="name">部门</label>
<select class="form-control">
{% for item in depart_list %}
<option value="{{ item.id }}">{{ item.title }}</option>

{% endfor %}
</select>
</div>

<div class="form-group">
<label for="name">性别</label>
<select class="form-control">
{% for item in gender_choices %}
<option value="{{ item.0 }}">{{ item.1 }}</option>

{% endfor %}
</select>
</div>

初识Form

  1. views.py

    forms.CharField要与models类定义的数据类型相同,有多少个字段就需要自己写上多少个

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class MyFrom(Form):
    user = forms.CharField(widget=forms.Input)
    pwd = forms.CharField(widget=forms.Input)
    email = forms.CharField(widget=forms.Input)

    def user_add(request):
    if request.method == "GET":
    form = MyForm()
    return render(request,"user_add.html",{"form":form})
  2. user_add.html

    这样就不用自己手写html输入框了,这种方法只能显示输入框,不会显示提示,如果要显示的话,需要加上form.name.label,这种方法连接的是models.py中模型的verbose_name字段

    1
    2
    3
    4
    5
    6
    <form method="POST">
    {% csrf_token %}
    {% for field in form %}
    {{ field.label }} : {{ field }}<br>
    {% endfor %}
    </form>

    更简便的写法

    1
    2
    3
    4
    5
    <form method="POST">
    {% for field in form %}
    {{ field.label }}:{{ field }}<br>
    {% endfor %}
    </form>

ModelForm(recommandation)

是针对数据库的操作,那么最好使用ModelFrom

快速上手

  1. views.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    from django.forms import ModelForm
    class MyForm(ModelForm):
    class Meta:
    # 支持自定义字段
    xx = form.CharField("....")
    model = UserInfo
    # 如果返回全部可以使用
    fields = "__all__"
    fields = ["user","pwd","email","xx"]

    def user_add(request):
    if request.method == "GET":
    form = MyForm()
    return render(request,"user_add.html",{"form":form})
  2. user_add.html

    1
    2
    3
    4
    5
    <form method="POST">
    {% for field in form %}
    {{ field.label }} : {{ field }}<br>
    {% endfor %}
    </form>

展示的结果是:

image-20240217114956547

此时就会发现部门的部分有点问题,因为循环拿出的是对象,此时打印出的是对象本本身,如果要使打印出来的为特定的字符串,需要再对象中添加__str__的魔术方法:

1
2
3
4
5
6
7
8
9
10
11
12
class DepartMent(models.Model):
"""
部门表
"""
# verbose_name为注解
# BigAutoField 还有一个 AutoField,
# 不过后者是一个 32 位整数,而前者是一个 64 位整数。
id = models.BigAutoField(primary_key=True, verbose_name="ID")
title = models.CharField(max_length=32, verbose_name="部门名称")

def __str__(self):
return self.title

这样打印出来的就是部门的名字,而不是对象本身:

image-20240217115250129

但是这样并没有css样式,因为field是自动生成的html标签,这时候可以在定义modelform的类中这样写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class UserModelForm(forms.ModelForm):
class Meta:
model = models.UserInfo
fields = "__all__"
# depart 不要写成 depart_id 它可以自动找到命名
# fields = ["name", "password", "age", "account", "depart", "
widgets = {
"name": forms.TextInput(attrs={"class": "form-control"}),
"password": forms.TextInput(attrs={"class": "form-control"}),
"age": forms.TextInput(attrs={"class": "form-control"}),
"account": forms.TextInput(attrs={"class": "form-control"}),
"depart": forms.Select(attrs={"class": "form-control"}),
"gender": forms.Select(attrs={"class": "form-control"}),
"create_time": forms.TextInput(attrs={"class": "form-control"}),
}

因为添加的class都一样,这样写代码不够简便,可以改为这样

1
2
3
4
5
6
7
8
9
10
11
class UserModelForm(forms.ModelForm):
class Meta:
model = models.UserInfo
fields = "__all__"
# depart 不要写成 depart_id 它可以自动找到命名
# fields = ["name", "password", "age", "account",

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for name, item in self.fields.items():
item.widget.attrs.update({"class": "form-control", "placeholder": "请输入%s" % (item.label,)})

结果为:

image-20240217123235243

数据的校验

这里只简单描述了如何校验空数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def user_modelform_add(request):
if request.method == "GET":
form = UserModelForm()
return render(request, "modelform_add.html", {"form": form})

form = UserModelForm(data=request.POST)

if form.is_valid():
print(form.cleaned_data)
form.save()
return redirect("/user/list")
else:
print(form.errors)
return render(request, "modelform_add.html", {"form": form})

需要注意的是,这边form.errors它是一个列表,我们一般只需要取第一个就可以,而且需要将他显示在页面上。

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
{% extends "layout.html" %}

{% block content %}
<div class="container">
<div class="panel panel-default">
<div class="panel-heading">
<h4>新建用户</h4>
</div>

<div class="panel-body">
{# 关闭浏览器空置验证 novalidate#}
<form method="post" action="/user/modelformadd" novalidate>
{% csrf_token %}
{% for field in form %}
<div class="form-group">
<label>{{ field.label }}</label>
{{ field }}
<span style="color: red;">{{ field.errors.0 }}</span>
</div>
{% endfor %}
<button type="submit" class="btn btn-primary">提交</button>
</form>
</div>
</div>
</div>
{% endblock %}

这样如果不输入数据就会有提示出现:

image-20240217123615667

可以将提示改为中文,修改settings.py中的LANGUAGE_CODE = "en-us"改为LANGUAGE_CODE = "zh-hans"

image-20240217123726170

如果需要自定义错误提示可以这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class UserModelForm(forms.ModelForm):
# 自定义字段
name = forms.CharField(min_length=2, label="姓名",
error_messages={"required": "不能为空", "min_length": "长度不能小于2"})
# 如果要使用正则表达式验证,可以使用validators
password = forms.CharField(min_length=6, label="密码",
error_messages={"required": "不能为空", "min_length": "长度不能小于6"})
age = forms.IntegerField(max_value=100, label="年龄",
error_messages={"required": "不能为空", "max_value": "不能大于100"})

class Meta:
model = models.UserInfo
fields = "__all__"
# depart 不要写成 depart_id 它可以自动找到命名
# fields = ["name", "password", "age", "account", "depart", "

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for name, item in self.fields.items():
# item.widget.attrs.update({"class": "form-control", "placeholder": "请输入%s" % (item.label,)})
item.widget.attrs = {"class": "form-control", "placeholder": "请输入%s" % (item.label,)}

编辑员工

我们的创建时间一般不需要精确到分钟,只需要到日就行

image-20240217141657625

因此我们需要将models.py中的create_time字段修改为

1
create_time = models.DateField(verbose_name="创建时间")

如果要单独插入数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def user_edit(request, nid):
instance_data = models.UserInfo.objects.filter(id=nid).first()
if request.method == "GET":
form = UserModelForm(instance=instance_data)
return render(request, "user_edit.html", {"form": form})

# 更新数据
form = UserModelForm(data=request.POST, instance=instance_data)
if form.is_valid():
print(form.cleaned_data)
# 如果想保存用户输入以外的其他值,可以使用以下方法
# form.instance.depart_id = request.POST.get("depart")
form.save()
return redirect("/user/list")
else:
print(form.errors)
return render(request, "user_edit.html", {"form": form})

主题:靓号管理

设计表结构

ID moblile price level status
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class PhoneNumber(models.Model):
"""靓号表"""
mobile = models.CharField(max_length=11, verbose_name="手机号")
# 如果是IntegerField,不需要设置长度
# 想要允许为空,可以设置null=True,blank=True
price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="价格")
level_choices = {
(1, "低"),
(2, "中"),
(3, "高"),
}
level = models.SmallIntegerField(choices=level_choices, default=1, verbose_name="靓号等级")

status_choices = {
(1, "未出售"),
(2, "已出售"),
}
status = models.SmallIntegerField(choices=status_choices, default=1, verbose_name="售卖状态")

初始状态为:

image-20240217144155453

新建靓号

  1. 新建连接
  2. 在views.py中定义操作函数
  3. 新建HTML文件,继承母版
  4. 使用modelform显示数据

手机号不允许重复

1
2
3
4
5
6
7
8
def clean_mobile(self):
mobile_ = self.cleaned_data["mobile"]
exits = models.PhoneNumber.objects.filter(mobile=mobile_).exists()
if exits:
raise forms.ValidationError("手机号已存在")
# 验证不通过

return mobile_

编辑与删除靓号

  • 编辑信息新建一个modelform来实现不一样的功能

  • 不允许用户编辑手机号

  • 手机号不允许重复

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 排除自己以外,其他的数据是否重复?

    # 可以使用self.instance获取当前编辑信息的id
    def clean_mobile(self):
    mobile_ = self.cleaned_data["mobile"]
    exits = models.PhoneNumber.objects.exclude(id=self.instance).filter(mobile=mobile_).exists()
    if exits:
    raise forms.ValidationError("手机号已存在")
    return mobile_

靓号搜索

按照索引

在数据库中查询

1
2
3
4
5
6
models.PhoneModileForm.objects.filter(mobile="19999991",id="123")


# 可以使用字典来作为索引
data_dict = {"mobile":"199999991","id":123}
models.PrettyNumber.objects.filter(**data_dict)

按照数字搜索

1
2
3
4
5
6
7
8
# ID大于12
models.PhoneModileForm.objects.filter(id__gt=12)
# ID大于等于12
models.PhoneModileForm.objects.filter(id__gte=12)
# ID小于12
models.PhoneModileForm.objects.filter(id__lt=12)
# ID小于等于12
models.PhoneModileForm.objects.filter(id__lte=12)

按照字符串搜索

1
2
3
4
5
6
7
models.PhoneModileForm.objects.filter(mobile=12)
# 以什么开头
models.PhoneModileForm.objects.filter(mobile__startswith=12)
# 以什么结尾
models.PhoneModileForm.objects.filter(mobile__endswith=12)
# 包含
models.PhoneModileForm.objects.filter(mobile__contains=12)

上述的方法都可以使用字典作为条件,这种方法可以使用多个筛选条件。

分页展示

因为queryset实质是一个列表,所以可以使用切片来展示数据

1
2
3
4
5
6
7
8
# 第一页
models.PhoneModileForm.objects.filter(mobile=12)[0:10]

# 第二页
models.PhoneModileForm.objects.filter(mobile=12)[10:20]

# 第三页
models.PhoneModileForm.objects.filter(mobile=12)[20:30]

使用page作为参数,这时候在html中生车工结构已经不合适了,需要在Python中生成在传入

分页组件的安装

价格分页系统直接分装为一个组件,需要注意的是,如果封装起来需要把跳转页面的代码也写入

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
from django.shortcuts import redirect


class Pagination():
def __init__(self, request, queryset, page_size=10, page_param="page", plus=5):
page = request.GET.get(page_param, "1")
if page.isdecimal():
page = int(page)
else:
page = 1
self.page = page
self.page_size = page_size

self.start = (page - 1) * page_size
self.end = page * page_size
print(page, type(page))

self.queryset = queryset[self.start:self.end]

total_count = queryset.count()

page_total, div = divmod(total_count, page_size)
if div > 0:
page_total += 1

self.page_total = page_total
self.plus = plus

self.jump_page = request.GET.get("jump")

def html(self):
# 数据库的数据较少 页码全部显示
if self.page_total < 2 * self.plus + 1:
start_page = 1
end_page = self.page_total + 1
else:
# 数据库的数据较多
if self.page <= self.plus:
start_page = 1
end_page = 2 * self.plus + 1
elif self.page > self.page_total - self.plus:
start_page = self.page_total - 2 * self.plus
end_page = self.page_total + 1
else:
start_page = self.page - self.plus
end_page = self.page + self.plus + 1

# 生成页码
page_data = []
for i in range(start_page, end_page):
if i == self.page:
hl = '<li class="active"><a href="?page={}">{} <span class="sr-only">(current)</span></a></li>'.format(
i, i)
else:
hl = '<li><a href="?page={}">{} <span class="sr-only">(current)</span></a></li>'.format(i, i)
page_data.append(hl)

# 首页 末页
shou_page = '<li><a href="?page={}">首页</a></li>'.format(1)
wei_page = '<li><a href="?page={}">末页</a></li>'.format(self.page_total)

# 上一页 下一页
prev_page = self.page - 1
next_page = self.page + 1
if prev_page > 1:
hl = '<li><a href="?page={}" aria-label="Previous"><span aria-hidden="true">&laquo;</span></a></li>'.format(
prev_page)
else:
hl = '<li class="disabled" disabled="True"><a href="?page={}" aria-label="Previous"><span aria-hidden="true">&laquo;</span></a></li>'.format(
1)
page_data.insert(0, hl)
if next_page <= self.page_total:
hl = '<li><a href="?page={}" aria-label="Next"><span aria-hidden="true">&raquo;</span></a></li>'.format(
next_page)
else:
hl = '<li class="disabled" disabled="True"><a href="?page={}" aria-label="Next"><span aria-hidden="true">&raquo;</span></a></li>'.format(
self.page_total)
page_data.append(hl)

page_data.insert(0, shou_page)
page_data.append(wei_page)
# print(page_data)
return page_data

def get_queryset(self):
return self.queryset

def jump_html(self):
print(f"跳跃的页码:{self.jump_page}")
# jump_page = int(jump_page)
if self.jump_page is not None:
jump_page = int(self.jump_page)
if jump_page <= 1:
jump_page = 1
elif jump_page > self.page_total:
jump_page = self.page_total
return redirect("/phone/list?page=%s" % (jump_page,))

修改后的html

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
from django.shortcuts import render, redirect
from management_sys import models
from management_sys.utils.forms import PhoneModelForm, PhoneEditModelForm
from django.utils.safestring import mark_safe
from management_sys.utils.pagination import Pagination


def phone_list(request):
data = {}
# models.PhoneNumber.objects.filter(id=1)
search_ = request.GET.get("q")
# 传过来的值可能为空
if search_:
data["mobile__contains"] = search_
# return render(request, "phone_list.html", {"data": data})

page = request.GET.get("page", 1)

pagination = Pagination(request, models.PhoneNumber.objects.filter(**data).order_by("-level"))

page_data = pagination.html()
data = pagination.get_queryset()

jump_page = request.GET.get("jump")
if jump_page is not None:
jump_page = pagination.jump_html()
return redirect("/phone/list?page=%s" % (jump_page,))

page_data = mark_safe("".join(page_data))
# print(page_data)
return render(request, "phone_list.html",
{"data": data, "search": search_, "page_data": page_data, "current_page": page})


def phone_add(request):
if request.method == "GET":
form = PhoneModelForm()
return render(request, "phone_add.html", {"form": form})

form = PhoneModelForm(data=request.POST)
if form.is_valid():
form.save()
return redirect("/phone/list")
else:
print(form.errors)
return render(request, "phone_add.html", {"form": form})


def phone_edit(request, nid):
instance_data = models.PhoneNumber.objects.filter(id=nid).first()
if request.method == "GET":
form = PhoneEditModelForm(instance=instance_data)
return render(request, "phone_edit.html", {"form": form})

form = PhoneEditModelForm(data=request.POST, instance=instance_data)

if form.is_valid():
form.save()
return redirect("/phone/list")
else:
print(form.errors)
return render(request, "phone_edit.html", {"form": form})


def phone_delete(request, nid):
models.PhoneNumber.objects.filter(id=nid).delete()
return redirect("/phone/list")

以后哪里用分页就放到哪里用就好,但出现一个小问题,搜索的时候需要保留搜索的条件

1
http://localhost:8000/phone/list?q=666

时间插件

额外插件:bootstrap-datetimepicker

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
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="{% static "plugins/bootstrap-3.4.1/css/bootstrap.min.css" %}">
<style>
.navbar {
{#margin-bottom: 0;#} border-radius: 0;
}
</style>
{% block css %}{% endblock %}
</head>
<body>
。。。。

<div>
{% block content %}{% endblock %}
</div>

<script src="{% static "js/jQuery.js" %}"></script>
<script src="{% static "plugins/bootstrap-3.4.1/js/bootstrap.min.js" %}"></script>
{% block js %}{% endblock %}
</body>
</html>

需要多引入东西,所以需要多写入几个block{% block js %}{% endblock %},{% block css %}{% endblock %}

需要引入以下依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{% block css %}
<link rel="stylesheet" href="{% static "/plugins/bootstrap-datetimepicker/css/bootstrap-datetimepicker.min.css" %}">
{% endblock %}

{% block js %}
<script src="{% static "plugins/bootstrap-datetimepicker/js/bootstrap-datetimepicker.min.js" %}"></script>
<script src="{% static "plugins/bootstrap-datetimepicker/js/locales/bootstrap-datetimepicker.zh-CN.js" %}"></script>
<script>
$("#dt").datepicker({
language: 'zh-CN', //语言
autoclose: true, //选择后自动关闭
clearBtn: true,//清除按钮
format: "yyyy-mm-dd"//日期格式
});
</script>
{% endblock %}
1
2
3
4
5
6
7
8
<script>
$("#dt").datepicker({
language: 'zh-CN', //语言
autoclose: true, //选择后自动关闭
clearBtn: true,//清除按钮
format: "yyyy-mm-dd"//日期格式
});
</script>

这个js的作用是找到id为dt的标签,然后作用在标签上。

所以在user_add.html中要这样添加

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
{% extends "layout.html" %}
{% load static %}

{% block css %}
<link rel="stylesheet" href="{% static "/plugins/bootstrap-datetimepicker/css/bootstrap-datetimepicker.min.css" %}">
{% endblock %}

{% block content %}


<input type="text" class="form-control" id="dt" name="create_time" placeholder="请输入创建时间">

{% endblock %}

{% block js %}
<script src="{% static "plugins/bootstrap-datetimepicker/js/bootstrap-datetimepicker.min.js" %}"></script>
<script src="{% static "plugins/bootstrap-datetimepicker/js/locales/bootstrap-datetimepicker.zh-CN.js" %}"></script>
<script>
$('#dt').datetimepicker({
language: 'zh-CN', // 中文语言包
autoclose: 1, // 选中日期后自动关闭
format: 'yyyy-mm-dd', // 日期格式
minView: "month", // 最小日期显示单元,这里最小显示月份界面,即可以选择到日
todayBtn: 1, // 显示今天按钮
todayHighlight: 1, // 显示今天高亮
});
</script>
{% endblock %}

但是对于modelform_add.html这个模版,因为是Django生成的,Django帮我们自己生成了相应的id,点击检查可以看到

image-20240219185355467

所以只需要将id部分改为id_create_time

1
2
3
4
5
6
7
8
9
10
<script>
$('#id_create_time').datetimepicker({
language: 'zh-CN', // 中文语言包
autoclose: 1, // 选中日期后自动关闭
format: 'yyyy-mm-dd', // 日期格式
minView: "month", // 最小日期显示单元,这里最小显示月份界面,即可以选择到日
todayBtn: 1, // 显示今天按钮
todayHighlight: 1, // 显示今天高亮
});
</script>

效果:

image-20240223141926737

ModelForm与BootStrap

  • ModelForm可以帮助我们生成HTML标签

    1
    2
    3
    4
    5
    class PhoneModelForm(ModelForm):
    class Meta:
    model = models.PhoneNumber
    fields = ["mobile", "price", "level", "status"]
    form = PhoneModelForm()
    1
    2
    {{ form.name }} 普通的输入框
    {{ form.age }} 普通的输入框
  • 定义插件 两种插件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class PhoneModelForm(ModelForm):
    class Meta:
    model = models.PhoneNumber
    fields = ["mobile", "price", "level", "status"]
    widget = {
    "name":forms.TextInput(attrs={"class":"form-control"}),
    "password":forms.PasswordInput(attrs={"class":"form-control"}),
    "age":forms.TextInput(attrs={"class":"form-control"})
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class PhoneModelForm(ModelForm):
    name = forms.TextInput(
    min_length=3,
    label="用户名",
    widge=forms.TextInput(attrs={"class":"form-control"})
    )
    class Meta:
    model = models.PhoneNumber
    fields = ["mobile", "price", "level", "status"]
    1
    2
    {{ form.name }} BootStrap的输入框
    {{ form.age }} BootStrap的输入框
  • 重新定义的init方法,批量设置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class PhoneModelForm(forms.ModelForm):

    class Meta:
    model = models.PhoneNumber
    fields = "__all__"
    # fields = ["mobile", "price", "level", "status"]
    # 排除哪个字段
    # exclude = ["status"]

    def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)
    # 循环Modelform中的所有字段,在每个字段设置插件
    for name, item in self.fields.items():
    item.widget.attrs = {
    "class": "form-control",
    "placeholder": "请输入%s" % (item.label,)
    }

    以上方法会导致原有的属性被覆盖,可以优化如下:

    1
    2
    3
    4
    5
    6
    7
    8
    def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)
    for name, item in self.fields.items():
    if item.widget.attrs:
    item.widget.attrs["class"] = "form-control"
    item.widget.attrs["placeholder"] = "请输入%s" % (item.label,)
    else:
    item.widget.attrs = {"class": "form-control", "placeholder": "请输入%s" % (item.label,)}
  • 定义一个类进行继承

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class BootStrapModelForm(forms.ModelForm):

    def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)
    # 循环Modelform中的所有字段,在每个字段设置插件
    for name, item in self.fields.items():
    item.widget.attrs = {
    "class": "form-control",
    "placeholder": "请输入%s" % (item.label,)
    }

    然后就可以将其他forms.ModelForm改为继承BootStrapModelForm

    替换:

    • 导入文件

      1
      from management_sys.utils.BootstrapModel import BootStrapModelForm
    • 将其他的init方法删除,保留钩子方法

  • 将所有ModelForm全写入一个py文件

    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
    50
    51
    52
    53
    54
    55
    from django import forms
    from management_sys import models
    from management_sys.utils.BootstrapModel import BootStrapModelForm


    class UserModelForm(BootStrapModelForm):
    # 自定义字段
    name = forms.CharField(min_length=2, label="姓名",
    error_messages={"required": "不能为空", "min_length": "长度不能小于2"})
    # 如果要使用正则表达式验证,可以使用validators
    password = forms.CharField(min_length=6, label="密码",
    error_messages={"required": "不能为空", "min_length": "长度不能小于6"})
    age = forms.IntegerField(max_value=100, label="年龄",
    error_messages={"required": "不能为空", "max_value": "不能大于100"})

    class Meta:
    model = models.UserInfo
    fields = "__all__"


    class PhoneModelForm(BootStrapModelForm):
    class Meta:
    model = models.PhoneNumber
    fields = "__all__"

    # 方式二:钩子方法
    def clean_mobile(self):
    mobile_ = self.cleaned_data["mobile"]
    exits = models.PhoneNumber.objects.filter(mobile=mobile_).exists()
    if exits:
    raise forms.ValidationError("手机号已存在")
    # 验证不通过
    if len(mobile_) != 11:
    raise forms.ValidationError("手机号长度不对")
    for i in mobile_:
    if i not in "0123456789":
    raise forms.ValidationError("手机号格式错误,必须全是数字")

    return mobile_


    class PhoneEditModelForm(BootStrapModelForm):
    mobile = forms.CharField(disabled=True, label="手机号")

    class Meta:
    model = models.PhoneNumber
    # 不让用户修改手机号
    fields = ["mobile", "price", "level", "status"]

    def clean_mobile(self):
    mobile_ = self.cleaned_data["mobile"]
    exits = models.PhoneNumber.objects.exclude(id=self.instance).filter(mobile=mobile_).exists()
    if exits:
    raise forms.ValidationError("手机号已存在")
    return mobile_

    在views.py中修改

    1
    from management_sys.utils.forms import UserModelForm, PhoneModelForm, PhoneEditModelForm
  • 可以将视图函数进行划分

    可以将视图函数按照业务功能划分,单独创建一个views文件夹,将不同的业务划分到不同的py文件中

    depart.py

    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
    from django import forms
    from management_sys import models
    from django.shortcuts import render, redirect

    def depart_list(request):
    data = models.DepartMent.objects.all()
    return render(request, "depart_list.html", {"depart_list": data})


    def depart_add(request):
    if request.method == "GET":
    return render(request, "depart_add.html")

    title = request.POST.get("department")
    models.DepartMent.objects.create(title=title)
    return redirect("/depart/list")


    def depart_delete(request):
    data = request.GET.get("nid")
    models.DepartMent.objects.filter(id=data).delete()
    return redirect("/depart/list")


    def depart_edit(request, nid):
    if request.method == "GET":
    # 返回值是一个列表
    title = models.DepartMent.objects.filter(id=nid).first().title
    return render(request, "depart_edit.html", {"title": title})

    temp = request.POST.get("department")
    # print(temp)
    # 多个字段更新 在每个字段后面加上逗号
    models.DepartMent.objects.filter(id=nid).update(title=temp, )
    return redirect("/depart/list")

    user.py

    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
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    from management_sys import models
    from django.shortcuts import render, redirect
    from management_sys.utils.forms import UserModelForm


    def user_list(request):
    data = models.UserInfo.objects.all()
    # 这里是通过Django的Python代码来实现的
    # for i in data:
    # # 将datetime 类型转化为字符串类型
    # # i.create_time = dt.strftime("%Y-%m-%d %H:%M:%S")
    # i.create_time = dt.strftime("%Y-%m-%d")
    # # 可以直接通过get_字段名_display()获取choices的值
    # i.gender = i.get_gender_display()
    # # 通过部门id获得部门名称 会将id自动转化名称
    # i.depart_id = i.depart.title
    return render(request, "user_list.html", {"user_list": data})


    def user_add(request):
    if request.method == "GET":
    data = {
    "gender_choices": models.UserInfo.gender_choice,
    "depart_list": models.DepartMent.objects.all()
    }

    return render(request, "user_add.html", data)

    username = request.POST.get("name")
    password = request.POST.get("password")
    age = request.POST.get("age")
    account = request.POST.get("account")
    create_time = request.POST.get("create_time")
    depart = request.POST.get("depart")
    gender = request.POST.get("gender")
    print(username, password, age, gender, depart, account, create_time)

    models.UserInfo.objects.create(name=username, password=password, gender=gender, depart_id=depart, age=age,
    account=account, create_time=create_time)
    return redirect("/user/list")


    # class UserModelForm(BootStrapModelForm):
    # # 自定义字段
    # name = forms.CharField(min_length=2, label="姓名",
    # error_messages={"required": "不能为空", "min_length": "长度不能小于2"})
    # # 如果要使用正则表达式验证,可以使用validators
    # password = forms.CharField(min_length=6, label="密码",
    # error_messages={"required": "不能为空", "min_length": "长度不能小于6"})
    # age = forms.IntegerField(max_value=100, label="年龄",
    # error_messages={"required": "不能为空", "max_value": "不能大于100"})
    #
    # class Meta:
    # model = models.UserInfo
    # fields = "__all__"
    # # depart 不要写成 depart_id 它可以自动找到命名
    # # fields = ["name", "password", "age", "account", "depart", "gender","create_time"]
    # # widgets = {
    # # "name": forms.TextInput(attrs={"class": "form-control"}),
    # # "password": forms.TextInput(attrs={"class": "form-control"}),
    # # "age": forms.TextInput(attrs={"class": "form-control"}),
    # # "account": forms.TextInput(attrs={"class": "form-control"}),
    # # "depart": forms.Select(attrs={"class": "form-control"}),
    # # "gender": forms.Select(attrs={"class": "form-control"}),
    # # "create_time": forms.TextInput(attrs={"class": "form-control"}),
    # # }
    #
    # # def __init__(self, *args, **kwargs):
    # # super().__init__(*args, **kwargs)
    # # for name, item in self.fields.items():
    # # # item.widget.attrs.update({"class": "form-control", "placeholder": "请输入%s" % (item.label,)})
    # # item.widget.attrs = {"class": "form-control", "placeholder": "请输入%s" % (item.label,)}


    def user_modelform_add(request):
    if request.method == "GET":
    form = UserModelForm()
    return render(request, "modelform_add.html", {"form": form})

    form = UserModelForm(data=request.POST)

    if form.is_valid():
    print(form.cleaned_data)
    form.save()
    return redirect("/user/list")
    else:
    print(form.errors)
    return render(request, "modelform_add.html", {"form": form})


    def user_edit(request, nid):
    instance_data = models.UserInfo.objects.filter(id=nid).first()
    if request.method == "GET":
    form = UserModelForm(instance=instance_data)
    return render(request, "user_edit.html", {"form": form})

    # 更新数据
    form = UserModelForm(data=request.POST, instance=instance_data)
    if form.is_valid():
    print(form.cleaned_data)
    # 如果想保存用户输入以外的其他值,可以使用以下方法
    # form.instance.depart_id = request.POST.get("depart")
    form.save()
    return redirect("/user/list")
    else:
    print(form.errors)
    return render(request, "user_edit.html", {"form": form})


    def user_delete(request, nid):
    models.UserInfo.objects.filter(id=nid).delete()
    return redirect("/user/list")

    phone.py

    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
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    from django.shortcuts import render, redirect
    from management_sys import models
    from management_sys.utils.forms import PhoneModelForm, PhoneEditModelForm
    from django.utils.safestring import mark_safe


    def phone_list(request):
    data = {}
    # models.PhoneNumber.objects.filter(id=1)
    search_ = request.GET.get("q")
    # 传过来的值可能为空
    if search_:
    data["mobile__contains"] = search_
    # return render(request, "phone_list.html", {"data": data})

    # 默认为第一页 不写有问题
    page = request.GET.get("page", 1)
    page = int(page)

    # 每页的数据大小
    page_size = 10
    # 根据要访问的页码计算出起止位置
    start = (page - 1) * page_size
    end = page * page_size

    # 可以进行排序 -level 表示按照级别降序
    # data = models.PhoneNumber.objects.all().order_by("-level")
    data = models.PhoneNumber.objects.filter(**data).order_by("-level")[start:end]

    # 数据总数
    total_count = models.PhoneNumber.objects.all().count()
    # 总页数
    page_total, div = divmod(total_count, page_size)
    if div > 0:
    page_total += 1

    # 计算出显示当前页码的前五页,后五页
    plus = 5
    # 数据库的数据较少 页码全部显示
    if page_total < 2 * plus + 1:
    start_page = 1
    end_page = page_total + 1
    else:
    # 数据库的数据较多
    if page <= plus:
    start_page = 1
    end_page = 2 * plus + 1
    elif page > page_total - plus:
    start_page = page_total - 2 * plus
    end_page = page_total + 1
    else:
    start_page = page - plus
    end_page = page + plus + 1

    # 生成页码
    page_data = []
    for i in range(start_page, end_page):
    if i == page:
    hl = '<li class="active"><a href="?page={}">{} <span class="sr-only">(current)</span></a></li>'.format(i, i)
    else:
    hl = '<li><a href="?page={}">{} <span class="sr-only">(current)</span></a></li>'.format(i, i)
    page_data.append(hl)

    # 首页 末页
    shou_page = '<li><a href="?page={}">首页</a></li>'.format(1)
    wei_page = '<li><a href="?page={}">末页</a></li>'.format(page_total)

    # 上一页 下一页
    prev_page = page - 1
    next_page = page + 1
    if prev_page > 1:
    hl = '<li><a href="?page={}" aria-label="Previous"><span aria-hidden="true">&laquo;</span></a></li>'.format(
    prev_page)
    else:
    hl = '<li class="disabled" disabled="True"><a href="?page={}" aria-label="Previous"><span aria-hidden="true">&laquo;</span></a></li>'.format(
    1)
    page_data.insert(0, hl)
    if next_page <= page_total:
    hl = '<li><a href="?page={}" aria-label="Next"><span aria-hidden="true">&raquo;</span></a></li>'.format(
    next_page)
    else:
    hl = '<li class="disabled" disabled="True"><a href="?page={}" aria-label="Next"><span aria-hidden="true">&raquo;</span></a></li>'.format(
    page_total)
    page_data.append(hl)

    page_data.insert(0, shou_page)
    page_data.append(wei_page)

    # 跳转页面
    jump_page = request.GET.get("jump")
    # jump_page = int(jump_page)
    if jump_page is not None:
    jump_page = int(jump_page)
    if jump_page <= 1:
    jump_page = 1
    elif jump_page > page_total:
    jump_page = page_total
    return redirect("/phone/list?page=%s" % (jump_page,))

    page_data = mark_safe("".join(page_data))
    return render(request, "phone_list.html",
    {"data": data, "search": search_, "page_data": page_data, "current_page": page})


    def phone_add(request):
    if request.method == "GET":
    form = PhoneModelForm()
    return render(request, "phone_add.html", {"form": form})

    form = PhoneModelForm(data=request.POST)
    if form.is_valid():
    form.save()
    return redirect("/phone/list")
    else:
    print(form.errors)
    return render(request, "phone_add.html", {"form": form})


    def phone_edit(request, nid):
    instance_data = models.PhoneNumber.objects.filter(id=nid).first()
    if request.method == "GET":
    form = PhoneEditModelForm(instance=instance_data)
    return render(request, "phone_edit.html", {"form": form})

    form = PhoneEditModelForm(data=request.POST, instance=instance_data)

    if form.is_valid():
    form.save()
    return redirect("/phone/list")
    else:
    print(form.errors)
    return render(request, "phone_edit.html", {"form": form})


    def phone_delete(request, nid):
    models.PhoneNumber.objects.filter(id=nid).delete()
    return redirect("/phone/list")

    需要导入的话需要把views.py给删除

    然后在url.py中导入

    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
    from django.urls import path
    from management_sys.views import depart, user, phone

    urlpatterns = [
    # path("admin/", admin.site.urls),
    # 部门管理
    path("index/", depart.index),
    path("depart/list", depart.depart_list),
    path("depart/add", depart.depart_add),
    path("depart/delete", depart.depart_delete),
    path("depart/<int:nid>/edit", depart.depart_edit),

    # 用户管理
    path("user/list", user.user_list),
    path("user/add", user.user_add),
    path("user/modelformadd", user.user_modelform_add),
    path("user/<int:nid>/edit", user.user_edit),
    path("user/<int:nid>/delete", user.user_delete),

    # 靓号管理
    path("phone/list", phone.phone_list),
    path("phone/add", phone.phone_add),
    path("phone/<int:nid>/edit", phone.phone_edit),
    path("phone/<int:nid>/delete", phone.phone_delete),
    ]

    给views.py存个档

    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
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    336
    337
    338
    339
    340
    341
    342
    343
    344
    345
    346
    347
    348
    349
    350
    351
    352
    353
    354
    355
    356
    357
    358
    from django import forms
    from django.shortcuts import render, redirect
    from management_sys import models
    from datetime import datetime
    from django.core.validators import RegexValidator
    # 设置安全信任
    from django.utils.safestring import mark_safe
    from management_sys.utils.BootstrapModel import BootStrapModelForm
    from management_sys.utils.forms import UserModelForm, PhoneModelForm, PhoneEditModelForm


    # Create your views here.
    def index(request):
    return render(request, "index.html")


    def depart_list(request):
    data = models.DepartMent.objects.all()
    return render(request, "depart_list.html", {"depart_list": data})


    def depart_add(request):
    if request.method == "GET":
    return render(request, "depart_add.html")

    title = request.POST.get("department")
    models.DepartMent.objects.create(title=title)
    return redirect("/depart/list")


    def depart_delete(request):
    data = request.GET.get("nid")
    models.DepartMent.objects.filter(id=data).delete()
    return redirect("/depart/list")


    def depart_edit(request, nid):
    if request.method == "GET":
    # 返回值是一个列表
    title = models.DepartMent.objects.filter(id=nid).first().title
    return render(request, "depart_edit.html", {"title": title})

    temp = request.POST.get("department")
    # print(temp)
    # 多个字段更新 在每个字段后面加上逗号
    models.DepartMent.objects.filter(id=nid).update(title=temp, )
    return redirect("/depart/list")


    def user_list(request):
    data = models.UserInfo.objects.all()
    # 这里是通过Django的Python代码来实现的
    # for i in data:
    # # 将datetime 类型转化为字符串类型
    # # i.create_time = dt.strftime("%Y-%m-%d %H:%M:%S")
    # i.create_time = dt.strftime("%Y-%m-%d")
    # # 可以直接通过get_字段名_display()获取choices的值
    # i.gender = i.get_gender_display()
    # # 通过部门id获得部门名称 会将id自动转化名称
    # i.depart_id = i.depart.title
    return render(request, "user_list.html", {"user_list": data})


    def user_add(request):
    if request.method == "GET":
    data = {
    "gender_choices": models.UserInfo.gender_choice,
    "depart_list": models.DepartMent.objects.all()
    }

    return render(request, "user_add.html", data)

    username = request.POST.get("name")
    password = request.POST.get("password")
    age = request.POST.get("age")
    account = request.POST.get("account")
    create_time = request.POST.get("create_time")
    depart = request.POST.get("depart")
    gender = request.POST.get("gender")
    print(username, password, age, gender, depart, account, create_time)

    models.UserInfo.objects.create(name=username, password=password, gender=gender, depart_id=depart, age=age,
    account=account, create_time=create_time)
    return redirect("/user/list")


    # class UserModelForm(BootStrapModelForm):
    # # 自定义字段
    # name = forms.CharField(min_length=2, label="姓名",
    # error_messages={"required": "不能为空", "min_length": "长度不能小于2"})
    # # 如果要使用正则表达式验证,可以使用validators
    # password = forms.CharField(min_length=6, label="密码",
    # error_messages={"required": "不能为空", "min_length": "长度不能小于6"})
    # age = forms.IntegerField(max_value=100, label="年龄",
    # error_messages={"required": "不能为空", "max_value": "不能大于100"})
    #
    # class Meta:
    # model = models.UserInfo
    # fields = "__all__"
    # # depart 不要写成 depart_id 它可以自动找到命名
    # # fields = ["name", "password", "age", "account", "depart", "gender","create_time"]
    # # widgets = {
    # # "name": forms.TextInput(attrs={"class": "form-control"}),
    # # "password": forms.TextInput(attrs={"class": "form-control"}),
    # # "age": forms.TextInput(attrs={"class": "form-control"}),
    # # "account": forms.TextInput(attrs={"class": "form-control"}),
    # # "depart": forms.Select(attrs={"class": "form-control"}),
    # # "gender": forms.Select(attrs={"class": "form-control"}),
    # # "create_time": forms.TextInput(attrs={"class": "form-control"}),
    # # }
    #
    # # def __init__(self, *args, **kwargs):
    # # super().__init__(*args, **kwargs)
    # # for name, item in self.fields.items():
    # # # item.widget.attrs.update({"class": "form-control", "placeholder": "请输入%s" % (item.label,)})
    # # item.widget.attrs = {"class": "form-control", "placeholder": "请输入%s" % (item.label,)}


    def user_modelform_add(request):
    if request.method == "GET":
    form = UserModelForm()
    return render(request, "modelform_add.html", {"form": form})

    form = UserModelForm(data=request.POST)

    if form.is_valid():
    print(form.cleaned_data)
    form.save()
    return redirect("/user/list")
    else:
    print(form.errors)
    return render(request, "modelform_add.html", {"form": form})


    def user_edit(request, nid):
    instance_data = models.UserInfo.objects.filter(id=nid).first()
    if request.method == "GET":
    form = UserModelForm(instance=instance_data)
    return render(request, "user_edit.html", {"form": form})

    # 更新数据
    form = UserModelForm(data=request.POST, instance=instance_data)
    if form.is_valid():
    print(form.cleaned_data)
    # 如果想保存用户输入以外的其他值,可以使用以下方法
    # form.instance.depart_id = request.POST.get("depart")
    form.save()
    return redirect("/user/list")
    else:
    print(form.errors)
    return render(request, "user_edit.html", {"form": form})


    def user_delete(request, nid):
    models.UserInfo.objects.filter(id=nid).delete()
    return redirect("/user/list")


    def phone_list(request):
    # for i in range(300):
    # models.PhoneNumber.objects.create(mobile="12345678901", price=100, level=1, status=1)

    data = {}
    # models.PhoneNumber.objects.filter(id=1)
    search_ = request.GET.get("q")
    # 传过来的值可能为空
    if search_:
    data["mobile__contains"] = search_
    # return render(request, "phone_list.html", {"data": data})

    # 默认为第一页 不写有问题
    page = request.GET.get("page", 1)
    page = int(page)

    # 每页的数据大小
    page_size = 10
    # 根据要访问的页码计算出起止位置
    start = (page - 1) * page_size
    end = page * page_size

    # 可以进行排序 -level 表示按照级别降序
    # data = models.PhoneNumber.objects.all().order_by("-level")
    data = models.PhoneNumber.objects.filter(**data).order_by("-level")[start:end]

    # 数据总数
    total_count = models.PhoneNumber.objects.all().count()
    # 总页数
    page_total, div = divmod(total_count, page_size)
    if div > 0:
    page_total += 1

    # 计算出显示当前页码的前五页,后五页
    plus = 5
    # 数据库的数据较少 页码全部显示
    if page_total < 2 * plus + 1:
    start_page = 1
    end_page = page_total + 1
    else:
    # 数据库的数据较多
    if page <= plus:
    start_page = 1
    end_page = 2 * plus + 1
    elif page > page_total - plus:
    start_page = page_total - 2 * plus
    end_page = page_total + 1
    else:
    start_page = page - plus
    end_page = page + plus + 1

    # 生成页码
    page_data = []
    for i in range(start_page, end_page):
    if i == page:
    hl = '<li class="active"><a href="?page={}">{} <span class="sr-only">(current)</span></a></li>'.format(i, i)
    else:
    hl = '<li><a href="?page={}">{} <span class="sr-only">(current)</span></a></li>'.format(i, i)
    page_data.append(hl)

    # 首页 末页
    shou_page = '<li><a href="?page={}">首页</a></li>'.format(1)
    wei_page = '<li><a href="?page={}">末页</a></li>'.format(page_total)

    # 上一页 下一页
    prev_page = page - 1
    next_page = page + 1
    if prev_page > 1:
    hl = '<li><a href="?page={}" aria-label="Previous"><span aria-hidden="true">&laquo;</span></a></li>'.format(
    prev_page)
    else:
    hl = '<li class="disabled" disabled="True"><a href="?page={}" aria-label="Previous"><span aria-hidden="true">&laquo;</span></a></li>'.format(
    1)
    page_data.insert(0, hl)
    if next_page <= page_total:
    hl = '<li><a href="?page={}" aria-label="Next"><span aria-hidden="true">&raquo;</span></a></li>'.format(
    next_page)
    else:
    hl = '<li class="disabled" disabled="True"><a href="?page={}" aria-label="Next"><span aria-hidden="true">&raquo;</span></a></li>'.format(
    page_total)
    page_data.append(hl)

    page_data.insert(0, shou_page)
    page_data.append(wei_page)

    # 跳转页面
    jump_page = request.GET.get("jump")
    # jump_page = int(jump_page)
    if jump_page is not None:
    jump_page = int(jump_page)
    if jump_page <= 1:
    jump_page = 1
    elif jump_page > page_total:
    jump_page = page_total
    return redirect("/phone/list?page=%s" % (jump_page,))

    page_data = mark_safe("".join(page_data))
    return render(request, "phone_list.html",
    {"data": data, "search": search_, "page_data": page_data, "current_page": page})


    # class PhoneModelForm(BootStrapModelForm):
    # # 方式一:在字段中添加验证规则
    # # validator中可以添加多个验证规则
    # # mobile = forms.CharField(
    # # min_length=11,
    # # label="手机号",
    # # validators=[RegexValidator(r"^1\d{10}$", "手机号格式错误")]
    # # )
    #
    # class Meta:
    # model = models.PhoneNumber
    # fields = "__all__"
    # # fields = ["mobile", "price", "level", "status"]
    # # 排除哪个字段
    # # exclude = ["status"]
    #
    # # def __init__(self, *args, **kwargs):
    # # super().__init__(*args, **kwargs)
    # # for name, item in self.fields.items():
    # # if item.widget.attrs:
    # # item.widget.attrs["class"] = "form-control"
    # # item.widget.attrs["placeholder"] = "请输入%s" % (item.label,)
    # # else:
    # # item.widget.attrs = {"class": "form-control", "placeholder": "请输入%s" % (item.label,)}
    #
    # # 方式二:钩子方法
    # def clean_mobile(self):
    # mobile_ = self.cleaned_data["mobile"]
    # exits = models.PhoneNumber.objects.filter(mobile=mobile_).exists()
    # if exits:
    # raise forms.ValidationError("手机号已存在")
    # # 验证不通过
    # if len(mobile_) != 11:
    # raise forms.ValidationError("手机号长度不对")
    # for i in mobile_:
    # if i not in "0123456789":
    # raise forms.ValidationError("手机号格式错误,必须全是数字")
    #
    # return mobile_


    def phone_add(request):
    if request.method == "GET":
    form = PhoneModelForm()
    return render(request, "phone_add.html", {"form": form})

    form = PhoneModelForm(data=request.POST)
    if form.is_valid():
    form.save()
    return redirect("/phone/list")
    else:
    print(form.errors)
    return render(request, "phone_add.html", {"form": form})


    def phone_edit(request, nid):
    instance_data = models.PhoneNumber.objects.filter(id=nid).first()
    if request.method == "GET":
    form = PhoneEditModelForm(instance=instance_data)
    return render(request, "phone_edit.html", {"form": form})

    form = PhoneEditModelForm(data=request.POST, instance=instance_data)

    if form.is_valid():
    form.save()
    return redirect("/phone/list")
    else:
    print(form.errors)
    return render(request, "phone_edit.html", {"form": form})


    # class PhoneEditModelForm(BootStrapModelForm):
    # mobile = forms.CharField(disabled=True, label="手机号")
    #
    # class Meta:
    # model = models.PhoneNumber
    # # fields = "__all__"
    # # 不让用户修改手机号
    # fields = ["mobile", "price", "level", "status"]
    # # 排除哪个字段
    # # exclude = ["status"]
    #
    # #
    # # def __init__(self, *args, **kwargs):
    # # super().__init__(*args, **kwargs)
    # # for name, item in self.fields.items():
    # # item.widget.attrs = {"class": "form-control", "placeholder": "请输入%s" % (item.label,)}
    #
    # def clean_mobile(self):
    # mobile_ = self.cleaned_data["mobile"]
    # exits = models.PhoneNumber.objects.exclude(id=self.instance).filter(mobile=mobile_).exists()
    # if exits:
    # raise forms.ValidationError("手机号已存在")
    # return mobile_


    def phone_delete(request, nid):
    models.PhoneNumber.objects.filter(id=nid).delete()
    return redirect("/phone/list")

管理员功能

表结构的设计

ID 用户名 密码
1
2
3
4
class Admin(models.Model):
"""管理员"""
username = models.CharField(max_length=32, verbose_name="用户名")
password = models.CharField(max_length=64, verbose_name="密码")
image-20240219201308330 image-20240219201520031

管理员列表

有了模板与工具类之后,编写列表变得很方便

需要注意的是,获取所有信息放在了Pagination工具类中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from django.shortcuts import render, redirect, HttpResponse
from management_sys import models
from management_sys.utils.forms import AdminModelForm, AdminEditModelForm, AdminResetModelForm
from management_sys.utils.pagination import Pagination


def admin_list(request):
search_parameter = {}
search_ = request.GET.get("q")
if search_:
search_parameter["username__contains"] = search_

pagination = Pagination(request, models.Admin.objects.filter(**search_parameter))

data = pagination.get_queryset()
html = pagination.html()

content = {
"data": data,
"html": html,
"search": search_
}
return render(request, "admin_list.html", content)

关于搜索

分页功能是有bug的,比如我搜索只后,点击下一页会直接从admin/list?q=123跳到admin/list?page=2,这是因为跳转链接并没有和搜索q参数产生关联。需要了解以下方法:

1
2
3
request.GET   # 这个对象会返回连接中的所有参数

request.GET.urlencode() # 会返回当前参数的连接形式

如:

发送GET请求:admin/list?aaa=123&tys=456&jjj=789

得到的结果为:

1
2
<QueryDict: {'aaa': ['123'], 'tys': ['456'], 'jjj': ['789']}>
aaa=123&tys=456&jjj=789

另外可以使用setlist(参数名,参数值列表)来设定一些特定的参数值,但是一般来说,这个方法是不允许使用的,原因为request.GET._mutable = False,也就是说默认并不让修改。

这时候可以使用copy的方法复制对象,然后再修改其中的_mutable = True

1
2
3
4
5
import copy

query_params = copy.deepcopy(request.GET)
query_params._mutable = True
query_params.setlist("page",[1])

返回的链接为:aaa=123&tys=456&jjj=789&page=1

因此我们可以重新编写分页的链接逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import copy


class Pagination():
def __init__(self, request, queryset, page_size=10, page_param="page", plus=5):
…………


# setlist 正常情况下是不能使用的,可以使用对象拷贝的方式修改属性来允许使用
self.query_params = copy.deepcopy(request.GET)
# print(request.GET)
self.query_params._mutable = True
# print(request.GET.urlencode())
…………

下面是生成链接的一个例子:

1
2
3
4
5
6
7
for i in range(start_page, end_page):
self.query_params.setlist("page", [i])
if i == self.page:

hl = '<li class="active"><a href="?{}">{} <span class="sr-only">(current)</span></a></li>'.format(self.query_params.urlencode(), i)
else:
hl = '<li><a href="?{}">{} <span class="sr-only">(current)</span></a></li>'.format(self.query_params.urlencode(), i)

添加账户

由于每次我们使用的添加和编辑页面几乎都相同,因此可以创建一个模板html

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
{% extends "layout.html" %}

{% block content %}
<div class="container">
<div class="panel panel-success">
<div class="panel-heading">
<h4>
<span class="glyphicon glyphicon-plus-sign"></span> {{ title }}
</h4>
</div>
<div class="panel-body">
<form method="post" novalidate>
{% csrf_token %}
{% for field in form %}
<div class="form-group">
<label>{{ field.label }}:</label>
{{ field }}
<span style="color: red">{{ field.errors.0 }}</span>
</div>
{% endfor %}
<button type="submit" class="btn btn-primary">提交</button>
</form>
</div>
</div>
</div>
{% endblock %}

开放title来让设定标题

1
2
3
4
5
6
7
8
9
10
11
12
def admin_add(request):
if request.method == "GET":
form = AdminModelForm()
return render(request, "change.html", {"form": form, "title": "新建管理员"})

form = AdminModelForm(data=request.POST)
if form.is_valid():
form.save()
return redirect("/admin/list")
else:
print(form.errors)
return render(request, "admin_add.html", {"form": form, "title": "新建管理员"})

这边要注意的是密码的加密,因为密码是不能明文储存的,我们这边使用常见的MD5加密

1
2
3
4
5
6
7
8
9
from django.conf import settings
import hashlib


def md5(data_string):
md5 = hashlib.md5(settings.SECRET_KEY.encode("utf-8"))
md5.update(data_string.encode("utf-8"))

return md5.hexdigest()

定义一个ModelFrom

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
class AdminModelForm(BootStrapModelForm):
confirm_password = forms.CharField(
max_length=64,
label="确认密码",
widget=forms.PasswordInput(render_value=True)
)

class Meta:
model = models.Admin
fields = "__all__"

widgets = {
"password": forms.PasswordInput(render_value=True)
}

# 对密码加密
def clean_password(self):
pwd = self.cleaned_data.get("password")
return md5(pwd)

def clean_confirm_password(self):
# 获得所有数据
confirm_data = self.cleaned_data
pwd = confirm_data.get("password")
confirm = md5(confirm_data.get("confirm_password"))
if pwd != confirm:
# 如果密码不对PasswordInput会自动清空 加上render_value=True就会保留
raise forms.ValidationError("两次密码不一致")

# 此字段返回什么,数据库就会保存什么
return confirm_data

self.cleaned_data可以获得当前表单的所有值,因为常规的交互逻辑是需要有一个确认密码的机制,可以添加一个新的字段,其中render_value=True可以让刷新页面时候密码保留而不消失

1
2
3
4
5
confirm_password = forms.CharField(
max_length=64,
label="确认密码",
widget=forms.PasswordInput(render_value=True)
)

编辑账户

推荐只能编辑账户的用户名,由于需求不一致,需要重新设定ModelForm

1
2
3
4
5
class AdminEditModelForm(BootStrapModelForm):
class Meta:
model = models.Admin
# 只允许编辑username
fields = ["username"]

直接使用change.html作为模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def admin_edit(request, nid):
data = models.Admin.objects.filter(id=nid).first()
if data is None:
return render(request, "error.html", {"message": "要修改的数据不存在"})

if request.method == "GET":
data = AdminEditModelForm(instance=data)
return render(request, "change.html", {"form": data, "title": "编辑管理员"})

form = AdminEditModelForm(data=request.POST, instance=data)
if form.is_valid():
form.save()
return redirect("/admin/list")
else:
return render(request, "change.html", {"form": form, "title": "编辑管理员"})

重置密码

这是常见的功能之一,关键点在于两点:

  1. 不能让用户看到之前存储的哈希值
  2. 重置的密码不能与上一次的一致

要获取当前的输入值,我们可以使用self.instance来获取,使用self.instance.pk获取当前的id值

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
class AdminResetModelForm(BootStrapModelForm):
confirm_password = forms.CharField(
max_length=64,
label="确认密码",
widget=forms.PasswordInput(render_value=True)
)

class Meta:
model = models.Admin
fields = ["password"]
widgets = {
"password": forms.PasswordInput(render_value=True)
}

def clean_password(self):
pwd = self.cleaned_data.get("password")
# 需要保证现在输入的密码与之前的不一致
md5_pwd = md5(pwd)
exists = models.Admin.objects.filter(id=self.instance.pk, password=md5_pwd).exists()
if exists:
raise forms.ValidationError("新密码不能与之前的密码一致")
return md5_pwd

def clean_confirm_password(self):
# 获得所有数据
confirm_data = self.cleaned_data
pwd = confirm_data.get("password")
confirm = md5(confirm_data.get("confirm_password"))
if pwd != confirm:
# 如果密码不对PasswordInput会自动清空 加上render_value=True就会保留
raise forms.ValidationError("两次密码不一致")

# 此字段返回什么,数据库就会保存什么
return confirm_data

由于不能让用户看到之前的密码储存值,因此不给AdminResetModelForm传递参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def admin_reset(request, nid):
data = models.Admin.objects.filter(id=nid).first()
if data is None:
return render(request, "error.html", {"message": "要重置的数据不存在"})
# form = AdminResetModelForm(instance=data)
if request.method == "GET":
# 不希望管理员看到密码 所以不传递instance
form = AdminResetModelForm()
return render(request, "change.html", {"form": form, "title": "重置密码"})

form = AdminResetModelForm(data=request.POST, instance=data)
if form.is_valid():
form.save()
return redirect("/admin/list")
else:
return render(request, "change.html", {"form": form, "title": "重置密码"})

登录功能

首先要明白什么是cookie和session

http协议的特点为:无状态 & 短连接

image-20240220203838369

为了让用户保持登录状态,服务器会为用户生成随机但唯一的键值对cookie,作为用户的访问凭证,但是这样可能会出现乱输入cookie伪造登录的情况,为了解决这个问题,需要在服务端将已发放的cookie存储下来,这样可以对用户发来的cookie值进行比对。

image-20240220205107017

在Django中使用request.session方法可以生成随机字符串,并将其存入django.session数据库中

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
50
51
52
from django.shortcuts import render, HttpResponse,redirect
from django import forms
from management_sys.utils.encrypt import md5
from management_sys import models


class LoginForm(forms.Form):
username = forms.CharField(
label="用户名",
widget=forms.TextInput(attrs={"class": "form-control"})
)
password = forms.CharField(
label="密码",
widget=forms.PasswordInput(attrs={"class": "form-control"}, render_value=True)
)

def clean_password(self):
password = self.cleaned_data.get("password")
return md5(password)


def login(request):
if request.method == "GET":
forms = LoginForm()

context = {
"forms": forms
}
return render(request, "login.html", context)

form = LoginForm(request.POST)
if form.is_valid():
print(form.cleaned_data)

create = models.Admin.objects.filter(**form.cleaned_data).first()
if not create:
# 需要添加错误信息
# field - 展示错误信息的字段
# error - 错误信息
form.add_error("password", "用户名或密码错误")
return render(request, "login.html", {"forms": form})

# 用户名与密码正确
# 网站需要生成随机字符,写到用户浏览器的cookie中
# 服务端需要记录这个随机字符,写到session中
# 会自动生成cookie与字典参数
request.session["info"] = {"id": create.id, "username": create.username}
# return HttpResponse("登录成功")
return redirect("/admin/list")
else:
print(form.errors)
return render(request, "login.html", {"forms": form})

request.session["info"] = {"id": create.id, "username": create.username}这段代码的意思是生成随机cookie,并将{“id”: create.id, “username”: create.username}作为数据存入

尝试登录

image-20240220215917951

进入数据库查看

image-20240220215826727

cookie是以浏览器为单位进行生成,而不是账户

使用chrome按照相同的账户进行登录

image-20240220220456351

此时还将BootstrapModel.py修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from django import forms


class BootStrap:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# 循环Modelform中的所有字段,在每个字段设置插件
for name, item in self.fields.items():
item.widget.attrs = {
"class": "form-control",
"placeholder": "请输入%s" % (item.label,)
}


class BootStrapModelForm(BootStrap, forms.ModelForm):
pass


class BootStrapForm(BootStrap, forms.Form):
pass

登录验证

登录成功后:

  • cookie,随机字符串
  • session,用户信息

在其他需要登录才能访问的页面中,都需要加入:

1
2
3
4
5
def admin_list(request):
# 检查用户是否登录
info = request.session.get("info")
if not info:
return redirect("/login/")

太麻烦,需要在18个视图函数中统一添加上述的判断

中间件

image-20240221155929564

如何定义一个中间件

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
from django.utils.deprecation import MiddlewareMixin


class M1(MiddlewareMixin):
"""
中间件1
"""

# 如果方法中没有返回值(None),则继续往后走
# 如果方法中有返回值,则不再继续往后走 直接返回
# 可以返回HttpResponse,render,redirect
def process_request(self, request):
print("M1 process_request")

def process_response(self, request, response):
print("M1 process_response")
return response


class M2(MiddlewareMixin):
"""
中间件2
"""

def process_request(self, request):
print("M2 process_request")

def process_response(self, request, response):
print("M2 process_response")
return response

process_response必须返回response

在中间件的process_request有返回值则不再继续后续的操作,直接返回

应用中间件需要在settings.py中添加,添加顺序即为访问顺序

1
2
3
4
5
6
7
8
9
10
11
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"management_sys.middleware.auth.M1",
"management_sys.middleware.auth.M2",
]
image-20240221162443368

登录的中间件

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
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import redirect


class M1(MiddlewareMixin):
"""
中间件1
"""

# 如果方法中没有返回值(None),则继续往后走
# 如果方法中有返回值,则不再继续往后走 直接返回
# 可以返回HttpResponse,render,redirect

def process_request(self, request):
# 排除不需要登录就能访问的页面
# 获取当前用户请求的url
if request.path_info in ["/login/", "/index/"]:
return

# 检查用户是否登录
session_info = request.session.get("info")
print("session_info:", session_info)
# 如果session_info有值,说明用户已经登录
if session_info:
return


# 如果session_info没有值,说明用户没有登录
# 但是这样会出现一个问题,如果用户没有登录,那么用户就无法访问任何页面
return redirect("/login/")

一定要排除一些登录界面,不然会一直出现重定向

登出只需要清除网页的cookie信息,数据库中的信息并没有删除

1
2
3
def logout(request):
request.session.clear()
return redirect("/login/")

登录成功的信息需要在layoout.html中使用来获取当前登录的用户名,之后就能在右上角看到用户信息

image-20240221172639142

优化登录(图片验证码)

1
pip install pillow

生成验证码

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
50
from PIL import Image, ImageDraw, ImageFont
import random


def random_color():
color_ = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
return color_


def random_character():
return chr(random.randint(65, 90))


def create_img_code():
img_height = 100
img_width = 250
# 创建一张图片
img = Image.new('RGB', (img_width, img_height), (255, 255, 255))

# 导入字体
font = ImageFont.truetype(
r'D:\Python_ProjectHouse\manage_system\management_sys\static\font\Monaco.ttf',
65)

# 创建一个画笔
draw = ImageDraw.Draw(img, mode='RGB')

for i in range(200):
x = random.randint(0, img_width)
y = random.randint(0, img_height)
draw.point((x, y), fill=random_color())

for i in range(50):
x1 = random.randint(0, img_width)
y1 = random.randint(0, img_height)
x2 = random.randint(0, img_width)
y2 = random.randint(0, img_height)
draw.line((x1, y1, x2, y2), fill=random_color())

code_str = ''
for i in range(4):
x = (img_width / 4) * i + 10
y = random.randint(10, 40)
code = random_character()
code_str += code
draw.text((x, y), text=code, fill=random_color(), font=font)

print(f"code_str:{code_str}")
# img.show()
return img, code_str

对于图片,保存到本地之后再读取有点麻烦,直接将其写入内存即可

1
2
3
img, code_str = create_img_code()
stream = BytesIO()
print(stream.getvalue())

我们将图片放在/image/code连接中,需要现在中间件中排除,不然无法访问这个链接,也无法获得图片

1
2
3
4
5
def process_request(self, request):
# 排除不需要登录就能访问的页面
# 获取当前用户请求的url
if request.path_info in ["/login/", "/index/", "/image/code"]:
return

完整的视图函数

1
2
3
4
5
6
7
8
9
10
from io import BytesIO
from management_sys.utils.create_captcha import create_img_code

def image_code(request):
img, code_str = create_img_code()
stream = BytesIO()
print(code_str)
img.save(stream, "png")

return HttpResponse(stream.getvalue())
image-20240221184729205

按照规定修改Form

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class LoginForm(BootStrapForm):
username = forms.CharField(
label="用户名",
widget=forms.TextInput()
)
password = forms.CharField(
label="密码",
widget=forms.PasswordInput(render_value=True)
)

captcha = forms.CharField(
label="验证码",
widget=forms.TextInput()
)

def clean_password(self):
password = self.cleaned_data.get("password")
return md5(password)
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

<form method="post" novalidate>
{% csrf_token %}
<div class="form-group">
<label>{{ forms.username.label }}</label>
{{ forms.username }}
<span style="color: red">{{ forms.username.errors.0 }}</span>
</div>
<div class="form-group">
<label for="password">{{ forms.password.label }}</label>
{{ forms.password }}
<span style="color: red">{{ forms.password.errors.0 }}</span>
</div>

<div class="form-group clearfix">
<label>{{ forms.captcha.label }}</label>
<div class="row">
<div class="col-md-8">
{{ forms.captcha }}
</div>
<div class="col-md-4">
<img src="/image/code" style="height: 34px" alt="验证码">
</div>
</div>
<span style="color: red">{{ forms.captcha.errors.0 }}</span>
</div>
<button type="submit" class="btn btn-primary">登 录</button>
</form>

为了验证验证码我们需要做两件事

  1. 存储当前的验证码 存储在session中
  2. 校验用户提交的验证码 并设定相关的状态
1
2
3
4
5
6
7
8
9
10
11
def image_code(request):
img, code_str = create_img_code()
stream = BytesIO()
img.save(stream, "png")

# 将验证码写入session 以便后续校验
request.session["image_code"] = code_str
# 不能让验证码一直有效,设置过期时间
request.session.set_expiry(60)

return HttpResponse(stream.getvalue())
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
if form.is_valid():
print(form.cleaned_data)

# 一定要用pop方法,否则会报错 因为数据库中没有captcha字段
code = form.cleaned_data.pop("captcha")
image_code = request.session.get("image_code", "")

if image_code == "":
form.add_error("captcha", "验证码已过期")
return render(request, "login.html", {"forms": form})

if image_code.upper() != code.upper():
form.add_error(field="captcha", error="验证码错误")
return render(request, "login.html", {"forms": form})

create = models.Admin.objects.filter(**form.cleaned_data).first()
if not create:
# 需要添加错误信息
# field - 展示错误信息的字段
# error - 错误信息
form.add_error("password", "用户名或密码错误")
return render(request, "login.html", {"forms": form})

# 登录成功删除验证码 防止被重复使用
request.session.pop("image_code")
# 用户名与密码正确
# 网站需要生成随机字符,写到用户浏览器的cookie中
# 服务端需要记录这个随机字符,写到session中
# 会自动生成cookie与字典参数
request.session["info"] = {"id": create.id, "username": create.username}
# 因为之前将session的过期时间设置为60秒,所以这里需要重新设置
# 设置session的过期时间为七天
request.session.set_expiry(60 * 60 * 24 * 7)
# return HttpResponse("登录成功")
return redirect("/admin/list")

Ajax请求

浏览器向我们的网站发送请求时:URL和表单的方式

  • GET
  • POST

这样的方法有个特点,每次都会导致页面刷新。

除此之外,也可以基于Ajax向后台发送请求(偷偷发送请求)

  • 依赖JQuery

  • 编写Ajax代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    $.ajax(
    {
    url:"发送的地址",
    type:"post",
    data:{
    n1:123,
    n2:456
    },
    success:function(res){
    console.log(res);
    }
    }
    )

前端骨架

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
{% extends "layout.html" %}


{% block content %}
<div class="container">
<h1>任务管理</h1>
<h3>示例一</h3>
<input type="button" class="btn btn-primary" value="点我" onclick="clickMe()">
</div>

{% endblock %}


{% block js %}
<script type="text/javascript">
function clickMe() {
$.ajax({
url: '/task/ajax',
type: 'post',
data: {
"name": "zhangsan",
"age": 18
},
success: function (data) {
console.log(data)
}
})
}
</script>
{% endblock %}

发送GET请求

1
2
3
4
5
6
7
8
9
10
11
12
$.ajax({
url: '/task/ajax',
type: 'get',
data: {
"name": "zhangsan",
"age": 18
},
success: function (data) {
console.log(data)
}

})
1
2
3
def task_ajax(request):
print(request.GET)
return HttpResponse("成功了")

发送POST请求

1
2
3
4
5
6
7
8
9
10
11
12
$.ajax({
url: '/task/ajax',
type: 'post',
data: {
"name": "zhangsan",
"age": 18
},
success: function (data) {
console.log(data)
}

})
1
2
3
4
5
6
7
from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse

@csrf_exempt
def task_ajax(request):
print(request.POST)
return HttpResponse("成功了")

基于jquery

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
{% extends "layout.html" %}


{% block content %}
<div class="container">
<h1>任务管理</h1>
<h3>示例一</h3>
<input type="button" class="btn btn-primary" value="点我" id="btn1">
</div>

{% endblock %}


{% block js %}
<script type="text/javascript">

$(function () {
// 页面加载完成后执行
clickMe();
})

function clickMe() {
$("#btn1").click(function () {
$.ajax({
url: '/task/ajax',
type: 'post',
data: {
"name": "zhangsan",
"age": 18
},
success: function (data) {
console.log(data)
}
})
})
}
</script>
{% endblock %}

基于Ajax的返回值

一般都会返回一个json格式

1
2
3
4
5
6
7
8
9
10
11
12
13
from django.http import JsonResponse
import json

def task_ajax(request):
# print(request.GET)
# print(request.POST)
data_dict = {
"name": "alex",
"age": 18
}
json_str = json.dumps(data_dict)
# return HttpResponse(json_str)
return JsonResponse(data_dict)

有两种方式

1
2
3
4
5
data_dict = {
"name": "alex",
"age": 18
}
return JsonResponse(data_dict)
1
2
3
4
5
6
data_dict = {
"name": "alex",
"age": 18
}
json_str = json.dumps(data_dict)
return HttpResponse(json_str)
image-20240222103515793

前端想要使用的话需要修改如下

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
$(function () {
// 页面加载完成后执行
clickMe();
})

function clickMe() {
$("#btn1").click(function () {
$.ajax({
url: '/task/ajax',
type: 'post',
data: {
"name": "zhangsan",
"age": 18
},
// 添加之后,返回的数据会自动转换为json格式
// 之后就可以直接使用data.name和data.age
dataType: 'json',
success: function (data) {
console.log(data)
console.log(data.name)
console.log(data.age)
}
})
})
}

任务列表

创建models

1
2
3
4
5
6
7
8
9
10
11
12
class TaskList(models.Model):
"""任务列表"""
level_choices = {
(1, "紧急"),
(2, "重要"),
(3, "临时"),
}

level = models.SmallIntegerField(verbose_name="级别", choices=level_choices, default=1)
title = models.CharField(verbose_name="标题", max_length=64)
detail = models.TextField(verbose_name="详情")
user = models.ForeignKey(verbose_name="负责人", to=Admin, to_field="id", on_delete=models.CASCADE)

创建form

1
2
3
4
class TaskModelForm(BootStrapModelForm):
class Meta:
model = models.TaskList
fields = "__all__"

创建视图函数,接受数据并返回信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def task_add(request):
data = request.POST.dict()

# 对用户输入的数据进行校验(使用Modelform进行校验)

form = TaskModelForm(data=data)

if form.is_valid():
form.save()
return JsonResponse({"status": True})

# data_dict = {"status": 0, "error": form.errors.as_json()}
# return JsonResponse(data_dict)
# 这样可以展示中文的错误信息
data_dict = {"status": False, "error": form.errors}
return HttpResponse(json.dumps(data_dict, ensure_ascii=False))

在前端中进行展示,这边有个注意点是button点击后会默认刷新页面,这时候有两种方法

  • 改为input标签
  • 在js中添加 event.preventDefault();

另外,为了防止标签填充出现的乱套情况,需要将form-group的位置设置为relative,span的位置设置为absolute

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<div class="panel panel-default">
<div class="panel-heading">
<h4>任务列表</h4>
</div>
<div class="panel-body">
<form novalidate id="formAdd">
<div class="clearfix">
{% for field in form %}
<div class="col-xs-6" style="margin-bottom: 20px">
<div class="form-group" style="position: relative">
<label>{{ field.label }}</label>
{{ field }}
<span class="error_msg" style="color: red;position:absolute;"></span>
</div>
</div>
{% endfor %}
</div>
<div class="col-xs-12">
<button type="submit" id="btnAdd" class="btn btn-primary">添加任务</button>
</div>
</form>
</div>
</div>

js方面,为了让错误信息清楚,在每次点击之前都清除所有span的信息

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
$(function () {
// 页面加载完成后执行
btnAddEvent();
})

function btnAddEvent() {
$("#btnAdd").click(function () {
// 每次点击按钮之前,先清空错误信息
{#$(".error_msg").text('')#}
$(".error_msg").empty()

// 不加这句会自动刷新控制台导致看不到结果
event.preventDefault();
$.ajax({
url: '/task/add',
type: 'post',
data: $("#formAdd").serialize(),
dataType: 'json',
success: function (res) {
if (res.status) {
alert('添加成功')
} else {
console.log(res.error)
$.each(res.error, function (name, value) {
console.log(name, value)
// console.log($("[name=" + name + "]"))
// 找到对应的id,然后找到下一个元素,设置文本
$("#id_" + name).next().text(value[0])
})
// alert('添加失败')
}
}
})
})
}
image-20240222151600557 image-20240222151636201

使用ajax不会自动刷新页面,所以我们需要使用js进行页面的刷新

1
2
3
4
5
if (res.status) {
alert('添加成功')
// 用js进行页面刷新
location.reload()
}

订单列表

添加订单

设计表结构

id 订单号 商品名称 价格 状态 用户ID
自动生成 当前时间+随机数字 已支付/未支付 关联用户
1
2
3
4
5
6
7
8
9
10
11
12
13
class Orders(models.Model):
"""订单表"""
oid = models.CharField(verbose_name="订单号", max_length=64)
title = models.CharField(verbose_name="名称", max_length=32)
price = models.IntegerField(verbose_name="价格")

status_choices = {
(1, "待支付"),
(2, "已支付")
}

status = models.SmallIntegerField(verbose_name="状态", choices=status_choices, default=1)
admin = models.ForeignKey(verbose_name="管理员", to="Admin", to_field="id", on_delete=models.CASCADE)

准备使用模态框来实现添加订单的功能

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
<div id="exampleModal" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true">&times;</span></button>
<h4 class="modal-title">添加订单</h4>
</div>
<div class="modal-body">
<form novalidate id="formAdd">
<div class="clearfix">
{% for item in form %}
<div class="form-group col-md-6" style="position:relative;margin-bottom: 30px">
<label>{{ item.label }}</label>
{{ item }}
<span style="color: red;position:absolute" class="error_msg"></span>
</div>
{% endfor %}
</div>
</form>
</div>
<div class="modal-footer">
<div class=" col-md-12">
<button type="button" class="btn btn-default" data-dismiss="modal">取 消</button>
<input id="btnSave" type="submit" class="btn btn-primary" value="保 存">
</div>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->

让模态框绑定按钮有两种方法

  1. 使用js
  2. 使用data-target:类名 data-toggle="modal"
1
2
3
4
5
6
7
<!-- 第一种:使用js -->
<div style="margin-bottom: 10px">
<input id="addBtn" type="button" value="新建订单1" class="btn btn-primary">
<!-- 第二种:使用data-toggle与data-target -->
<input type="button" value="新建订单2" class="btn btn-primary" data-toggle="modal"
data-target="#exampleModal">
</div>
1
2
3
4
5
6
7
8
9
10
$(function () {
bindModalEvent();
bindSaveEvent();
})

function bindModalEvent() {
$("#addBtn").click(function () {
$("#exampleModal").modal("show");
})
}

要填写数据,需要先定义ModelForm,其中有两个值不允许用户自己填写

  1. oid - 由系统自动生成
  2. admin - 自动获取
1
2
3
4
5
class OrderModelForm(BootStrapModelForm):
class Meta:
model = models.Orders
fields = "__all__"
exclude = ["oid", "admin"]

将表单写入模态框

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
<div class="panel panel-default">
<div class="panel-heading">
<h3>订单列表</h3>
</div>
<div class="panel-body">
<table class="table-hover table-bordered table">
<thead>
<tr>
<th>ID</th>
<th>OID</th>
<th>标题</th>
<th>价格</th>
<th>状态</th>
<th>管理员</th>
<th>操作</th>
</tr>
</thead>

<tbody>
{% for item in data %}
<tr>
<td>{{ item.id }}</td>
<td>{{ item.oid }}</td>
<td>{{ item.title }}</td>
<td>{{ item.price }}</td>
<td>{{ item.get_status_display }}</td>
<td>{{ item.admin.username }}</td>
<td>
<a href="#" class="btn btn-primary btn-xs">
<span class="glyphicon glyphicon-edit" aria-hidden="true"></span> 编辑
</a>
<a href="#" class="btn btn-danger btn-xs">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> 删除
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def order_list(request):
form = OrderModelForm()

queryset = models.Orders.objects.all().order_by("-id")

pagination = Pagination(request, queryset)
data = pagination.get_queryset()
pages_html = pagination.html()

context = {
"form": form,
"data": data,
"pages_html": pages_html
}
return render(request, "order_list.html", context)
image-20240222182943176

编写添加的逻辑

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
from django.views.decorators.csrf import csrf_exempt
from datetime import *
import random


@csrf_exempt
def order_add(request):
data = request.POST
form = OrderModelForm(data=data)

if form.is_valid():
print(form.cleaned_data)
# 订单号:这时候是没有oid的 需要自己生成
# 小写的m d是不带/的
form.instance.oid = datetime.now().strftime("%Y%m%d%H%M%S") + str(random.randint(1000, 9999))

# 固定设置管理员的ID
# 设置当前登录用户的ID
form.instance.admin_id = request.session.get("info")["id"]

form.save()
# print("验证成功")
return JsonResponse({"status": True})
# print("验证失败")
return JsonResponse({"status": False, "error": form.errors})

使用form.instance.属性名来直接设置其中的值

为提交按钮绑定js

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
function bindSaveEvent() {

$("#btnSave").click(function (event) {
$(".error_msg").empty()
event.preventDefault()
$.ajax({
url: "/order/add",
type: "post",
data: $('#formAdd').serialize(),
dataType: 'json',
success: function (res) {
console.log(res)
if (res.status) {
alert("添加成功")
// 清空表单 取0得到DOM对象
$("#formAdd")[0].reset();
// 关闭对话框
$("#exampleModal").modal("hide");
// 刷新页面
location.reload()
} else {
console.log(res.error)
$.each(res.error, function (name, value) {
console.log(name, value)
// console.log($("[name=" + name + "]"))
// 找到对应的id,然后找到下一个元素,设置文本
$("#id_" + name).next().text(value[0])
})
}
}
})
})
}

删除订单

使用ajax来实现删除

但是这样有个问题,如何获取当前删除数据的id

  1. 偷偷将id藏到一个地方
  2. 定义成全局变量

采用自定义属性的方式获取到当前的id

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<tbody>
{% for item in data %}
<tr uid="{{ item.id }}">
<td>{{ item.id }}</td>
<td>{{ item.oid }}</td>
<td>{{ item.title }}</td>
<td>{{ item.price }}</td>
<td>{{ item.get_status_display }}</td>
<td>{{ item.admin.username }}</td>
<td>
<a uid="{{ item.id }}" class="btn btn-primary btn-xs btnEdit">
<span class="glyphicon glyphicon-edit" aria-hidden="true"></span> 编辑
</a>
<a uid="{{ item.id }}" class="btn btn-danger btn-xs btnDelete">
<span class="glyphicon glyphicon-trash"
aria-hidden="true"></span> 删除
</a>
</td>
</tr>
{% endfor %}
</tbody>

在js中可以通过$(this).attr("uid")获取到当前的uid

1
2
3
4
5
6
7
8
9
10
let DELETE_ID = 0

function bindDeleteEvent() {
$(".btnDelete").click(function () {
$("#wrongModal").modal("show");
// 获取当前点击的元素的uid
DELETE_ID = $(this).attr("uid")
// console.log(DELETE_ID)
})
}

删除的逻辑

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
function bindDeleteConfirmEvent() {
$("#btnDeleteConfirm").click(function () {
$.ajax({
url: "/order/delete",
type: "GET",
data: {"uid": DELETE_ID},
dataType: 'json',
success: function (res) {
if (res.status) {
alert("删除成功");

// 关闭对话框
$("#wrongModal").modal("hide");


// 删除标签 这一步是不必要的
//$("tr[uid = DELETE_ID]").remove();

// 全局变量清空
DELETE_ID = 0;

// 刷新页面
location.reload();

} else {
alert("删除失败" + res.error);
console.log("删除失败" + res.error);
}
}
})
})
}
1
2
3
4
5
6
7
def order_delete(request):
uid = request.GET.get("uid")
exists = models.Orders.objects.filter(id=uid).exists()
if not exists:
return JsonResponse({"status": False, "error": "删除失败,数据不存在"})
models.Orders.objects.get(id=uid).delete()
return JsonResponse({"status": True})
image-20240222204544788

编辑订单

编辑订单也必须从后台获得当前id的所有数据,而且由于编辑与添加功能相似,因此使用同一个前端对话框,但是需要修改相应的逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function bindSaveEvent() {

$("#btnSave").click(function (event) {
$(".error_msg").empty()

event.preventDefault()

if (EDIT_ID) {
editDate()
} else {
addDate()
}
})
}

设定一个常亮来标记当前对话框的使用状态EDIT_ID:

  • undefined - 添加

    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
    function addDate() {
    $.ajax({
    url: "/order/add",
    type: "post",
    data: $('#formAdd').serialize(),
    dataType: 'json',
    success: function (res) {
    console.log(res)
    if (res.status) {
    alert("添加成功")
    // 清空表单 取0得到DOM对象
    $("#formAdd")[0].reset();
    // 关闭对话框
    $("#exampleModal").modal("hide");
    // 刷新页面
    location.reload()
    } else {
    console.log(res.error)
    $.each(res.error, function (name, value) {
    console.log(name, value)
    // console.log($("[name=" + name + "]"))
    // 找到对应的id,然后找到下一个元素,设置文本
    $("#id_" + name).next().text(value[0])
    })
    }
    }
    })
    }
  • uid - 编辑

    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
    function editDate() {
    $.ajax({
    url: "/order/edit" + "?uid=" + EDIT_ID,
    type: "post",
    data: $('#formAdd').serialize(),
    dataType: 'json',
    success: function (res) {
    console.log(res)
    if (res.status) {
    alert("编辑成功")
    // 清空表单 取0得到DOM对象
    $("#formAdd")[0].reset();
    // 关闭对话框
    $("#exampleModal").modal("hide");
    // 刷新页面
    location.reload()
    } else {
    console.log(res.error)
    if (res.error) {
    $.each(res.error, function (name, value) {
    console.log(name, value)
    // console.log($("[name=" + name + "]"))
    // 找到对应的id,然后找到下一个元素,设置文本
    $("#id_" + name).next().text(value[0])
    }
    )
    } else {
    alert(res.tips)
    }
    }
    }
    })
    }

为了将所有的数据展示在对话框上,我们需要将数据传到前端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def order_detail(request):
uid = request.GET.get("uid")
object_dict = models.Orders.objects.filter(id=uid).values("title", "price", "status").first()
if not object_dict:
return JsonResponse({"status": False, "error": "编辑失败,数据不存在"})

# 方式一:逐一获取数据
obj = models.Orders.objects.filter(id=uid).first()
data_dict = {
"oid": obj.oid,
"title": obj.title,
"price": obj.price,
"status": obj.get_status_display(),
"admin": obj.admin.username
}
# return JsonResponse({"status": True, "data": data_dict})
# 方式二:使用value
# obj = models.Orders.objects.filter(id=uid).values("title", "price", "status").first()
# object_dict["status"] = obj.get_status_display()
# print(object_dict)
return JsonResponse({"status": True, "data": object_dict})

想要去数据库中获取数据时,这里需要区分三个不同的返回值

1
2
3
row_object = models.Orders.objects.filter(id=uid).first()
row_object.oid
row_object.price
1
2
3
# 返回值是一个字典  {"id":1,"uid":454545,……}
row_object = models.Orders.objects.filter(id=uid).values("id","oid","title","price").first()
row_object["id"]
1
2
# [{"id":1,"uid":454545,……},{"id":2,"uid":454545,……}]
row_object = models.Orders.objects.filter(id=uid).values("id","oid","title","price")
1
2
3
# 返回值是一个元组[(1,"xx"),(2,"xxxx")]
row_object = models.Orders.objects.filter(id=uid).value_list("id","oid","title","price").first()
row_object[0]

最好直接获取字典

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@csrf_exempt
def order_edit(request):
uid = request.GET.get("uid")

data = models.Orders.objects.filter(id=uid).first()

if not data:
return JsonResponse({"status": False, "tips": "编辑失败,数据不存在"})

form = OrderModelForm(data=request.POST, instance=data)

if form.is_valid():
form.save()
return JsonResponse({"status": True})

return JsonResponse({"status": False, "error": form.errors})

使用不同的返回值tipserror来分辨是哪种错误

图表

  • highchart 国外
  • echarts 国内百度开源
  1. 引入js
  2. 使用ajax传送数据

文件的上传

获取文件对象

1
2
3
4
5
6
7
8
<div class="container">
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<input type="text" name="username">
<input type="file" name="ff">
<input type="submit" value="提交">
</form>
</div>

对于文件的上传,一定要加上enctype="multipart/form-data"这个属性,如果不加,那么只能获得文件的名称,而不能获得相应的文件

1
2
3
4
5
6
7
8
9
10
11
from django.shortcuts import render, HttpResponse


def file_upload(request):
if request.method == "GET":
return render(request, "upload.html")

print(request.POST)
print(request.FILES)

return HttpResponse("上传成功")

输出的结果分别为:

1
2
<QueryDict: {'csrfmiddlewaretoken': ['EJkSoxCazcl7eh3UVQSZMIKU5jcjJrXtcD9mTKJIuqPkpsRZNtr0zNMlo5AcKBcP'], 'username': ['BA']}>
<MultiValueDict: {'ff': [<InMemoryUploadedFile: b84fb4acde186f87aca35bb043b0ec9237507923.jpg (image/jpeg)>]}>

POST请求中并没有文件,文件在request.FILES

获得的是一个文件对象,文件内部有许多的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def file_upload(request):
if request.method == "GET":
return render(request, "upload.html")

# print(request.POST)
# print(request.FILES)

# 获取文件对象
file_obj = request.FILES.get("ff")
# 获得文件名
print(file_obj.name)
# 获取文件大小
print(file_obj.size)
# 获取文件类型
print(file_obj.content_type)
# 获取文件内容
with open(file_obj.name, "wb") as f:
for chunk in file_obj.chunks():
f.write(chunk)

return HttpResponse("上传成功")

打印结果

1
2
3
4
5
6
7
8
9
10
# post请求的数据
<QueryDict: {'csrfmiddlewaretoken': ['34GFrzXsmFUL5b1vDccdGJZquoCrORzfBYv9WM40hToYgmPAvPLetO1RNa0kP1OB'], 'username': ['BA']}>
# 文件对象
<MultiValueDict: {'ff': [<InMemoryUploadedFile: b84fb4acde186f87aca35bb043b0ec9237507923.jpg (image/jpeg)>]}>
# 文件名
b84fb4acde186f87aca35bb043b0ec9237507923.jpg
# 文件大小
1079422
# 文件类型
image/jpeg

文件的校验:批量上传数据

要进行数据的校验,需要使用Modelform和form,案例是使用excel批量上传数据

关于excel的处理问题,需要使用第三方库

1
pip install openpyxl

使用方法

1
2
3
4
5
6
7
8
9
10
11
from openpyxl import load_workbook

wb = load_workbook("文件路劲或者文件对象")

sheet = wb.worksheet[0]

for row in sheet.iter_rows(min_row=2):
text = row[0].value
exists: = models.Department.objects.filter("title=text").exists()
if not exists:
models.Department.objects.create(title=text)

对应的前端页面为:

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<div id="exampleModal" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true">&times;</span></button>
<h4 class="modal-title">上传Excel</h4>
</div>
<div class="modal-body">
<form method="post" novalidate id="formAdd" enctype="multipart/form-data">
{% csrf_token %}
<div class="form-group">
<input type="file" class="form-control" name="ff">
</div>
</form>
</div>

<div class="modal-footer">
<input type="submit" value="提交" class="btn btn-info" id="uploadExcelBtn">
<button type="button" class="btn btn-default" data-dismiss="modal">取 消</button>
</div>
</div>
</div>
</div>

{% endblock %}

{% block js %}
<script type="text/javascript">
$(function () {
uploadBtnEvent();
uploadExcel();
})


function uploadBtnEvent() {
$("#uploadBtn").click(function () {
$("#exampleModal").modal('show');
})
}

function uploadExcel() {
$("#uploadExcelBtn").click(function (event) {

// 阻止表单默认提交
event.preventDefault();

// 获取表单数据
var formData = new FormData($("#formAdd")[0]);

$.ajax({
url: '/depart/upload',
type: 'post',
data: formData,
processData: false, // 告诉jQuery不要处理发送的数据
contentType: false, // 告诉jQuery不要设置Content-Type请求头
success: function (res) {
if (res.status) {
console.log(res.msg);
$("#exampleModal").modal('hide');
alert('上传成功');
location.reload();
} else {
console.log(res.msg);
}
},
})
})
}
</script>
{% endblock %}

案例:混合数据(form)

提交页面时,用户输入数据+文件(输入不能为空,报错)

表单设计如下:

1
2
3
4
class uploadForm(BootStrapForm):
name = forms.CharField(max_length=64, label="用户名")
age = forms.IntegerField(label="年龄")
img = forms.FileField(label="头像")

这边为了取消文件上传的样式修改了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from django import forms


class BootStrap:
bootstrap_exclude_fields = ["img"]

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# 循环Modelform中的所有字段,在每个字段设置插件
for name, item in self.fields.items():
if name in self.bootstrap_exclude_fields:
continue
item.widget.attrs = {
"class": "form-control",
"placeholder": "请输入%s" % (item.label,)
}

数据库设计如下:

1
2
3
4
5
6
7
class Boss(models.Model):
"""老板表(处理混合数据)"""
name = models.CharField(max_length=32, verbose_name="姓名")
age = models.IntegerField(verbose_name="年龄")
# 如何将文件内容存到数据库?
# 将文件存到指定路径,然后将路径存到数据库
img = models.CharField(verbose_name="头像", max_length=128)

这边要注意的就是为什么图像时charfield类型,因为我们一般不直接存图像数据,一般是改为存图像的路径。

对于文件路径,推荐采用os来拼接,这样能适应多样的系统环境

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
def upload_form(request):
if request.method == "GET":
form = uploadForm()
return render(request, "upload_form.html", {"form": form, "title": "上传文件"})

# 相比之前,如果有文件上传,需要传入request.FILES
form = uploadForm(data=request.POST, files=request.FILES)
if form.is_valid():
# print(form.cleaned_data)
# print(form.cleaned_data)
db_file_path = os.path.join("static/img", form.cleaned_data.get("img").name)
file_path = os.path.join("management_sys", db_file_path)
img_data = form.cleaned_data.get("img")
with open(file_path, "wb") as f:
for chunk in img_data.chunks():
f.write(chunk)

# 将文件路径存到数据库
data = {
"name": form.cleaned_data.get("name"),
"age": form.cleaned_data.get("age"),
"img": db_file_path
}
# 这样的文件路径存储方式不好,用户是看不的
# 因为Django是只能展示static文件夹下的文件
models.Boss.objects.create(**data)

return HttpResponse("上传成功")
# 显示错位信息
return render(request, "upload_form.html", {"form": form, "title": "上传文件"})

对于路径存放问题,目前来说,Django只能读取static中的文件对象,当然它也可以读取其他的路径的文件,这在后续会说

1
2
file_path = os.path.join("management_sys", db_file_path)
img_data = form.cleaned_data.get("img")

启用media目录

在Django的开发过程中两个特殊文件夹:

  • static,存放静态文件的路径,包括:css,js,项目图片。
  • media,用户上传的数据的目录(需要启用)。

在url.py中进行配置

1
2
3
4
5
6
7
from django.urls import path, re_path
from django.views.static import serve
from django.conf import settings

urlpatterns = [
re_path(r"^media/(?P<path>.*)$", serve, {"document_root": settings.MEDIA_ROOT}, name="media"),
]

在settings.py中进行配置

1
2
3
import os
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
MEDIA_URL = "/media/"
image-20240225115701386

之后就可以在浏览器中访问这个文件

image-20240225115736540

这样就将上传与静态文件分开了

修改之前的保存文件路径

1
2
3
4
5
6
7
8
9
10
11
12
from django.conf import settings


# 这种方法得到的是绝对路径
# media_file_path = os.path.join(settings.MEDIA_ROOT, form.cleaned_data.get("img").name)
# 这种方法得到的是相对路径
media_file_path = os.path.join("media", form.cleaned_data.get("img").name)

img_data = form.cleaned_data.get("img")
with open(media_file_path, "wb") as f:
for chunk in img_data.chunks():
f.write(chunk)
image-20240225120713592

Modelform案例

前提是配置好media目录

1
2
3
4
5
6
class City(models.Model):
"""城市"""
name = models.CharField(max_length=32, verbose_name="名称")
people = models.IntegerField(verbose_name="人口")
# FileField本质上也是CharField,但是它会自动帮你上传文件到指定路径且保存到数据库
img = models.FileField(max_length=128, verbose_name="Logo", upload_to="city/")

在视图函数中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def upload_modelform(request):
title = "modelform上传文件"
if request.method == "GET":
form = UploadModelForm()
return render(request, "upload_form.html", {"form": form, "title": title})

form = UploadModelForm(data=request.POST, files=request.FILES)

if form.is_valid():
# 不需要再手动写文件上传的代码
# form.save()会自动帮你上传文件,并且将文件路径存到数据库
form.save()
return HttpResponse("上传成功")

return render(request, "upload_form.html", {"form": form, "title": title})
image-20240225130914781

上传文件小结

  • 自己动手去写

    1
    2
    form = request.FILES.get("ff")
    ……
  • form组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    form = uploadForm(data=request.POST, files=request.FILES)

    # 将文件路径存到数据库
    data = {
    "name": form.cleaned_data.get("name"),
    "age": form.cleaned_data.get("age"),
    "img": db_file_path
    }
    # 这样的文件路径存储方式不好,用户是看不的
    # 因为Django是只能展示static文件夹下的文件
    models.Boss.objects.create(**data)

    具体的文件操作还是得自己写(比如文件的保存)
  • modelform组件(表单验证+自动保存数据库+自动保存文件)

    1
    2
    3
    - media文件夹
    - model.py的类型定要
    img = models.FileField(max_length=128, verbose_name="Logo", upload_to="city/")