从零开始的Django框架入门到实战教程(内含实战实例) - 04 号码管理部分ModelForm的应用、输入校验、搜索号码、分页功能详解(学习笔记)

11 篇文章 48 订阅


  Django是目前比较火爆的框架,之前有在知乎刷到,很多毕业生进入大厂实习后因为不会git和Django框架3天就被踢掉了,因为他们很难把自己的工作融入到整个组的工作中。因此,我尝试自学Django并整理出如下笔记。
  这篇博客是对前面几篇博客的应用,同时完善了很多细节。内容包括利用ModelForm在前端控制后台数据库的增删改,在输入部分加入了数据的校验(两种方式),在前端界面完善了搜索功能,此外,还通过封装PAGE类方法完成了界面的分页。

1. 号码管理

  跟用户管理和部门管理差不多,这部分还是侧重于增删改查的操作,但是在完成这些操作的同时会涉及一些其他的知识点,可以简化或者改进之前的操作。当然也增加了新功能的实现(搜索和分页)。

1.1. 展示号码

  这部分主要是复习ModelForm的一些操作,并且搭建整个框架的壳子。

  为了更好的展示我们的结果,我们先用mysql给号码管理这个表手动加入一些数据。

  跟之前的views.py文件类似,我们这里的思路还是用render函数直接展示我们之前做好的layout模板html。

  views.py

# 靓号列表
def num_list(request):
    num_list = models.YourNum.objects.all().order_by("-level")
    return render(request, "num_list.html", {"num_list": num_list})

  layout.html

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Olsen</title>
    <link rel="stylesheet" href="{% static 'plugins/bootstrap-3.4.1-dist/css/bootstrap.min.css' %}">
    <style>
        .navbar{
            border-radius: 0;
        }
    </style>
</head>
<body>

<nav class="navbar navbar-default">
    <!--  <div class="container-fluid">-->
    <div class="container">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
                    data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="#">Olsen用户管理系统</a>
        </div>
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <ul class="nav navbar-nav">
                <li><a href="/depart/list">部门管理</a></li>
                <li><a href="/user/list">用户管理</a></li>
                <li><a href="/num/list">靓号管理</a></li>
            </ul>
            <ul class="nav navbar-nav navbar-right">
                <li><a href="#">登录</a></li>
                <li class="dropdown">
                    <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
                       aria-expanded="false">ClarkHu <span class="caret"></span></a>
                    <ul class="dropdown-menu">
                        <li><a href="#">个人资料</a></li>
                        <li><a href="#">我的信息</a></li>
                        <li role="separator" class="divider"></li>
                        <li><a href="#">注销</a></li>
                    </ul>
                </li>
            </ul>
        </div>
    </div>
</nav>

<div>
    {% block content %}
    {% endblock %}
</div>


<script src="{% static 'js/jquery.js' %}"></script>
<script src="{% static 'plugins/bootstrap-3.4.1-dist/js/bootstrap.min.js' %}"></script>
</body>
</html>

  num_list.html

{% extends 'layout.html' %}

{% block content %}
<div class="container">
    <div style="margin-bottom: 10px">
        <a class="btn btn-success" href="/num/add">
            <span class="glyphicon glyphicon-plus-sign" aria-hidden="true"></span>
            添加靓号
        </a>
    </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 -->
        <table class="table table-bordered">
            <thead>
            <tr>
                <th>ID</th>
                <th>号码</th>
                <th>价格</th>
                <th>级别</th>
                <th>状态</th>
            </tr>
            </thead>
            <tbody>
            {% for obj in num_list %}
            <tr>
                <th>{{ obj.id }}</th>
                <td>{{ obj.mobile }}</td>
                <td>{{ obj.price }}</td>
                <td>{{ obj.get_level_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 }}/del">删除</a>
                </td>
            </tr>
            {% endfor %}
            </tbody>
        </table>
    </div>
</div>
{% endblock %}

  效果如下:

在这里插入图片描述

1.2. 增加号码

  这部分主要是复习ModelForm的一些操作,以及尝试两种新的校验方式。代码中有必要的注释,如果有大片看不懂的,可以去上一篇博客中翻阅(here

  第一种校验方式主要是利用正则表达式进行校验,还是django自带的正则表达式函数,在代码种暂时注释掉,毕竟两种一起用还是会冲突的。

  第二种校验方式就比较灵活了,更多是手写校验来实现的,可以自己设计各种情况。当然,在这个任务中可以做的比较复杂,比如输入号码后,我们到后台数据库中查找是否存在本号码,拒绝添加已存在的号码。还有很多灵活的应用,在这里不示范。将会在1.4中解决。

  views.py

# 添加靓号
from django.core.validators import RegexValidator, ValidationError
class NumModelForm(forms.ModelForm):
    # 正则表达式校验
    # mobile = forms.CharField(
    #     label="手机号",
    #     validators=[RegexValidator(r'^1[3-9]\d{9}$', '手机号输入错误')]
    # )

    # 另一种校验方式,手写的,也可以去数据库去校对之类的
    def clean_mobile(self):
        text_mobile = self.cleaned_data["mobile"]
        if len(text_mobile) != 11:
            raise ValidationError("格式错误")
        return text_mobile

    class Meta:
        model = models.YourNum
        fields = "__all__"
        # fields = ["mobile", "price", "level", "status"]
        # 如果是不要某一条
        # exclude = ["level"]
    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 num_add(request):
    if request.method == "GET":
        form = NumModelForm()
        return render(request, "num_add.html", {"form": form})
    form = NumModelForm(data=request.POST)
    if form.is_valid():
        form.save()
        return redirect("/num/list")
    return render(request, "num_add.html", {"form": form})

1.3. 编辑号码

  这部分我们跟用户管理不一样,我们不像之前一样还跟add一起用一个ModelForm了,我们重新写一个,这样可以只允许用户修改其中几个元素。

  views.py

class NumEditModelForm(forms.ModelForm):
    class Meta:
        model = models.YourNum
        fields = ["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 num_edit(request, nid):
    raw_object = models.YourNum.objects.filter(id=nid).first()
    if request.method == 'GET':
        form = NumEditModelForm(instance=raw_object)
        return render(request, "num_edit.html", {"form": form})
    form = NumEditModelForm(data=request.POST, instance=raw_object)
    if form.is_valid():
        form.save()
        return redirect("/num/list")
    return render(request, "num_edit.html", {"form": form})

  num_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>
                {% csrf_token %}
                {% for field in form %}
                <div class="form-group">
                    <label>{{ field.label }}</label>
                    {{ field }}
                    <span style="color:red;">{{ field.errors.0 }}</span>
                </div>
                {% endfor %}
                <button type="submit" class="btn btn-primary">提 交</button>
            </form>
        </div>
    </div>
</div>
{% endblock %}

  效果如下,确实是没有给出编辑号码的权限。

在这里插入图片描述

  当然也可以显示出号码,但是不给改的权限,只需要修改views.py里的NumEditModelForm类就好了:

class NumEditModelForm(forms.ModelForm):
    mobile = forms.CharField(disabled=True, label="手机号")
    class Meta:
        model = models.YourNum
        fields = "__all__"

    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}

  效果如下,手机号框是灰色的,就是不可修改的意思:

在这里插入图片描述

1.4. 手机号去重

  在增加号码部分,我们提及数据校验的操作,其中提到添加手机号的时候需要考虑到手机号已存在的问题,这个问题在1.3编辑号码中也有涉及,我们在这里给出统一的解决方法。

  其实就是对NumModelForm再做修改,用exists再做查询:

class NumModelForm(forms.ModelForm):
    def clean_mobile(self):
        text_mobile = self.cleaned_data["mobile"]
        exists = models.YourNum.objects.filter(mobile=text_mobile).exists()
        if exists:
            raise ValidationError("手机号已存在")
        if len(text_mobile) != 11:
            raise ValidationError("格式错误")
        return text_mobile

    class Meta:
        model = models.YourNum
        fields = "__all__"
        # fields = ["mobile", "price", "level", "status"]
        # 如果是不要某一条
        # exclude = ["level"]
    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 clean_mobile(self):
    text_mobile = self.cleaned_data["mobile"]
    # 先获取当前编辑的那一个字段的主键,也就是id
    num_id = self.instance.pk
    # 用exclude排除自己
    exists = models.YourNum.objects.exclude(id=num_id).filter(mobile=text_mobile).exists()
    if exists:
        raise ValidationError("手机号已存在")
    if len(text_mobile) != 11:
        raise ValidationError("格式错误")
    return text_mobile

1.5. 删除号码

  这个就很简单,没什么新意,直接上代码。

  先在urls.py里面加上这个path,里面还有其他的path,我们只用关注最后一个。

from django.urls import path
from application01 import views

urlpatterns = [
    # path('admin/', admin.site.urls),
    path('depart/list', views.depart_list),
    path('depart/add', views.depart_add),
    path('depart/del', views.depart_del),
    path('depart/<int:nid>/edit', views.depart_edit),
    path('user/list', views.user_list),
    path('user/add', views.user_add),
    path('user/madd', views.user_madd),
    path('user/<int:nid>/edit', views.user_edit),
    path('user/<int:nid>/del', views.user_del),
    path('num/list', views.num_list),
    path('num/add', views.num_add),
    path('num/<int:nid>/edit', views.num_edit),
    path('num/<int:nid>/del', views.num_del),
]

  views.py

# 删除靓号
def num_del(request, nid):
    models.YourNum.objects.filter(id=nid).delete()
    return redirect("/num/list")

1.6. 号码搜索

  到这部分的时候,简单的增删改查已经不能满足我们的需求了,毕竟玩不出啥新花样了。我们将目标锁定在搜索功能上。

  在正式上手之前,我们先熟悉一下Django中ORM这部分的语法,主要是两个知识点:

  • 查找的输入
  • __的运用
# 查找有两种查找方式
# 1. 直接查找
models.YourNum.objects.filter(id=1)
# 2. 字典形式(好处是批量查找的时候很好用)
data = {"mobile": "12345678901", "id": 12345}
models.YourNum.objects.filter(**data)


# 操作符和平时也不一样
models.YourNum.objects.filter(id=1)			# 等于1
models.YourNum.objects.filter(id__gt=1)		# 大于1
models.YourNum.objects.filter(id__gte=1)	# 大于等于1
models.YourNum.objects.filter(id__lt=1)		# 小于1
models.YourNum.objects.filter(id__ltw=1)	# 小于等于1
# 字符串
models.YourNum.objects.filter(mobile__startwith="159")		# 以159开头
models.YourNum.objects.filter(mobile__endwith="159")		# 以159结尾
models.YourNum.objects.filter(mobile__contains="159")		# 包含159
data = {"mobile__contains": "999"}
models.YourNum.objects.filter(**data)		# 包含159

  在这里我们先不完成这个功能,因为后期会将这个功能和1.7. 分页功能一起封装成一个类,方便直接在其他代码中使用。

1.7. 分页(封装类实现)

  这个任务我们主要是通过自己实现一个类,然后封装这个类来完成,因为Django自带的分页组件不太好用。

  想要分页,首先得先有一页展示不下的数据,先批量生成这么一批手机号:

def num_a(request):
    for i in range(300):
        models.YourNum.objects.create(mobile="13566"+str(random.randint(100000, 999999)), price=random.randint(1,19999), level=random.randint(1, 5), status=random.randint(0, 1))
    return HttpResponse(r"Ilove Olsen!!!!!")

  首先我们需要完成一个类,这个类的功能包括搜索号码,分页显示数据,搜索页码。我们将这个类单独写一个文件page.py。主要的注释已经放在代码中了,代码水平比较一般,较机械繁复。

# -*- coding:utf-8 -*-
# 自定义分页主键
from django.utils.safestring import mark_safe


class PAGE(object):
    def __init__(self, request, queryset, page_size=10, plus=5, page_param="page"):
        """
        :param request: 进入这个页面时的request,请求对象
        :param queryset: 符合条件的对象,就是经过搜索筛选之后仍符合要求的数据
        :param page_size: 每页发的那个多少行数据
        :param plus: 分页时显示本页前后plus页的页码
        :param page_param: 再url中传递的获取分页的参数
        """
        # 获取当前页码
        page = request.GET.get(page_param, "1")
        # 防止页码为字母等等
        if page.isdecimal():
            page = int(page)
        else:
            page = 1

        # 获取总样本数
        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.page = page
        # 防止搜索的时候超限
        if self.page < 1:
            self.page = 1
        if self.page > self.total_page_count:
            self.page = self.total_page_count

        self.plus = plus
        self.page_param = page_param
        self.page_size = page_size
        # 搜索结果数量为0时,保证self.start == 0
        self.start = (self.page - 1) * self.page_size * (self.page != 0)
        self.end = self.page * self.page_size

        # 搜索的时候分页会把原来搜索的条件替换掉,用这四行代码解决
        import copy
        querydict = copy.deepcopy(request.GET)      # 把包含原来所有参数的网址给弄过来
        querydict.mutable = True                    # 这句话要看源码去找_mutable
        self.query_dict = querydict

        self.page_queryset = queryset[self.start: self.end]

    """生成html代码"""
    def create_html(self):
        # 显示当前页的前plus页和后plus页
        if self.total_page_count <= 2 * self.plus + 1:
            start_page = 1
            end_page = self.total_page_count
        else:
            if self.page <= self.plus:
                start_page = 1
                end_page = 2 * self.plus + 1
            else:
                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 = 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])
            nex = '<li><a href="?{}">下一页</a></li>'.format(self.query_dict.urlencode())
        else:
            self.query_dict.setlist(self.page_param, [self.total_page_count])
            nex = '<li><a href="?{}">下一页</a></li>'.format(self.query_dict.urlencode())
        page_str_list.append(nex)
        # 尾页
        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()))
        # 需要mark_safe后html才接受
        page_string = mark_safe("".join(page_str_list))
        return page_string
    
    """搜索页码"""
    def search(self):
        search_string = """
        <form method="get">
            <div class="input-group" style="width: 200px; float: right;">
                <input name = "page" type="text" class="form-control" style="width: 200px;" placeholder="页码">
                <span class="input-group-btn">
                    <button class="btn btn-default" type="submit">跳转</button>
                </span>
            </div>
        </form>
        """
        return mark_safe(search_string)

  此时再在原来的views.py代码中加入分页操作:

def num_list(request):
    search_dict = dict()
    value = request.GET.get("search", "")
    if value:
        search_dict["mobile__contains"] = value

    num_list = models.YourNum.objects.filter(**search_dict).order_by("-level")
    page_object = PAGE(request, num_list, page_size=8)
    context = {
        "num_list": page_object.page_queryset,      # 分完页的数据
        "search_data": value,                       # 搜索之后的数据
        "page_string": page_object.create_html(),   # 生成的页码
        "search_page": page_object.search()         # 搜索功能
    }
    return render(request, "num_list.html", context)

  并且修改原来的num_list.html代码:

{% extends 'layout.html' %}

{% block content %}
<div class="container">
    <div style="margin-bottom: 10px" class="clearfix">
        <a class="btn btn-success" href="/num/add">
            <span class="glyphicon glyphicon-plus-sign" aria-hidden="true"></span>
            添加靓号
        </a>
        <div style="float: right; width: 300px;">
            <form method="get">
                <div class="input-group">
                    <input type="text" class="form-control" placeholder="搜索号码" name="search" value="{{search_data}}">
                    <span class="input-group-btn">
                        <button class="btn btn-default" type="submit">
                            <span class="glyphicon glyphicon-search"></span>
                        </button>
                    </span>
                </div>
            </form>
        </div>
    </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 -->
        <table class="table table-bordered">
            <thead>
            <tr>
                <th>ID</th>
                <th>号码</th>
                <th>价格</th>
                <th>级别</th>
                <th>状态</th>
            </tr>
            </thead>
            <tbody>
            {% for obj in num_list %}
            <tr>
                <th>{{ obj.id }}</th>
                <td>{{ obj.mobile }}</td>
                <td>{{ obj.price }}</td>
                <td>{{ obj.get_level_display }}</td>
                <td>
                    <a class="btn btn-primary btn-xs" href="/num/{{ obj.id }}/edit">编辑</a>
                    <a class="btn btn-danger btn-xs" href="/num/{{ obj.id }}/del">删除</a>
                </td>
            </tr>
            {% endfor %}
            </tbody>
        </table>
    </div>
    <ul class="pagination" style="width:100%;">
        {{ page_string }}
        {{ search_page }}
    </ul>
</div>
{% endblock %}

  最终完成分页部分的代码,结果如下:
请添加图片描述

2. 总结

主要完成了:

  1. 熟练运用ModelForm的基本操作,巩固了之前的知识
  2. 添加了校验功能,网页更稳定
  3. 加入了搜索功能,更人性化
  4. 加入分页功能,还采取了类封装的形式
  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

铖铖的花嫁

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

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

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

打赏作者

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

抵扣说明:

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

余额充值