2084 lines
57 KiB
Markdown
2084 lines
57 KiB
Markdown
# django 开发指南
|
||
|
||
|
||
|
||
|
||
|
||
## 1. 前言
|
||
|
||
这开发指南是我在bibibl网站的看到并学习,前后用了近三个月时间,因为年龄大了,所以学的比较慢,跟着视频一步一步学习。B站用户:**别了青春12** 2022最新python3.9全栈开发600集沉浮式学习从小白入门到精通实战
|
||
|
||
整个学习是武沛奇老师的授课,讲的很详细,也很具体,完全贴近实战,非常有用。
|
||
|
||
|
||
|
||
|
||
|
||
## 2. django项目创建
|
||
|
||
|
||
|
||
### 2.1 安装django
|
||
|
||
```py
|
||
pip install django
|
||
```
|
||
|
||
|
||
|
||
### 2.2 查看django版本
|
||
|
||
```text
|
||
python -m django --version
|
||
```
|
||
|
||
|
||
|
||
### 2.3 创建django项目
|
||
|
||
1. *第一种方法通过命令行方式创建*
|
||
|
||
```text
|
||
>>> django-admin startproject mysite
|
||
```
|
||
|
||
创建后,会生成相关目录
|
||
|
||
![app/gitmind-cn/resources/docs/dv8ffwikp1y1/1707966981091_ThJ3jI0X64TbCfmbc2lpBFxqfxDyec.png](https://gitmindsz.aoscdn.com/app%2Fgitmind-cn%2Fresources%2Fdocs%2Fdv8ffwikp1y1%2F1707966981091_ThJ3jI0X64TbCfmbc2lpBFxqfxDyec.png?auth_key=1714192093-302126-539123-45cc5099184088f88d4b01e86318eed8&Expires=1714192093)
|
||
|
||
2. 第二种方法通过pycharm创建,必须是专业版
|
||
|
||
![image-20240426123131039](C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20240426123131039.png)
|
||
|
||
- 可参考视频:https://www.bilibili.com/video/BV1ua4y1E72e/?from=search&vd_source=cf28b53b9b35b80fe6fa1e4943ef8031
|
||
|
||
- ***<u>注意:</u>***如果**是pycharm创建的项目,在setting.py文件中要删除 DIR templates
|
||
|
||
|
||
|
||
### 2.4 创建应用
|
||
|
||
- 一个项目可以创建多个应用,多数以app表示
|
||
|
||
```text
|
||
>>>python manage.py startapp app01
|
||
```
|
||
|
||
- 创建后会生成多个目录和文件,具体解释如下:
|
||
|
||
![app/gitmind-cn/resources/docs/dv8ffwikp1y1/1707967984192_bz4aLmpjRvHGxDD91ZoVsFJYoSkWDE.png](https://gitmindsz.aoscdn.com/app%2Fgitmind-cn%2Fresources%2Fdocs%2Fdv8ffwikp1y1%2F1707967984192_bz4aLmpjRvHGxDD91ZoVsFJYoSkWDE.png?auth_key=1714192093-516781-264197-2f124005ca3c0158255479db7dc673db&Expires=1714192093)
|
||
|
||
|
||
|
||
|
||
|
||
- 注册app
|
||
- 修改setting.py的INSTALLED_APPS列表,
|
||
- 如果不注册app,则无法在models.py中生成数据库表结构
|
||
|
||
```python
|
||
# Application definition
|
||
INSTALLED_APPS = [
|
||
...
|
||
...
|
||
'app.apps.AppConfig', # 注册自己的app
|
||
]
|
||
```
|
||
|
||
|
||
|
||
- setting.py配置相关目录和数据
|
||
|
||
1. 在app目录下创建static目录和templates两个目录
|
||
- [ ] static目录下存放图片、插件、css、js等
|
||
- [ ] templates目录存放的是前端界面
|
||
|
||
2. 在setting.py文件中
|
||
|
||
```
|
||
STATIC_URL = 'static/'
|
||
```
|
||
|
||
|
||
|
||
|
||
|
||
### 2.5 快速搭建一个应用
|
||
|
||
- 编写URL和视图函数的对应关系
|
||
|
||
1. 在urls.py中定义路径和要执行的函数
|
||
|
||
```py
|
||
from app.views import depart, prettyNumber, user, admin, account,task # 引入项目需要使用的视图函数
|
||
|
||
urlpatterns = [
|
||
# 部门管理
|
||
path('depart/list/', depart.depart_list),
|
||
# <int:nid>是一个正则表达式,用来接收id,在views.py文件中加一个接收形参就可以接收到
|
||
# http://127.0.0.1:8001/depart/1/edit
|
||
path('depart/<int:nid>/edit/', depart.depart_edit),
|
||
]
|
||
```
|
||
|
||
|
||
|
||
2. 在views.py中编写视图函数
|
||
|
||
```py
|
||
def depart_list(request):
|
||
"""
|
||
部门列表
|
||
"""
|
||
data_list = Department.objects.all()
|
||
return render(request, 'depart_list.html', {'queryset': data_list})
|
||
```
|
||
|
||
3. 在前端页面中加入链接地址,与url视图中定义的一致
|
||
|
||
```html
|
||
<ul class="nav navbar-nav">
|
||
<li><a href="/admin/list/">管理员帐户</a></li>
|
||
<li><a href="/depart/list/">部门管理</a></li>
|
||
<li><a href="/user/list/">用户管理</a></li>
|
||
<li><a href="/PrettyNumber/list/">靓号管理</a></li>
|
||
<li><a href="/task/list/">任务管理</a></li>
|
||
</ul>
|
||
```
|
||
|
||
4. 启动应用
|
||
|
||
```py
|
||
python manage.py runserver 端口号(默认8000)
|
||
```
|
||
|
||
- 浏览器浏览
|
||
|
||
```html
|
||
http://127.0.0.1:8000/app
|
||
```
|
||
|
||
|
||
|
||
------
|
||
|
||
|
||
|
||
## 3. 数据库创建及数据表操作介绍
|
||
|
||
|
||
|
||
### 3.1 ORM 介绍
|
||
|
||
- Django引入ORM框架来操作数据库,ORM不直接操作数据库,他其实是一个翻译,把Django语句翻译为SQL语句
|
||
- 创建、修改、删除数据表不用写Sql语句,但唯独不能【创建数据库】
|
||
|
||
- 所有的对数据库结构和关联关系的操作都写在models.py中
|
||
- 操作数据表的数据,也不用写sql语句,通过ORM来翻译
|
||
|
||
|
||
|
||
### 3.2 数据库创建
|
||
|
||
- 配置数据库(MYSQL)
|
||
|
||
1. 第三方模块
|
||
|
||
```py
|
||
pip install mysqlclient
|
||
```
|
||
|
||
2. 创建数据库
|
||
|
||
在mysql或Navicat中创建数据库
|
||
|
||
***注意***(数据表的创建由models.py中的类和数据迁移命令完成)
|
||
|
||
3. 配置数据库连接,修改setting.py文件
|
||
|
||
```py
|
||
DATABASES = {
|
||
'default': {
|
||
'ENGINE': 'django.db.backends.mysql',
|
||
'NAME': 'employee', # 员工表
|
||
'USER': 'root',
|
||
'PASSWORD': '',
|
||
'HOST': '127.0.0.1',
|
||
'PORT': 3306,
|
||
}
|
||
}
|
||
```
|
||
|
||
|
||
|
||
|
||
|
||
### 3.3 数据表操作
|
||
|
||
#### 3.3.1 创建表
|
||
|
||
- 在models.py中编写数据表的对应类
|
||
|
||
```
|
||
from django.db import models
|
||
|
||
class UserInfo(models.Model):
|
||
"""
|
||
员工表
|
||
"""
|
||
name = models.CharField(verbose_name='姓名', max_length=16)
|
||
password = models.CharField(verbose_name='密码', max_length=64)
|
||
age = models.IntegerField(verbose_name='年龄')
|
||
|
||
# max_digits最大几位数,decimal_places小数点后几位,default默认值为0
|
||
account = models.DecimalField(verbose_name='帐户余额', max_digits=10, decimal_places=2, default=0)
|
||
|
||
create_time = models.DateField(verbose_name='入职时间')
|
||
|
||
```
|
||
|
||
#### 3.3.2 数据库迁移
|
||
|
||
- 每次创建一个类后都执行一次
|
||
|
||
```
|
||
>>>python manage.py makemigrations
|
||
>>>python manage.py migrate
|
||
```
|
||
|
||
- 在执行数据迁移时注意
|
||
|
||
- 删除版本校验,不然Django4.0以上版本会报错
|
||
|
||
![app/gitmind-cn/resources/docs/hdezjjcbxirs/1708417742041_3IU96xIIoFZy6MBKDTskqE6fe0lqBe.png](https://gitmindsz.aoscdn.com/app%2Fgitmind-cn%2Fresources%2Fdocs%2Fhdezjjcbxirs%2F1708417742041_3IU96xIIoFZy6MBKDTskqE6fe0lqBe.png?auth_key=1714192093-645123-343618-fcd2887c5f0f9e571216e8dbe2034e96&Expires=1714192093)
|
||
|
||
|
||
|
||
- 在这里**注意**,用数据迁移方法生成的表,在实际的数据库存储中都是以“**应用名_数据表**”名存在
|
||
|
||
- 例如:app_user ,app_admin等等
|
||
|
||
- 同时django会自动生成一些自己使用的数据表,表名都是以 “**django_**” 开头
|
||
- 如果使用了**中间件**,也会以自定义中间件文件名为开头生成一些数据表,例如auth_group等
|
||
|
||
|
||
|
||
|
||
|
||
#### 3.3.3 修改表结构
|
||
|
||
- 如果表中已有数据,在执行makemigrations命令时,会提示是否需要有一个默认值来填充已有数据
|
||
|
||
![app/gitmind-cn/resources/docs/hdezjjcbxirs/1708419345584_96oiGslsug8SomKaE7CEgcsHjl5WYj.png](https://gitmindsz.aoscdn.com/app%2Fgitmind-cn%2Fresources%2Fdocs%2Fhdezjjcbxirs%2F1708419345584_96oiGslsug8SomKaE7CEgcsHjl5WYj.png?auth_key=1714192093-255998-451723-d17d2207e69a9587ce7664bebc648977&Expires=1714192093)
|
||
|
||
1-设置默认值 2-退出
|
||
|
||
<img src="https://gitmindsz.aoscdn.com/app%2Fgitmind-cn%2Fresources%2Fdocs%2Fhdezjjcbxirs%2F1708419411296_ytelIXnXJzwa2Yhu2lVY7fow4VGBRw.png?auth_key=1714192093-942604-285870-67b03efad3cc6ff62977cfa3cdd82865&Expires=1714192093" alt="app/gitmind-cn/resources/docs/hdezjjcbxirs/1708419411296_ytelIXnXJzwa2Yhu2lVY7fow4VGBRw.png" style="zoom: 150%;" />
|
||
|
||
也可以在代码中设置默认值或可以为空
|
||
|
||
#### 3.3.4 删除表
|
||
|
||
- 删除表和表字段 ,只需要把相应的类或字段名在代码中注释掉,再执行迁移命令即可。
|
||
|
||
|
||
|
||
#### 3.3.5 数据关联和主键约束
|
||
|
||
- 外键约束
|
||
|
||
```python
|
||
# 有约束
|
||
# 创建一个与Department表id有约束关系的列
|
||
# 在Django底层会将depart变量自动生成列名为depart_id的字段名
|
||
# 1.有约束并且级联删除
|
||
# - to :关联哪个表
|
||
# - to_field : 关联哪个字段
|
||
# - on_delete=models.CASCADE : 级联删除,部门删除后,该部门的员工也删除
|
||
depart = models.ForeignKey(verbose_name='部门ID', to='Department', to_field='id', on_delete=models.CASCADE)
|
||
|
||
# 2.有约束,可以置空,删除部门后,员工不删除,并且将列置空
|
||
# - null=True,blank=True 该列可以为空
|
||
# - on_delete=models.SET_NULL 该列可以为空
|
||
depart = models.ForeignKey(verbose_name='部门ID', to='Department', to_field='id', null=True,blank=True,on_delete=models.SET_NULL)
|
||
```
|
||
|
||
|
||
|
||
- django自己的约束
|
||
|
||
```py
|
||
# 在django中做约束
|
||
# 定义一个元组
|
||
# 参数
|
||
# - choices参数来判断性别显示
|
||
gender_choices = (
|
||
(1, '男'),
|
||
(2, '女')
|
||
)
|
||
gender = models.SmallIntegerField(verbose_name='性别', choices=gender_choices)
|
||
```
|
||
|
||
|
||
|
||
#### 3.3.6 定义前端显示内容
|
||
|
||
```py
|
||
# 定制在前端要显示的内容,用于在前端关联查询的结果,不然会返回一个对象
|
||
# 这个显示会在ModelForm中使用到
|
||
def __str__(self):
|
||
return self.title
|
||
```
|
||
|
||
|
||
|
||
|
||
|
||
------
|
||
|
||
|
||
|
||
### 3.4 数据(记录)操作
|
||
|
||
- 匹配记录是一个 QuerySet 集合。它可以有 0 个,1 个或者多个,类似于列表结构,每行数据就是一个对象,他是有返回值的
|
||
|
||
```py
|
||
<QuerySet [<UserInfo: UserInfo object (1)>, <UserInfo: UserInfo object (2)>, <UserInfo: UserInfo object (4)>]>
|
||
```
|
||
|
||
- 数据的操作都在views.py视图文件中进行
|
||
|
||
|
||
|
||
#### 3.4.1 查询
|
||
|
||
- 全部数据
|
||
|
||
- 返回的是一个QuerySet 集合
|
||
|
||
```py
|
||
# 对象,不可序列化
|
||
# queryset = {obj,obj,obj}
|
||
data_list = UserInfo.objects.all()
|
||
|
||
# 取值,可使用迭代对象.字段
|
||
for item in data_list:
|
||
print(data_list.id)
|
||
```
|
||
|
||
- 返回的是一个字典,可序列化
|
||
|
||
```python
|
||
# 字典格式,可序列化,需要哪个字段就填哪个
|
||
# queryset = [{'id':1,'title':'abc'},{'id':2,'title':'def'}]
|
||
data_dict = UserInfo.objects.all().values('id','title')
|
||
|
||
|
||
# queryset = [(1,'abc'),(2,'def')]
|
||
data_dict = UserInfo.objects.all().values_list('id','title')
|
||
```
|
||
|
||
|
||
|
||
```py
|
||
# 只取前10条记录
|
||
data_list = UserInfo.objects.all()[0:10]
|
||
```
|
||
|
||
```python
|
||
# 倒序
|
||
data_list = userInfo.objects.all().order_by('-id')
|
||
```
|
||
|
||
|
||
|
||
- 条件查询
|
||
|
||
- 返回的是一个QuerySet 集合
|
||
|
||
```pyt
|
||
data_list = UserInfo.objects.filter(id=1)
|
||
data_list = UserInfo.objects.filter(name='张三',id=1)
|
||
|
||
# 也可以传入一个字典
|
||
data_list = UserInfo.objects.filter(**data_dict)
|
||
```
|
||
|
||
- **filter的使用**
|
||
|
||
- 条件判断是**数字类型**的
|
||
|
||
```py
|
||
# 等于
|
||
data_list = UserInfo.objects.filter(id=1)
|
||
|
||
# 大于
|
||
data_list = UserInfo.objects.filter(id__gt=1)
|
||
|
||
# 大于等于
|
||
data_list = UserInfo.objects.filter(id__gte=1)
|
||
|
||
# 小于等于
|
||
data_list = UserInfo.objects.filter(id__lte=1)
|
||
|
||
# 大于
|
||
data_list = UserInfo.objects.filter(id__lt=1)
|
||
```
|
||
|
||
- 条件判断是**字符串类型**的
|
||
|
||
```py
|
||
# 以字符串开头,以张姓开头的
|
||
data_list = UserInfo.objects.filter(name__startswith='张')
|
||
|
||
# 以字符串结束,姓名以军结束的
|
||
data_list = UserInfo.objects.filter(name__endswith='军')
|
||
|
||
# 包含字符串的,姓名中包含有建字的
|
||
data_list = UserInfo.objects.filter(name__contains='建')
|
||
|
||
```
|
||
|
||
- 查询第1条记录
|
||
|
||
```py
|
||
# data_list是一个对象
|
||
data_list = UserInfo.objects.filter(id=1).first()
|
||
|
||
# 取值
|
||
data_list.id
|
||
data_list.name
|
||
```
|
||
|
||
匹配的是一个对象,不能再用循环,直接使用data_list.id就可取得数据
|
||
|
||
```python
|
||
|
||
# 字典格式,可序列化,需要哪个字段就填哪个
|
||
# queryset = [{'id':1,'title':'abc'},{'id':2,'title':'def'}]
|
||
data_dict = UserInfo.objects.filter(id=1).values('id','title').first()
|
||
# 取值
|
||
data_list['title']
|
||
|
||
|
||
# queryset = [(1,'abc'),(2,'def')]
|
||
data_dict = UserInfo.objects.filter(id=1).values_list('id','title').first()
|
||
```
|
||
|
||
|
||
|
||
#### 3.4.2 新增
|
||
|
||
```py
|
||
UserInfo.objects.create(name='tom',password='666')
|
||
```
|
||
|
||
|
||
|
||
#### 3.4.3 删除
|
||
|
||
- 条件删除
|
||
|
||
```py
|
||
UserInfo.objects.filter(id=3).delete()
|
||
```
|
||
|
||
|
||
|
||
- 全部删除
|
||
|
||
```py
|
||
UserInfo.objects.all().delete()
|
||
```
|
||
|
||
|
||
|
||
#### 3.4.4 修改
|
||
|
||
- 修改全部数据
|
||
|
||
```py
|
||
# 更新所有数据
|
||
UserInfo.objects.all().update(password='999')
|
||
```
|
||
|
||
|
||
|
||
- 修改条件数据
|
||
|
||
```py
|
||
# 更新符合条件的数据
|
||
UserInfo.objects.filter(id=1).update(password='9999')
|
||
```
|
||
|
||
|
||
|
||
|
||
|
||
## 4. 开始写代码
|
||
|
||
- 编写URL,是在路由视图urls.py文件中编写
|
||
- views.py,视图函数,用于编写业务逻辑,此功能可以进入拆分
|
||
- templates 模板目录,用于编写前端界面,HTML模板(含有模板语法、继承模板、静态路径等)
|
||
|
||
### 4.1 views.py 视图函数
|
||
|
||
#### 4.1.1 原始方法
|
||
|
||
- 通过POST对数据的业务逻辑
|
||
|
||
缺点:
|
||
1.要重复前端form表单
|
||
2.用户提交数据未校验
|
||
3.前端输入错误没有提示
|
||
4.关联的数据要手动获取,前端要循环渲染
|
||
|
||
```python
|
||
def user_add(request):
|
||
"""
|
||
添加用户 (原始实现方式)
|
||
"""
|
||
if request.method == 'GET':
|
||
content = {
|
||
'gender': UserInfo.gender_choices,
|
||
'depart': Department.objects.all()
|
||
}
|
||
|
||
return render(request, 'user_add.html', content)
|
||
|
||
name = 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_id = request.POST.get('depart')
|
||
gender = request.POST.get('gender')
|
||
|
||
UserInfo.objects.create(name=name, password=password, age=age,
|
||
account=account, create_time=create_time,
|
||
depart_id=depart_id, gender=gender)
|
||
return redirect('/user/list')
|
||
```
|
||
|
||
```python
|
||
def depart_add(request):
|
||
"""
|
||
新增部门
|
||
"""
|
||
if request.method == 'GET':
|
||
return render(request, 'depart_add.html')
|
||
title = request.POST.get('title')
|
||
Department.objects.create(title=title)
|
||
return redirect('/depart/list')
|
||
```
|
||
|
||
|
||
|
||
#### 4.1.2 Form类方法
|
||
|
||
![app/gitmind-cn/resources/docs/q83dfak2hmvm/1708588566645_hywcfflEtsUCR3IZZWwcXuRKrUeP8H.png](https://gitmindsz.aoscdn.com/app%2Fgitmind-cn%2Fresources%2Fdocs%2Fq83dfak2hmvm%2F1708588566645_hywcfflEtsUCR3IZZWwcXuRKrUeP8H.png?auth_key=1714192093-030230-490725-9d68d7b910f6c0797a4f21acc57f8e55&Expires=1714192093)
|
||
|
||
|
||
|
||
#### 4.1.3 ModelForm 类 方法(推荐)
|
||
|
||
表单域模型
|
||
作用:生成HTML标签用于定义渲染到前端的数据,设置校验规则等
|
||
|
||
##### 基本用法
|
||
|
||
- 首先从django.forms导入ModelForm;
|
||
|
||
```python
|
||
from django import forms
|
||
```
|
||
|
||
- 编写一个自己的类,继承ModelForm;
|
||
|
||
```python
|
||
class UserModelForm(forms.ModelForm):
|
||
```
|
||
|
||
- 设置字段属性
|
||
|
||
```python
|
||
name = forms.CharField(min_length=2, label='姓名')
|
||
```
|
||
|
||
# required -- 指定字段是否为必填字段的布尔值。 默认为 True。
|
||
# widget -- 一个 Widget 类,或者一个 Widget 类的实例,它应该 显示此字段时用于此字段。每个字段都有一个 默认的 Widget,如果你不指定这个,它将使用它。在 大多数情况下,默认的 widget 是 TextInput。
|
||
# label -- 此字段的详细名称,用于显示此字段表单中的字段。默认情况下,Django 将使用“pretty”表单字段名称的版本,如果该字段是 表单。
|
||
# initial -- 在此字段的初始显示中使用的值。此值如果未提供数据,则 # *not*用作回退。
|
||
# help_text -- 用作此字段的“帮助文本”的可选字符串。
|
||
# error_messages -- 一个可选的字典来覆盖默认的字段将引发的消息。
|
||
# show_hidden_initial -- 布尔值,指定是否需要渲染 隐藏的小部件,在小部件之后带有初始值。
|
||
# validators -- 要使用的其他验证器列表
|
||
# localize -- 布尔值,指定是否应本地化字段。
|
||
# disabled -- 指定字段是否被禁用的布尔值,即是它的小部件以表单形式显示,但不可编辑。
|
||
# label_suffix -- 要添加到标签中的后缀。重写表单的label_suffix。
|
||
|
||
- 在新类里,设置元类Meta;
|
||
|
||
```python
|
||
class Meta:
|
||
```
|
||
|
||
- 在Meta中,设置model属性为你要关联的ORM模型,这里是UserInfo;
|
||
|
||
```python
|
||
model = UserInfo # 指定需要显示的表名(在models.py中定义)
|
||
```
|
||
|
||
- 在Meta中,设置fields属性为你要在表单中使用的字段列表;列表里的值,应该是ORM模型model中的字段名。
|
||
|
||
```python
|
||
# 指定要操作的字段名,是一个列表
|
||
fields = ['name', 'age', 'gender', 'depart', 'create_time', 'account']
|
||
```
|
||
|
||
|
||
|
||
##### fields属性讲解
|
||
|
||
- **fields**
|
||
|
||
是字段名称的可选列表。如果提供,请仅包括返回字段中的命名字段。如果省略或“__all__”,则使用领域。
|
||
|
||
用法:fields = ['mobile', 'price', 'level', 'status']
|
||
|
||
```py
|
||
fields = '__all__' # 渲染所有字段
|
||
```
|
||
|
||
- **exclude**
|
||
|
||
是字段名称的可选列表。如果提供,请排除返回字段中的命名字段,即使它们列在''fields'' 参数中。
|
||
|
||
```python
|
||
exclude = ['level'] # 排除某个字段
|
||
```
|
||
|
||
|
||
|
||
##### 解决前端显示问题
|
||
|
||
- 在数据模型models.py中定义__str__方法
|
||
|
||
```python
|
||
class Department(models.Model):
|
||
title = models.CharField(verbose_name='部门名称', max_length=32)
|
||
# 定制在前端要显示的内容,用于在前端关联查询的结果,不然会返回一个对象
|
||
# 这个显示会在ModelForm中使用到
|
||
def __str__(self):
|
||
return self.title
|
||
```
|
||
|
||
|
||
|
||
##### 插件
|
||
|
||
- 也就是字段的属性,比如样式、前端显示类型等
|
||
|
||
```python
|
||
widgets = {
|
||
'name': forms.TextInput(attrs={'class': 'form-control'}),
|
||
'password': forms.PasswordInput(attrs={'class': 'form-control'}),
|
||
'create_time': forms.DateInput(attrs={'class': 'form-control'}),
|
||
}
|
||
```
|
||
|
||
|
||
|
||
##### 数据校验
|
||
|
||
**方法1:钩子方法**
|
||
|
||
- 作用:
|
||
|
||
对字段进行一些数据校验或操作称为钩子方法,更灵活,比如手机号是否已经存在...
|
||
|
||
名称必须是**clean_字段名**,通过form.is_valid():方法调用
|
||
|
||
- 在setting.py文件中修改语言属性就可以显示中文提示信息:LANGUAGE_CODE = 'zh-hans'
|
||
|
||
- 在ModelForm类中定义
|
||
|
||
```python
|
||
|
||
def clean_mobile(self):
|
||
txt_value = self.cleaned_data['mobile'] # 获取到用户输入的字段值
|
||
|
||
# instance是编辑时传入的值,pk是id主键
|
||
nid = self.instance.id
|
||
# print(self.instance.mobile)
|
||
|
||
# 修改时,要排除当前号码以外的其他数据
|
||
# 也就是 where mobile=18995009009 and id!=2
|
||
num_exist = PrettyNumber.objects.filter(mobile=txt_value).exclude(id=nid)
|
||
if num_exist:
|
||
raise ValidationError("号码已经存在")
|
||
return txt_value
|
||
```
|
||
|
||
**方法2:重新定义字段属性**
|
||
|
||
- 在表单域创建时,重新定义字段属性
|
||
|
||
```python
|
||
class PrettyNumberForm(BootstrapModelForm):
|
||
# 重新定义mobile字段的属性和校验规则 方式1
|
||
mobile = forms.CharField(
|
||
min_length=11, # 最小长度
|
||
label='号码',
|
||
# 通过正则进行数据校验
|
||
validators=[RegexValidator(r'^1[3-9]\d{9}$', '号码错误,长度不能超过11位')],
|
||
)
|
||
```
|
||
|
||
##### 错误信息引用 & 展示
|
||
|
||
在html模板文件中
|
||
|
||
```html
|
||
<span style="color:red;"> {{ item.errors.0 }}</span>
|
||
```
|
||
|
||
在钩子函数中
|
||
|
||
```python
|
||
raise ValidationError("号码已经存在")
|
||
```
|
||
|
||
也可在表单域模型创建字段时规定好内容
|
||
|
||
```python
|
||
mobile = forms.CharField(
|
||
min_length=11, # 最小长度
|
||
label='号码',
|
||
# 通过正则进行数据校验
|
||
validators=[RegexValidator(r'^1[3-9]\d{9}$', '号码错误,长度不能超过11位')],
|
||
)
|
||
```
|
||
|
||
##### 用户提交以外的数据添加
|
||
|
||
```python
|
||
form.instance.oid = datetime.now().strftime('%Y%m%d%H%M%S') + str(random.randint(1000, 9999))
|
||
|
||
# 在models.py中定义的是user,是有主键约束的,所以django在生成数据表结构时,会自动加一个_id
|
||
form.instance.user_id = request.session['info']['id']
|
||
|
||
form.save() # 向数据表添加数据
|
||
```
|
||
|
||
|
||
|
||
##### 完整示例代码
|
||
|
||
```python
|
||
|
||
class UserModelForm(BootstrapModelForm):
|
||
"""
|
||
用户类表单模型
|
||
作用:用于定义渲染到前端的数据,设置校验规则等
|
||
继承BootstrapModelForm类实现表单控件属性样式
|
||
"""
|
||
|
||
# 重新定义name字段的属性和校验规则
|
||
name = forms.CharField(min_length=2, label='姓名')
|
||
|
||
class Meta:
|
||
model = UserInfo # 指定需要显示的表名(在models.py中定义)
|
||
|
||
# 指定要操作的字段名,是一个列表
|
||
fields = ['name', 'age', 'gender', 'depart', 'create_time', 'account']
|
||
# fields = '__all__' # 渲染所有字段
|
||
# exclude = ['level'] # 排除某个字段
|
||
|
||
# 给表单控件逐个添加属性
|
||
# widgets = {
|
||
# 'name': forms.TextInput(attrs={'class': 'form-control'}),
|
||
# 'password': forms.PasswordInput(attrs={'class': 'form-control'}),
|
||
# 'create_time': forms.DateInput(attrs={'class': 'form-control'}),
|
||
# }
|
||
|
||
def clean_mobile(self):
|
||
txt_value = self.cleaned_data['mobile'] # 获取到用户输入的字段值
|
||
|
||
# instance是编辑时传入的值,pk是id主键
|
||
nid = self.instance.id
|
||
# print(self.instance.mobile)
|
||
|
||
# 修改时,要排除当前号码以外的其他数据
|
||
# 也就是 where mobile=18995009009 and id!=2
|
||
num_exist = PrettyNumber.objects.filter(mobile=txt_value).exclude(id=nid)
|
||
if num_exist:
|
||
raise ValidationError("号码已经存在")
|
||
return txt_value
|
||
|
||
def user_add_modelform(request):
|
||
"""
|
||
添加用户(通过Django ModelForm组件实现 最简便)
|
||
"""
|
||
if request.method == 'GET':
|
||
form = UserModelForm() # 实例化类
|
||
return render(request, 'user_add_modelform.html', {'form': form})
|
||
|
||
# POST请求
|
||
form = UserModelForm(data=request.POST) # 拿到所有提交过来的数据
|
||
if form.is_valid(): # 数据校验
|
||
|
||
# 如果你想对数据表中的字段另外再传递值来保存,就用
|
||
# form.instance.字段名 = '值'
|
||
form.instance.password = '0000' # 默认密码是0000
|
||
|
||
form.save() # 向 model = UserInfo 定义的表保存数据
|
||
return redirect('/user/list/')
|
||
else:
|
||
# form中带了POST过来的请求
|
||
return render(request, 'user_add_modelform.html', {'form': form})
|
||
```
|
||
|
||
|
||
|
||
#### 4.1.4 分页实现
|
||
|
||
- 通过视频我已经实现了一个分页类Pagination,在utils目录下。
|
||
|
||
- 使用方法步骤:
|
||
|
||
- 在视图函数的列表函数中添加代码
|
||
|
||
- 将查询到的结果集放到类实例化中
|
||
|
||
```python
|
||
queryset = UserInfo.objects.all()
|
||
page_object = Pagination(request, queryset, PAGE_SIZE=10) # 实例化分页导航条类,每页显示10条记录
|
||
page_queryset = page_object.page_queryset # 获得分完页的数据结果
|
||
```
|
||
|
||
- 生成导航条的html代码,此工作已在Pagination类中实现
|
||
|
||
```python
|
||
# 生成分页导航条
|
||
page_nav = page_object.html()
|
||
```
|
||
|
||
- 将导航条包裹的数据连同导航条html一同生成字典
|
||
|
||
```python
|
||
context = {
|
||
'queryset': page_queryset, # 分完页的数据结果
|
||
'page_nav': page_nav # 生成的页码导航条
|
||
}
|
||
```
|
||
|
||
- 将数据返回给前端
|
||
|
||
```python
|
||
return render(request, 'user_list.html', context)
|
||
```
|
||
|
||
- 在前端html代码中展示导航条
|
||
|
||
```html
|
||
<ul class="pagination">
|
||
{{ page_nav }}
|
||
</ul>
|
||
```
|
||
|
||
|
||
|
||
**完整代码:**
|
||
|
||
```python
|
||
|
||
from app.utils.pagination import Pagination # 导入分页导航条
|
||
|
||
def user_list(request):
|
||
"""
|
||
用户列表
|
||
"""
|
||
queryset = UserInfo.objects.all()
|
||
page_object = Pagination(request, queryset, PAGE_SIZE=10) # 实例化分页导航条类,每页显示10条记录
|
||
page_queryset = page_object.page_queryset # 获得分完页的数据结果
|
||
|
||
# 生成分页导航条
|
||
page_nav = page_object.html()
|
||
context = {
|
||
'queryset': page_queryset, # 分完页的数据结果
|
||
'page_nav': page_nav # 生成的页码导航条
|
||
}
|
||
return render(request, 'user_list.html', context)
|
||
```
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
### 4.2 urls.py
|
||
|
||
路由视图,用于规则链接地址
|
||
|
||
```python
|
||
from app.views import depart, prettyNumber, user, admin, account,task # 引入项目需要使用的视图函数
|
||
urlpatterns = [
|
||
# path('admin/', admin.site.urls),
|
||
# 部门管理
|
||
path('depart/list/', depart.depart_list),
|
||
path('depart/add/', depart.depart_add),
|
||
path('depart/delete/', depart.depart_delete),
|
||
# <int:nid>是一个正则表达式,用来接收id,在views.py文件中加一个接收形参就可以接收到
|
||
# http://127.0.0.1:8001/depart/1/edit
|
||
path('depart/<int:nid>/edit/', depart.depart_edit),
|
||
```
|
||
|
||
|
||
|
||
### 4.3 models.py
|
||
|
||
创建应用的表结构,全部继承django中的models.Model来创建
|
||
|
||
```python
|
||
from django.db import models
|
||
class UserInfo(models.Model):
|
||
"""
|
||
员工表
|
||
"""
|
||
name = models.CharField(verbose_name='姓名', max_length=16)
|
||
password = models.CharField(verbose_name='密码', max_length=64)
|
||
age = models.IntegerField(verbose_name='年龄')
|
||
|
||
# max_digits最大几位数,decimal_places小数点后几位,default默认值为0
|
||
account = models.DecimalField(verbose_name='帐户余额', max_digits=10, decimal_places=2, default=0)
|
||
|
||
# create_time = models.DateTimeField(verbose_name='入职时间')
|
||
create_time = models.DateField(verbose_name='入职时间')
|
||
|
||
# 有约束
|
||
# 创建一个与Department表id有约束关系的列
|
||
# 在Django底层会将depart变量自动生成列名为depart_id的字段名
|
||
# 1.有约束并且级联删除
|
||
# - to :关联哪个表
|
||
# - to_field : 关联哪个字段
|
||
# - on_delete=models.CASCADE : 级联删除,部门删除后,该部门的员工也删除
|
||
depart = models.ForeignKey(verbose_name='部门ID', to='Department', to_field='id', on_delete=models.CASCADE)
|
||
|
||
# 2.有约束,可以置空,删除部门后,员工不删除,并且将列置空
|
||
# - null=True,blank=True 该列可以为空
|
||
# - on_delete=models.SET_NULL 该列可以为空
|
||
# depart = models.ForeignKey(verbose_name='部门ID', to='Department', to_field='id', null=True,blank=True,on_delete=models.SET_NULL)
|
||
|
||
# 在django中做约束
|
||
# 定义一个元组
|
||
# 参数
|
||
# - choices参数来判断性别显示
|
||
gender_choices = (
|
||
(1, '男'),
|
||
(2, '女')
|
||
)
|
||
gender = models.SmallIntegerField(verbose_name='性别', choices=gender_choices)
|
||
|
||
|
||
```
|
||
|
||
在这里choices属性功能很强大,可以将数据表中存储的代码,转换为对象显示,也可显示为字典Key值
|
||
|
||
```python
|
||
# 返回管理员的姓名,此功能在task.py中使用了,不然下拉框显示的就是对象
|
||
def __str__(self):
|
||
return self.username
|
||
```
|
||
|
||
|
||
|
||
### 4.4 模板和静态文件(HTML文件)
|
||
|
||
- 所有的html文件都放在app\templates目录下
|
||
|
||
- 基本实现原理:客户端发url请求,视图函数进行渲染和替换模板中的语法
|
||
|
||
![app/gitmind-cn/resources/docs/byqd7b04orhy/1707999926012_rjnXJxyDDRKZTTJVl4eRFlNcaHLWvc.png](https://gitmindsz.aoscdn.com/app%2Fgitmind-cn%2Fresources%2Fdocs%2Fbyqd7b04orhy%2F1707999926012_rjnXJxyDDRKZTTJVl4eRFlNcaHLWvc.png?auth_key=1714192093-930167-857761-b454fbae97a3cd9fd6b46ff2c340e843&Expires=1714192093)
|
||
|
||
#### 4.4.1 模板调用
|
||
|
||
在视图函数(views.py)文件中通过render(request, "user_list.html")调用模板文件
|
||
|
||
|
||
|
||
#### 4.4.2 模板基本语法
|
||
|
||
- **占位符**
|
||
|
||
模板语法都是以{{ id }}两对花括号进行占位符,在视图文件中调用模板时,可携带参数,必须是一个字典
|
||
|
||
```python
|
||
def tpl(request):
|
||
message = 'django模板语法学习'
|
||
return render(request, "tpl.html", {'msg': message})
|
||
```
|
||
|
||
在html文件中通过占位符显示
|
||
|
||
```html
|
||
<td>{{ item.msg }}</td>
|
||
```
|
||
|
||
- **条件判断**
|
||
|
||
```html
|
||
{% if 1==1 %}
|
||
...
|
||
{% elif 2==2 %}
|
||
...
|
||
{% else %}
|
||
...
|
||
{% endif %}
|
||
```
|
||
|
||
- **循环**
|
||
|
||
{% for 变量 in 可迭代对象%}
|
||
|
||
```html
|
||
<tbody>
|
||
{% for item in queryset%}
|
||
<tr>
|
||
<th>{{ item.id }}</th>
|
||
<td>{{ item.name }}</td>
|
||
<td>{{ item.get_gender_display }}</td>
|
||
<th>{{ item.age }}</th>
|
||
<td>{{ item.account }}</td>
|
||
<td>{{ item.create_time|date:"Y年m月d日" }}</td>
|
||
<td>{{ item.depart.title }}</td>
|
||
<td>
|
||
<a class="btn btn-primary btn-xs" href="/user/{{ item.id }}/edit_ModelForm/">修改</a>
|
||
<a class="btn btn-danger btn-xs" href="/user/{{ item.id }}/delete_ModelForm/">删除</a>
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
```
|
||
|
||
|
||
|
||
- 列表、字典、元组的循环(综合应用)
|
||
|
||
```html
|
||
<!--循环数组-->
|
||
<h1>数组的取值方法</h1>
|
||
<h2>
|
||
<ul>
|
||
{% for item in roles %}
|
||
<li>{{ item }}</li>
|
||
{% endfor %}
|
||
</ul>
|
||
</h2>
|
||
<hr>
|
||
<h1>字典的取值方法-姓名:{{ userMsg.name }}</h1>
|
||
<h2>
|
||
<!--userMsg.keys键名,userMsg.values值,userMsg.items所有键值-->
|
||
<ul>
|
||
{% for key,val in userMsg.items %}
|
||
<li>{{ key }}:{{ val }}</li>
|
||
{% endfor %}
|
||
</ul>
|
||
</h2>
|
||
<hr>
|
||
<h1>元组和字典嵌套的取值方法,带条件判断-职务:{{ offMsg.0.offName }}</h1>
|
||
<h2>
|
||
<!--userMsg.keys键名,userMsg.values值,userMsg.items所有键值-->
|
||
<ul>
|
||
{% for item in offMsg %}
|
||
{% if item.offName == '经理' %}
|
||
<li>{{ item.offName }}-{{ item.work }} </li>
|
||
{% else %}
|
||
<li style="color:red;">{{ item.offName }}-{{ item.work }} </li>
|
||
{% endif %}
|
||
{% endfor %}
|
||
</ul>
|
||
</h2>
|
||
<hr>
|
||
```
|
||
|
||
|
||
|
||
##### **使用字段值**
|
||
|
||
- 普通字符串、数字
|
||
|
||
```html
|
||
迭代对象.字段名
|
||
{{ obj.name }}
|
||
```
|
||
|
||
- models.py中定义的字典值
|
||
|
||
```html
|
||
迭代对象.get_字段名_display
|
||
|
||
{{ obj.get_gender_display }}
|
||
```
|
||
|
||
- 有约束关联其他表的字段,如用户表中的title字段存储的是部门中的id
|
||
|
||
迭代对象.对象.对象字段,对象是指在models.py中定义约束关系的字段名
|
||
|
||
```html
|
||
{{ obj.depart.tiele }}
|
||
|
||
(其中depart是models.py中定义的一个数据表字段名,他是有约束关联关系的,在这里引用后就是一个对象,代表的就是depart数据表,所以他可以点出字段)
|
||
```
|
||
|
||
- 日期字段
|
||
|
||
```html
|
||
{{ item.create_time|date:"Y年m月d日" }}
|
||
```
|
||
|
||
|
||
|
||
#### 4.4.3 模板继承
|
||
|
||
模板继承可以减少代码编写量,主要用在界面的统一风格中,比如页头和页尾都是一样的效果,那就专门做一个母版,将页头和页尾的Html代码写好,在中间部分用占位符填充。子页面在引用时,导入母版和占位符,然后写自己的代码就好。
|
||
|
||
- 母版
|
||
|
||
在母版layout.html的位置插入以下占位符代码,可以自定义多个占位符
|
||
|
||
```html
|
||
{% block content %}{% endblock %}
|
||
{% block css %}{% endblock %}
|
||
{% block js %}{% endblock %}
|
||
```
|
||
|
||
- 子页面
|
||
|
||
```html
|
||
<!--导入母版-->
|
||
{% extends 'layout.html' %}
|
||
|
||
{% block content %}
|
||
|
||
<div class="container">
|
||
自己的html代码
|
||
</div>
|
||
|
||
{% endblock %}
|
||
```
|
||
|
||
|
||
|
||
#### 4.4.4 静态文件
|
||
|
||
- 在app目录下创建static目录和templates两个目录
|
||
- static目录下存放图片、插件、css、js等
|
||
- templates目录存放的是前端界面
|
||
|
||
- 在setting.py文件中修改静态文件地址,在后面的模板文件中就可以使用了
|
||
|
||
```
|
||
STATIC_URL = 'static/'
|
||
```
|
||
|
||
|
||
|
||
#### 4.4.5 静态文件引入
|
||
|
||
```html
|
||
<!--引入静态文件-->
|
||
{% load static %}
|
||
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>Title</title>
|
||
</head>
|
||
<body>
|
||
```
|
||
|
||
#### 4.4.6 静态文件的使用
|
||
|
||
```html
|
||
<link rel="stylesheet" href="{% static 'plugins/bootstrap-3.3.7-dist/css/bootstrap.min.css' %}">
|
||
|
||
<img src="{% static 'img/tx1.jpg' %}" alt="">
|
||
```
|
||
|
||
|
||
|
||
|
||
|
||
### 4.5 BootStrap样式
|
||
|
||
- 因为模型中的字段在前端都是循环读取出来的,所以无法添加Bootstrap样式
|
||
|
||
- 只要继承了BootstrapForm或BootstrapModelForm类,生成的字段就会以Bootstrap样式在前端中循环展示出来
|
||
|
||
- 使用方法
|
||
|
||
```python
|
||
|
||
from app.utils.bootstrap import BootstrapModelForm,BootstrapForm # 导入表单控件的bootstrap属性样式
|
||
|
||
# 使用Form表单
|
||
class Adim(BootstrapForm):
|
||
|
||
exclude_filed = ['img'] # 过滤字段不让引用Bootstrap样式
|
||
|
||
name = forms.CharField(label='姓名')
|
||
age = forms.IntegerField(label='年龄')
|
||
img = forms.FileField(label='头像')
|
||
|
||
# 使用ModelForm表单
|
||
class UserModelForm(BootstrapModelForm):
|
||
pass
|
||
```
|
||
|
||
|
||
|
||
- bootstrap.py文件完整代码
|
||
|
||
```python
|
||
from django import forms
|
||
|
||
"""
|
||
这两个类是实现前端显示输入控件的样式与Bootstrap保持一致
|
||
"""
|
||
|
||
class Bootstrap:
|
||
"""
|
||
定义Bootstrap基类,让所有表单控件的class都继承这个类,减少代码量
|
||
"""
|
||
|
||
exclude_filed = [] # 过滤字段不让引用Bootstrap样式
|
||
|
||
# 给所有表单控件添加属性
|
||
def __init__(self, *args, **kwargs):
|
||
super().__init__(*args, **kwargs)
|
||
for name, field in self.fields.items():
|
||
# 判断要过滤掉的字段
|
||
if name in self.exclude_filed:
|
||
continue
|
||
# 如果表单控件中本来就有值,就追加属性
|
||
if field.widget.attrs:
|
||
field.widget.attrs["class"] = "form-control"
|
||
else:
|
||
field.widget.attrs = {"class": "form-control"}
|
||
|
||
class BootstrapModelForm(Bootstrap, forms.ModelForm):
|
||
pass
|
||
|
||
class BootstrapForm(Bootstrap, forms.Form):
|
||
pass
|
||
|
||
|
||
|
||
```
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
## 5.请求和响应
|
||
|
||
### 5.1 请求
|
||
|
||
- 视图函数中的request参数
|
||
|
||
作用:是用户通过浏览器发送回来的所有数据,本身是一个对象
|
||
|
||
```python
|
||
def task(request):
|
||
pass
|
||
```
|
||
|
||
- 获取请求方式(GET & POST)
|
||
|
||
```python
|
||
if request.method == 'GET':
|
||
form = PrettyNumberForm() # 实例化表单模型类
|
||
return render(request, 'PrettyNumber_add_modelform.html', {'form': form})
|
||
```
|
||
|
||
#### 5.1.1 GET请求
|
||
|
||
request.GET 接收浏览器请求,获得是一个字典
|
||
|
||
#### 5.1.2 POST请求
|
||
|
||
```python
|
||
form = PrettyNumberForm(data=request.POST) # 拿到所有提交过来的数据
|
||
```
|
||
|
||
- 获取值
|
||
|
||
```python
|
||
username = req.POST.get('user')
|
||
```
|
||
|
||
#### 5.1.3 机制校验CSRF Token
|
||
|
||
在POST 表单中写入 {% csrf_token %} 模板标记,不然会提示以下错误
|
||
|
||
![app/gitmind-cn/resources/docs/bqge00m6b92o/1708008418471_NXpzlLJzY4MkXVahfdhrAB2HxOKnxL.png](https://gitmindsz.aoscdn.com/app%2Fgitmind-cn%2Fresources%2Fdocs%2Fbqge00m6b92o%2F1708008418471_NXpzlLJzY4MkXVahfdhrAB2HxOKnxL.png?auth_key=1714192093-002515-539984-81b0b349a8a189b7e3a2829100fb5c4f&Expires=1714192093)
|
||
|
||
|
||
|
||
#### 5.1.4 Ajax请求
|
||
|
||
- 概念:过去的请求方式只有URL(get)请求,和表单的(post)请求,会造成页面刷新
|
||
|
||
- 功能:在浏览器不刷新的情况下,完成向网络后端发送请求,保证输入的完整性,也可理解为(偷偷发请求)
|
||
|
||
- 依赖Jquery
|
||
|
||
- 基本用法
|
||
|
||
```javascript
|
||
$.ajax({
|
||
url:'/task/ajax/',
|
||
type:'get',
|
||
data:{
|
||
n1:123,
|
||
n2:'abc',
|
||
age: $('#txtAge').val(),
|
||
},
|
||
success:function(res){
|
||
console.log('ajax运行了');
|
||
}
|
||
})
|
||
```
|
||
|
||
- 如果是POST请求数据时,CRSF机制要进行以下一些设置
|
||
|
||
```python
|
||
# 如果ajax请求时,免除crsf认证
|
||
from django.views.decorators.csrf import csrf_exempt
|
||
|
||
@csrf_exempt
|
||
def task_add(request):
|
||
pass
|
||
```
|
||
|
||
|
||
|
||
- 模板文件完整示例
|
||
|
||
```html
|
||
<form id="addForm">
|
||
<div class="clearfix">
|
||
{% for field in form %}
|
||
<div class="col-xs-6">
|
||
{#style="position: relative" 相对定位,absolute绝对定位#}
|
||
<div class="form-group" style="position: relative;margin-bottom: 20px">
|
||
<label>{{ field.label }}</label>
|
||
{{ field }}
|
||
<span class="error" style='color: red;position: absolute'></span>
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
<div class="col-xs-12">
|
||
<button id='saveBtn' type="button" class="btn btn-primary">保存</button>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
|
||
|
||
|
||
|
||
<script type="text/javascript">
|
||
// 利用jquery来绑定按钮事件
|
||
$(function () {
|
||
// 页面加载完成后,代码自动执行
|
||
bindSaveBtnEvent();
|
||
})
|
||
|
||
function bindSaveBtnEvent() {
|
||
$('#saveBtn').click(function () {
|
||
|
||
// 每次先清除span标签内容
|
||
$('.error').empty()
|
||
|
||
$.ajax({
|
||
url: '/task/add/',
|
||
type: 'post',
|
||
{#获取表单所有文本框的内容并打包,向后端传递的数据#}
|
||
data: $('#addForm').serialize(),
|
||
{#因为后端返回的数据是字符串形式的字典,所以在此指定数据类型为json格式 数据来源:task.py#}
|
||
dataType: "JSON",
|
||
success: function (res) {
|
||
//console.log(res.error_msg);
|
||
//console.log(res.status);
|
||
if(res.status){
|
||
alert('添加数据成功');
|
||
// 用JS实现页面刷新
|
||
location.reload()
|
||
}else{
|
||
// 不成功要返回错误信息,each循环
|
||
// 错误信息包括字段名,和错误信息
|
||
// 浏览器自动会为文本框添加一个id属性,格式为id_title
|
||
// 所以在循环读出错误信息时,拼接一个id属性
|
||
// msg是一个列表所以读出msg[0]
|
||
$.each(res.error_msg,function (name,msg) {
|
||
$('#id_'+ name).next().text(msg[0])
|
||
})
|
||
}
|
||
}
|
||
})
|
||
})
|
||
}
|
||
|
||
</script>
|
||
|
||
```
|
||
|
||
#### 5.1.5 相关JQuery语言
|
||
|
||
```javascript
|
||
// 清空对话框表单
|
||
$('#saveForm')[0].reset()
|
||
|
||
// 获取当前行的订单ID,order_id是标签的自定义属性
|
||
var order_id = $(this).attr('order_id')
|
||
|
||
// each循环读取后端传递过来的字典
|
||
// name是键名,msg是键值
|
||
// 浏览器自动会为文本框添加一个id属性,格式为id_title
|
||
// 所以在循环读出错误信息时,拼接一个id属性
|
||
// .next()指的是在文本框标签后的第一个标签,也就指span标签,给它设置text()值
|
||
// msg是一个列表所以读出msg[0]
|
||
$.each(res.error_msg, function (name, msg) {
|
||
$('#id_' + name).next().text(msg[0])
|
||
})
|
||
|
||
// 同上,给字段赋值
|
||
// 将返回的data字典通过循环赋值给各自的文本框(说明详见
|
||
$.each(res.data, function (name, value) {
|
||
$('#id_' + name).val(value)
|
||
})
|
||
|
||
// 修改标签id为myModalLabel的文本值
|
||
$('#myModalLabel').text('修改订单')
|
||
|
||
// 清空class名为error的标签内容
|
||
$('.error').empty()
|
||
|
||
// 刷新页面
|
||
location.reload();
|
||
```
|
||
|
||
|
||
|
||
-
|
||
- 视图函数完整示例
|
||
- 重点是返回给前端的数据是json格式
|
||
|
||
```python
|
||
@csrf_exempt
|
||
def task_add(request):
|
||
print(request.POST)
|
||
|
||
# 对ajax提交近来的数据进行校验
|
||
form = TaskModelForm(data=request.POST)
|
||
if form.is_valid():
|
||
form.save() # 向数据表添加数据
|
||
# 将标记传回前端
|
||
data_dict = {"status": True, }
|
||
return HttpResponse(json.dumps(data_dict))
|
||
# 如果有错误
|
||
# 将生成错误以json格式返回给前端
|
||
data_dict = {"status": False, 'error_msg': form.errors}
|
||
return HttpResponse(json.dumps(data_dict, ensure_ascii=False)) # ensure_ascii编码格式
|
||
```
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
### 5.2 响应
|
||
|
||
- 引用模块
|
||
|
||
```python
|
||
from django.shortcuts import render, HttpResponse, redirect
|
||
```
|
||
|
||
- render响应,一般form传递是表单域模型
|
||
|
||
```python
|
||
return render(request, 'PrettyNumber.html', {'form': form})
|
||
```
|
||
|
||
- redirect响应(重定向)
|
||
|
||
```python
|
||
return redirect('/admin/list')
|
||
```
|
||
|
||
- HttpResponse 响应
|
||
|
||
```python
|
||
return HttpResponse('Hello word')
|
||
```
|
||
|
||
- 生命周期
|
||
|
||
![app/gitmind-cn/resources/docs/q83y5ame4huu/1708497558088_FLHA2CueJKDb4qHPhoVwHmHTWDV2P1.png](https://gitmindsz.aoscdn.com/app%2Fgitmind-cn%2Fresources%2Fdocs%2Fq83y5ame4huu%2F1708497558088_FLHA2CueJKDb4qHPhoVwHmHTWDV2P1.png?auth_key=1714192093-244998-220312-ac73f39b03e89fb6ca7d7809e86be4ea&Expires=1714192093)
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
## 6. 中间件
|
||
|
||
- 控制浏览器与视图函数间通信的软件,在django中可以理解为是一个类
|
||
- 浏览器在访问视图函数views.py时,总要先穿越访问在setting.py中(MIDDLEWARE)定义的中间件列表,以达到一些鉴权操作等,实际上它也是一个类
|
||
|
||
![app/gitmind-cn/resources/docs/xorxcu8y1sio/1713777229980_yFsnFuWOxwejbNO0heABeD1uGs7wsd.png](https://gitmindsz.aoscdn.com/app%2Fgitmind-cn%2Fresources%2Fdocs%2Fxorxcu8y1sio%2F1713777229980_yFsnFuWOxwejbNO0heABeD1uGs7wsd.png?auth_key=1714192093-647964-676645-28d1751ade606f972479ed872b692899&Expires=1714192093)
|
||
|
||
|
||
|
||
### 6.1 定义中间件
|
||
|
||
- 定义一个类,先要导入django的中间件模块,定义该类继承MiddlewareMixin
|
||
|
||
- process_request方法是进入中间件时操作
|
||
|
||
- process_response方法是离开中间件时操作
|
||
- 如果process_request方法有返回值,则转到process_response中,没有返回值,则返回None,继续向下执行
|
||
|
||
```python
|
||
from django.utils.deprecation import MiddlewareMixin
|
||
class AuthMiddleWare(MiddlewareMixin):
|
||
""" 中间件 """
|
||
|
||
def process_request(self, request):
|
||
# print('M1.process_request,进来了')
|
||
# (重要)排除不需要进行登录验证的页面,例如登录界面,不然回造成死循环
|
||
# request.path_info 当前页面的URL,例如:'/login/'
|
||
if request.path_info in ['/login/', '/image/code/']:
|
||
return # 返回None
|
||
|
||
# 判断是否已登录,返回None则继续向下一个中间件执行,最终到达视图函数
|
||
info = request.session.get('info')
|
||
# print(info)
|
||
if info:
|
||
return # 返回None
|
||
# 没有登录,则返回登录界面
|
||
return redirect('/login/')
|
||
|
||
def process_response(self, request, response):
|
||
# print('M1.process_request,走了')
|
||
return response
|
||
```
|
||
|
||
|
||
|
||
### 6.2 注册中间件
|
||
|
||
- 在setting.py文件中修改MIDDLEWARE列表。
|
||
- middleware是app/的目录
|
||
- auth是py文件
|
||
- AuthMiddleWare是py文件中定义的类
|
||
|
||
```python
|
||
|
||
MIDDLEWARE = [
|
||
...
|
||
'app.middleware.auth.AuthMiddleWare', # 引入自定义的中间件,用于鉴权
|
||
]
|
||
|
||
```
|
||
|
||
|
||
|
||
## 7. cookis % session
|
||
|
||
- 工作原理
|
||
|
||
- 先生成一个随机字符
|
||
- 写入到本地浏览器的cookie中
|
||
- 再写入到服务器(网站)的session中
|
||
|
||
- 在django中简化了
|
||
|
||
```python
|
||
request.session['info'] = {'id': admin_object.id, 'username': admin_object.username}
|
||
|
||
# 如果登录成功,session会保存7天,免登录
|
||
request.session.set_expiry(60 * 60 * 24 * 7)
|
||
```
|
||
|
||
- 然后就可以使用中间件进行鉴权操作了
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
## 8.图表
|
||
|
||
第三方插件
|
||
|
||
- highchart 国外
|
||
- echarts 百度开源 https://echarts.apache.org/zh/index.html
|
||
|
||
### 8.1 获取 Apache ECharts
|
||
|
||
> 关于这些文件的介绍,可以在https://echarts.apache.org/handbook/zh/get-started/了解更多信息。
|
||
|
||
|
||
|
||
### 8.2 快速简单的图表
|
||
|
||
```html
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<!-- 引入刚刚下载的 ECharts 文件 -->
|
||
<script src="echarts.js"></script>
|
||
</head>
|
||
</html>
|
||
```
|
||
|
||
```html
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<title>ECharts</title>
|
||
<!-- 引入刚刚下载的 ECharts 文件 -->
|
||
<script src="echarts.js"></script>
|
||
</head>
|
||
<body>
|
||
<!-- 为 ECharts 准备一个定义了宽高的 DOM -->
|
||
<div id="main" style="width: 600px;height:400px;"></div>
|
||
<script type="text/javascript">
|
||
// 基于准备好的dom,初始化echarts实例
|
||
var myChart = echarts.init(document.getElementById('main'));
|
||
|
||
// 指定图表的配置项和数据
|
||
var option = {
|
||
title: {
|
||
text: 'ECharts 入门示例'
|
||
},
|
||
tooltip: {},
|
||
legend: {
|
||
data: ['销量']
|
||
},
|
||
xAxis: {
|
||
data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
|
||
},
|
||
yAxis: {},
|
||
series: [
|
||
{
|
||
name: '销量',
|
||
type: 'bar',
|
||
data: [5, 20, 36, 10, 10, 20]
|
||
}
|
||
]
|
||
};
|
||
|
||
// 使用刚指定的配置项和数据显示图表。
|
||
myChart.setOption(option);
|
||
</script>
|
||
</body>
|
||
</html>
|
||
```
|
||
|
||
### 8.3 前后端数据分离实现
|
||
|
||
- 前端代码 chart_list.html
|
||
|
||
- 本质来说就是通过ajax请求后端数据,更新JS代码中的option参数值(X轴数据、分类数据等)
|
||
|
||
```html
|
||
<script type="text/javascript">
|
||
$(function () {
|
||
initBar();
|
||
})
|
||
|
||
/**
|
||
* 初如化柱状图
|
||
* **/
|
||
function initBar() {
|
||
// 基于准备好的dom,初始化echarts实例
|
||
var myChart = echarts.init(document.getElementById('m2'));
|
||
|
||
// 指定图表的配置项和数据
|
||
var option = {
|
||
title: {
|
||
text: '商品类型',
|
||
textAlign: 'auto',
|
||
left: 'center'
|
||
},
|
||
tooltip: {},
|
||
legend: {
|
||
data: [], //后台获取
|
||
bottom: 0,
|
||
},
|
||
xAxis: {
|
||
data: [], //后台获取
|
||
},
|
||
yAxis: {},
|
||
series: [] //后台获取
|
||
};
|
||
|
||
// 发起ajax请求到后端取数据
|
||
$.ajax({
|
||
url: '/chart/bar/',
|
||
type: 'get',
|
||
dataType: 'JSON',
|
||
success: function (res) {
|
||
// 将后端返回的数据,更新到option中
|
||
option.legend.data = res.data.data_list;
|
||
option.xAxis.data = res.data.x_axis;
|
||
option.series = res.data.series_list;
|
||
|
||
// 使用刚指定的配置项和数据显示图表。
|
||
myChart.setOption(option);
|
||
|
||
}
|
||
})
|
||
|
||
}
|
||
|
||
</script>
|
||
```
|
||
|
||
|
||
|
||
- 后端代码 chart_bar.py
|
||
|
||
- 把前端需要的数据从数据库中查询到后,形成相关的变量,再传回前端
|
||
|
||
```python
|
||
|
||
|
||
def chart_bar(request):
|
||
data_list = ['一级任务', '二级任务']
|
||
x_axis = ['1月', '2月', '3月', '4月', '5月', '6月']
|
||
series_list = [
|
||
{
|
||
'name': '一级任务',
|
||
'type': 'bar',
|
||
'data': [5, 25, 100, 13, 10, 20]
|
||
},
|
||
{
|
||
'name': '二级任务',
|
||
'type': 'bar',
|
||
'data': [6, 15, 23, 0, 30, 80]
|
||
},
|
||
]
|
||
|
||
result = {
|
||
'status': True,
|
||
'data': {
|
||
'series_list': series_list,
|
||
'x_axis':x_axis,
|
||
'data_list':data_list,
|
||
},
|
||
}
|
||
|
||
return HttpResponse(json.dumps(result))
|
||
|
||
```
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
## 9.文件操作
|
||
|
||
### 9.1 基本操作
|
||
|
||
- 前端html
|
||
|
||
```html
|
||
<div class="panel-body">
|
||
<form method="post" enctype="multipart/form-data">
|
||
{% csrf_token %}
|
||
<input type="text" name="username"/>
|
||
<input type="file" name="avator"/>
|
||
<input type="submit" value="上传">
|
||
</form>
|
||
</div>
|
||
|
||
```
|
||
|
||
- 后端upload.py
|
||
- 请求体
|
||
|
||
```python
|
||
print(request.POST) # 请求体
|
||
# <QueryDict: {'csrfmiddlewaretoken': ['p30M5m6wPFB3luc56KLV1Fct9SFxomNs01H9Vxx67d27CnSyDqMHtR7tsnnUwFUz'], 'username': ['文件LOGO']}>
|
||
|
||
print(request.FILES) # 请求到后端的文件,是一个文件对象
|
||
# <MultiValueDict: {'avator': [<InMemoryUploadedFile: 780.jpg (image/jpeg)>]}>
|
||
```
|
||
|
||
- 完整代码
|
||
|
||
```python
|
||
from django.shortcuts import render
|
||
|
||
def upload_list(request):
|
||
""" 上传文件 """
|
||
if request.method == 'GET':
|
||
return render(request, 'upload_list.html')
|
||
|
||
# POST请求
|
||
file_object = request.FILES.get('avator') # 文件
|
||
file_name = file_object.name # 文件名
|
||
|
||
# 将接收到的文件,分块写入
|
||
with open(f'app/upload/{file_name}', 'wb') as f:
|
||
for chunk in file_object.chunks():
|
||
f.write(chunk)
|
||
|
||
return render(request, 'upload_list.html')
|
||
|
||
```
|
||
|
||
### 9.2 存放文件目录概念
|
||
|
||
- static 目录 CSS、JS、plus、项目图片文件
|
||
|
||
- MEDIA 用户上传的文件
|
||
|
||
- 启用MEDIA目录
|
||
|
||
1. 在urls.py中配置
|
||
|
||
```python
|
||
from django.conf import settings
|
||
from django.urls import path,re_path
|
||
from django.views.static import serve
|
||
urlpatterns = [
|
||
re_path(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}, name='media'),
|
||
]
|
||
```
|
||
|
||
2. 在setting.py中配置(media目录在应用目录上一级)
|
||
|
||
```python
|
||
MEDIA_ROOT = os.path.join(BASE_DIR,'media') #以后会自动将文件上传到指定的文件夹中
|
||
MEDIA_URL = '/media/' #以后可以使用这个路由来访问上传的媒体文件
|
||
```
|
||
|
||
3. 在浏览器上访问地址
|
||
|
||
```html
|
||
http://127.0.0.1/media/图片.jpg
|
||
```
|
||
|
||
|
||
|
||
### 9.3 Excel文件操作
|
||
|
||
- 导入模块
|
||
|
||
```python
|
||
pip install openpyxl
|
||
```
|
||
|
||
- 代码功能:把一个excel文件中的所有行,插入到mysql数据表中
|
||
- Excel表格只有一列数据部门名称数据
|
||
|
||
```python
|
||
import os
|
||
from openpyxl import load_workbook
|
||
|
||
|
||
def depart_multi(request):
|
||
""" 批量上传文件 (excel文件) """
|
||
|
||
file_object = request.FILES.get('exc') # 从depart_list.html中读取上传的文件
|
||
# 直接打开excel文件并读取
|
||
wb = load_workbook(file_object) # load_workbook可以打开一个文件对象
|
||
sheet = wb.worksheets[0] # 第1个工作薄
|
||
|
||
# row = sheet.cell(1, 1) # xls表第1行,第1列数据
|
||
# print(row.value)
|
||
|
||
# 循环读取所有行
|
||
# 第1行是标题,数据从第2行开始读取
|
||
for row in sheet.iter_rows(min_row=2):
|
||
data = row[0].value
|
||
if not models.Department.objects.filter(title=data).exists(): # 判断部门值是否存在
|
||
models.Department.objects.create(title=data) # 写入部门数据表
|
||
|
||
return redirect('/depart/list')
|
||
|
||
```
|
||
|
||
|
||
|
||
### 9.4 表单域中混合数据
|
||
|
||
- 用户在提交表单时,还有其他文本框数据 + 上传文件
|
||
|
||
#### 9.4.1 Form表单提交
|
||
|
||
- 表单的 ***enctype=‘multipart/form-data’***不然不能上传
|
||
|
||
- 获取form表单提交过来的上传文件files=request.FILES
|
||
|
||
- 获取其他表单数据request.POST
|
||
|
||
```python
|
||
form = UpForm(data=request.POST, files=request.FILES)
|
||
```
|
||
|
||
- 表单验证
|
||
|
||
```python
|
||
# form变量保存的是 表单数据 + 文件对象
|
||
# 通过cleaned_data方法取值 = {'name': 'tom', 'age': 24, 'img': < InMemoryUploadedFile: 780.jpg(image / jpeg) >}
|
||
# 图像是一个对象
|
||
image_object = form.cleaned_data.get('img')
|
||
file_name = image_object.name
|
||
```
|
||
|
||
- django只认静态文件保存在static目录下,所以想展示图片,数据库中的路径要是static/upload/xxx.jpg,如果要修改保存目录,只能在setting.py文件中修改
|
||
- 代码
|
||
|
||
```python
|
||
def upload_form(request):
|
||
"""Form表单上传文件演示 """
|
||
title = 'Form上传'
|
||
|
||
# get请求,只显示空白表单
|
||
if request.method == 'GET':
|
||
form = UpForm()
|
||
return render(request, 'upload_form.html', {'form': form, 'title': title})
|
||
|
||
form = UpForm(data=request.POST, files=request.FILES)
|
||
if form.is_valid():
|
||
print(form.cleaned_data)
|
||
# cleaned_data = {'name': 'tom', 'age': 24, 'img': < InMemoryUploadedFile: 780.jpg(image / jpeg) >}
|
||
# 下一步就是处理获取到数据
|
||
|
||
# 对于上传的图像来说,先保存到本地,数据表中存储路径
|
||
image_object = form.cleaned_data.get('img')
|
||
file_name = image_object.name
|
||
|
||
# 重点:django只认静态文件保存在static目录下,用户上传的文件在media目录下(需配置)
|
||
# 所以想展示图片,就保存在media目录下
|
||
# file_path = os.path.join(settings.MEDIA_ROOT, 'upload', file_name) # 取得是绝对路径D:\xx\xxx\xxx
|
||
file_path = os.path.join('media', 'upload', file_name)
|
||
|
||
# 将接收到的文件,分块写入
|
||
with open(file_path, 'wb') as f:
|
||
for chunk in image_object.chunks():
|
||
f.write(chunk)
|
||
|
||
# 保存数据到数据库 name,age,img对应表字段,值是从form.cleaned_data获取到的
|
||
# 因为form.cleaned_data里面最初保存的是一个文件对象,所以把他替换为文件路径
|
||
models.Boss.objects.create(
|
||
name=form.cleaned_data['name'],
|
||
age=form.cleaned_data['age'],
|
||
img=file_path, # 图片地址保存的是static目录下
|
||
)
|
||
|
||
return HttpResponse('...')
|
||
|
||
return render(request, 'upload_form.html', {'form': form, 'title': title})
|
||
```
|
||
|
||
|
||
|
||
#### 9.4.2 ModelForm
|
||
|
||
- 在生成数据表时,图像字段是FileField,本质上还是CharField,区别在于上传文件并保存的事情由Django来做不需要写代码
|
||
|
||
- models.py
|
||
|
||
```python
|
||
class City(models.Model):
|
||
""" 一个测试使用的表,用于测试文件上件使用 基于modelForm """
|
||
|
||
name = models.CharField(verbose_name='城市', max_length=64)
|
||
count = models.IntegerField(verbose_name='人口')
|
||
# 本质上还是CharField字符串,区别在于上传文件并保存的事情由Django来做不需要写代码
|
||
# upload_to 是保存的文件夹名称,是media目录中已存在的目录
|
||
img = models.FileField(verbose_name='Logo', max_length=128, upload_to='upload/')
|
||
```
|
||
|
||
- 定义ModelForm
|
||
|
||
```python
|
||
class UploadModelForm(BootstrapModelForm):
|
||
exclude_filed = ['img'] # 过滤字段不让引用Bootstrap样式
|
||
class Meta:
|
||
model = models.City
|
||
fields = '__all__'
|
||
```
|
||
|
||
- views.py
|
||
|
||
只需要使用save()方法就可以保存
|
||
|
||
```python
|
||
def upload_modelform(request):
|
||
""" 上传文件 """
|
||
if request.method == 'GET':
|
||
form = UploadModelForm()
|
||
return render(request, 'upload_form.html', {'form': form,'title':'ModelForm上传文件'})
|
||
|
||
form = UploadModelForm(data=request.POST, files=request.FILES)
|
||
if form.is_valid():
|
||
# 对于文件会自动保存
|
||
# 路径是在models中定义的路径
|
||
# 字段中保存的是路径字符串
|
||
form.save()
|
||
return HttpResponse('上传成功')
|
||
|
||
return render(request, 'upload_form.html', {'form': form, 'title': 'ModelForm上传文件'})
|
||
```
|
||
|
||
|
||
|
||
|
||
|
||
## 10 模板语法
|
||
|
||
### 10.1 在Django/Python中如何对每行进行三列循环
|
||
|
||
```python
|
||
{% for project in projects %}
|
||
{% if forloop.counter0|divisibleby:3 %}
|
||
<div class="row">
|
||
{% endif %}
|
||
<div class="col-md-4">
|
||
<h2>{{ project.name }}</h2>
|
||
<p>{{ project.description }}</p>
|
||
<p>Created at: {{ project.created_at }}</p>
|
||
</div>
|
||
{% if forloop.counter|divisibleby:3 or forloop.last %}
|
||
</div>
|
||
{% endif %}
|
||
{% endfor %}
|
||
```
|
||
|
||
- 在上面的代码中,我们首先使用`for`标签对`projects`列表进行循环遍历。然后,我们使用`if`标签判断当前循环的索引值是否能够被3整除,如果是,则表示需要开始一个新的行,我们使用`div`标签来创建一个新的行。然后,我们使用`div`标签创建一个包含项目信息的列。
|
||
- 在每个项目的列结束时,我们再次使用`if`标签判断当前循环的索引值是否能够被3整除,如果是,则表示当前行已经包含了三列,我们使用`div`标签来结束当前行。
|
||
- 通过上述代码,我们可以在网站的首页上按照每行三列的格式展示项目列表。
|