57 KiB
django 开发指南
1. 前言
这开发指南是我在bibibl网站的看到并学习,前后用了近三个月时间,因为年龄大了,所以学的比较慢,跟着视频一步一步学习。B站用户:别了青春12 2022最新python3.9全栈开发600集沉浮式学习从小白入门到精通实战
整个学习是武沛奇老师的授课,讲的很详细,也很具体,完全贴近实战,非常有用。
2. django项目创建
2.1 安装django
pip install django
2.2 查看django版本
python -m django --version
2.3 创建django项目
- 第一种方法通过命令行方式创建
>>> django-admin startproject mysite
创建后,会生成相关目录
- 第二种方法通过pycharm创建,必须是专业版
-
可参考视频:https://www.bilibili.com/video/BV1ua4y1E72e/?from=search&vd_source=cf28b53b9b35b80fe6fa1e4943ef8031
-
***注意:*如果是pycharm创建的项目,在setting.py文件中要删除 DIR templates
2.4 创建应用
- 一个项目可以创建多个应用,多数以app表示
>>>python manage.py startapp app01
- 创建后会生成多个目录和文件,具体解释如下:
- 注册app
- 修改setting.py的INSTALLED_APPS列表,
- 如果不注册app,则无法在models.py中生成数据库表结构
# Application definition
INSTALLED_APPS = [
...
...
'app.apps.AppConfig', # 注册自己的app
]
- setting.py配置相关目录和数据
-
在app目录下创建static目录和templates两个目录
- static目录下存放图片、插件、css、js等
- templates目录存放的是前端界面
-
在setting.py文件中
STATIC_URL = 'static/'
2.5 快速搭建一个应用
- 编写URL和视图函数的对应关系
-
在urls.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), ]
-
在views.py中编写视图函数
def depart_list(request): """ 部门列表 """ data_list = Department.objects.all() return render(request, 'depart_list.html', {'queryset': data_list})
-
在前端页面中加入链接地址,与url视图中定义的一致
<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>
-
启动应用
python manage.py runserver 端口号(默认8000)
- 浏览器浏览
http://127.0.0.1:8000/app
3. 数据库创建及数据表操作介绍
3.1 ORM 介绍
-
Django引入ORM框架来操作数据库,ORM不直接操作数据库,他其实是一个翻译,把Django语句翻译为SQL语句
-
创建、修改、删除数据表不用写Sql语句,但唯独不能【创建数据库】
-
所有的对数据库结构和关联关系的操作都写在models.py中
-
操作数据表的数据,也不用写sql语句,通过ORM来翻译
3.2 数据库创建
-
配置数据库(MYSQL)
-
第三方模块
pip install mysqlclient
-
创建数据库
在mysql或Navicat中创建数据库
注意(数据表的创建由models.py中的类和数据迁移命令完成)
-
配置数据库连接,修改setting.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_user ,app_admin等等
-
同时django会自动生成一些自己使用的数据表,表名都是以 “django_” 开头
-
如果使用了中间件,也会以自定义中间件文件名为开头生成一些数据表,例如auth_group等
3.3.3 修改表结构
1-设置默认值 2-退出
也可以在代码中设置默认值或可以为空
3.3.4 删除表
- 删除表和表字段 ,只需要把相应的类或字段名在代码中注释掉,再执行迁移命令即可。
3.3.5 数据关联和主键约束
-
外键约束
# 有约束 # 创建一个与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自己的约束
# 在django中做约束 # 定义一个元组 # 参数 # - choices参数来判断性别显示 gender_choices = ( (1, '男'), (2, '女') ) gender = models.SmallIntegerField(verbose_name='性别', choices=gender_choices)
3.3.6 定义前端显示内容
# 定制在前端要显示的内容,用于在前端关联查询的结果,不然会返回一个对象
# 这个显示会在ModelForm中使用到
def __str__(self):
return self.title
3.4 数据(记录)操作
-
匹配记录是一个 QuerySet 集合。它可以有 0 个,1 个或者多个,类似于列表结构,每行数据就是一个对象,他是有返回值的
<QuerySet [<UserInfo: UserInfo object (1)>, <UserInfo: UserInfo object (2)>, <UserInfo: UserInfo object (4)>]>
-
数据的操作都在views.py视图文件中进行
3.4.1 查询
-
全部数据
- 返回的是一个QuerySet 集合
# 对象,不可序列化 # queryset = {obj,obj,obj} data_list = UserInfo.objects.all() # 取值,可使用迭代对象.字段 for item in data_list: print(data_list.id)
- 返回的是一个字典,可序列化
# 字典格式,可序列化,需要哪个字段就填哪个 # 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')
# 只取前10条记录 data_list = UserInfo.objects.all()[0:10]
# 倒序 data_list = userInfo.objects.all().order_by('-id')
-
条件查询
- 返回的是一个QuerySet 集合
data_list = UserInfo.objects.filter(id=1) data_list = UserInfo.objects.filter(name='张三',id=1) # 也可以传入一个字典 data_list = UserInfo.objects.filter(**data_dict)
-
filter的使用
- 条件判断是数字类型的
# 等于 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)
- 条件判断是字符串类型的
# 以字符串开头,以张姓开头的 data_list = UserInfo.objects.filter(name__startswith='张') # 以字符串结束,姓名以军结束的 data_list = UserInfo.objects.filter(name__endswith='军') # 包含字符串的,姓名中包含有建字的 data_list = UserInfo.objects.filter(name__contains='建')
-
查询第1条记录
# data_list是一个对象 data_list = UserInfo.objects.filter(id=1).first() # 取值 data_list.id data_list.name
匹配的是一个对象,不能再用循环,直接使用data_list.id就可取得数据
# 字典格式,可序列化,需要哪个字段就填哪个 # 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 新增
UserInfo.objects.create(name='tom',password='666')
3.4.3 删除
-
条件删除
UserInfo.objects.filter(id=3).delete()
-
全部删除
UserInfo.objects.all().delete()
3.4.4 修改
-
修改全部数据
# 更新所有数据 UserInfo.objects.all().update(password='999')
-
修改条件数据
# 更新符合条件的数据 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.关联的数据要手动获取,前端要循环渲染
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')
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类方法
4.1.3 ModelForm 类 方法(推荐)
表单域模型 作用:生成HTML标签用于定义渲染到前端的数据,设置校验规则等
基本用法
- 首先从django.forms导入ModelForm;
from django import forms
- 编写一个自己的类,继承ModelForm;
class UserModelForm(forms.ModelForm):
-
设置字段属性
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;
class Meta:
- 在Meta中,设置model属性为你要关联的ORM模型,这里是UserInfo;
model = UserInfo # 指定需要显示的表名(在models.py中定义)
- 在Meta中,设置fields属性为你要在表单中使用的字段列表;列表里的值,应该是ORM模型model中的字段名。
# 指定要操作的字段名,是一个列表
fields = ['name', 'age', 'gender', 'depart', 'create_time', 'account']
fields属性讲解
- fields
是字段名称的可选列表。如果提供,请仅包括返回字段中的命名字段。如果省略或“all”,则使用领域。
用法:fields = ['mobile', 'price', 'level', 'status']
fields = '__all__' # 渲染所有字段
- exclude
是字段名称的可选列表。如果提供,请排除返回字段中的命名字段,即使它们列在''fields'' 参数中。
exclude = ['level'] # 排除某个字段
解决前端显示问题
- 在数据模型models.py中定义__str__方法
class Department(models.Model):
title = models.CharField(verbose_name='部门名称', max_length=32)
# 定制在前端要显示的内容,用于在前端关联查询的结果,不然会返回一个对象
# 这个显示会在ModelForm中使用到
def __str__(self):
return self.title
插件
- 也就是字段的属性,比如样式、前端显示类型等
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类中定义
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:重新定义字段属性
- 在表单域创建时,重新定义字段属性
class PrettyNumberForm(BootstrapModelForm):
# 重新定义mobile字段的属性和校验规则 方式1
mobile = forms.CharField(
min_length=11, # 最小长度
label='号码',
# 通过正则进行数据校验
validators=[RegexValidator(r'^1[3-9]\d{9}$', '号码错误,长度不能超过11位')],
)
错误信息引用 & 展示
在html模板文件中
<span style="color:red;"> {{ item.errors.0 }}</span>
在钩子函数中
raise ValidationError("号码已经存在")
也可在表单域模型创建字段时规定好内容
mobile = forms.CharField(
min_length=11, # 最小长度
label='号码',
# 通过正则进行数据校验
validators=[RegexValidator(r'^1[3-9]\d{9}$', '号码错误,长度不能超过11位')],
)
用户提交以外的数据添加
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() # 向数据表添加数据
完整示例代码
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目录下。
-
使用方法步骤:
-
在视图函数的列表函数中添加代码
-
将查询到的结果集放到类实例化中
queryset = UserInfo.objects.all() page_object = Pagination(request, queryset, PAGE_SIZE=10) # 实例化分页导航条类,每页显示10条记录 page_queryset = page_object.page_queryset # 获得分完页的数据结果
-
生成导航条的html代码,此工作已在Pagination类中实现
# 生成分页导航条 page_nav = page_object.html()
-
将导航条包裹的数据连同导航条html一同生成字典
context = { 'queryset': page_queryset, # 分完页的数据结果 'page_nav': page_nav # 生成的页码导航条 }
-
将数据返回给前端
return render(request, 'user_list.html', context)
-
在前端html代码中展示导航条
<ul class="pagination"> {{ page_nav }} </ul>
-
完整代码:
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
路由视图,用于规则链接地址
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来创建
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值
# 返回管理员的姓名,此功能在task.py中使用了,不然下拉框显示的就是对象
def __str__(self):
return self.username
4.4 模板和静态文件(HTML文件)
-
所有的html文件都放在app\templates目录下
-
基本实现原理:客户端发url请求,视图函数进行渲染和替换模板中的语法
4.4.1 模板调用
在视图函数(views.py)文件中通过render(request, "user_list.html")调用模板文件
4.4.2 模板基本语法
- 占位符
模板语法都是以{{ id }}两对花括号进行占位符,在视图文件中调用模板时,可携带参数,必须是一个字典
def tpl(request):
message = 'django模板语法学习'
return render(request, "tpl.html", {'msg': message})
在html文件中通过占位符显示
<td>{{ item.msg }}</td>
-
条件判断
{% if 1==1 %} ... {% elif 2==2 %} ... {% else %} ... {% endif %}
-
循环
{% for 变量 in 可迭代对象%}
<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>
- 列表、字典、元组的循环(综合应用)
<!--循环数组-->
<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>
使用字段值
- 普通字符串、数字
迭代对象.字段名
{{ obj.name }}
- models.py中定义的字典值
迭代对象.get_字段名_display
{{ obj.get_gender_display }}
- 有约束关联其他表的字段,如用户表中的title字段存储的是部门中的id
迭代对象.对象.对象字段,对象是指在models.py中定义约束关系的字段名
{{ obj.depart.tiele }}
(其中depart是models.py中定义的一个数据表字段名,他是有约束关联关系的,在这里引用后就是一个对象,代表的就是depart数据表,所以他可以点出字段)
- 日期字段
{{ item.create_time|date:"Y年m月d日" }}
4.4.3 模板继承
模板继承可以减少代码编写量,主要用在界面的统一风格中,比如页头和页尾都是一样的效果,那就专门做一个母版,将页头和页尾的Html代码写好,在中间部分用占位符填充。子页面在引用时,导入母版和占位符,然后写自己的代码就好。
- 母版
在母版layout.html的位置插入以下占位符代码,可以自定义多个占位符
{% block content %}{% endblock %}
{% block css %}{% endblock %}
{% block js %}{% endblock %}
- 子页面
<!--导入母版-->
{% 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 静态文件引入
<!--引入静态文件-->
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
4.4.6 静态文件的使用
<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样式在前端中循环展示出来
-
使用方法
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文件完整代码
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参数
作用:是用户通过浏览器发送回来的所有数据,本身是一个对象
def task(request):
pass
- 获取请求方式(GET & POST)
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请求
form = PrettyNumberForm(data=request.POST) # 拿到所有提交过来的数据
- 获取值
username = req.POST.get('user')
5.1.3 机制校验CSRF Token
在POST 表单中写入 {% csrf_token %} 模板标记,不然会提示以下错误
5.1.4 Ajax请求
-
概念:过去的请求方式只有URL(get)请求,和表单的(post)请求,会造成页面刷新
-
功能:在浏览器不刷新的情况下,完成向网络后端发送请求,保证输入的完整性,也可理解为(偷偷发请求)
-
依赖Jquery
-
基本用法
$.ajax({ url:'/task/ajax/', type:'get', data:{ n1:123, n2:'abc', age: $('#txtAge').val(), }, success:function(res){ console.log('ajax运行了'); } })
-
如果是POST请求数据时,CRSF机制要进行以下一些设置
# 如果ajax请求时,免除crsf认证 from django.views.decorators.csrf import csrf_exempt @csrf_exempt def task_add(request): pass
-
模板文件完整示例
<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语言
// 清空对话框表单
$('#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格式
@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 响应
- 引用模块
from django.shortcuts import render, HttpResponse, redirect
- render响应,一般form传递是表单域模型
return render(request, 'PrettyNumber.html', {'form': form})
- redirect响应(重定向)
return redirect('/admin/list')
- HttpResponse 响应
return HttpResponse('Hello word')
- 生命周期
6. 中间件
- 控制浏览器与视图函数间通信的软件,在django中可以理解为是一个类
- 浏览器在访问视图函数views.py时,总要先穿越访问在setting.py中(MIDDLEWARE)定义的中间件列表,以达到一些鉴权操作等,实际上它也是一个类
6.1 定义中间件
-
定义一个类,先要导入django的中间件模块,定义该类继承MiddlewareMixin
-
process_request方法是进入中间件时操作
-
process_response方法是离开中间件时操作
-
如果process_request方法有返回值,则转到process_response中,没有返回值,则返回None,继续向下执行
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文件中定义的类
MIDDLEWARE = [
...
'app.middleware.auth.AuthMiddleWare', # 引入自定义的中间件,用于鉴权
]
7. cookis % session
-
工作原理
- 先生成一个随机字符
- 写入到本地浏览器的cookie中
- 再写入到服务器(网站)的session中
-
在django中简化了
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 快速简单的图表
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<!-- 引入刚刚下载的 ECharts 文件 -->
<script src="echarts.js"></script>
</head>
</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轴数据、分类数据等)
<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
-
把前端需要的数据从数据库中查询到后,形成相关的变量,再传回前端
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
<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
- 请求体
print(request.POST) # 请求体
# <QueryDict: {'csrfmiddlewaretoken': ['p30M5m6wPFB3luc56KLV1Fct9SFxomNs01H9Vxx67d27CnSyDqMHtR7tsnnUwFUz'], 'username': ['文件LOGO']}>
print(request.FILES) # 请求到后端的文件,是一个文件对象
# <MultiValueDict: {'avator': [<InMemoryUploadedFile: 780.jpg (image/jpeg)>]}>
- 完整代码
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目录
- 在urls.py中配置
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'), ]
- 在setting.py中配置(media目录在应用目录上一级)
MEDIA_ROOT = os.path.join(BASE_DIR,'media') #以后会自动将文件上传到指定的文件夹中 MEDIA_URL = '/media/' #以后可以使用这个路由来访问上传的媒体文件
- 在浏览器上访问地址
http://127.0.0.1/media/图片.jpg
9.3 Excel文件操作
-
导入模块
pip install openpyxl
-
代码功能:把一个excel文件中的所有行,插入到mysql数据表中
-
Excel表格只有一列数据部门名称数据
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
form = UpForm(data=request.POST, files=request.FILES)
-
表单验证
# 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文件中修改
-
代码
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
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
class UploadModelForm(BootstrapModelForm): exclude_filed = ['img'] # 过滤字段不让引用Bootstrap样式 class Meta: model = models.City fields = '__all__'
-
views.py
只需要使用save()方法就可以保存
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中如何对每行进行三列循环
{% 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
标签来结束当前行。 - 通过上述代码,我们可以在网站的首页上按照每行三列的格式展示项目列表。