Django web 开发(四) - Django项目实践(五)-靓号管理

靓号管理

表结构

修改myproject/employee_management/models.py

class PrettyNum(models.Model):
    """靓号表"""
    # 如果想要为空 null=True blank=True
    mobile = models.CharField(verbose_name="手机号", max_length=32)
    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)

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

生成数据库表

python3 manage.py makemigrations
python3 manage.py migrate

在这里插入图片描述
手动模拟添加一些数据

insert into employee_management_prettynum(mobile, price, level, status) values("15811223344", 500, 2, 2);
insert into employee_management_prettynum(mobile, price, level, status) values("13846783361", 100, 3, 2);

在这里插入图片描述

靓号列表

相信到这里大家可以做到这里,可以说是已经轻车熟路了

修改myproject/employee_management/views.py增加pretty_list函数

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

    # select * from 表 by level desc;
    pretty_data = PrettyNum.objects.all().order_by("-level")

    return render(request, "pretty_list.html", {
    
    "pretty_data": pretty_data})

修改myproject/myproject/urls.py

path('pretty/list/', views.pretty_list),

新建myproject/employee_management/templates/pretty_list.html

{% extends 'layout.html' %}

{% block content %}
<div class="container">
    <div style="margin-bottom: 10px">
        <a class="btn btn-primary" href="/user/model/form/add/" target="_blank">新建靓号</a>
    </div>
    <div>
        <div class="panel panel-default">
            <!-- Default panel contents -->
            <div class="panel-heading">
                <span class="glyphicon glyphicon-th-list" aria-hidden="true" style="margin-right: 5px;"></span>
                <span>靓号列表</span>
            </div>

            <!-- Table -->
            <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 pretty_data %}
                    <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 }}/delete/">删除</a>
                        </td>
                    </tr>
                    {% endfor %}
                </tbody>
            </table>
        </div>
    </div>
</div>

{% endblock %}

运行,浏览器访问测试
在这里插入图片描述

这里在导航栏中增加一个"靓号管理"的标签

修改myproject/employee_management/templates/layout.html

<li><a href="/pretty/list/">靓号管理</a></li>

在这里插入图片描述
在这里插入图片描述

靓号添加

修改myproject/employee_management/views.py

from django.core.validators import RegexValidator

class PrettyModelForm(forms.ModelForm):

    # 数据校验
    mobile = forms.CharField(
        label="手机号",
        validators=[RegexValidator(r'^1[3-9]\d{9}$', '手机号格式错误'),],
    )

    class Meta:
        model = PrettyNum
        # fields = "__all__"    表示取表中所有的字段
        fields = ['mobile', 'price', 'level', 'status']
        # exclude = ['level']   表示取除了表中的某个字段的所有字段

    # 循环找到所有的插件,加入css样式,添加 "class": "form-control"
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        for name, field in self.fields.items():
            print(name, field)
            field.widget.attrs = {
    
    "class": "form-control"}

def pretty_add(request):
    """添加靓号"""

    if request.method == "GET":
        form = PrettyModelForm()
        return render(request, "pretty_add.html", {
    
    "form": form})

    # 用户POST请求提交数据,需要进行数据校验
    form = PrettyModelForm(data=request.POST)
    if form.is_valid():
        print(form.cleaned_data)
        # 直接保存至数据库
        form.save()
        return redirect("/pretty/list/")
    
    # 校验失败(在页面上显示错误信息)
    return render(request, "pretty_add.html", {
    
    "form": form})

修改/root/python/myproject/myproject/urls.py

path('pretty/add/', views.pretty_add),

修改myproject/employee_management/templates/pretty_list.html

修改 href

<a class="btn btn-primary" href="/pretty/add/" target="_blank">新建靓号</a>

在这里插入图片描述
新建myproject/employee_management/templates/pretty_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 action="/pretty/add/" 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 %}
</body>

</html>

浏览器访问测试
在这里插入图片描述
格式只能11位手机号
在这里插入图片描述
点击保存
在这里插入图片描述

数据校验的方式还有另外一种,更为复杂的

修改myproject/employee_management/views.py

from django.core.exceptions import ValidationError

class PrettyModelForm(forms.ModelForm):

	# 此处省略中间内容
	...

    # 数据校验: 验证方式2
    def clean_mobile(self):
        txt_mobile = self.cleaned_data['mobile']

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

        # 验证通过
        return txt_mobile

在这里插入图片描述
同样的效果,选择其中一种方式即可

靓号编辑

重新复习一下编辑实现的逻辑:

  1. 列表页面点击"编辑"按钮
  2. 匹配urls.py中的定义的path
  3. views.py中定义的函数接收到POST请求提交的nid
  4. 函数根据nid找到对应的数据行
  5. templates模板下的HTML文件根据Django的方法展示数据

修改myproject/employee_management/views.py

def pretty_edit(request, nid):
    """编辑靓号"""
    row_obj = PrettyNum.objects.filter(id=nid).first()
    
    # GET请求
    if request.method == "GET":
        form = PrettyModelForm(instance=row_obj)
        return render(request, "pretty_edit.html", {
    
    "form": form})
    
    # POST请求
    form = PrettyModelForm(data=request.POST, instance=row_obj)
    if form.is_valid():
        form.save()
        return redirect("/pretty/list/")

    return render(request, "pretty_edit.html", {
    
    "form": form})

修改myproject/myproject/urls.py

path('pretty/<int:nid>/edit/', views.pretty_edit),

新建/root/python/myproject/employee_management/templates/pretty_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 %}
</body>

</html>

修改/root/python/myproject/employee_management/templates/pretty_list.html
在这里插入图片描述
浏览器点击"编辑"更改数据测试即可

现在有一个新的需求,不想让用户编辑手机号字段,该怎么做?
其实我们可以单独新增一个class,去掉mobile字段

修改myproject/employee_management/views.py

class PrettyEditModelForm(forms.ModelForm):

    class Meta:
        model = PrettyNum
        # fields = "__all__"    表示取表中所有的字段
        fields = ['price', 'level', 'status']
        # exclude = ['level']   表示取除了表中的某个字段的所有字段

    # 循环找到所有的插件,加入css样式,添加 "class": "form-control"
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        for name, field in self.fields.items():
            print(name, field)
            field.widget.attrs = {
    
    "class": "form-control"}


def pretty_edit(request, nid):
    """编辑靓号"""
    row_obj = PrettyNum.objects.filter(id=nid).first()
    
    # GET请求
    if request.method == "GET":
        form = PrettyEditModelForm(instance=row_obj)
        return render(request, "pretty_edit.html", {
    
    "form": form})
    
    # POST请求
    form = PrettyEditModelForm(data=request.POST, instance=row_obj)
    if form.is_valid():
        form.save()
        return redirect("/pretty/list/")

    return render(request, "pretty_edit.html", {
    
    "form": form})

在这里插入图片描述
浏览器访问,可以发现没有手机号了
在这里插入图片描述

这时可能有人会说,我就是想要加上手机号字段,但是不想让他可编辑
这个当然也可以实现哈

修改myproject/employee_management/views.py

mobile = forms.CharField(disabled=True, label="手机号")

在这里插入图片描述
浏览器刷新
在这里插入图片描述

那如果也允许编辑手机号字段呢
那么这样会有一个问题,如果手机号已经存在怎么办?
这里就涉及到了手机号的重复性问题

不允许手机号重复

  • 添加: 如果手机号已存在,提示"手机号已存在"
  • 编辑: 如果手机号除了当前手机号以外已存在,提示"手机号已存在"

添加和编辑的逻辑不太一样,编辑需要将它自己先排除然后再做判断

首先实现"添加"功能的手机号重复验证
修改myproject/employee_management/views.pyclass PrettyModelFormclean_mobile函数

# 数据校验: 验证方式2
def clean_mobile(self):
    txt_mobile = self.cleaned_data['mobile']

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

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

    # 验证通过
    return txt_mobile

在这里插入图片描述
浏览器访问测试
在这里插入图片描述
实现"编辑"功能的手机号重复验证

注意: 下面修改的是PrettyEditModelForm类下面的函数

修改myproject/employee_management/views.pyclass PrettyEditModelFormclean_mobile函数

# 数据校验: 验证方式2
def clean_mobile(self):
    txt_mobile = self.cleaned_data['mobile']

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

    # exclude 表示排除哪一个数据
    # self.instance.pk 表示当前编辑的哪一行 id
    exists_data = PrettyNum.objects.exclude(id=self.instance.pk).filter(mobile=txt_mobile).exists()
    if exists_data:
        raise ValidationError("手机号已存在")

    # 验证通过
    return txt_mobile

在这里插入图片描述
在这里插入图片描述
不更改直接保存,正常
将手机号更改为已经存在的其他电话号码,保存,则提示"手机号已存在"
在这里插入图片描述

靓号删除

修改myproject/employee_management/views.py

def pretty_delete(request, nid):
    """删除靓号"""
    PrettyNum.objects.filter(id=nid).delete()
    return redirect('/pretty/list/')

修改myproject/myproject/urls.py

path('pretty/<int:nid>/delete/', views.pretty_delete),

修改/root/python/myproject/employee_management/templates/pretty_list.html
在这里插入图片描述
浏览器测试即可
在这里插入图片描述

手机号搜索

query1 = PrettyNum.objects.filter(mobile=15576547933, id=4)
print(query1)

# 如果是空字典,表示获取所有
query_dict = {
    
    "mobile": "15576547933", "id": 4}
query2 = PrettyNum.objects.filter(**query_dict)
print(query2)
PrettyNum.objects.filter(id=4)			# 等于4
PrettyNum.objects.filter(id__gt=4)		# 大于4
PrettyNum.objects.filter(id__gte=4)		# 大于等于4
PrettyNum.objects.filter(id__lt=4)		# 小于4
PrettyNum.objects.filter(id__lte=4)		# 小于等于4

PrettyNum.objects.filter(mobile__startswith="1999")		# 筛选出以"1999"开头的
PrettyNum.objects.filter(mobile__endswith="1999")		# 筛选出以"1999"结尾的
PrettyNum.objects.filter(mobile__contains="1999")		# 筛选出包含"1999"开头的

接下来我们来实现这个功能

修改myproject/employee_management/views.py

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

    data_dict = {
    
    }
    
    search_data = request.GET.get('query', "")  # 不加后面的 "", 首次访问浏览器,搜索框中不会显示前端页面中的 placeholder="Search for..." 属性
    if search_data:
    	# mobile_contains  表示筛选出字段 mobile 包含 search_data 数据的行
        data_dict["mobile__contains"] = search_data

    # select * from 表 by level desc;
    # data_dict 如果是空字典,表示获取所有
    pretty_data = PrettyNum.objects.filter(**data_dict).order_by("-level")

	# 加入search_data的目的是,当搜索后,搜索框内的值不会置为空
    return render(request, "pretty_list.html", {
    
    "pretty_data": pretty_data, "search_data": search_data})

修改myproject/employee_management/templates/pretty_list.html

{% extends 'layout.html' %}

{% block content %}
<div class="container">

    <div>
        <div style="margin-bottom: 10px; ">
            <a class="btn btn-primary" href="/pretty/add/" target="_blank">新建靓号</a>

            <div style="float: right; width: 300px;">
                <form method="get">
                    <div class="input-group">
                        <input type="text" name="query" class="form-control" placeholder="Search for..."
                            value="{
     
     { search_data }}">
                        <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>
        </div>
    </div>

    <div>
        <div class="panel panel-default">
            <!-- Default panel contents -->
            <div class="panel-heading">
                <span class="glyphicon glyphicon-th-list" aria-hidden="true" style="margin-right: 5px;"></span>
                <span>靓号列表</span>
            </div>

            <!-- Table -->
            <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 pretty_data %}
                    <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="/pretty/{
     
     { obj.id }}/edit/">编辑</a>
                            <a class="btn btn-danger btn-xs" href="/pretty/{
     
     { obj.id }}/delete/">删除</a>
                        </td>
                    </tr>
                    {% endfor %}
                </tbody>
            </table>
        </div>
    </div>
</div>

{% endblock %}

浏览器访问测试
在这里插入图片描述
进行搜索
在这里插入图片描述

分页

quertset = PrettyNum.objects.filter(id=1)[0:10]
quertset = PrettyNum.objects.all()[0:10]			# 取0到10行数据

先使用最原始的代码方式实现分页
内容较多,仔细看,有很多的细节

修改myproject/employee_management/views.py

from django.utils.safestring import mark_safe

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

    ################### 纯代码实现分页 ###################
    # 插入一些演示数据,使用完注释掉
    # for i in range(300):
    #     PrettyNum.objects.create(mobile="13811223344", price=100, level=3, status=1)

    page_now = int(request.GET.get('page', 1))

    page_range = 5  # 当前页面前后留几个页码
    start = (page_now - 1) * 10
    end = page_now * 10 + 2

    # 总数据行数
    data_num = PrettyNum.objects.all().count()

    # 总页码
    page_num, div = divmod(data_num, 10)
    if div:
        page_num += 1

    page_show = 5
    # 如果总页码数量大于 11
    if page_num > page_show * 2 + 1:
        # 如果当前页面页码位置小于等于5
        if page_now <= 5:
            start_page = 1
            end_page = page_show * 2 + 2
        # 否则,当前页面页码位置大于5时
        else:
            # 防止页码超出范围
            if page_now >= page_num - page_show:
                start_page = page_num - page_show * 2
                end_page = page_num + 1
            else:
                # 计算出当前页的前5页和后5页
                start_page = page_now - page_show
                end_page = page_now + page_show + 1

    else:
        start_page = 1
        end_page = page_num + 1


    ######## 创建页码 ########
    # 页码
    page_str_list = []

    # 跳到首页
    head_page = '?page={}'.format(1)
    
    # 跳到上10页
    # 如果当前页面小于 11, 防止超过最小页数
    if page_now < page_show * 2 + 1:
        prev = '<li><a href="?page={}">{}</a></li>'.format(1, "<<")
        page_str_list.append(prev)
    else:
        prev = '<li><a href="?page={}">{}</a></li>'.format(page_now - 10, "<<")
        page_str_list.append(prev)

    for i in range(start_page, end_page):

        # 如果是当前页,高亮显示页码颜色
        if page_now == i:
            ele = '<li class="active"><a href="?page={}">{}</a></li>'.format(i, i)
        else:
            ele = '<li><a href="?page={}">{}</a></li>'.format(i, i)
        page_str_list.append(ele)

    ### 跳到下10页
    # 如果当前页面页数 大于 最大页面数量减去(page_show*2+1),则直接跳到最后一页,防止超过最大页数
    if page_now >= page_num - page_show * 2 + 1:
        next = '<li><a href="?page={}">{}</a></li>'.format(page_num, ">>")
        page_str_list.append(next)
    else:
        next = '<li><a href="?page={}">{}</a></li>'.format(page_now + 10, ">>")
        page_str_list.append(next)

    page_string = mark_safe("".join(page_str_list))

    # 跳到尾页
    end_page = '?page={}'.format(page_num)

    ################### 代码实现分页结束 ###################

    data_dict = {
    
    }
    # 如果是空字典,表示获取所有
    search_data = request.GET.get('query', "")  # 不加后面的 "", 首次访问浏览器,搜索框中不会显示前端页面中的 placeholder="Search for..." 属性
    if search_data:
        data_dict["mobile__contains"] = search_data

    # select * from 表 by level desc;
    pretty_data = PrettyNum.objects.filter(**data_dict).order_by("-level")[start:end]

    return render(request, "pretty_list.html", {
    
    "pretty_data": pretty_data, "search_data": search_data, "page_string": page_string, "head_page": head_page, "end_page": end_page})

修改myproject/employee_management/templates/pretty_list.html

<ul class="pagination">
    <li><a href="{
     
     { head_page }}" aria-label="Previous"><span aria-hidden="true">首页</span></a></li>
    {
   
   { page_string }}
    <li><a href="{
     
     { end_page }}" aria-label="Next"><span aria-hidden="true">尾页</span></a></li>

</ul>
<br>

<form method="get">
    <div style="display:inline-block; width: 150px;">
        <div class="input-group">
            <span> <input type="text" class="form-control" placeholder="请输入页码" name="page"></span>
            <span class="input-group-btn">
                <button class="btn btn-primary" type="submit">跳转</button>
            </span>
        </div>
    </div>
</form>

在这里插入图片描述
在这里插入图片描述

封装分页

实现分页代码的封装,方便以后其他代码的调用

myproject/employee_management目录下新建utils目录
utils下新建pagination.py文件

"""
自定义的分页组件
"""
from django.utils.safestring import mark_safe

class Pagination(object):
    def __init__(self, request, queryset, page_size=10, page_param="page", page_show=5):
        """
        :param request: 请求的对象
        :param queryset: 符合条件的数据(根据此数据进行分页处理)
        :param page_size: 每页显示多少条数据
        :param page_param: 获取在URL中传递的分页参数, 例如: /pretty/list/?page=21
        :param page_show: 页码显示前几页后几页
        """
        page = int(request.GET.get(page_param, 1))

        # 如果不是整数
        if type(page) != int:
            # 强制让页码为1
            page = 1
            
        self.page = page

        self.start = (page - 1) * page_size
        self.end = page * page_size

        # 每页展示的数据行数
        self.page_queryset = queryset[self.start:self.end]

        total_data_count = queryset.count()     # 数据行数
        total_page_count, div = divmod(total_data_count, page_size)
        if div:
            total_page_count += 1
        self.total_page_count = total_page_count    # 总页码数量
        self.page_show = page_show  # 当前页前后展示的页码数量
        self.request = request


    def html(self):
        # 如果总页码数量大于 11
        if self.total_page_count > self.page_show * 2 + 1:
            # 如果当前页面页码位置小于等于5
            if self.page <= 5:
                start_page = 1
                end_page = self.page_show * 2 + 2
            # 否则,当前页面页码位置大于5时
            else:
                # 防止页码超出范围
                if self.page >= self.total_page_count - self.page_show:
                    start_page = self.total_page_count - self.page_show * 2
                    end_page = self.total_page_count + 1
                else:
                    # 计算出当前页的前5页和后5页
                    start_page = self.page - self.page_show
                    end_page = self.page + self.page_show + 1

        else:
            start_page = 1
            end_page = self.total_page_count + 1

        ######## 创建页码 ########
        # 页码
        page_str_list = []

        # 跳到首页
        self.head_page = '?page={}'.format(1)

        # 跳到上10页
        # 如果当前页面小于 11, 防止超过最小页数
        if self.page < self.page_show * 2 + 1:
            prev = '<li><a href="?page={}">{}</a></li>'.format(1, "<<")
            page_str_list.append(prev)
        else:
            prev = '<li><a href="?page={}">{}</a></li>'.format(self.page - 10, "<<")
            page_str_list.append(prev)

        for i in range(start_page, end_page):

            # 如果是当前页,高亮显示页码颜色
            if self.page == i:
                ele = '<li class="active"><a href="?page={}">{}</a></li>'.format(i, i)
            else:
                ele = '<li><a href="?page={}">{}</a></li>'.format(i, i)
            page_str_list.append(ele)

        # 跳到下10页
        # 如果当前页面页数 大于 最大页面数量减去(page_show*2+1),则直接跳到最后一页,防止超过最大页数
        if self.page >= self.total_page_count - self.page_show * 2 + 1:
            next = '<li><a href="?page={}">{}</a></li>'.format(self.total_page_count, ">>")
            page_str_list.append(next)
        else:
            next = '<li><a href="?page={}">{}</a></li>'.format(self.page + 10, ">>")
            page_str_list.append(next)

        self.page_string = mark_safe("".join(page_str_list))

        # 跳到尾页
        self.end_page = '?page={}'.format(self.total_page_count)

修改myproject/employee_management/views.py

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

    data_dict = {
    
    }
    # 如果是空字典,表示获取所有
    # 不加后面的 "", 首次访问浏览器,搜索框中不会显示前端页面中的 placeholder="Search for..." 属性
    search_data = request.GET.get('query', "")
    if search_data:
        data_dict["mobile__contains"] = search_data 
    
    queryset = PrettyNum.objects.filter(**data_dict).order_by("-level")

    ### 引入封装的 Pagination 类并初始化
    # 初始化
    page_object = Pagination(request, queryset, page_size=10, page_param="page")
    page_queryset = page_object.page_queryset

    # 调用对象的html方法,生成页码
    page_object.html()

    page_string = page_object.page_string
    head_page = page_object.head_page
    end_page = page_object.end_page

    context = {
    
    
        "pretty_data": page_queryset,   # 分页的数据
        "search_data": search_data,     # 搜索的内容
        "page_string": page_string,     # 页码
        "head_page": head_page,         # 首页
        "end_page": end_page,           # 尾页
    }

    return render(request, "pretty_list.html", context)

浏览器刷新访问,你会发现效果和之前一样

解决小BUG

当我们进行搜索后,可以展示出搜索的对应数据
但是如果此时我们进行翻页,搜索数据将清空,重新展示所有的数据
这样是不符合逻辑的

下面的代码对之前的代码进行了优化:

  • 将首页和尾页加入到了page_str_list列表中,方便后续其他页面进行调用

修改myproject/employee_management/utils/pagination.py

"""
自定义的分页组件

"""

from django.utils.safestring import mark_safe
import copy


class Pagination(object):
    def __init__(self, request, queryset, page_size=10, page_param="page", page_show=5):
        """
        :param request: 请求的对象
        :param queryset: 符合条件的数据(根据此数据进行分页处理)
        :param page_size: 每页显示多少条数据
        :param page_param: 获取在URL中传递的分页参数, 例如: /pretty/list/?page=21
        :param page_show: 页码显示前几页后几页
        """

        # 防止搜索出结果进行翻页时,URL参数没有了搜索参数
        query_dict = copy.deepcopy(request.GET)
        query_dict._mutable = True
        self.query_dict = query_dict

        self.page_param = page_param

        page = int(request.GET.get(page_param, 1))

        # 如果不是整数
        if type(page) != int:
            # 强制让页码为1
            page = 1

        self.page = page

        self.start = (page - 1) * page_size
        self.end = page * page_size

        # 每页展示的数据行数
        self.page_queryset = queryset[self.start:self.end]

        total_data_count = queryset.count()     # 数据行数
        total_page_count, div = divmod(total_data_count, page_size)
        if div:
            total_page_count += 1
        self.total_page_count = total_page_count    # 总页码数量
        self.page_show = page_show  # 当前页前后展示的页码数量
        self.request = request

    def html(self):
        # 如果总页码数量大于 11
        if self.total_page_count > self.page_show * 2 + 1:
            # 如果当前页面页码位置小于等于5
            if self.page <= 5:
                start_page = 1
                end_page = self.page_show * 2 + 2
            # 否则,当前页面页码位置大于5时
            else:
                # 防止页码超出范围
                if self.page >= self.total_page_count - self.page_show:
                    start_page = self.total_page_count - self.page_show * 2
                    end_page = self.total_page_count + 1
                else:
                    # 计算出当前页的前5页和后5页
                    start_page = self.page - self.page_show
                    end_page = self.page + self.page_show + 1

        else:
            start_page = 1
            end_page = self.total_page_count + 1

        ######## 创建页码 ########
        # 页码
        page_str_list = []

        # self.query_dict.setlist(self.page_param, [1])
        # page_str_list.append('<li><a href="?page={}">{}</a></li>'.format(self.query_dict.urlencode()))

        # 跳到首页
        self.query_dict.setlist(self.page_param, [1])
        self.head_page = '<li><a href="?{}" aria-label="Previous"><span aria-hidden="true">首页</span></a></li>'.format(self.query_dict.urlencode())
        page_str_list.append(self.head_page)

        # 跳到上10页
        # 如果当前页面小于 11, 防止超过最小页数
        if self.page < self.page_show * 2 + 1:
            self.query_dict.setlist(self.page_param, [1])
            prev = '<li><a href="?{}">{}</a></li>'.format(
                self.query_dict.urlencode(), "<<")
            page_str_list.append(prev)
        else:
            self.query_dict.setlist(self.page_param, [self.page - 10])
            prev = '<li><a href="?{}">{}</a></li>'.format(
                self.query_dict.urlencode(), "<<")
            page_str_list.append(prev)

        for i in range(start_page, end_page):
            # 如果是当前页,高亮显示页码颜色
            if self.page == i:
                self.query_dict.setlist(self.page_param, [i])
                ele = '<li class="active"><a href="?{}">{}</a></li>'.format(
                    self.query_dict.urlencode(), i)
            else:
                self.query_dict.setlist(self.page_param, [i])
                ele = '<li><a href="?{}">{}</a></li>'.format(
                    self.query_dict.urlencode(), i)
            page_str_list.append(ele)

        # 跳到下10页
        # 如果当前页面页数 大于 最大页面数量减去(page_show*2+1),则直接跳到最后一页,防止超过最大页数
        if self.page >= self.total_page_count - self.page_show * 2 + 1:
            self.query_dict.setlist(self.page_param, [self.total_page_count])
            next = '<li><a href="?{}">{}</a></li>'.format(
                self.query_dict.urlencode(), ">>")
            page_str_list.append(next)
        else:
            self.query_dict.setlist(self.page_param, [self.page + 10])
            next = '<li><a href="?page={}">{}</a></li>'.format(
                self.query_dict.urlencode(), ">>")
            page_str_list.append(next)

        # 跳到尾页
        self.query_dict.setlist(self.page_param, [self.total_page_count])
        self.end_page = '<li><a href="?{}" aria-label="Next"><span aria-hidden="true">尾页</span></a></li>'.format(self.query_dict.urlencode())
        page_str_list.append(self.end_page)

        self.page_string = mark_safe("".join(page_str_list))

对上面一段代码的解析:

query_dict = copy.deepcopy(request.GET)
query_dict._mutable = True
self.query_dict = query_dict

上面这一段代码是获取保留目前已有的URL参数
query_dict._mutable = True表示将URL改为参数可拼接

self.query_dict.setlist(self.page_param, [self.page + 10])
    next = '<li><a href="?page={}">{}</a></li>'.format(self.query_dict.urlencode(), ">>")

上面这一段代码表示在原来URL参数的基础上进行拼接,不是像原来一样直接覆盖URL参数
setlist表示增加URL参数
self.query_dict.urlencode()表示将self.page_param与数值self.page + 10拼接起来,不会讲原来已有的URL参数覆盖
例如setlist(page, [10])表示page=10
修改myproject/employee_management/templates/pretty_list.html

<ul class="pagination">
    {
   
   { page_string }}
</ul>

在这里插入图片描述

浏览器访问测试

在这里插入图片描述
但是目前输入页码跳转到指定页的功能还是有这个问题,现在还无法解决,需要使用到ajax

用户列表增加分页

修改myproject/employee_management/views.py

def user_list(request):
    """用户列表"""
    # 获取所有用户列表
    queryset = UserInfo.objects.all()

    page_object = Pagination(request, queryset, page_size=2)

    # 用 python 的语法获取数据
    """
    for obj in user_data:
        # obj.get_gender_display() 表示匹配 男/女,原始字段名为gender,obj.get_字段名称_display()
        # obj.create_time.strftime("%Y-%m-%d") 表示将时间格式转换成固定格式的字符串
        # obj.depart.title 表示获取depart_id对应的部门名称,因为我们在models中定义表时与另外一张表设置了级联关系,有外键
        print(obj.id, obj.name, obj.password, obj.age, obj.account, obj.get_gender_display(), obj.depart.title, obj.create_time.strftime("%Y-%m-%d"))
    """

    page_object.html()

    context = {
    
    
        "queryset": page_object.page_queryset,
        "page_string": page_object.page_string,
    }

    return render(request, "user_list.html", context)

修改myproject/employee_management/templates/user_list.html


{% for obj in queryset %}

...

<ul class="pagination">
    {
   
   { page_string }}
</ul>

在这里插入图片描述
浏览器访问测试
在这里插入图片描述

部门列表增加分页

修改myproject/employee_management/views.py

def depart_list(request):
    """部门列表"""

    queryset = Department.objects.all()
    page_object = Pagination(request, queryset, page_size=2)

    page_object.html()

    context = {
    
    
        "queryset": page_object.page_queryset,
        "page_string": page_object.page_string,
    }

    return render(request, "depart_list.html", context)

修改myproject/employee_management/templates/depart_list.html

{% for obj in queryset %}

...
            
<ul class="pagination">
    {
   
   { page_string }}
</ul>

在这里插入图片描述
浏览器访问
在这里插入图片描述

项目代码优化

ModelForm优化

新建myproject/employee_management/utils/modelform.py

from django import forms

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

修改myproject/employee_management/views.py

将目前的三个class类改为继承BootStrapModelForm自定义类

class UserModelForm(BootStrapModelForm):
class PrettyModelForm(BootStrapModelForm):
class PrettyEditModelForm(BootStrapModelForm):

删除三个class的init函数

刷新浏览器查看效果,你会发现和以前一样,没有变化

整合form

新建myproject/employee_management/form目录
新建myproject/employee_management/form/form.py文件,将/root/python/myproject/employee_management/views.py文件中的相关class移动到此文件中

from employee_management.utils.modelform import BootStrapModelForm
from employee_management.models import UserInfo, PrettyNum
from django.core.exceptions import ValidationError
from django import forms

class UserModelForm(BootStrapModelForm):

    ### 自定义数据校验
    # 例如: 用户名最小三个字符
    #name = forms.CharField(min_length=3, label="用户名")

    class Meta:
        model = UserInfo
        fields = ["name", "password", "age", "account", "create_time", "gender", "depart"]
        # 逐一控制标签的样式
        # widgets = {
    
    
        #     "name": forms.TextInput(attrs={"class": "form-control"}),
        #     "password": forms.PasswordInput(attrs={"class": "form-control"}),
        # }
        
        # 这里让日期可以手动点击鼠标选择,所以单独拎出来,加上日期插件
        widgets = {
    
    
            # "create_time": forms.DateInput(attrs={'class': 'form-control', 'id': 'myDate'}),
            "create_time": forms.DateInput(attrs={
    
    'id': 'myDate'}),
        }


class PrettyModelForm(BootStrapModelForm):

    # 数据校验: 验证方式1
    # mobile = forms.CharField(
    #     label="手机号",
    #     validators=[RegexValidator(r'^1[3-9]\d{9}$', '手机号格式错误'),],
    # )

    class Meta:
        model = PrettyNum
        # fields = "__all__"    表示取表中所有的字段
        fields = ['mobile', 'price', 'level', 'status']
        # exclude = ['level']   表示取除了表中的某个字段的所有字段

    # 数据校验: 验证方式2
    def clean_mobile(self):
        txt_mobile = self.cleaned_data['mobile']

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

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

        # 验证通过
        return txt_mobile


class PrettyEditModelForm(BootStrapModelForm):

    # mobile = forms.CharField(disabled=True, label="手机号")

    # 数据校验: 验证方式1
    # mobile = forms.CharField(
    #     label="手机号",
    #     validators=[RegexValidator(r'^1[3-9]\d{9}$', '手机号格式错误'),],
    # )

    class Meta:
        model = PrettyNum
        # fields = "__all__"    表示取表中所有的字段
        fields = ['mobile', 'price', 'level', 'status']
        # exclude = ['level']   表示取除了表中的某个字段的所有字段

    # 数据校验: 验证方式2
    def clean_mobile(self):
        txt_mobile = self.cleaned_data['mobile']

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

        # exclude 表示排除哪一个数据
        # self.instance.pk 表示当前编辑的哪一行 id
        exists_data = PrettyNum.objects.exclude(id=self.instance.pk).filter(mobile=txt_mobile).exists()
        if exists_data:
            raise ValidationError("手机号已存在")

        # 验证通过
        return txt_mobile

整理之后的views.py文件是这样的
在这里插入图片描述

优化views.py

我们也可以将/root/python/myproject/employee_management/views.py进行拆分
按照部门进行拆分

新建/root/python/myproject/employee_management/views目录
然后在其下依次新建user.py depart.py pretty.py

depart.py

from django.shortcuts import render, redirect
from employee_management.models import Department
from employee_management.utils.pagination import Pagination


def depart_list(request):
    """部门列表"""

    queryset = Department.objects.all()
    page_object = Pagination(request, queryset, page_size=2)

    page_object.html()

    context = {
    
    
        "queryset": page_object.page_queryset,
        "page_string": page_object.page_string,
    }

    return render(request, "depart_list.html", context)

def depart_add(request):
    """部门添加"""
    if request.method == "GET":
        return render(request, "depart_add.html")

    # 获取用户提交的部门数据
    depart_title = request.POST.get("depart_title")

    # 保存到数据库
    Department.objects.create(title=depart_title)

    # 重定向回部门列表
    return redirect("/depart/list/")

def depart_delete(request):
    """部门删除"""

    nid = request.GET.get('nid')
    Department.objects.filter(id=nid).delete()

    # 重定向回部门列表
    return redirect("/depart/list/")

def depart_edit(request, nid):
    """部门编辑"""

    if request.method == "GET":
        # 根据nid,获取数据
        row_object = Department.objects.filter(id=nid).first()
        return render(request, 'depart_edit.html', {
    
    "row_object": row_object})
    
    # 如果是POST请求,保存修改
    depart_title = request.POST.get('depart_title')
    Department.objects.filter(id=nid).update(title=depart_title)

    # 重定向回部门列表
    return redirect('/depart/list/')

user.py

from django.shortcuts import render, redirect
from employee_management.models import UserInfo
from employee_management.utils.pagination import Pagination
from employee_management.form.form import UserModelForm


def user_list(request):
    """用户列表"""
    # 获取所有用户列表
    queryset = UserInfo.objects.all()

    page_object = Pagination(request, queryset, page_size=2)

    # 用 python 的语法获取数据
    """
    for obj in user_data:
        # obj.get_gender_display() 表示匹配 男/女,原始字段名为gender,obj.get_字段名称_display()
        # obj.create_time.strftime("%Y-%m-%d") 表示将时间格式转换成固定格式的字符串
        # obj.depart.title 表示获取depart_id对应的部门名称,因为我们在models中定义表时与另外一张表设置了级联关系,有外键
        print(obj.id, obj.name, obj.password, obj.age, obj.account, obj.get_gender_display(), obj.depart.title, obj.create_time.strftime("%Y-%m-%d"))
    """

    page_object.html()

    context = {
    
    
        "queryset": page_object.page_queryset,
        "page_string": page_object.page_string,
    }

    return render(request, "user_list.html", context)

def user_model_form_add(request):
    """添加用户(ModelForm版本)"""
    if request.method == "GET": 
        form = UserModelForm()
        return render(request, "user_model_form_add.html", {
    
    "form": form})
    
    # 用户POST请求提交数据,需要进行数据校验
    form = UserModelForm(data=request.POST)
    if form.is_valid():
        print(form.cleaned_data)
        # 直接保存至数据库
        form.save()
        return redirect("/user/list/")
    
    # 校验失败(在页面上显示错误信息)
    return render(request, "user_model_form_add.html", {
    
    "form": form})

def user_edit(request, nid):
    """编辑用户"""
    
    row_obj = UserInfo.objects.filter(id=nid).first()
    
    # GET请求
    if request.method == "GET":
        form = UserModelForm(instance=row_obj)
        return render(request, "user_edit.html", {
    
    "form": form})
    
    # POST请求
    form = UserModelForm(data=request.POST, instance=row_obj)
    if form.is_valid():
        form.save()
        return redirect("/user/list/")

    return render(request, "user_edit.html", {
    
    "form": form})    

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

pretty.py

from django.shortcuts import render, redirect
from employee_management.models import PrettyNum
from employee_management.utils.pagination import Pagination
from employee_management.form.form import PrettyModelForm, PrettyEditModelForm
 

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


    print(request.GET)
    print(request.GET.urlencode())

    data_dict = {
    
    }
    # 如果是空字典,表示获取所有
    # 不加后面的 "", 首次访问浏览器,搜索框中不会显示前端页面中的 placeholder="Search for..." 属性
    search_data = request.GET.get('query', "")
    if search_data:
        data_dict["mobile__contains"] = search_data 
    
    queryset = PrettyNum.objects.filter(**data_dict).order_by("-level")

    ### 引入封装的 Pagination 类并初始化
    # 初始化
    page_object = Pagination(request, queryset, page_size=10, page_param="page")
    page_queryset = page_object.page_queryset

    # 调用对象的html方法,生成页码
    page_object.html()

    page_string = page_object.page_string

    context = {
    
    
        "pretty_data": page_queryset,   # 分页的数据
        "search_data": search_data,     # 搜索的内容
        "page_string": page_string,     # 页码
    }

    

    return render(request, "pretty_list.html", context)

def pretty_add(request):
    """添加靓号"""

    if request.method == "GET":
        form = PrettyModelForm()
        return render(request, "pretty_add.html", {
    
    "form": form})

    # 用户POST请求提交数据,需要进行数据校验
    form = PrettyModelForm(data=request.POST)
    if form.is_valid():
        print(form.cleaned_data)
        # 直接保存至数据库
        form.save()
        return redirect("/pretty/list/")
    
    # 校验失败(在页面上显示错误信息)
    return render(request, "pretty_add.html", {
    
    "form": form})

def pretty_edit(request, nid):
    """编辑靓号"""
    row_obj = PrettyNum.objects.filter(id=nid).first()
    
    # GET请求
    if request.method == "GET":
        form = PrettyEditModelForm(instance=row_obj)
        return render(request, "pretty_edit.html", {
    
    "form": form})
    
    # POST请求
    form = PrettyEditModelForm(data=request.POST, instance=row_obj)
    if form.is_valid():
        form.save()
        return redirect("/pretty/list/")

    return render(request, "pretty_edit.html", {
    
    "form": form})

def pretty_delete(request, nid):
    """删除靓号"""
    PrettyNum.objects.filter(id=nid).delete()
    return redirect('/pretty/list/')

重命名原来的views.py文件为views_old.py,不然会跟目前的views目录冲突
修改myproject/myproject/urls.py

from django.contrib import admin
from django.urls import path
from employee_management.views import depart,user,pretty

urlpatterns = [
    path('admin/', admin.site.urls),
    path('depart/list/', depart.depart_list),
    path('depart/add/', depart.depart_add),
    path('depart/delete/', depart.depart_delete),
    path('depart/<int:nid>/edit/', depart.depart_edit),
    path('user/list/', user.user_list),
    path('user/model/form/add/', user.user_model_form_add),
    path('user/<int:nid>/edit/', user.user_edit),
    path('user/<int:nid>/delete/', user.user_delete),
    path('pretty/list/', pretty.pretty_list),
    path('pretty/add/', pretty.pretty_add),
    path('pretty/<int:nid>/edit/', pretty.pretty_edit),
    path('pretty/<int:nid>/delete/', pretty.pretty_delete),
]

刷新浏览器,效果还是一样,不同的是我们的代码更加规整

猜你喜欢

转载自blog.csdn.net/qq_43139145/article/details/128716177