django_project_demo/django 开发手册.md
2024-08-24 11:32:42 +08:00

57 KiB
Raw Blame History

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项目

  1. 第一种方法通过命令行方式创建
>>> django-admin startproject mysite

创建后,会生成相关目录

app/gitmind-cn/resources/docs/dv8ffwikp1y1/1707966981091_ThJ3jI0X64TbCfmbc2lpBFxqfxDyec.png

  1. 第二种方法通过pycharm创建必须是专业版

image-20240426123131039

2.4 创建应用

  • 一个项目可以创建多个应用多数以app表示
>>>python manage.py startapp app01
  • 创建后会生成多个目录和文件,具体解释如下:

app/gitmind-cn/resources/docs/dv8ffwikp1y1/1707967984192_bz4aLmpjRvHGxDD91ZoVsFJYoSkWDE.png

  • 注册app
    • 修改setting.py的INSTALLED_APPS列表
    • 如果不注册app则无法在models.py中生成数据库表结构
# 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中定义路径和要执行的函数

    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中编写视图函数

    def depart_list(request):
        """
        部门列表
        """
        data_list = Department.objects.all()
        return render(request, 'depart_list.html', {'queryset': data_list})
    
  3. 在前端页面中加入链接地址与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>
    
  4. 启动应用

    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

    1. 第三方模块

      pip install mysqlclient
      
    2. 创建数据库

      在mysql或Navicat中创建数据库

      注意数据表的创建由models.py中的类和数据迁移命令完成

    3. 配置数据库连接修改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/gitmind-cn/resources/docs/hdezjjcbxirs/1708417742041_3IU96xIIoFZy6MBKDTskqE6fe0lqBe.png

  • 在这里注意,用数据迁移方法生成的表,在实际的数据库存储中都是以“应用名_数据表”名存在

  • 例如app_user ,app_admin等等

  • 同时django会自动生成一些自己使用的数据表表名都是以 “django_” 开头

  • 如果使用了中间件也会以自定义中间件文件名为开头生成一些数据表例如auth_group等

3.3.3 修改表结构

  • 如果表中已有数据在执行makemigrations命令时会提示是否需要有一个默认值来填充已有数据

    app/gitmind-cn/resources/docs/hdezjjcbxirs/1708419345584_96oiGslsug8SomKaE7CEgcsHjl5WYj.png

1-设置默认值 2-退出

app/gitmind-cn/resources/docs/hdezjjcbxirs/1708419411296_ytelIXnXJzwa2Yhu2lVY7fow4VGBRw.png

也可以在代码中设置默认值或可以为空

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类方法

app/gitmind-cn/resources/docs/q83dfak2hmvm/1708588566645_hywcfflEtsUCR3IZZWwcXuRKrUeP8H.png

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请求视图函数进行渲染和替换模板中的语法

app/gitmind-cn/resources/docs/byqd7b04orhy/1707999926012_rjnXJxyDDRKZTTJVl4eRFlNcaHLWvc.png

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 %} 模板标记,不然会提示以下错误

app/gitmind-cn/resources/docs/bqge00m6b92o/1708008418471_NXpzlLJzY4MkXVahfdhrAB2HxOKnxL.png

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')
  • 生命周期

app/gitmind-cn/resources/docs/q83y5ame4huu/1708497558088_FLHA2CueJKDb4qHPhoVwHmHTWDV2P1.png

6. 中间件

  • 控制浏览器与视图函数间通信的软件在django中可以理解为是一个类
  • 浏览器在访问视图函数views.py时总要先穿越访问在setting.py中MIDDLEWARE定义的中间件列表以达到一些鉴权操作等实际上它也是一个类

app/gitmind-cn/resources/docs/xorxcu8y1sio/1713777229980_yFsnFuWOxwejbNO0heABeD1uGs7wsd.png

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.图表

第三方插件

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目录

    1. 在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'),
    ]
    
    1. 在setting.py中配置(media目录在应用目录上一级)
    MEDIA_ROOT = os.path.join(BASE_DIR,'media')    #以后会自动将文件上传到指定的文件夹中
    MEDIA_URL = '/media/'   #以后可以使用这个路由来访问上传的媒体文件
    
    1. 在浏览器上访问地址
    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标签来结束当前行。
  • 通过上述代码,我们可以在网站的首页上按照每行三列的格式展示项目列表。