Django 04 :靓号管理【 靓号的增删改 + 搜索 + 分页 + 时间插件 + ModelForm与BootStrap】


上一篇文章: Django 03 :员工管理【 模板继承 + Form + ModelForm】

1、用户管理:编辑与删除

1.1、编辑用户

任务:

  • 点击编辑,跳转到编辑页面(将编辑行的ID携带过去)。

  • 编辑页面(显示所编辑用户的默认数据 ==> 根据ID获取并设置到页面中)

  • 提交:

    • 错误提示

    • 数据校验

    • 在数据库更新

      models.UserInfo.filter(id=4).update(...)
      

(1)前端基础效果

  • urls.py
path('user/<int:nid>/edit/', views.user_edit),
  • user_list.py
<td>
                            <a class="btn btn-primary btn-xs" href="/user/{{ obj.id }}/edit/">编辑</a>
                            <a class="btn btn-danger btn-xs" href="#">删除</a>
                        </td>
  • user_edit.html

可以copyuser_add.html

{% extends 'layout.html' %}


{% block content %}

    <div class="container">
        <div class="panel panel-default">
            <div class="panel-heading">
                <h3 class="panel-title"> 编辑用户 </h3>
            </div>
            <div class="panel-body">
                <form method="post" novalidate> {# novalidate : 关闭浏览器帮我们做的校验,用自己的 #}
                    {% csrf_token %}

                    {% for field in form %}
                        <div class="form-group">
                            <label>{{ field.label }}</label>
                            {{ field }}
                            <span style="color: red;">{{ field.errors.0 }}</span> {# 错误信息可能有很多,我们只显示第0个就好了 #}
                        </div>
                    {% endfor %}

                    <button type="submit" class="btn btn-primary">提 交</button>
                </form>
            </div>
        </div>
    </div>

{% endblock %}

如何展示默认数据呢?

views.py

def user_edit(request, nid):
    """编辑用户"""
    # 根据ID获取数据库中数据
    row_object = models.UserInfo.objects.filter(id=nid).first()
    form = MyForm(instance=row_object)  # 使用 instance ,ModelForm就可以默认将数据显示出来
    return render(request, 'user_edit.html', {"form": form})

就这几行代码即可实现基本效果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zplFf4WI-1645970639887)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220225150607031.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D7X18nMs-1645970639894)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220225150617303.png)]

(2)提交

修改一下视图函数(views.py)就OK了

  • views.py
def user_edit(request, nid):
    """编辑用户"""
    if request.method == "GET":
        # 根据ID获取数据库中数据
        row_object = models.UserInfo.objects.filter(id=nid).first()
        form = MyForm(instance=row_object)  # 使用 instance ,ModelForm就可以默认将数据显示出来
        return render(request, 'user_edit.html', {"form": form})

    row_object = models.UserInfo.objects.filter(id=nid).first()  # 这行代码勿忘,否则就成新增用户为不是编辑用户了
    form = MyForm(data=request.POST, instance=row_object)
    if form.is_valid():
        form.save()
        return redirect('/user/list/')
    # 如果数据不合格,则返回"user_edit.html"
    return render(request, 'user_edit.html', {"form": form})

效果展示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YFCeQoU8-1645970639898)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220225162448082.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JfuWtSOP-1645970639898)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220225162532504.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O7SVNB0v-1645970639898)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220225162546154.png)]

(3)补充知识

1、小优化:员工的入职时间精确到日就OK了

  • models.py
# create_time = models.DateTimeField(verbose_name="入职时间")
create_time = models.DateField(verbose_name="入职时间")

注意,既然变更了数据库,就要执行那俩指令了

python manage.py makemigrations

python manage.py migrate

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-noImDu2H-1645970639899)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220225163815767.png)]

2、form.save() 默认保存的是用户输入的所有数据,如果想要再用户输入以外增加一点值

  • views.py
    if form.is_valid():
        # 默认保存的是用户输入的所有数据,如果想要再用户输入以外增加一点值
        # form.instance.字段名 = 值 如:form.age = 20
        form.save()
        return redirect('/user/list/')

1.2、删除用户

基本和之前写的【删除部门】一样

  • user_list.html

    添加 href

    <a class="btn btn-danger btn-xs" href="/user/{{ obj.id }}/detele">删除</a>
    
  • urls.py

    path("user/<int:nid>/detele/", views.user_detele),
    
  • views.py

    def user_detele(request, nid):
        """删除用户"""
        models.UserInfo.objects.filter(id=nid).delete()
        return redirect("/user/list/")
    

这样就完成删除功能了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8AXhCAQE-1645970639899)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220225164914328.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OgLHmapw-1645970639899)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220225164920809.png)]

2、靓号管理

靓号:通迅所使用的手机号码

2.1、数据库的表结构

首先就是设计表结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JQhlfEIM-1645970639900)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220225165306216.png)]

根据表结构的需求,在models.py中创建类(由类生成数据库中的表)。

  • models.py
class MobileNum(models.Model):
    """靓号表"""
    mobile = models.CharField(verbose_name="手机号", max_length=11)
    # 想要允许为空 null=True, blank=True
    price = models.IntegerField(verbose_name="价格", default=0)

    # 靓号级别
    level_choices = (
        (1, "1级"),
        (2, "2级"),
        (3, "3级"),
        (4, "4级"),
    )
    level = models.SmallIntegerField(verbose_name="级别", choices=level_choices, default=1)  # default=1 : 默认为 1

    status_choices = (
        (1, "已占用"),
        (2, "未使用")
    )
    status = models.SmallIntegerField(verbose_name="状态", choices=status_choices, default=2)
python manage.py makemigrations

python manage.py migrate

我们先用SQL语句创建点数据:

insert into app01_mobilenum(mobile,price,level,status)values("111111111",19,1,1);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xFsvVUFD-1645970639900)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220225170121248.png)]

2.2、靓号列表

  • URL

  • 函数

    • 获取所有的靓号

    • 结合 html + render 将靓号罗列出来

      id	号码	价格	级别(中文)	状态(中文)
      

开始敲键盘

  • urls.py

    path("mobile/list/", views.mobile_list),
    
  • views.py

    def mobile_list(request):
        """ 靓号列表 """
        queryset = models.MobileNum.objects.all().order_by("-level")
        return render(request, 'mobile_list.html', {"queryset": queryset})
    
  • mobile_list.html

    直接copyuser_list.html, 修改文字即可

    {% extends 'layout.html' %}
    
    {% block content %}
        {# 用户列表 #}
        <div class="container">
            <div style="margin-bottom: 10px">
                <a class="btn btn-success" href="/user/add/">
                    {# 可以添加一个 target="_blank" : 使页面在新页面打开,如果不设置,会在原页面打开(这里我们还是在当前页面打开) #}
                    <span class="glyphicon glyphicon-plus-sign" aria-hidden="true"></span>
                    新建用户
                </a>
                {#新建一个 depart_add.html , 用来写新建部门界面#}
            </div>
            <div class="panel panel-default">
                <!-- Default panel contents -->
                <div class="panel-heading">
                    <span class="glyphicon glyphicon-th-list" aria-hidden="true"></span>
                    用户列表
                </div>
    
                <!-- 表格部分 -->
                <table class="table table-bordered">
                    <thead>
                    <tr>
                        <th>ID</th>
                        <th>号码</th>
                        <th>价格</th>
                        <th>级别</th>
                        <th>状态</th>
                        <th>操作</th>
                    </tr>
                    </thead>
                    <tbody>
                    {% for obj in queryset %}
                        <tr>
                            <th>{{ obj.id }}</th>
                            <td>{{ obj.mobile }}</td>
                            <td>{{ obj.price }}</td>
                            <td>{{ obj.get_level_display }}</td>
                            <td>{{ obj.get_status_display }}</td>
                            <td>
                                <a class="btn btn-primary btn-xs" href="/user/{{ obj.id }}/edit/">编辑</a>
                                <a class="btn btn-danger btn-xs" href="/user/{{ obj.id }}/detele">删除</a>
                            </td>
                        </tr>
                    {% endfor %}
                    </tbody>
                </table>
            </div>
        </div>
    {% endblock %}
    
  • layout.html

    在导航条添加【靓号管理】

    <ul class="nav navbar-nav">
                    <li><a href="/depart/list/">部门管理</a></li>
                    <li><a href="/user/list/">用户管理</a></li>
                    <li><a href="/mobile/list/">靓号管理</a></li>
                </ul>
    

效果展示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iLbnYjTS-1645970639900)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220225171238879.png)]

2.3、靓号:新建、编辑、删除

  • 列表点击跳转:/pretty/add/

  • URL

  • ModelForm类

    from django import forms
    
    class MobileModelForm(forms.ModelForm):
    	...
    
  • 函数

    • 实例化类的对象
    • 通过render将对象传入到HTML中。
    • 模板的循环展示所有的字段。
  • 点击提交

    • 数据校验
    • 保存到数据库
    • 跳转回靓号列表

从【用户管理】那Copy就OK了,删删改改就完成了基础的【靓号管理】

(1)新建靓号

  • urls.py

    path("mobile/list/", views.mobile_list),
    path("mobile/add/", views.mobile_add),
    
  • mobile_add.html

    {% extends 'layout.html' %}
    
    
    {% block content %}
    
        <div class="container">
            <div class="panel panel-default">
                <div class="panel-heading">
                    <h3 class="panel-title"> 新建靓号 </h3>
                </div>
                <div class="panel-body">
                    <form method="post" novalidate> {# novalidate : 关闭浏览器帮我们做的校验,用自己的 #}
                        {% csrf_token %}
                        {% for field in form %}
                            <div class="form-group">
                                <label>{{ field.label }}</label>
                                {{ field }}
                                <span style="color: red;">{{ field.errors.0 }}</span> {# 错误信息可能有很多,我们只显示第0个就好了 #}
                            </div>
                        {% endfor %}
    
                        <button type="submit" class="btn btn-primary">提 交</button>
                    </form>
                </div>
            </div>
        </div>
    
    {% endblock %}
    
    
  • views.py

    class MobileModelForm(forms.ModelForm):
        """靓号管理"""
        class Meta:
            model = models.MobileNum
            fields = ["mobile", "price", "level", "status"]
    
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            for name, field in self.fields.items():
                field.widget.attrs = {"class": "form-control", "placeholder": field.label}
    
    
    def mobile_add(request):
        """"添加靓号"""
        if request.method == "GET":
            form = MobileModelForm()
            return render(request, "mobile_add.html", {"form": form})
    
        form = MobileModelForm(data=request.POST)
        if form.is_valid():
            print(form.cleaned_data)
            form.save()
            return redirect("/mobile/list/")
        return render(request, 'mobile_add.html', {"form": form})
    
    

效果展示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PGDeEql5-1645970639900)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220225193003797.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UjDzVLzl-1645970639901)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220225193727992.png)]

优化(添加验证)
  • 验证方式1:字段+正则表达式

通过正则表达式,限制输入格式

  • views.py
from django.core.validators import RegexValidator


class MobileModelForm(forms.ModelForm):
    """靓号管理"""
    mobile = forms.CharField(
        label="手机号",
        validators=[RegexValidator(r'^1[3-9]\d{9}', "手机号格式错误",],  # 通过正则表达式限制输入格式,1开头,然后3~9,然后再来就个数
        # 当然这个正则表达式可以叠加,多重限制
    )

    class Meta:

效果展示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QayJU9SE-1645970639901)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220225211554427.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nVxTX3qr-1645970639901)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220225212019142.png)]

可能遇到的bug:

为何惊现双输入框?!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7fYMeG0E-1645970639902)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220225200948886.png)]

原因:

虚晃一枪

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JKkxomEK-1645970639902)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220225201012549.png)]

然后:

TypeError: 'str' object is not callable

从报错说某个变量不可调用,找了半天才发现,括号打错地方了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gmsuHEyv-1645970639902)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220225211728804.png)]

  • 验证方式2:钩子方法
from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError


class MobileModelForm(forms.ModelForm):
    """靓号管理"""
    # # 验证:方式1
    # mobile = forms.CharField(
    #     label="手机号",
    #     validators=[RegexValidator(r'^1[3-9]\d{9}$', "手机号格式错误")],  # 通过正则表达式限制输入格式,1开头,然后3~9,然后再来就个数
    #     # 当然这个正则表达式可以叠加,多重限制
    # )

    class Meta:
        model = models.MobileNum
        fields = ["mobile", "price", "level", "status"]

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for name, field in self.fields.items():
            field.widget.attrs = {"class": "form-control", "placeholder": field.label}

    # 验证:方式2
    def clean_mobile(self):
        txt_mobile = self.cleaned_data["mobile"]

        if len(txt_mobile) != 11:
            # 验证不通过
            raise ValidationError("格式错误")

        exists = models.MobileNum.objects.filter(mobile=txt_mobile).exists()
        if exists:
            raise ValidationError("手机号已存在")

        # 验证通过,用户输入的值返回
        return txt_mobile

效果展示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7UtcP1Qm-1645970639902)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220226153602191.png)]

(2)编辑靓号

仿着编辑用户来就好了

  • mobile_list.html

    <a class="btn btn-primary btn-xs" href="/mobile/{{ obj.id }}/edit/">编辑</a>
    <a class="btn btn-danger btn-xs" href="/mobile/{{ obj.id }}/detele">删除</a>
    
  • urls.py

    path('mobile/<int:nid>/edit/', views.mobile_edit),
    
  • views.py

    def mobile_edit(request, nid):
        """编辑用户"""
        row_object = models.MobileNum.objects.filter(id=nid).first()  # 这行代码勿忘,否则就成新增用户为不是编辑用户了
        if request.method == "GET":
            # 根据ID获取数据库中数据
            form = MobileModelForm(instance=row_object)  # 使用 instance ,ModelForm就可以默认将数据显示出来
            return render(request, 'mobile_edit.html', {"form": form})
    
        form = MobileModelForm(data=request.POST, instance=row_object)
        if form.is_valid():
            form.save()
            return redirect('/mobile/list/')
        # 如果数据不合格,则返回"user_edit.html"
        return render(request, 'mobile_edit.html', {"form": form})
    
  • mobile_edit.html

    {% extends 'layout.html' %}
    
    
    {% block content %}
    
        <div class="container">
            <div class="panel panel-default">
                <div class="panel-heading">
                    <h3 class="panel-title"> 编辑靓号 </h3>
                </div>
                <div class="panel-body">
                    <form method="post" novalidate> {# novalidate : 关闭浏览器帮我们做的校验,用自己的 #}
                        {% csrf_token %}
    
                        {% for field in form %}
                            <div class="form-group">
                                <label>{{ field.label }}</label>
                                {{ field }}
                                <span style="color: red;">{{ field.errors.0 }}</span> {# 错误信息可能有很多,我们只显示第0个就好了 #}
                            </div>
                        {% endfor %}
    
                        <button type="submit" class="btn btn-primary">提 交</button>
                    </form>
                </div>
            </div>
        </div>
    
    {% endblock %}
    
    

效果展示:
若点击【编辑】,传到的是【用户编辑】,且代码无误,重启项目即可

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j0j6qeYK-1645970639903)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220226154256016.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fVuUJI2c-1645970639903)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220226154746973.png)]

(3)删除靓号

  • mobile_list.html

     <td>
                                <a class="btn btn-primary btn-xs" href="/mobile/{{ obj.id }}/edit/">编辑</a>
                                <a class="btn btn-danger btn-xs" href="/mobile/{{ obj.id }}/detele">删除</a>
                            </td>
    
  • urls.p

    path("mobile/<int:nid>/detele/", views.mobile_detele),
    
  • views.py

    def mobile_detele(request, nid):
        """删除用户"""
        models.MobileNum.objects.filter(id=nid).delete()
        return redirect("/mobile/list/")
    

效果展示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mRmzd9Zc-1645970639903)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220226155633513.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lid5KjYd-1645970639903)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220226155639244.png)]

(4)优化:编辑靓号

如果我们不想【编辑靓号】的界面和【新建靓号】的界面一样

那么就要为【编辑】单写一个ModelForm

修改一下views.py即可

  • view.py

    class MobileEditModelForm(forms.ModelForm):
        mobile = forms.CharField(disabled=True, label="手机号")  # 设置手机号不可改变
    
        class Meta:
            model = models.MobileNum
            fields = ['mobile', 'price', 'level', 'status']
    
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            for name, field in self.fields.items():
                field.widget.attrs = {"class": "form-control", "placeholder": field.label}
    
    
    def mobile_edit(request, nid):
        """编辑用户"""
        row_object = models.MobileNum.objects.filter(id=nid).first()  # 这行代码勿忘,否则就成新增用户为不是编辑用户了
        if request.method == "GET":
            # 根据ID获取数据库中数据
            form = MobileEditModelForm(instance=row_object)  # 使用 instance ,ModelForm就可以默认将数据显示出来
            return render(request, 'mobile_edit.html', {"form": form})
    
        form = MobileEditModelForm(data=request.POST, instance=row_object)
        if form.is_valid():
            form.save()
            return redirect('/mobile/list/')
        # 如果数据不合格,则返回"user_edit.html"
        return render(request, 'mobile_edit.html', {"form": form})
    
    

效果展示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3Wer8ucx-1645970639904)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220226162114705.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ybMVyyLa-1645970639904)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220226162131644.png)]

(5)优化:手机号不可重复

  • 添加:【正则表达式】

    新建靓号时,输入的手机号不可为已存在的手机号

  • 编辑:【正则表达式】

    编辑靓号时,手机号更改后的手机号不可已存在的手机号

  • 【新建靓号】

    常用的仨语句

    # 返回[obj,obj,obj]
    queryset = models.PrettyNum.objects.filter(mobile="1888888888")
    
    obj = models.PrettyNum.objects.filter(mobile="1888888888").first()
    
    # 返回 True/False
    exists = models.PrettyNum.objects.filter(mobile="1888888888").exists()
    

    我们要修改【钩子】

    exists = models.MobileNum.objects.filter(mobile=txt_mobile).exists()
    if exists:
    	raise ValidationError("手机号已存在")
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jyt2l7Zw-1645970639904)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220226171044396.png)]

  • 【编辑】

    只用修改一句

    exists = models.MobileNum.objects.exclude(id=self.instance.pk).filter(mobile=txt_mobile).exists()
    

    完整展示:

    class MobileEditModelForm(forms.ModelForm):
        # mobile = forms.CharField(disabled=True, label="手机号")  # 设置手机号不可改变
    
        class Meta:
            model = models.MobileNum
            fields = ['mobile', 'price', 'level', 'status']
    
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            for name, field in self.fields.items():
                field.widget.attrs = {"class": "form-control", "placeholder": field.label}
    
        # 验证:方式2
        def clean_mobile(self):
            print(self.instance.pk)  # 当前编辑的哪一行的ID
            txt_mobile = self.cleaned_data["mobile"]
            exists = models.MobileNum.objects.exclude(id=self.instance.pk).filter(mobile=txt_mobile).exists()
            if exists:
                raise ValidationError("手机号已存在")
    
            # 验证通过,用户输入的值返回
            return txt_mobile
    

    效果展示:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-unjOCTED-1645970639904)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220226180615063.png)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t8UDBrvv-1645970639905)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220226180638167.png)]

总结

类似这种校验方式,实际上我们只用在ModelForm中做修改,用上面讲的两种方式:

  • 字段 + 正则表达式
  • 钩子函数

一般验证可以放在正则表达式,深层次一点的就放在钩子函数中即可

2.4、搜索靓号

常用搜索方式:

# filter中放搜索条件
models.PrettyNum.objects.filter(mobile="19999999991",id=12)

# 或者换一种写法
data_dict = {"mobile":"19999999991","id":123}
models.PrettyNum.objects.filter(**data_dict)

对于一些变量的控制:

models.PrettyNum.objects.filter(id=12)       # ID等于12
models.PrettyNum.objects.filter(id__gt=12)   # ID大于12
models.PrettyNum.objects.filter(id__gte=12)  # ID大于等于12
models.PrettyNum.objects.filter(id__lt=12)   # ID小于12
models.PrettyNum.objects.filter(id__lte=12)  # ID小于等于12

data_dict = {"id__lte":12}
models.PrettyNum.objects.filter(**data_dict)
models.PrettyNum.objects.filter(mobile="999")               # 等于
models.PrettyNum.objects.filter(mobile__startswith="1999")  # 筛选出以1999开头
models.PrettyNum.objects.filter(mobile__endswith="999")     # 筛选出以999结尾
models.PrettyNum.objects.filter(mobile__contains="999")     # 筛选出包含999

data_dict = {"mobile__contains":"999"}
models.PrettyNum.objects.filter(**data_dict)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5MqJd1wK-1645970639905)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220226195647774.png)]

还可以把“Go!”换成一个图标

<div style="margin-bottom: 10px" class="clearfix">
            <a class="btn btn-success" href="/mobile/add/">
                {# 可以添加一个 target="_blank" : 使页面在新页面打开,如果不设置,会在原页面打开(这里我们还是在当前页面打开) #}
                <span class="glyphicon glyphicon-plus-sign" aria-hidden="true"></span>
                新建靓号
            </a>
            {#新建一个 depart_add.html , 用来写新建部门界面#}

            <div style="float: right;width: 300px">
                {# 搜索框 #}
                <div class="col-lg-6">
                    <div class="input-group">
                        <input type="text" class="form-control" placeholder="Search for...">
                        <span class="input-group-btn">
                            <button class="btn btn-default" type="button">Go!</button>
                        </span>
                    </div>
                </div>
            </div>

        </div>

效果展示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FgfBK6Pd-1645970639905)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220227012339682.png)]

若遇到搜索框偏左,勿忘设置宽度属性width: 300px

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C9MVFOIw-1645970639905)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220227005446998.png)]

前端效果出来了,下面就是如何真正实现【搜索】

【后端】

views.py

def mobile_list(request):
    """ 靓号列表 """
    data_dict = {}
    search_result = request.GET.get('q', "")  # 如果有值就返回“q”,如果没有就返回空“”
    if search_result:
        data_dict["mobile__contains"] = search_result

    queryset = models.MobileNum.objects.filter(**data_dict).order_by("-level")
    return render(request, 'mobile_list.html', {"queryset": queryset, "search_result": search_result})

【前端】:修改三点

  • 建立form

    <form method="get">
    	...
    </form>
    
  • 对设置namevalue

    <input type="text" name="q" class="form-control" placeholder="Search for..."
                                   value="{{ search_result }}">
    
  • 对搜索键添加属性type="submit"

    <button class="btn btn-default" type="button" type="submit">
    

mobile_list.html代码展示:

<div style="float: right;width: 300px;">
                <form method="get">
                    <div class="input-group">
                        <input type="text" name="q" class="form-control" placeholder="Search for..."
                               value="{{ search_result }}">
                        <span class="input-group-btn">
                        <button class="btn btn-default" type="submit">
                            <span class="glyphicon glyphicon-search" aria-hidden="true"></span>
                        </button>
                      </span>
                    </div>
                </form>
            </div>

若没有搜索效果,可能原因:

objects.filter(**data_dict) 而不是 objects.all()

queryset = models.MobileNum.objects.filter(**data_dict).order_by("-level")

效果展示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Brk90B57-1645970639906)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220227013131554.png)]

2.5、分页

具体见视频:2022 B站最详细django3教程(django从入门到实践)_哔哩哔哩_bilibili

内容暂省略

当靓号多了,我们不可能一个界面去展示,应该【分页】展示

(1)先造点数据

def mobile_list(request):
    """ 靓号列表 """

    for i in range(300):
        models.MobileNum.objects.create(mobile="12343212343", price=20, level=1, status=1)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rpFRsyIp-1645970639906)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220227025828915.png)]

(2)页码前端效果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zMSfbmvJ-1645970639906)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220227025904086.png)]

(3)分页

大致原理:

一次显示十个数据、

queryset = models.PrettyNum.objects.all()

queryset = models.PrettyNum.objects.filter(id=1)[0:10]


# 第1页
queryset = models.PrettyNum.objects.all()[0:10]

# 第2页
queryset = models.PrettyNum.objects.all()[10:20]

# 第3页
queryset = models.PrettyNum.objects.all()[20:30]
data = models.PrettyNum.objects.all().count()
data = models.PrettyNum.objects.filter(id=1).count()

然后写一些逻辑,让代码按数据量自己分页,达成下面效果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v4F5ozN3-1645970639907)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220227030109581.png)]

其中比较繁琐,具体看视频,暂略

2.6、分页组件的封装

任务:希望把【分页】这个功能打包成组件,方便以后使用(可以迁移到用户管理等界面)

  • 封装分页类

    • 从头到尾开发
    • 写项目用【pagination.py】公共组件。
  • 注意:搜索 + 分页情况下,存在小Bug,搜索和分页不能同时正常进行,这个需要特殊处理一下。

    分页时候,保留原来的搜索条件
    
    http://127.0.0.1:8000/pretty/list/?q=888
    http://127.0.0.1:8000/pretty/list/?page=1
    
    http://127.0.0.1:8000/pretty/list/?q=888&page=23
    

直接上代码:

  • views.py

    关键代码:

    page_object = Pagination(request, queryset)
    
        context = {
            "search_data": search_result,
    
            "queryset": page_object.page_queryset,  # 分完页的数据
            "page_string": page_object.html()  # 页码
        }
        return render(request, 'mobile_list.html', context)
    

    整体代码:

    from app01.utils.pagination import Pagination
    
    
    def mobile_list(request):
        """ 靓号列表 """
        data_dict = {}
        search_result = request.GET.get('q', "")  # 如果有值就返回“q”,如果没有就返回空“”
        if search_result:
            data_dict["mobile__contains"] = search_result
    
        queryset = models.MobileNum.objects.filter(**data_dict).order_by("-level")
    
        page_object = Pagination(request, queryset)
    
        context = {
            "search_data": search_result,
    
            "queryset": page_object.page_queryset,  # 分完页的数据
            "page_string": page_object.html()  # 页码
        }
        return render(request, 'mobile_list.html', context)
    
  • app01/utils/pagination.py

    """
    自定义的分页组件,以后如果想要使用这个分页组件,你需要做如下几件事:
    
    在视图函数中:
        def pretty_list(request):
    
            # 1.根据自己的情况去筛选自己的数据
            queryset = models.PrettyNum.objects.all()
    
            # 2.实例化分页对象
            page_object = Pagination(request, queryset)
    
            context = {
                "queryset": page_object.page_queryset,  # 分完页的数据
                "page_string": page_object.html()       # 生成页码
            }
            return render(request, 'pretty_list.html', context)
    
    在HTML页面中
    
        {% for obj in queryset %}
            {{obj.xx}}
        {% endfor %}
    
        <ul class="pagination">
            {{ page_string }}
        </ul>
    
    """
    
    from django.utils.safestring import mark_safe
    
    
    class Pagination(object):
    
        def __init__(self, request, queryset, page_size=10, page_param="page", plus=5):
            """
            :param request: 请求的对象
            :param queryset: 符合条件的数据(根据这个数据给他进行分页处理)
            :param page_size: 每页显示多少条数据
            :param page_param: 在URL中传递的获取分页的参数,例如:/etty/list/?page=12
            :param plus: 显示当前页的 前或后几页(页码)
            """
    
            from django.http.request import QueryDict
            import copy
            query_dict = copy.deepcopy(request.GET)
            query_dict._mutable = True
            self.query_dict = query_dict
    
            self.page_param = page_param
            page = request.GET.get(page_param, "1")
    
            if page.isdecimal():
                page = int(page)
            else:
                page = 1
    
            self.page = page
            self.page_size = page_size
    
            self.start = (page - 1) * page_size
            self.end = page * page_size
    
            self.page_queryset = queryset[self.start:self.end]
    
            total_count = queryset.count()
            total_page_count, div = divmod(total_count, page_size)
            if div:
                total_page_count += 1
            self.total_page_count = total_page_count
            self.plus = plus
    
        def html(self):
            # 计算出,显示当前页的前5页、后5页
            if self.total_page_count <= 2 * self.plus + 1:
                # 数据库中的数据比较少,都没有达到11页。
                start_page = 1
                end_page = self.total_page_count
            else:
                # 数据库中的数据比较多 > 11页。
    
                # 当前页<5时(小极值)
                if self.page <= self.plus:
                    start_page = 1
                    end_page = 2 * self.plus + 1
                else:
                    # 当前页 > 5
                    # 当前页+5 > 总页面
                    if (self.page + self.plus) > self.total_page_count:
                        start_page = self.total_page_count - 2 * self.plus
                        end_page = self.total_page_count
                    else:
                        start_page = self.page - self.plus
                        end_page = self.page + self.plus
    
            # 页码
            page_str_list = []
    
            self.query_dict.setlist(self.page_param, [1])
            page_str_list.append('<li><a href="?{}">首页</a></li>'.format(self.query_dict.urlencode()))
    
            # 上一页
            if self.page > 1:
                self.query_dict.setlist(self.page_param, [self.page - 1])
                prev = '<li><a href="?{}">上一页</a></li>'.format(self.query_dict.urlencode())
            else:
                self.query_dict.setlist(self.page_param, [1])
                prev = '<li><a href="?{}">上一页</a></li>'.format(self.query_dict.urlencode())
            page_str_list.append(prev)
    
            # 页面
            for i in range(start_page, end_page + 1):
                self.query_dict.setlist(self.page_param, [i])
                if i == self.page:
                    ele = '<li class="active"><a href="?{}">{}</a></li>'.format(self.query_dict.urlencode(), i)
                else:
                    ele = '<li><a href="?{}">{}</a></li>'.format(self.query_dict.urlencode(), i)
                page_str_list.append(ele)
    
            # 下一页
            if self.page < self.total_page_count:
                self.query_dict.setlist(self.page_param, [self.page + 1])
                prev = '<li><a href="?{}">下一页</a></li>'.format(self.query_dict.urlencode())
            else:
                self.query_dict.setlist(self.page_param, [self.total_page_count])
                prev = '<li><a href="?{}">下一页</a></li>'.format(self.query_dict.urlencode())
            page_str_list.append(prev)
    
            # 尾页
            self.query_dict.setlist(self.page_param, [self.total_page_count])
            page_str_list.append('<li><a href="?{}">尾页</a></li>'.format(self.query_dict.urlencode()))
    
            search_string = """
                <li>
                    <form style="float: left;margin-left: -1px" method="get">
                        <input name="page"
                               style="position: relative;float:left;display: inline-block;width: 80px;border-radius: 0;"
                               type="text" class="form-control" placeholder="页码">
                        <button style="border-radius: 0" class="btn btn-default" type="submit">跳转</button>
                    </form>
                </li>
                """
    
            page_str_list.append(search_string)
            page_string = mark_safe("".join(page_str_list))
            return page_string
    
    
  • mobile_list.html

    关键代码:在mobile.html底部添加【分页】

    {# 分页 #}
            <div class="clearfix">
                <ul class="pagination">
                    {{ page_string }}
                </ul>
            </div>
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WrdpiwjE-1645970639907)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220227115153612.png)]

效果展示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9jDXkqcz-1645970639907)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220227114803619.png)]

使用分页组件

如何使用我们这个【分页组件】呢?三步走

  • 准备好pagination.py

    from django.utils.safestring import mark_safe
    
    
    class Pagination(object):
    
        def __init__(self, request, queryset, page_size=10, page_param="page", plus=5):
            """
            :param request: 请求的对象
            :param queryset: 符合条件的数据(根据这个数据给他进行分页处理)
            :param page_size: 每页显示多少条数据
            :param page_param: 在URL中传递的获取分页的参数,例如:/etty/list/?page=12
            :param plus: 显示当前页的 前或后几页(页码)
            """
    
            from django.http.request import QueryDict
            import copy
            query_dict = copy.deepcopy(request.GET)
            query_dict._mutable = True
            self.query_dict = query_dict
    
            self.page_param = page_param
            page = request.GET.get(page_param, "1")
    
            if page.isdecimal():
                page = int(page)
            else:
                page = 1
    
            self.page = page
            self.page_size = page_size
    
            self.start = (page - 1) * page_size
            self.end = page * page_size
    
            self.page_queryset = queryset[self.start:self.end]
    
            total_count = queryset.count()
            total_page_count, div = divmod(total_count, page_size)
            if div:
                total_page_count += 1
            self.total_page_count = total_page_count
            self.plus = plus
    
        def html(self):
            # 计算出,显示当前页的前5页、后5页
            if self.total_page_count <= 2 * self.plus + 1:
                # 数据库中的数据比较少,都没有达到11页。
                start_page = 1
                end_page = self.total_page_count
            else:
                # 数据库中的数据比较多 > 11页。
    
                # 当前页<5时(小极值)
                if self.page <= self.plus:
                    start_page = 1
                    end_page = 2 * self.plus + 1
                else:
                    # 当前页 > 5
                    # 当前页+5 > 总页面
                    if (self.page + self.plus) > self.total_page_count:
                        start_page = self.total_page_count - 2 * self.plus
                        end_page = self.total_page_count
                    else:
                        start_page = self.page - self.plus
                        end_page = self.page + self.plus
    
            # 页码
            page_str_list = []
    
            self.query_dict.setlist(self.page_param, [1])
            page_str_list.append('<li><a href="?{}">首页</a></li>'.format(self.query_dict.urlencode()))
    
            # 上一页
            if self.page > 1:
                self.query_dict.setlist(self.page_param, [self.page - 1])
                prev = '<li><a href="?{}">上一页</a></li>'.format(self.query_dict.urlencode())
            else:
                self.query_dict.setlist(self.page_param, [1])
                prev = '<li><a href="?{}">上一页</a></li>'.format(self.query_dict.urlencode())
            page_str_list.append(prev)
    
            # 页面
            for i in range(start_page, end_page + 1):
                self.query_dict.setlist(self.page_param, [i])
                if i == self.page:
                    ele = '<li class="active"><a href="?{}">{}</a></li>'.format(self.query_dict.urlencode(), i)
                else:
                    ele = '<li><a href="?{}">{}</a></li>'.format(self.query_dict.urlencode(), i)
                page_str_list.append(ele)
    
            # 下一页
            if self.page < self.total_page_count:
                self.query_dict.setlist(self.page_param, [self.page + 1])
                prev = '<li><a href="?{}">下一页</a></li>'.format(self.query_dict.urlencode())
            else:
                self.query_dict.setlist(self.page_param, [self.total_page_count])
                prev = '<li><a href="?{}">下一页</a></li>'.format(self.query_dict.urlencode())
            page_str_list.append(prev)
    
            # 尾页
            self.query_dict.setlist(self.page_param, [self.total_page_count])
            page_str_list.append('<li><a href="?{}">尾页</a></li>'.format(self.query_dict.urlencode()))
    
            search_string = """
                <li>
                    <form style="float: left;margin-left: -1px" method="get">
                        <input name="page"
                               style="position: relative;float:left;display: inline-block;width: 80px;border-radius: 0;"
                               type="text" class="form-control" placeholder="页码">
                        <button style="border-radius: 0" class="btn btn-default" type="submit">跳转</button>
                    </form>
                </li>
                """
    
            page_str_list.append(search_string)
            page_string = mark_safe("".join(page_str_list))
            return page_string
    
    
  • 修改视图函数

    from app01.utils.pagination import Pagination
    
    
    def mobile_list(request):
        """ 靓号列表 """
        data_dict = {}
        search_result = request.GET.get('q', "")  # 如果有值就返回“q”,如果没有就返回空“”
        if search_result:
            data_dict["mobile__contains"] = search_result
    
        queryset = models.MobileNum.objects.filter(**data_dict).order_by("-level")
    
        page_object = Pagination(request, queryset)
    
        context = {
            "search_data": search_result,
            "queryset": page_object.page_queryset,  # 分完页的数据
            "page_string": page_object.html()  # 页码
        }
        return render(request, 'mobile_list.html', context)
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mmaeVVay-1645970639907)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220227115641297.png)]

  • 修改前端,如mobile_list.html

    {# 分页 #}
            <div class="clearfix">
                <ul class="pagination">
                    {{ page_string }}
                </ul>
            </div>
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZMs1fKyb-1645970639908)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220227115831009.png)]

下面我们在【用户管理界面】使用【分页组件】

3、时间插件

之前填写入职时间,我们是纯手工输入,这样就容易出现格式问题,如何规范化输入呢?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E89E4eMc-1645970639908)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220227140551995.png)]

要实现的效果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DvULFaSn-1645970639908)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220227150238106.png)]

我们直接看一下总共要加入那些东西:

  • 插件(css、js)
  • 修改(引入 id )
  • 添加【js函数】
<link rel="stylesheet" href="static/plugins/bootstrap-3.4.1/css/bootstrap.css">
<link rel="stylesheet" href="static/plugins/bootstrap-datepicker/css/bootstrap-datepicker.css">


<input type="text" id="dt" class="form-control" placeholder="入职日期">



<script src="static/js/jquery-3.6.0.min.js"></script>
<script src="static/plugins/bootstrap-3.4.1/js/bootstrap.js"></script>
<script src="static/plugins/bootstrap-datepicker/js/bootstrap-datepicker.js"></script>
<script src="static/plugins/bootstrap-datepicker/locales/bootstrap-datepicker.zh-CN.min.js"></script>


<script>
    $(function () {
        $('#dt').datepicker({
            format: 'yyyy-mm-dd',
            startDate: '0',
            language: "zh-CN",
            autoclose: true
        });

    })
</script>

(1)首先,引入插件

在母版那修改一下,使子板可以自定义引入css、js

{% block css %}{% endblock %}
{% block js %}{% endblock %}

子板:

无论用不用ModelForm都要引入插件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FyjC0INT-1645970639908)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220227144225942.png)]

(2)实现效果

具体去改子板内容时会遇到两种情况:

  • 使用ModelForm
  • 未使用ModelForm
  • 如果没使用ModelForm,指定“id”即可使用我们写的js函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WinobvHV-1645970639909)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220227145201759.png)]

  • 若使用了ModelForm,怎么引入id呢?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0eTwMKAL-1645970639909)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220227145303746.png)]

我们发现,使用 ModelForm 的 id 会有一个规律,其 id = "id_" + "name"

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FZhvzwfQ-1645970639909)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220227145512968.png)]

那就简单了,直接改一下之前写的 js函数 就好了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sxMsfZip-1645970639909)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220227145859687.png)]

效果展示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cYEBopx4-1645970639910)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220227145806821.png)]

4、ModelForm和BootStrap

我们先回顾一下之前ModelForm与BootStrap的联系,后面再进一步优化我们的Code

  • (1)ModelForm可以帮助我们生成HTML标签

  • 我们知道,ModelForm可以帮助我们生成HTML标签,但生成的标签没有 BootStrap 样式

    class UserModelForm(forms.ModelForm):
        class Meta:
            model = models.UserInfo
            fields = ["name", "password",]
    
    form = UserModelForm()
    
    {{form.name}}      普通的input框
    {{form.password}}  普通的input框
    
  • (2)定义插件

    我们通过定义插件,为HTML标签引入 BootStrap 样式

    class UserModelForm(forms.ModelForm):
        class Meta:
            model = models.UserInfo
            fields = ["name", "password",]
            widgets = {
                "name": forms.TextInput(attrs={"class": "form-control"}),
                "password": forms.PasswordInput(attrs={"class": "form-control"}),
                "age": forms.TextInput(attrs={"class": "form-control"}),
            }
    
    class UserModelForm(forms.ModelForm):
        name = forms.CharField(
            min_length=3,
            label="用户名",
            widget=forms.TextInput(attrs={"class": "form-control"})
        )
    
        class Meta:
            model = models.UserInfo
            fields = ["name", "password", "age"]
    
    {{form.name}}      BootStrap的input框
    {{form.password}}  BootStrap的input框
    
  • (3)重新定义的init方法,批量设置

    一个一个引入 BootStrap 样式太繁琐了,所以后来我们重新定义的init方法,批量设置

    class UserModelForm(forms.ModelForm):
        class Meta:
            model = models.UserInfo
            fields = ["name", "password", "age",]
    
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            
            # 循环ModelForm中的所有字段,给每个字段的插件设置
            for name, field in self.fields.items():
    			field.widget.attrs = {
                    "class": "form-control", 
                    "placeholder": field.label
                }
    
    class UserModelForm(forms.ModelForm):
        class Meta:
            model = models.UserInfo
            fields = ["name", "password", "age",]
    
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            
            # 循环ModelForm中的所有字段,给每个字段的插件设置
            for name, field in self.fields.items():
                # 字段中有属性,保留原来的属性,没有属性,才增加。
                if field.widget.attrs:
    				field.widget.attrs["class"] = "form-control"
    				field.widget.attrs["placeholder"] = field.label
                else:
                    field.widget.attrs = {
                        "class": "form-control", 
                        "placeholder": field.label
                    }
    
    class UserEditModelForm(forms.ModelForm):
        class Meta:
            model = models.UserInfo
            fields = ["name", "password", "age",]
    
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            
            # 循环ModelForm中的所有字段,给每个字段的插件设置
            for name, field in self.fields.items():
                # 字段中有属性,保留原来的属性,没有属性,才增加。
                if field.widget.attrs:
    				field.widget.attrs["class"] = "form-control"
    				field.widget.attrs["placeholder"] = field.label
                else:
                    field.widget.attrs = {
                        "class": "form-control", 
                        "placeholder": field.label
                    }
    

我们发现,我们重复的写了一堆init方法,每次用ModelForm都要重写一遍,太繁琐了,咋整呢???

  • 自定义类

    utils/bootstrap.py

from django import forms


class BootStrap:
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # 循环ModelForm中的所有字段,给每个字段的插件设置
        for name, field in self.fields.items():
            # 字段中有属性,保留原来的属性,没有属性,才增加。
            if field.widget.attrs:
                field.widget.attrs["class"] = "form-control"
                field.widget.attrs["placeholder"] = field.label
            else:
                field.widget.attrs = {
                    "class": "form-control",
                    "placeholder": field.label
                }


# 使用ModelForm时继承            
class BootStrapModelForm(BootStrap, forms.ModelForm):
    pass


# 使用Form时继承
class BootStrapForm(BootStrap, forms.Form):
    pass

这样 我们写 ModelForm 就可以通过【继承】,省却不少功夫

from app01 import models
from app01.utils.bootstrap import BootStrapModelForm
from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError
from django import forms


class MyForm(BootStrapModelForm):
    name = forms.CharField(min_length=3, label="用户名")

    class Meta:
        model = models.UserInfo
        fields = ["name", "password", "age", 'account', 'create_time', "gender", "depart"]


class MobileModelForm(BootStrapModelForm):
    """靓号管理"""
    # 验证:方式1
    mobile = forms.CharField(
        label="手机号",
        validators=[RegexValidator(r'^1[3-9]\d{9}$', "手机号格式错误")],  # 通过正则表达式限制输入格式,1开头,然后3~9,然后再来就个数
        # 当然这个正则表达式可以叠加,多重限制
    )

    class Meta:
        model = models.MobileNum
        fields = ["mobile", "price", "level", "status"]

    # 验证:方式2
    def clean_mobile(self):
        txt_mobile = self.cleaned_data["mobile"]
        exists = models.MobileNum.objects.filter(mobile=txt_mobile).exists()
        if exists:
            raise ValidationError("手机号已存在")

        # 验证通过,用户输入的值返回
        return txt_mobile


class MobileEditModelForm(BootStrapModelForm):
    # mobile = forms.CharField(disabled=True, label="手机号")  # 设置手机号不可改变

    class Meta:
        model = models.MobileNum
        fields = ['mobile', 'price', 'level', 'status']

    def clean_mobile(self):
        print(self.instance.pk)  # 当前编辑的哪一行的ID
        txt_mobile = self.cleaned_data["mobile"]
        exists = models.MobileNum.objects.exclude(id=self.instance.pk).filter(mobile=txt_mobile).exists()
        if exists:
            raise ValidationError("手机号已存在")
        return txt_mobile

5、整理文件

进行到现在,我们的文件数和代码量也越来也越大,需要规范一下了

  • 提取公共的类
  • 提取公共的类
  • 视图函数的归类
  • 等等

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-za3GMGoY-1645970639910)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220227151043960.png)]

最终效果:

  • views/user.py

    from django.shortcuts import render, redirect
    from app01 import models
    from app01.utils.form import MyForm
    
    
    def user_list(request):
        """用户管理"""
        # 获取所有的用户列表
        queryset = models.UserInfo.objects.all()
        for obj in queryset:
            print(obj.id, obj.name, obj.account, obj.create_time.strftime("%Y-%m-%d"), type(obj.create_time))
    
            # 想要id获取关联的数据
            # 方式一: 传统方法,根据id获取关联的数据
            temp = models.Department.objects.filter(id=obj.depart_id).first()
            print(temp.title)
            # 方式二: Django的方法(注意区别obj.depart_id与obj.depart.title)
            print(obj.depart_id)  # 获取数据库中存储的那个字段值
            print(obj.depart.title)  # 根据id自动去关联的表中获取哪一行数据depart对象
        return render(request, 'user_list.html', {"queryset": queryset})
    
    
    def user_add(request):
        if request.method == "GET":
            form = MyForm()
            return render(request, 'user_add.html', {"form": form})
    
        # 用户POST提交数据,数据效验(不能为空)
        # 如果数据合法,提交到数据库
        form = MyForm(data=request.POST)
        if form.is_valid():
            print(form.cleaned_data)
            # 将数据存储至数据库
            # 方法一:老套路
            # models.UserInfo.objects.create(...)
            # 方法二:ModelForm
            form.save()
            return redirect("/user/list/")
        # else:
        #     print(form.errors)
    
        return render(request, 'user_add.html', {"form": form})
    
    
    def user_edit(request, nid):
        """编辑用户"""
        if request.method == "GET":
            # 根据ID获取数据库中数据
            row_object = models.UserInfo.objects.filter(id=nid).first()
    
            form = MyForm(instance=row_object)  # 使用 instance ,ModelForm就可以默认将数据显示出来
    
            return render(request, 'user_edit.html', {"form": form})
    
        row_object = models.UserInfo.objects.filter(id=nid).first()  # 这行代码勿忘,否则就成新增用户为不是编辑用户了
        form = MyForm(data=request.POST, instance=row_object)
        if form.is_valid():
            # 默认保存的是用户输入的所有数据,如果想要再用户输入以外增加一点值
            # form.instance.字段名 = 值 如:form.age = 20
            form.save()
            return redirect('/user/list/')
        # 如果数据不合格,则返回"user_edit.html"
        return render(request, 'user_edit.html', {"form": form})
    
    
    def user_detele(request, nid):
        """删除用户"""
        models.UserInfo.objects.filter(id=nid).delete()
        return redirect("/user/list/")
    
    
  • views/depart.py

    from django.shortcuts import render, redirect
    from app01 import models
    
    
    def depart_list(request):
        """部门列表"""
    
        # 去数据库中获取所有的部门列表
        # 通过导入模块的方式直接获取所有表,存入queryset,省的表多的时候一个一个导入太麻烦了
        queryset = models.Department.objects.all()  # queryset 形式就是一个一个对象 [对象,对象,对象]
    
        return render(request, "depart_list.html", {'queryset': queryset})
    
    
    def depart_add(request):
        """ 添加部门 """
        # (1)处理GET请求
        # 在部门聊表界面点击”新建部门“ ==> 提交GET请求(去 depart_add.html)
        if request.method == "GET":
            return render(request, 'depart_add.html')
    
        # (2)获得用户POST提交过来的数据
        title = request.POST.get("title")
        """
            遗留问题:
            输入为空怎么办?输入合不合法?等等
        """
    
        # (3)保存到数据库
        models.Department.objects.create(title=title)
    
        # (4)重定向回部门列表
        return redirect("/depart/list/")
    
    
    def depart_detele(request):
        """删除部门"""
    
        # 获取ID
        # 'https//127.0.0.1:8000/depart/delete/?nid=1'
        nid = request.GET.get("nid")
    
        # 删除
        models.Department.objects.filter(id=nid).delete()
    
        # 重定向回部门列表
        return redirect("/depart/list/")
    
    
    def depart_edit(request, nid):
        """修改部门"""
        if request.method == "GET":
            # 根据nid,获得他的数据
            row_object = models.Department.objects.filter(id=nid).first()
            print(row_object.id, row_object.title)
    
            return render(request, 'depart_edit.html', {'row_object': row_object})
    
        # 获取用户提交的标题
        title = request.POST.get("title")
    
        # 根据ID找到数据库中的数据(部门名称),并更新
        models.Department.objects.filter(id=nid).update(title=title)
    
        # 重定向回部门列表
        return redirect("/depart/list/")
    
    
  • views/mobile.py

    from django.shortcuts import render, redirect
    from app01 import models
    from app01.utils.pagination import Pagination
    from app01.utils.form import MobileModelForm, MobileEditModelForm
    
    
    def mobile_list(request):
        """ 靓号列表 """
        data_dict = {}
        search_result = request.GET.get('q', "")  # 如果有值就返回“q”,如果没有就返回空“”
        if search_result:
            data_dict["mobile__contains"] = search_result
    
        queryset = models.MobileNum.objects.filter(**data_dict).order_by("-level")
    
        page_object = Pagination(request, queryset)
    
        context = {
            "search_data": search_result,
            "queryset": page_object.page_queryset,  # 分完页的数据
            "page_string": page_object.html()  # 页码
        }
        return render(request, 'mobile_list.html', context)
    
    
    def mobile_add(request):
        """"添加靓号"""
        if request.method == "GET":
            form = MobileModelForm()
            return render(request, "mobile_add.html", {"form": form})
        form = MobileModelForm(data=request.POST)
        if form.is_valid():
            form.save()
            return redirect("/mobile/list/")
        return render(request, 'mobile_add.html', {"form": form})
    
    
    def mobile_edit(request, nid):
        """编辑用户"""
        row_object = models.MobileNum.objects.filter(id=nid).first()  # 这行代码勿忘,否则就成新增用户为不是编辑用户了
        if request.method == "GET":
            # 根据ID获取数据库中数据
            form = MobileEditModelForm(instance=row_object)  # 使用 instance ,ModelForm就可以默认将数据显示出来
            return render(request, 'mobile_edit.html', {"form": form})
    
        form = MobileEditModelForm(data=request.POST, instance=row_object)
        if form.is_valid():
            form.save()
            return redirect('/mobile/list/')
        # 如果数据不合格,则返回"user_edit.html"
        return render(request, 'mobile_edit.html', {"form": form})
    
    
    def mobile_detele(request, nid):
        """删除用户"""
        models.MobileNum.objects.filter(id=nid).delete()
        return redirect("/mobile/list/")
    
  • utils/bootstrap.py

from django import forms


class BootStrap:
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # 循环ModelForm中的所有字段,给每个字段的插件设置
        for name, field in self.fields.items():
            # 字段中有属性,保留原来的属性,没有属性,才增加。
            if field.widget.attrs:
                field.widget.attrs["class"] = "form-control"
                field.widget.attrs["placeholder"] = field.label
            else:
                field.widget.attrs = {
                    "class": "form-control",
                    "placeholder": field.label
                }


# 使用ModelForm时继承            
class BootStrapModelForm(BootStrap, forms.ModelForm):
    pass


# 使用Form时继承
class BootStrapForm(BootStrap, forms.Form):
    pass

  • utils/form.py
from app01 import models
from app01.utils.bootstrap import BootStrapModelForm
from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError
from django import forms


class MyForm(BootStrapModelForm):
    name = forms.CharField(min_length=3, label="用户名")

    class Meta:
        model = models.UserInfo
        fields = ["name", "password", "age", 'account', 'create_time', "gender", "depart"]


class MobileModelForm(BootStrapModelForm):
    """靓号管理"""
    # 验证:方式1
    mobile = forms.CharField(
        label="手机号",
        validators=[RegexValidator(r'^1[3-9]\d{9}$', "手机号格式错误")],  # 通过正则表达式限制输入格式,1开头,然后3~9,然后再来就个数
        # 当然这个正则表达式可以叠加,多重限制
    )

    class Meta:
        model = models.MobileNum
        fields = ["mobile", "price", "level", "status"]

    # 验证:方式2
    def clean_mobile(self):
        txt_mobile = self.cleaned_data["mobile"]
        exists = models.MobileNum.objects.filter(mobile=txt_mobile).exists()
        if exists:
            raise ValidationError("手机号已存在")

        # 验证通过,用户输入的值返回
        return txt_mobile


class MobileEditModelForm(BootStrapModelForm):
    # mobile = forms.CharField(disabled=True, label="手机号")  # 设置手机号不可改变

    class Meta:
        model = models.MobileNum
        fields = ['mobile', 'price', 'level', 'status']

    def clean_mobile(self):
        print(self.instance.pk)  # 当前编辑的哪一行的ID
        txt_mobile = self.cleaned_data["mobile"]
        exists = models.MobileNum.objects.exclude(id=self.instance.pk).filter(mobile=txt_mobile).exists()
        if exists:
            raise ValidationError("手机号已存在")
        return txt_mobile

  • utils/pagination.py

    from django.utils.safestring import mark_safe
    
    
    class Pagination(object):
    
        def __init__(self, request, queryset, page_size=10, page_param="page", plus=5):
            """
            :param request: 请求的对象
            :param queryset: 符合条件的数据(根据这个数据给他进行分页处理)
            :param page_size: 每页显示多少条数据
            :param page_param: 在URL中传递的获取分页的参数,例如:/etty/list/?page=12
            :param plus: 显示当前页的 前或后几页(页码)
            """
    
            from django.http.request import QueryDict
            import copy
            query_dict = copy.deepcopy(request.GET)
            query_dict._mutable = True
            self.query_dict = query_dict
    
            self.page_param = page_param
            page = request.GET.get(page_param, "1")
    
            if page.isdecimal():
                page = int(page)
            else:
                page = 1
    
            self.page = page
            self.page_size = page_size
    
            self.start = (page - 1) * page_size
            self.end = page * page_size
    
            self.page_queryset = queryset[self.start:self.end]
    
            total_count = queryset.count()
            total_page_count, div = divmod(total_count, page_size)
            if div:
                total_page_count += 1
            self.total_page_count = total_page_count
            self.plus = plus
    
        def html(self):
            # 计算出,显示当前页的前5页、后5页
            if self.total_page_count <= 2 * self.plus + 1:
                # 数据库中的数据比较少,都没有达到11页。
                start_page = 1
                end_page = self.total_page_count
            else:
                # 数据库中的数据比较多 > 11页。
    
                # 当前页<5时(小极值)
                if self.page <= self.plus:
                    start_page = 1
                    end_page = 2 * self.plus + 1
                else:
                    # 当前页 > 5
                    # 当前页+5 > 总页面
                    if (self.page + self.plus) > self.total_page_count:
                        start_page = self.total_page_count - 2 * self.plus
                        end_page = self.total_page_count
                    else:
                        start_page = self.page - self.plus
                        end_page = self.page + self.plus
    
            # 页码
            page_str_list = []
    
            self.query_dict.setlist(self.page_param, [1])
            page_str_list.append('<li><a href="?{}">首页</a></li>'.format(self.query_dict.urlencode()))
    
            # 上一页
            if self.page > 1:
                self.query_dict.setlist(self.page_param, [self.page - 1])
                prev = '<li><a href="?{}">上一页</a></li>'.format(self.query_dict.urlencode())
            else:
                self.query_dict.setlist(self.page_param, [1])
                prev = '<li><a href="?{}">上一页</a></li>'.format(self.query_dict.urlencode())
            page_str_list.append(prev)
    
            # 页面
            for i in range(start_page, end_page + 1):
                self.query_dict.setlist(self.page_param, [i])
                if i == self.page:
                    ele = '<li class="active"><a href="?{}">{}</a></li>'.format(self.query_dict.urlencode(), i)
                else:
                    ele = '<li><a href="?{}">{}</a></li>'.format(self.query_dict.urlencode(), i)
                page_str_list.append(ele)
    
            # 下一页
            if self.page < self.total_page_count:
                self.query_dict.setlist(self.page_param, [self.page + 1])
                prev = '<li><a href="?{}">下一页</a></li>'.format(self.query_dict.urlencode())
            else:
                self.query_dict.setlist(self.page_param, [self.total_page_count])
                prev = '<li><a href="?{}">下一页</a></li>'.format(self.query_dict.urlencode())
            page_str_list.append(prev)
    
            # 尾页
            self.query_dict.setlist(self.page_param, [self.total_page_count])
            page_str_list.append('<li><a href="?{}">尾页</a></li>'.format(self.query_dict.urlencode()))
    
            search_string = """
                <li>
                    <form style="float: left;margin-left: -1px" method="get">
                        <input name="page"
                               style="position: relative;float:left;display: inline-block;width: 80px;border-radius: 0;"
                               type="text" class="form-control" placeholder="页码">
                        <button style="border-radius: 0" class="btn btn-default" type="submit">跳转</button>
                    </form>
                </li>
                """
    
            page_str_list.append(search_string)
            page_string = mark_safe("".join(page_str_list))
            return page_string
    
    
  • urls.py

    from django.contrib import admin
    from django.urls import path
    from app01.views import depart, user, mobile
    
    
    urlpatterns = [
        path('depart/list/', depart.depart_list),
        path('depart/add/', depart.depart_add),
        path('depart/detele/', depart.depart_detele),
        path('depart/<int:nid>/edit/', depart.depart_edit),
        path('user/list/', user.user_list),
        path('user/add/', user.user_add),
        path('user/<int:nid>/edit/', user.user_edit),
        path("user/<int:nid>/detele/", user.user_detele),
        path("mobile/list/", mobile.mobile_list),
        path("mobile/add/", mobile.mobile_add),
        path('mobile/<int:nid>/edit/', mobile.mobile_edit),
        path("mobile/<int:nid>/detele/", mobile.mobile_detele),
    ]
    
    

更改后该有的效果无误

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zZlSkDJB-1645970639910)(C:\Users\pc\AppData\Roaming\Typora\typora-user-images\image-20220227220002625.png)]

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DLNovice

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值