# 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 - ***注意:***如果**是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), # 是一个正则表达式,用来接收id,在views.py文件中加一个接收形参就可以接收到 # http://127.0.0.1:8001/depart/1/edit path('depart//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 ``` 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-退出 app/gitmind-cn/resources/docs/hdezjjcbxirs/1708419411296_ytelIXnXJzwa2Yhu2lVY7fow4VGBRw.png ​ 也可以在代码中设置默认值或可以为空 #### 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 , , ]> ``` - 数据的操作都在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 {{ item.errors.0 }} ``` 在钩子函数中 ```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
    {{ page_nav }}
``` **完整代码:** ```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), # 是一个正则表达式,用来接收id,在views.py文件中加一个接收形参就可以接收到 # http://127.0.0.1:8001/depart/1/edit path('depart//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 {{ item.msg }} ``` - **条件判断** ```html {% if 1==1 %} ... {% elif 2==2 %} ... {% else %} ... {% endif %} ``` - **循环** {% for 变量 in 可迭代对象%} ```html {% for item in queryset%} {{ item.id }} {{ item.name }} {{ item.get_gender_display }} {{ item.age }} {{ item.account }} {{ item.create_time|date:"Y年m月d日" }} {{ item.depart.title }} 修改 删除 {% endfor %} ``` - 列表、字典、元组的循环(综合应用) ```html

数组的取值方法

    {% for item in roles %}
  • {{ item }}
  • {% endfor %}


字典的取值方法-姓名:{{ userMsg.name }}

    {% for key,val in userMsg.items %}
  • {{ key }}:{{ val }}
  • {% endfor %}


元组和字典嵌套的取值方法,带条件判断-职务:{{ offMsg.0.offName }}

    {% for item in offMsg %} {% if item.offName == '经理' %}
  • {{ item.offName }}-{{ item.work }}
  • {% else %}
  • {{ item.offName }}-{{ item.work }}
  • {% endif %} {% endfor %}


``` ##### **使用字段值** - 普通字符串、数字 ```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 %}
自己的html代码
{% endblock %} ``` #### 4.4.4 静态文件 - 在app目录下创建static目录和templates两个目录 - static目录下存放图片、插件、css、js等 - templates目录存放的是前端界面 - 在setting.py文件中修改静态文件地址,在后面的模板文件中就可以使用了 ``` STATIC_URL = 'static/' ``` #### 4.4.5 静态文件引入 ```html {% load static %} Title ``` #### 4.4.6 静态文件的使用 ```html ``` ### 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
{% for field in form %}
{#style="position: relative" 相对定位,absolute绝对定位#}
{{ field }}
{% endfor %}
``` #### 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 ``` ```html ECharts
``` ### 8.3 前后端数据分离实现 - 前端代码 chart_list.html - 本质来说就是通过ajax请求后端数据,更新JS代码中的option参数值(X轴数据、分类数据等) ```html ``` - 后端代码 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
{% csrf_token %}
``` - 后端upload.py - 请求体 ```python print(request.POST) # 请求体 # print(request.FILES) # 请求到后端的文件,是一个文件对象 # ]}> ``` - 完整代码 ```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.*)$', 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 %}
{% endif %}

{{ project.name }}

{{ project.description }}

Created at: {{ project.created_at }}

{% if forloop.counter|divisibleby:3 or forloop.last %}
{% endif %} {% endfor %} ``` - 在上面的代码中,我们首先使用`for`标签对`projects`列表进行循环遍历。然后,我们使用`if`标签判断当前循环的索引值是否能够被3整除,如果是,则表示需要开始一个新的行,我们使用`div`标签来创建一个新的行。然后,我们使用`div`标签创建一个包含项目信息的列。 - 在每个项目的列结束时,我们再次使用`if`标签判断当前循环的索引值是否能够被3整除,如果是,则表示当前行已经包含了三列,我们使用`div`标签来结束当前行。 - 通过上述代码,我们可以在网站的首页上按照每行三列的格式展示项目列表。