Bootstrap

Django学习(三):模板继承、ModelForm的使用

主题:员工管理系统

参考:最新Python的web开发全家桶(django+前端+数据库)_哔哩哔哩_bilibili

1. 创建项目并生成数据库与数据表

新建项目,创建注册app,具体参见前两节,这里只展示在【models.py】中定义好的两个类,即对应着数据库中的两个数据表

#【models.py】
from django.db import models

# Create your models here.

class Department(models.Model):
    # 部门表
    title = models.CharField(verbose_name='部门标题',max_length=32) #verbose_name:对当前列的注解,可写可不写,写了便于理解

    def __str__(self): # 应对使用ModelForm时,输出部门对象无法输出title的情况 [1]
        return self.title

class UserInfo(models.Model):
    # 员工表
    name = models.CharField(verbose_name='姓名',max_length=16) # [2]
    password = models.CharField(verbose_name='密码', max_length=64)
    age = models.IntegerField(verbose_name='年龄')
    account = models.DecimalField(verbose_name="账户余额",max_digits=10,decimal_places=2,default=0) # 分别定义m,d
    create_time = models.DateTimeField(verbose_name="入职时间")

    # django中的约束:在填写性别时只能填写0或1  [3]
    gender_choices=(
        (0, "女"),
        (1, '男')
    )
    gender = models.SmallIntegerField(verbose_name="性别",choices=gender_choices)


    # 外键设置  [4]
    depart = models.ForeignKey(to="Department", to_field="id", on_delete=models.CASCADE)
    # to:关联的表,to_field关联表的列
    # django自动的外键命名方式:设置列名 + ‘_id’ , 如:depart_id
    # 设置关联表相关数据删除时的处理方式:
    # 1. 级联删除:depart = models.ForeignKey(to="Department",to_field="id",on_delete=models.CASCADE)
    # 2. 置空:depart = models.ForeignKey(to="Department",to_field="id",null=True,blank=True,on_delete=models.SET_NULL)

【注意】

  1. 在部门表中,需要重写类的__str__()函数,以保证在直接print对象时,可以输出指定的内容(这里是部门名称),以解决后面在form表单输出时直接输出部门对象导致无法输出部门名称的问题 [1]

  2. 在员工表中的成员定义中的verbose_name字段,是对当前成员的解释信息,后面使用ModelForm的时候会用到

  3. 在员工表中,在性别列的定义中,我们可以使用choice字段,来完成自定义的映射和数据约束,即使用一个以双元素元组为元素的元组定义映射,然后在声明成员时将这个元组当作choice字段的值 [2]

  4. 在员工表中的“所属部门”需要和部门表进行关联映射,所以需要设置为外键以满足数据约束(员工表中数据必须在部门表中存在),设置方式即

     外键名 = models.ForeignKey(to="关联表名", to_field="关联表的列(一般是主键)", on_delete=关联表删除数据时的处理方式)
    

2. 模板的继承

随便找的一个巨丑的模板:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>用户管理系统</title>
<style>
  /* 添加一些基本样式,可以根据需要进行自定义 */
  body {
    font-family: Arial, sans-serif;
  }
  .navbar {
    background-color: #333;
    overflow: hidden;
  }
  .navbar a {
    float: left;
    display: block;
    color: white;
    text-align: center;
    padding: 14px 16px;
    text-decoration: none;
  }
  .navbar a:hover {
    background-color: #ddd;
    color: black;
  }
  .title {
    text-align: center;
    padding: 20px;
    background-color: #f4f4f4;
  }
  table {
    width: 100%;
    border-collapse: collapse;
  }
  th, td {
    padding: 8px;
    text-align: left;
    border-bottom: 1px solid #ddd;
  }
  th {
    background-color: #f2f2f2;
  }
  .btn {
    padding: 4px 8px;
    border: none;
    cursor: pointer;
  }
  .btn-edit {
    background-color: #4CAF50;
    color: white;
  }
  .btn-delete {
    background-color: #f44336;
    color: white;
  }
  .input-container {
    text-align: center;
    padding: 20px;
  }
  .input-field {
    padding: 8px;
  }
</style>
</head>
<body>

<div class="title">
  <h1>用户管理系统</h1>
</div>

<div class="navbar">
  <a href="user_list.html">用户列表</a>
  <a href="department_list.html">部门列表</a>
</div>

<div class="title">
  <h2>用户列表</h2>
</div>

<table>
  <tr>
    <th>ID</th>
    <th>部门</th>
    <th>操作</th>
  </tr>
  <tr>
    <td>1</td>
    <td>销售部</td>
    <td>
      <button class="btn btn-edit">编辑</button>
      <button class="btn btn-delete">删除</button>
    </td>
  </tr>
  <tr>
    <td>2</td>
    <td>人力资源部</td>
    <td>
      <button class="btn btn-edit">编辑</button>
      <button class="btn btn-delete">删除</button>
    </td>
  </tr>
  <!-- 添加更多行... -->
</table>

<div class="input-container">
  <h2>添加新部门</h2>
  <input type="text" class="input-field" placeholder="部门名称">
  <button class="btn btn-submit">提交</button>
</div>

</body>
</html>

大概是这样的:在这里插入图片描述

但是有导航栏,功能齐全,我们想让后面写得所有页面都有导航栏,又不想每个页面都把这份代码写一遍(而且那样也不好修改),于是我们可以使用django的模板继承功能。

2.1 编写模板页面

在app【templates】目录下,编写【layout.html】文件(当然也可以不叫这个名)

<!-- 【layout.html】 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
    {% block title %}

    {% endblock %}
<style>
  /* 添加一些基本样式,可以根据需要进行自定义 */
  body {
    font-family: Arial, sans-serif;
  }
  .navbar {
    background-color: #333;
    overflow: hidden;
  }
  .navbar a {
    float: left;
    display: block;
    color: white;
    text-align: center;
    padding: 14px 16px;
    text-decoration: none;
  }
  .navbar a:hover {
    background-color: #ddd;
    color: black;
  }
  .title {
    text-align: center;
    padding: 20px;
    background-color: #f4f4f4;
  }
  a:link {
      color: white;
      background-color: transparent;
      text-decoration: none;
  }

  a:visited {
      color: white;
      background-color: transparent;
      text-decoration: none;
  }

  a:hover {
      color: white;
      background-color: transparent;
      text-decoration: underline;
  }

  a:active {
      color: white;
      background-color: transparent;
      text-decoration: underline;
  }
  table {
    width: 100%;
    border-collapse: collapse;
  }
  th, td {
    padding: 8px;
    text-align: left;
    border-bottom: 1px solid #ddd;
  }
  th {
    background-color: #f2f2f2;
  }
  .btn {
    padding: 4px 8px;
    border: none;
    cursor: pointer;
  }
  .btn-edit {
    background-color: #4CAF50;
    color: white;
  }
  .btn-delete {
    background-color: #f44336;
    color: white;
  }
  .input-container {
    text-align: center;
    padding: 20px;
  }
  .input-field {
    padding: 8px;
  }
</style>
</head>
<body>

<div class="title">
  <h1>员工管理系统</h1>
</div>

<div class="navbar">
    <a href="/depart/list/">部门列表</a>
    <a href="/user/list/">用户列表</a>

</div>

    {% block content %}

    {% endblock %}


<div class="title">
  <h2>欢迎访问员工管理系统</h2>
</div>

</body>
</html>

注意,在每个页面都要保留的部分和样式表,我们都原封不动的保留,但是在每个页面需要特异性的地方,我们使用格式:

    {% block 名称 %}

    {% endblock %}

进行占位,这样,【layout.html】就成了挖好了空的“代码模板”,供我们后续使用

2.2 继承并使用模板

在编写特定页面时,我们只需要继承模板页面,然后只需要填写在挖空部分的代码即可

<!-- 【depart_list.html】 -->
{% extends 'layout.html' %}  <!-- 使用此代码继承 layout.html -->

{% block title %}			<!-- 在占位符之间加入需要的代码 -->
    <title>部门列表</title>  
{% endblock %}

{% block content %}
    <h2 class="title">部门列表</h2>
    <button><a href="/depart/add/">添加部门</a></button>
    <table>
      <tr>
        <th>ID</th>
        <th>部门</th>
        <th>操作</th>
      </tr>
    <tbody>
        {% for obj in data_list %}
            <tr>
                <td>{{ obj.id }}</td>
                <td>{{ obj.title }}</td>
                <td>
                  <button class="btn btn-edit" ><a href="/depart/{{ obj.id }}/update/">编辑</a></button>
                  <button class="btn btn-delete"><a href="/depart/delete/?nid={{ obj.id }}">删除</a></button>
                </td>
            </tr>
        {% endfor %}


    </tbody>
    </table>

{% endblock %}

这样,我们就可以在多种不同页面中使用同一套布局和样式

<!-- 【depart_add.html】 -->
{% extends 'layout.html' %}

{% block title %}
    <title>添加部门</title>
{% endblock %}

{% block content %}
    <h2 class="title">添加部门</h2>
    <div class="input-container">
        <form method="post" ><!--post当前页面时,action可以不写-->

            {% csrf_token %} <!-- 别忘了!! -->
              <input type="text" name="depart_name" class="input-field" placeholder="部门名称">
              <button class="btn btn-submit">提交</button>
        </form>


    </div>


{% endblock %}

在这里插入图片描述

在这里插入图片描述

3. 完善部门管理功能

关于页面数据表的增加与删除,前面的学习笔记中都有记录,这里不再赘述,具体代码如下:

# 【urls.py】
from django.urls import path
from app01 import views

urlpatterns = [
    #path('admin/', admin.site.urls),
    path('depart/list/', views.depart_list),
    path('depart/add/', views.depart_add),
    path('depart/delete/', views.depart_del),

    #格式化的地址输入,/depart/和/update/之间必须有一个int型的数值
    path('depart/<int:nid>/update/', views.depart_upd),
]

# 【views.py】
from django.shortcuts import render, redirect
from app01.models import Department,UserInfo

# Create your views here.


def depart_list(request):
    ## 部门列表
    data_list = Department.objects.all()
    return render(request,'depart_list.html',{'data_list':data_list})

def depart_add(request):
    if request.method == 'GET':
        return render(request,'depart_add.html')
    depart_name = request.POST.get("depart_name")
    Department.objects.create(title=depart_name)
    return redirect('/depart/list/')

def depart_del(request):
    nid = request.GET.get('nid')
    Department.objects.filter(id=nid).delete()
    return redirect("/depart/list/")

def depart_upd(request,nid): # 多传一个nid的参数
    if request.method == 'GET':
        row_obj = Department.objects.filter(id=nid).first()
        return render(request,"depart_update.html",{"row_obj": row_obj})
    title = request.POST.get("depart_name")
    Department.objects.filter(id=nid).update(title=title)
    return redirect("/depart/list/")

这里主要新增了一个更改部门名称的功能,首先,修改数据和新增数据需要的操作和输入基本相同,其页面在新增部门的页面基础上稍作修改即可。主要是获取到修改部门的id,然后根据id在输入框中显示出部门名称。

<!-- 【depart_update.html】 -->
{% extends 'layout.html' %}

{% block title %}
    <title>编辑部门</title>
{% endblock %}

{% block content %}
    <h2 class="title">编辑部门</h2>
    <div class="input-container">
        <form method="post" ><!--post当前页面时,action可以不写-->

            {% csrf_token %} <!-- 别忘了!! -->
              <input type="text" name="depart_name" class="input-field" placeholder="部门名称" value="{{ row_obj.title }}">
              <button class="btn btn-submit">提交</button>
        </form>


    </div>


{% endblock %}

在修改部门的数据传递上,除去之前学到的使用url后面加上 ’ ?nid=xx ’ 的方式,我们也可以使用另外一种格式化在地址中加入数值的方式,即在【urls.py】的path中将地址写为:

path('depart/<int:nid>/update/', views.depart_upd),

/<int:nid>/代表url在当前位置必须加入一个 int 型数值的参数,参数名为 nid。

而相应的,在【views.py】定义的函数中,我们不需要使用 GET 方法获取nid,而是直接在函数参数中写上nid,即可直接获得nid的值。

def depart_upd(request,nid): 
    ....

在部门列表页面【depart_list.html】中通过url传递数据时,按照【urls.py】中定义的格式在相应位置写入nid的值即可

<button class="btn btn-edit" ><a href="/depart/{{ obj.id }}/update/">编辑</a></button>

4. 通过ModelForm功能实现员工信息管理

上面部门管理时进行数据管理的缺点:

- 用户提交数据没有校验,发生错误应该有错误提示
- 页面上每个字段都要独立写一遍
- 数据关联全凭手动

以新建用户为例,使用ModelForm组件:

#【views.py】
from django import forms
from app01 import models
class UserModelForm(forms.ModelForm):
    # 定义数据约束(默认非空,无需设置)
    age = forms.IntegerField(min_value=1) #重写age,限制其输入必须为正数

    class Meta:
        model = models.UserInfo
        # 选择需要输入数据的列名
        fields = ['name','password','age','account','create_time','gender','depart']

    def __init__(self,*args,**kwargs):
        super().__init__(*args,**kwargs)
        for name,field in self.fields.items():#修改构造函数,给所有输入框加上样式表和其他属性(如果有想用不同样式的,特判name即可)
            field.widget.attrs = {'class':'input-field','placeholder':field.label}
            
def user_add(request):
    if request.method == 'GET':
        form = UserModelForm()
        return render(request,'user_add.html',{"form":form})
    # 提交数据校验
    form = UserModelForm(data=request.POST)
    if form.is_valid():
        #数据合法,一键保存到数据库
        form.save() #直接保存到form对应那个的表里面
        return redirect('/user/list/')
    else:
        # 校验失败,在页面显示错误信息
        return render(request,'user_add.html',{"form":form})

由上面可以看到,在【views.py】页面,除去编写url对应函数外,需要声明一个继承自ModelForm的类,其中可以设置数据约束、选择待编辑列名和输入表单样式属性等,在函数部分,直接将form对象传入html文件即可。

而在获取到用户输入的表单信息后,可以进行数据合法性判断,若数据合法,可以使用form.save()一键保存到数据库,若不合法,返回错误信息到页面即可。

<!-- 【user_add.html】 -->
{% extends 'layout.html' %}

{% block title %}
    <title>添加用户</title>
{% endblock %}

{% block content %}
    <h2 class="title">添加用户</h2>
    <div class="input-container">
        <form method="post" ><!--post当前页面时,action可以不写-->

            {% csrf_token %} <!-- 别忘了!! -->
            {% for field in form %}
                <div>
                    {{ field.label }} : {{ field }}
                    <span>{{ field.errors.0 }}</span><!-- 显示错误信息!! -->
                <br><br><br>
                </div>
            {% endfor %}

            <button class="btn btn-edit">提交</button>
        </form>

    </div>


{% endblock %}

在html文件中,直接将form对象进行渲染,其中field.lable是之前在【models.py】定义列名时写下的verbose_name字段,field会直接打印相应对象(所以外键需要设置字符输出)

此外,若编辑用户时输入错误或不符合数据要求,还可以通过field.errors返回相应的错误信息 对输入者进行提示。

5. 用户编辑功能处理

# 【views.py】
def user_upd(request,nid):
    row_obj = UserInfo.objects.filter(id=nid).first()
    if request.method == 'GET':
        # 先获取nid对应的数据
        form = UserModelForm(instance=row_obj) # 表达自动显示默认值
        return render(request,'user_update.html',{"form":form})

    form = UserModelForm(data=request.POST,instance=row_obj)# 只有加上instance才能正确修改
    if form.is_valid():
        form.save()
        return redirect("/user/list/")
    return render(request,'user_update.html',{"form":form})

用户编辑页面和用户添加页面大致相同,需要注意的是在显示表单时,我们可以通过instance字段来使用目前获得到的数据库数据对表单进行填充

在获取到用户使用POST上传的编辑好的信息后,也是需要instance字段来保证正确修改(不然就会在数据库中直接新建一个用户,而不是修改)

<!-- 【user_update.html】 -->
{% extends 'layout.html' %}

{% block title %}
    <title>编辑用户</title>
{% endblock %}

{% block content %}
    <h2 class="title">编辑用户</h2>
    <div class="input-container">
        <form method="post" ><!--post当前页面时,action可以不写-->

            {% csrf_token %} <!-- 别忘了!! -->
            {% for field in form %}
                <div>
                    {{ field.label }} : {{ field }}
                    <span>{{ field.errors.0 }}</span><!-- 显示错误信息!! -->
                <br><br><br>
                </div>
            {% endfor %}

            <button class="btn btn-edit">提交</button>
        </form>

    </div>


{% endblock %}

6. 用户管理其他功能完善

# 【urls.py】
urlpatterns = [
    #【之前的那一部分】...
    
    path('user/list/', views.user_list),
    path('user/add/', views.user_add),
    path('user/delete/', views.user_del),

    # 格式化的地址输入,/depart/和/update/之间必须有一个int型的数值
    path('user/<int:nid>/update/', views.user_upd),
]

【user_list.html】与部门列表相差无几,可以注意的是使用了choice=gender_choice后需要使用get_gender_display来进行自动映射,而外键可以直接使用外键成员名即可完成自动映射

<!-- 【user_list.html】 -->
{% extends 'layout.html' %}

{% block title %}
    <title>员工列表</title>
{% endblock %}

{% block content %}
    <h2 class="title">员工列表</h2>
    <button class="btn btn-edit"><a href="/user/add/">⊕添加员工</a></button>
    <table>
        <tr>
        <th>ID</th>
        <th>姓名</th>
        <th>密码</th>
        <th>年龄</th>
        <th>余额</th>
        <th>创建时间</th>
        <th>性别</th>
        <th>部门</th>
        <th>操作</th>
        </tr>
        <tbody>
            {% for obj in data_list %}
                <tr>
                    <td>{{ obj.id }}</td>
                    <td>{{ obj.name}}</td>
                    <td>{{ obj.password }}</td>
                    <td>{{ obj.age}}</td>
                    <td>{{ obj.account }}</td>
                    <td>{{ obj.create_time|date:"Y-m-d"}}</td><!-- 模板语法不允许加括号,时间格式化 -->
                    <td>{{ obj.get_gender_display}}</td> <!-- 使用gender_choices自动映射 -->
                    <td>{{ obj.depart}}</td><!-- 直接写外键成员名就可以自动映射 -->
                    <td>
                      <button class="btn btn-edit" ><a href="/user/{{ obj.id }}/update/">编辑</a></button>
                      <button class="btn btn-delete"><a href="/user/delete/?nid={{ obj.id }}">删除</a></button>
                    </td>
                </tr>
            {% endfor %}


        </tbody>

    </table>

{% endblock %}

列表和删除功能与部门做法一样,不再赘述

# 【views.py】
def user_list(request):
    data_list = UserInfo.objects.all()
    return render(request,'user_list.html',{'data_list':data_list})

def user_del(request):
    nid = request.GET.get('nid')
    UserInfo.objects.filter(id=nid).delete()
    return redirect("/user/list/")

效果:

在这里插入图片描述

编辑用户时,可以做到输入检查

在这里插入图片描述

;