基于Pycharm的Django学习 —— 项目实战(分页、cookie、session、中间件、Ajax)

号外号外,基于Pycharm的Django学习,项目实战后续来啦!

前面我们已经学习了最基础的增删改查,现在主要是对前面的补充,以及尝试一些新花样。

靓号管理

首先数据库表结构准备起来!

class PrettyNum(models.Model):
    """靓号管理"""
    mobile = models.CharField(verbose_name="手机号", max_length=11)
    price = models.IntegerField(verbose_name="价格", default=0)
    level_choices = (
        (1, "1级"),
        (2, "2级"),
        (3, "3级"),
        (4, "4级"),
        (5, "5级")
    )
    level = models.SmallIntegerField(verbose_name="级别", choices=level_choices, default=1)
    status_choices = (
        (1, "已占用"),
        (2, "未使用")
    )
    status = models.SmallIntegerField(verbose_name="状态", choices=status_choices, default=2)

在这里插入图片描述
在这里插入图片描述
然后自己准备点数据进行测试(按理来说,后续应该都是自己爬取或者使用数据集):

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

number_list

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

{% extends 'layout.html' %}

{% block content %}
<!--添加该页面内容-->
<div style="margin-bottom:20px">
    <a class="btn btn-primary" href="#"><span class="glyphicon glyphicon-plus-sign"></span>
        新建靓号</a>
</div>
<!--面板+表格-->
<div>
    <div class="panel panel-default">
        <div class="panel-heading">
            <span class="glyphicon glyphicon-th-list"></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 data in data_list %}
            <tr>
                <th scope="row">{{data.id}}</th>
                <td>{{data.mobile}}</td>
                <td>{{data.price}}</td>
                <td>{{data.get_level_display}}</td>
                <td>{{data.get_status_display}}</td>
                <td>
                    <a class="btn btn-success btn-xs" href="#">编辑</a>
                    <a class="btn btn-danger btn-xs" href="#">删除</a>
                </td>
            </tr>
            {% endfor %}
            </tbody>
        </table>
    </div>
</div>
</div>
</div>
{% endblock %}

在这里插入图片描述

number_add

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

class NumberModelForm(forms.ModelForm):
    # 验证一
    # mobile = forms.CharField(
    #     label="手机号",
    #     # validators必须是数组!
    #     validators=[RegexValidator(r'^\d{11}$', '手机号必须11位')]
    # )

    class Meta:
        model = models.PrettyNum
        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}

    # 验证二 (可以使用除了正则以外的验证方法)
    def clean_mobile(self):
        txt_mobile = self.cleaned_data["mobile"]
        if len(txt_mobile) != 11:
            # 验证不通过
            raise ValidationError("格式错误")
        # 验证通过 将用户通过的值返回 数据库就保存的是这个数据
        return txt_mobile


def number_add(request):
    if request.method == "GET":
        form = NumberModelForm()
        return render(request, "number_add.html", {"form": form})
    form = NumberModelForm(data=request.POST)
    if form.is_valid():
        # 如果数据合法  保存到数据库
        print(form.cleaned_data)
        form.save()
        return redirect("/number/list/")
    else:
        print(form.errors)
        return render(request, "number_add.html", {"form": form})

其中对字段的有效值验证有两种方法:

# 验证一
    mobile = forms.CharField(
        label="手机号",
        # validators必须是数组!
        validators=[RegexValidator(r'^\d{11}$', '手机号必须11位')]
    )
# 验证二 (可以使用除了正则以外的验证方法)
    def clean_mobile(self):
        txt_mobile = self.cleaned_data["mobile"]
        if len(txt_mobile) != 11:
            # 验证不通过
            raise ValidationError("格式错误")
        # 验证通过 将用户通过的值返回 数据库就保存的是这个数据
        return txt_mobile

对于添加页面,和用户添加差不多,前期主要是功能的实现,等到后期就是进行代码优化等了。

{% extends 'layout.html' %}

{% block content %}
<!--添加该页面内容-->
<div class="panel panel-default">
    <div class="panel-heading">
        <h3 class="panel-title">新建靓号</h3>
    </div>
    <div class="panel-body">
        <!--novalidate去除浏览器默认验证-->
        <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>
{% endblock %}

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

number_edit

在这里插入图片描述
在这里插入图片描述
编辑可以和新建所使用的ModelForm一样,也可以重新写,比如在编辑的时候,不允许所有字段都编辑。

class NumberEditModelForm(forms.ModelForm):

    class Meta:
        model = models.PrettyNum
        # 不允许编辑手机号
        exclude = ["mobile"]

    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 number_edit(request, nid):
    # 根据id去数据库获取编辑的那一行
    row_object = models.PrettyNum.objects.filter(id=nid).first()
    if request.method == "GET":
        form = NumberEditModelForm(instance=row_object)
        return render(request, "number_edit.html", {"form": form})
    # 用Post提交的数据 进行数据校验
    form = NumberEditModelForm(data=request.POST, instance=row_object)
    if form.is_valid():
        # 如果数据合法  保存到数据库
        print(form.cleaned_data)
        # 默认保存的是用户输入的所有数据 如果想要再保存用户输入的额外的值
        # form.instance.字段名=值
        form.save()
        return redirect("/number/list/")
    else:
        print(form.errors)
        return render(request, "number_edit.html", {"form": form})

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
现在假设我们添加或者编辑手机号,并且不允许手机号重复,那怎么办呢?

我们可以去数据库搜是否有和其是一样的,但是有时候编辑不编辑手机号,所以搜的时候还要排除自己啊。

这种验证只能在钩子函数中操作,因为其涉及数据库。

class NumberModelForm(forms.ModelForm):
    # 验证一
    mobile = forms.CharField(
        label="手机号",
        # validators必须是数组!
        validators=[RegexValidator(r'^\d{11}$', '手机号必须11位')]
    )

    class Meta:
        model = models.PrettyNum
        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}

    # 验证二 (可以使用除了正则以外的验证方法)
    # def clean_mobile(self):
    #     txt_mobile = self.cleaned_data["mobile"]
    #     if len(txt_mobile) != 11:
    #         # 验证不通过
    #         raise ValidationError("格式错误")
    #     # 验证通过 将用户通过的值返回 数据库就保存的是这个数据
    #     return txt_mobile

    # 验证二 (主要用于设计数据库操作的验证)
    def clean_mobile(self):
        txt_mobile = self.cleaned_data["mobile"]
        # 查看数据库中该手机号是否存在
        exists = models.PrettyNum.objects.filter(mobile=txt_mobile).exists()
        if exists:
            # 验证不通过
            raise ValidationError("手机号已存在")
        # 验证通过 将用户通过的值返回 数据库就保存的是这个数据
        return txt_mobile


def number_add(request):
    if request.method == "GET":
        form = NumberModelForm()
        return render(request, "number_add.html", {"form": form})
    form = NumberModelForm(data=request.POST)
    if form.is_valid():
        # 如果数据合法  保存到数据库
        print(form.cleaned_data)
        form.save()
        return redirect("/number/list/")
    else:
        print(form.errors)
        return render(request, "number_add.html", {"form": form})


class NumberEditModelForm(forms.ModelForm):
    mobile = forms.CharField(
        label="手机号",
        # validators必须是数组!
        validators=[RegexValidator(r'^\d{11}$', '手机号必须11位')]
    )

    class Meta:
        model = models.PrettyNum
        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}

    # 验证二 (主要用于设计数据库操作的验证)
    def clean_mobile(self):
        # 获取当前编辑的对象的id
        # self.instance.pk
        txt_mobile = self.cleaned_data["mobile"]
        # 查看数据库中除编辑的该手机号之外是否存在
        exists = models.PrettyNum.objects.exclude(id=self.instance.pk).filter(mobile=txt_mobile).exists()
        if exists:
            # 验证不通过
            raise ValidationError("手机号已存在")
        # 验证通过 将用户通过的值返回 数据库就保存的是这个数据
        return txt_mobile


def number_edit(request, nid):
    # 根据id去数据库获取编辑的那一行
    row_object = models.PrettyNum.objects.filter(id=nid).first()
    if request.method == "GET":
        form = NumberEditModelForm(instance=row_object)
        return render(request, "number_edit.html", {"form": form})
    # 用Post提交的数据 进行数据校验
    form = NumberEditModelForm(data=request.POST, instance=row_object)
    if form.is_valid():
        # 如果数据合法  保存到数据库
        print(form.cleaned_data)
        # 默认保存的是用户输入的所有数据 如果想要再保存用户输入的额外的值
        # form.instance.字段名=值
        form.save()
        return redirect("/number/list/")
    else:
        print(form.errors)
        return render(request, "number_edit.html", {"form": form})

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

number_delete

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

在这里插入图片描述
点击删除就可以了!

在这里插入图片描述

number_list(新增搜索)

现在想实现在页面中添加搜索功能!

常见语法如下:

在这里插入图片描述

def number_list(request):
    # 拿到搜索的值 如果没有设置为空
    data_dict = {}
    search_data = request.GET.get("q", "")
    if search_data:
        data_dict["mobile__contains"] = search_data
    # 按照level降序排列
    data_list = models.PrettyNum.objects.filter(**data_dict).order_by("-level")
    return render(request, "number_list.html", {"data_list": data_list, "search_data": search_data})

{% extends 'layout.html' %}

{% block content %}
<!--添加该页面内容-->
<div style="margin-bottom:20px">
    <a class="btn btn-primary" href="/number/add/"><span class="glyphicon glyphicon-plus-sign"></span>
        新建靓号</a>

    <!--输入框组-->
    <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_data}}">
                <span class="input-group-btn">
                    <button class="btn btn-info" type="submit">
                        <span class="glyphicon glyphicon-search"></span>
                    </button>
                </span>
            </div>
        </form>
    </div>
</div>
<!--面板+表格-->
<div>
    <div class="panel panel-default">
        <div class="panel-heading">
            <span class="glyphicon glyphicon-th-list"></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 data in data_list %}
            <tr>
                <th scope="row">{{data.id}}</th>
                <td>{{data.mobile}}</td>
                <td>{{data.price}}</td>
                <td>{{data.get_level_display}}</td>
                <td>{{data.get_status_display}}</td>
                <td>
                    <a class="btn btn-success btn-xs" href="/number/{{data.id}}/edit">编辑</a>
                    <a class="btn btn-danger btn-xs" href="/number/{{data.id}}/delete">删除</a>
                </td>
            </tr>
            {% endfor %}
            </tbody>
        </table>
    </div>
</div>
</div>
</div>
{% endblock %}

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

number_list(分页管理)

在这里插入图片描述
首先要根据数据计算每一页的下标!

在这里插入图片描述
在这里插入图片描述
但是这样的话,不懂我们页面设计的人可能就不会了,所以要设置分页工具。

我就简单的生成了15条数据,所以这里每一页我设置的3条奥!

先来测试一下:

def number_list(request):
    # 拿到搜索的值 如果没有设置为空
    data_dict = {}
    search_data = request.GET.get("q", "")
    if search_data:
        data_dict["mobile__contains"] = search_data

    page = int(request.GET.get("page", 1))
    pagesize = 3
    start = (page-1)*pagesize
    end = page*pagesize

    # 按照level降序排列
    # data_list = models.PrettyNum.objects.filter(**data_dict).order_by("-level")[start:end]
    data_list = models.PrettyNum.objects.filter(**data_dict)[start:end]
    return render(request, "number_list.html", {"data_list": data_list, "search_data": search_data})
{% extends 'layout.html' %}

{% block content %}
<!--添加该页面内容-->
<div style="margin-bottom:20px">
    <a class="btn btn-primary" href="/number/add/"><span class="glyphicon glyphicon-plus-sign"></span>
        新建靓号</a>

    <!--输入框组-->
    <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_data}}">
                <span class="input-group-btn">
                    <button class="btn btn-info" type="submit">
                        <span class="glyphicon glyphicon-search"></span>
                    </button>
                </span>
            </div>
        </form>
    </div>
</div>
<!--面板+表格-->
<div>
    <div class="panel panel-default">
        <div class="panel-heading">
            <span class="glyphicon glyphicon-th-list"></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 data in data_list %}
            <tr>
                <th scope="row">{{data.id}}</th>
                <td>{{data.mobile}}</td>
                <td>{{data.price}}</td>
                <td>{{data.get_level_display}}</td>
                <td>{{data.get_status_display}}</td>
                <td>
                    <a class="btn btn-success btn-xs" href="/number/{{data.id}}/edit">编辑</a>
                    <a class="btn btn-danger btn-xs" href="/number/{{data.id}}/delete">删除</a>
                </td>
            </tr>
            {% endfor %}
            </tbody>
        </table>
    </div>
</div>

<!--分页功能-->
<nav aria-label="Page navigation">
  <ul class="pagination">
    <li>
      <a href="#" aria-label="Previous">
        <span aria-hidden="true">&laquo;</span>
      </a>
    </li>
    <li><a href="?page=1">1</a></li>
    <li><a href="?page=2">2</a></li>
    <li><a href="?page=3">3</a></li>
    <li><a href="?page=4">4</a></li>
    <li><a href="?page=5">5</a></li>
    <li>
      <a href="#" aria-label="Next">
        <span aria-hidden="true">&raquo;</span>
      </a>
    </li>
  </ul>
</nav>
{% endblock %}

在这里插入图片描述
但是这里的分页我们设置死了,但是如果其数据比较多,我们需要其自己自动跳转,所以还需要处理。

def number_list(request):
    # 拿到搜索的值 如果没有设置为空
    data_dict = {}
    search_data = request.GET.get("q", "")
    if search_data:
        data_dict["mobile__contains"] = search_data

    page = int(request.GET.get("page", 1))
    pagesize = 3
    start = (page-1)*pagesize
    end = page*pagesize

    # 按照level降序排列
    # data_list = models.PrettyNum.objects.filter(**data_dict).order_by("-level")[start:end]
    data_list = models.PrettyNum.objects.filter(**data_dict)[start:end]

    # 数据总条数
    total_count = models.PrettyNum.objects.filter(**data_dict).count()

    # 总页码计算 c(商),d(余数) = divmod(a,b)
    total_page_count, div = divmod(total_count, pagesize)
    if div:
        total_page_count += 1

    

    # 页码
    page_str_list = []

    # range()前取后不取
    for i in range(1, total_page_count+1):
        ele = '<li><a href="?page={}">{}</a></li>'.format(i, i)
        page_str_list.append(ele)
    # 将字符串使用mark_safe()后才能变成html
    page_string = mark_safe("".join(page_str_list))

    return render(request, "number_list.html", {"data_list": data_list, "search_data": search_data, "page_string":page_string})

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

这里的页面网址跳转是解决了,但是还是有一个问题,如果页面太多了咋整?所以还是要解决一下!

def number_list(request):
    # 拿到搜索的值 如果没有设置为空
    data_dict = {}
    search_data = request.GET.get("q", "")
    if search_data:
        data_dict["mobile__contains"] = search_data

    page = int(request.GET.get("page", 1))
    pagesize = 2
    start = (page-1)*pagesize
    end = page*pagesize

    # 按照level降序排列
    # data_list = models.PrettyNum.objects.filter(**data_dict).order_by("-level")[start:end]
    data_list = models.PrettyNum.objects.filter(**data_dict)[start:end]

    # 数据总条数
    total_count = models.PrettyNum.objects.filter(**data_dict).count()

    # 总页码计算 c(商),d(余数) = divmod(a,b)
    total_page_count, div = divmod(total_count, pagesize)
    if div:
        total_page_count += 1

    # 计算出显示当前页的前2页 后2页
    plus = 2

    # 设置不要出现负值
    if total_page_count <= 2 * plus + 1:
        # 数据库数据较少 都没有11页
        start_page = 1
        end_page = total_page_count
    else:
        # 数据库数据较多
        if page <= plus:
            # 不能有负值
            start_page = 1
            end_page = 2 * plus + 1
        else:
            # 设置不要超出总页码
            if (page + plus) >= total_page_count:
                start_page = total_page_count - 2 * plus
                end_page = total_page_count
            else:
                start_page = page - plus
                end_page = page + plus

    # 页码
    page_str_list = []

    # 首页
    first_page = '<li><a href="?page={}">首页</a></li>'.format(1)
    page_str_list.append(first_page)

    # 上一页
    if page>1:
        prev = '<li><a href="?page={}">上一页</a></li>'.format(page-1)
    else:
        prev = '<li><a href="?page={}">上一页</a></li>'.format(1)
    page_str_list.append(prev)

    # range()前取后不取 加不加一一定要注意啊!
    for i in range(start_page, end_page+1):
        if i == page:
            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)

    # 下一页
    if page<total_page_count:
        next = '<li><a href="?page={}">下一页</a></li>'.format(page+1)
    else:
        next = '<li><a href="?page={}">下一页</a></li>'.format(total_page_count)
    page_str_list.append(next)

    # 尾页
    last_page = '<li><a href="?page={}">尾页</a></li>'.format(total_page_count)
    page_str_list.append(last_page)

    # 将字符串使用mark_safe()后才能变成html
    page_string = mark_safe("".join(page_str_list))

    return render(request, "number_list.html", {"data_list": data_list, "search_data": search_data, "page_string":page_string})

在这里插入图片描述
要是页面很多,能不能搞一个输入框,是输入一个页码数就跳到输入的页码呢?将page传到前端来,这样只用改前端即可奥!

{% extends 'layout.html' %}

{% block content %}
<!--添加该页面内容-->
<div style="margin-bottom:20px">
    <a class="btn btn-primary" href="/number/add/"><span class="glyphicon glyphicon-plus-sign"></span>
        新建靓号</a>

    <!--输入框组-->
    <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_data}}">
                <span class="input-group-btn">
                    <button class="btn btn-info" type="submit">
                        <span class="glyphicon glyphicon-search"></span>
                    </button>
                </span>
            </div>
        </form>
    </div>
</div>
<!--面板+表格-->
<div>
    <div class="panel panel-default">
        <div class="panel-heading">
            <span class="glyphicon glyphicon-th-list"></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 data in data_list %}
            <tr>
                <th scope="row">{{data.id}}</th>
                <td>{{data.mobile}}</td>
                <td>{{data.price}}</td>
                <td>{{data.get_level_display}}</td>
                <td>{{data.get_status_display}}</td>
                <td>
                    <a class="btn btn-success btn-xs" href="/number/{{data.id}}/edit">编辑</a>
                    <a class="btn btn-danger btn-xs" href="/number/{{data.id}}/delete">删除</a>
                </td>
            </tr>
            {% endfor %}
            </tbody>
        </table>
    </div>
</div>

<!--分页功能-->
  <!--<ul class="pagination">
    <li><a href="?page=1">1</a></li>
    <li><a href="?page=2">2</a></li>
    <li><a href="?page=3">3</a></li>
    <li><a href="?page=4">4</a></li>
    <li><a href="?page=5">5</a></li>
  </ul>-->

<div>
    <ul class="pagination">
        {{page_string}}
    </ul>
    <!--输入框组-->
    <div style="float:right;width:200px">
        <form method="get">
            <div class="input-group">
                <!--设置name=page即可-->
                <input type="text" name="page" class="form-control" placeholder="输入页码" value="{{page}}">
                <span class="input-group-btn">
                    <button class="btn btn-success" type="submit">
                        <span class="glyphicon glyphicon-search"></span>
                    </button>
                </span>
            </div>
        </form>
    </div>

</div>

{% endblock %}

在这里插入图片描述

现在是实现了,但是有没有发现代码很冗余,所以我们想要把这个代码进行封装一下,相当于一个公共组件,那里想用哪里用!

# -*- coding:utf-8 -*-
# @Author  : 雾里看花花里看雾(王晓曼)
# @Time    : 2022/2/19 23:26
# @FileName: Pagination.py
# @Software: PyCharm
# @Blog    :https://blog.csdn.net/qq_43779149

from django.utils.safestring import mark_safe
import copy

"""
分页组件使用说明:
    1、筛选数据
    # 拿到搜索的值 如果没有设置为空
    data_dict = {}
    search_data = request.GET.get("q", "")
    if search_data:
        data_dict["mobile__contains"] = search_data
    data_list = models.PrettyNum.objects.filter(**data_dict)
    2、实例化对象
    page_object = Pagination(request, data_list)
    page_list = page_object.data_list
    page_string = page_object.html()
    3、处理传参数据
    content = {
        "page_list": page_list,
        "search_data": search_data,
        "page_string": page_string,
    }
    return render(request, "number_list.html", content)
    4、前端分页数据
    <ul class="pagination">
        {{page_string}}
    </ul>
"""


class Pagination(object):
    def __init__(self, request, data_list, pagesize=2, page_param="page", plus=2):
        """
        :param  request是请求请求的对象
        :param  data_list是符合条件的数据
        :param  pagesize是每页显示多少条数据
        :param  page_param是url中获取的分页参数
        :param  plus是显示当前的前后多少页
        :return page_string是html分页栏
        """

        # 拿到url里面的参数
        print(request.GET.urlencode())

        query_dict = copy.deepcopy(request.GET)
        query_dict.mutable = True
        self.query_dict = query_dict
        self.page_param = page_param

        # query_dict.setlist('page', [3])
        # print(query_dict.urlencode())

        page = request.GET.get(page_param, "1")
        # 如果page是十进制的数
        if page.isdecimal():
            page = int(page)
        else:
            page = 1

        self.page = page
        self.pagesize = pagesize
        self.plus = plus

        self.start = (page - 1) * pagesize
        self.end = page * pagesize
        # 注意有的是,有的是:不要搞错了!
        self.data_list = data_list[self.start:self.end]

        # 数据总条数
        total_count = data_list.count()

        # 总页码计算 c(商),d(余数) = divmod(a,b)
        total_page_count, div = divmod(total_count, pagesize)
        if div:
            total_page_count += 1

        self.total_page_count = total_page_count

    def html(self):

        # 设置不要出现负值
        if self.total_page_count <= 2 * self.plus + 1:
            # 数据库数据较少 都没有11页
            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 = []


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

        # 上一页
        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)

        # range()前取后不取 加不加一一定要注意啊!
        for i in range(start_page, end_page + 1):
            if i == self.page:
                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)

        # 下一页
        if self.page < self.total_page_count:
            self.query_dict.setlist(self.page_param, [self.page + 1])
            next = '<li><a href="?{}">下一页</a></li>'.format(self.query_dict.urlencode())
        else:
            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)

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

        # 将字符串使用mark_safe()后才能变成html
        page_string = mark_safe("".join(page_str_list))

        return page_string

怎么使用呢?

def number_list(request):
    # 拿到搜索的值 如果没有设置为空
    data_dict = {}
    search_data = request.GET.get("q", "")
    if search_data:
        data_dict["mobile__contains"] = search_data

    data_list = models.PrettyNum.objects.filter(**data_dict)

    page_object = Pagination(request, data_list)
    page_list = page_object.data_list
    page_string = page_object.html()

    content = {
        "page_list": page_list,
        "search_data": search_data,
        "page_string": page_string,
    }
    return render(request, "number_list.html", content)
{% extends 'layout.html' %}

{% block content %}
<!--添加该页面内容-->
<div style="margin-bottom:20px">
    <a class="btn btn-primary" href="/number/add/"><span class="glyphicon glyphicon-plus-sign"></span>
        新建靓号</a>

    <!--输入框组-->
    <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_data}}">
                <span class="input-group-btn">
                    <button class="btn btn-info" type="submit">
                        <span class="glyphicon glyphicon-search"></span>
                    </button>
                </span>
            </div>
        </form>
    </div>
</div>
<!--面板+表格-->
<div>
    <div class="panel panel-default">
        <div class="panel-heading">
            <span class="glyphicon glyphicon-th-list"></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 data in page_list %}
            <tr>
                <th scope="row">{{data.id}}</th>
                <td>{{data.mobile}}</td>
                <td>{{data.price}}</td>
                <td>{{data.get_level_display}}</td>
                <td>{{data.get_status_display}}</td>
                <td>
                    <a class="btn btn-success btn-xs" href="/number/{{data.id}}/edit">编辑</a>
                    <a class="btn btn-danger btn-xs" href="/number/{{data.id}}/delete">删除</a>
                </td>
            </tr>
            {% endfor %}
            </tbody>
        </table>
    </div>
</div>

<!--分页功能-->
  <!--<ul class="pagination">
    <li><a href="?page=1">1</a></li>
    <li><a href="?page=2">2</a></li>
    <li><a href="?page=3">3</a></li>
    <li><a href="?page=4">4</a></li>
    <li><a href="?page=5">5</a></li>
  </ul>-->

<div>
    <ul class="pagination">
        {{page_string}}
    </ul>
    <!--输入框组-->
    <div style="float:right;width:200px">
        <form method="get">
            <div class="input-group">
                <!--设置name=page即可-->
                <input type="text" name="page" class="form-control" placeholder="输入页码">
                <span class="input-group-btn">
                    <button class="btn btn-success" type="submit">
                        <span class="glyphicon glyphicon-search"></span>
                    </button>
                </span>
            </div>
        </form>
    </div>

</div>

{% endblock %}

在这里插入图片描述

ModelForm和BootStrap

我们每写一个类就要写一个bootstrap样式,这样太累了吧,想想继承?

# -*- coding:utf-8 -*-
# @Author  : 雾里看花花里看雾(王晓曼)
# @Time    : 2022/2/20 1:02
# @FileName: BootStrap.py
# @Software: PyCharm
# @Blog    :https://blog.csdn.net/qq_43779149

"""BootStrap样式类"""


class BootStrapModelForm(object):
    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}

这样就将一个BootStrap样式类写好了,需要加样式就继承该类就好啦。

优化项目目录

由于当业务需求越来越广时,我们要编辑的视图函数和类会越来越多,如果都放在一个py文件中,就会看着很缭乱,所以我们需要想办法优化一下项目目录。

在这里插入图片描述

管理员管理

表结构如下:

class Admin(models.Model):
    """管理员"""
    username = models.CharField(verbose_name="用户名", max_length=32)
    password = models.CharField(verbose_name="密码", max_length=64)

在这里插入图片描述
在这里插入图片描述
写一个函数准备点数据:

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

admin_list

为了安全起见,一般管理员的密码是不让显示出来的。

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

现在我们尝试一下,上面写的分页管理,进行简单的应用一下。

我们先来回顾一下分页组件使用说明:

分页组件使用说明

分页组件使用说明:
    1、筛选数据
    # 拿到搜索的值 如果没有设置为空
    data_dict = {}
    search_data = request.GET.get("q", "")
    if search_data:
        data_dict["mobile__contains"] = search_data
    data_list = models.PrettyNum.objects.filter(**data_dict)
    2、实例化对象
    page_object = Pagination(request, data_list)
    page_list = page_object.data_list
    page_string = page_object.html()
    3、处理传参数据
    content = {
        "page_list": page_list,
        "search_data": search_data,
        "page_string": page_string,
    }
    return render(request, "number_list.html", content)
    4、前端分页数据
    <ul class="pagination">
        {{page_string}}
    </ul>

具体可以根据使用更改:

def admin_list(request):
    data_list = models.Admin.objects.all()
    page_object = Pagination(request, data_list)
    content = {
        "data_list": page_object.data_list,
        "page_string": page_object.html()
    }
    return render(request, "admin_list.html", content)

在这里插入图片描述

admin_add

由于我们前面写的添加页面,基本上都是差不多的,所以这里写一个公共的添加页面。

{% extends 'layout.html' %}

{% block content %}
<!--添加该页面内容-->
<div class="panel panel-default">
    <div class="panel-heading">
        <h3 class="panel-title">{{title}}</h3>
    </div>
    <div class="panel-body">
        <!--novalidate去除浏览器默认验证-->
        <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>
{% endblock %}

在这里插入图片描述
继承样式类后,就只用写这么一点咯。

class AdminModelForm(BootStrapModelForm, forms.ModelForm):
    class Meta:
        model = models.Admin
        fields = "__all__"

在这里插入图片描述
先只写了get测试:

在这里插入图片描述
在这里插入图片描述
对于密码,我们希望确认密码,并且显示的是密码框。

class AdminModelForm(BootStrapModelForm, forms.ModelForm):
    # 增加一个确认密码输入框 并且设置密码不可见
    confirm_password = forms.CharField(
        label="确认密码",
        # render_value=True用于密码不一致的时候 不清空密码
        widget=forms.PasswordInput(render_value=True)
    )

    class Meta:
        model = models.Admin
        fields = ["username", "password", "confirm_password"]
        widgets = {
            # 对于密码也设置不可见
            "password": forms.PasswordInput(render_value=True)
        }

    # 密码md5加密
    def clean_password(self):
        pwd = self.cleaned_data.get("password")
        # 存储的是密码密文
        return md5(pwd)

    def clean_confirm_password(self):
        # 第一次输入的密码
        pwd = self.cleaned_data.get("password")
        # 确认输入的密码
        confirm = md5(self.cleaned_data.get("confirm_password"))
        if pwd != confirm:
            raise ValidationError("密码不一致")
        # 放在cleaned_data 到时候save到数据库
        return confirm

同时还希望进行md5加密。

import hashlib
from employeesystem.settings import SECRET_KEY


def md5(data_string):
    """md5加密"""
    obj = hashlib.md5(SECRET_KEY.encode("utf-8"))
    obj.update(data_string.encode("utf-8"))
    return obj.hexdigest()

可以看出:

def admin_add(request):
    if request.method == "GET":
        form = AdminModelForm()
        content = {
            "form": form,
            "title": "新建管理员"
        }
        return render(request, "common.html", content)
    form = AdminModelForm(data=request.POST)
    if form.is_valid():
        # 如果数据合法  保存到数据库
        print(form.cleaned_data)
        form.save()
        return redirect("/admin/list/")
    else:
        print(form.errors)
        return render(request, "common.html", {"form": form, "title": "新建管理员"})

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

admin_edit

这里我们设置只允许修改用户名。

class AdminEditModelForm(BootStrapModelForm, forms.ModelForm):
    class Meta:
        model = models.Admin
        fields = ["username"]
def admin_edit(request, nid):
    # 对象/None
    row_object = models.Admin.objects.filter(id=nid).first()
    if not row_object:
        return render(request, "error.html", {"msg": "数据不存在"})
    if request.method == "GET":
        form = AdminEditModelForm(instance=row_object)
        return render(request, "common.html", {"form": form, "title": "编辑管理员"})
    # 用Post提交的数据 进行数据校验
    form = AdminEditModelForm(data=request.POST, instance=row_object)
    if form.is_valid():
        # 如果数据合法  保存到数据库
        print(form.cleaned_data)
        # 默认保存的是用户输入的所有数据 如果想要再保存用户输入的额外的值
        # form.instance.字段名=值
        form.save()
        return redirect("/admin/list/")
    else:
        print(form.errors)
        return render(request, "common.html", {"form": form, "title": "编辑管理员"})

在这里插入图片描述

admin_delete

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

重置密码

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

class AdminResetModelForm(BootStrapModelForm, forms.ModelForm):
    # 增加一个确认密码输入框 并且设置密码不可见
    confirm_password = forms.CharField(
        label="确认密码",
        widget=forms.PasswordInput()
    )

    class Meta:
        model = models.Admin
        fields = ["password", "confirm_password"]
        widgets = {
            # 对于密码也设置不可见
            "password": forms.PasswordInput()
        }

    # 密码md5加密
    def clean_password(self):
        pwd = self.cleaned_data.get("password")
        # 存储的是密码密文
        return md5(pwd)

    def clean_confirm_password(self):
        # 第一次输入的密码
        pwd = self.cleaned_data.get("password")
        # 确认输入的密码
        confirm = md5(self.cleaned_data.get("confirm_password"))
        if pwd != confirm:
            raise ValidationError("密码不一致")
        # 放在cleaned_data 到时候save到数据库
        return confirm
def admin_reset(request, nid):
    # 对象/None
    row_object = models.Admin.objects.filter(id=nid).first()
    if not row_object:
        return render(request, "error.html", {"msg": "数据不存在"})
    if request.method == "GET":
        form = AdminResetModelForm(instance=row_object)
        return render(request, "common.html", {"form": form, "title": "重置密码——{}".format(row_object.username)})
    # 用Post提交的数据 进行数据校验
    form = AdminResetModelForm(data=request.POST, instance=row_object)
    if form.is_valid():
        # 如果数据合法  保存到数据库
        print(form.cleaned_data)
        # 默认保存的是用户输入的所有数据 如果想要再保存用户输入的额外的值
        # form.instance.字段名=值
        form.save()
        return redirect("/admin/list/")
    else:
        print(form.errors)
        return render(request, "common.html", {"form": form, "title": "重置密码——{}".format(row_object.username)})

在这里插入图片描述
但是需要注意,重置密码的时候不能和之前的一样!

在这里插入图片描述

Cookie和Session

cookie:
在这里插入图片描述
session:
在这里插入图片描述

登录认证

在这里插入图片描述
这个是使用form来做的:

# 使用form来做的
class LoginForm(BootStrapModelForm, forms.Form):
    username = forms.CharField(label="用户名", widget=forms.TextInput)
    password = forms.CharField(label="密码", widget=forms.PasswordInput)

    # 密码md5加密
    def clean_password(self):
        pwd = self.cleaned_data.get("password")
        return md5(pwd)
def login(request):
    if request.method == "GET":
        form = LoginForm()
        return render(request, "login.html", {"form": form})
    form = LoginForm(data=request.POST)
    if form.is_valid():
        # form.cleaned_data拿到的数据 是一个字典
        admin_object = models.Admin.objects.filter(**form.cleaned_data).first()
        if not admin_object:
            # 显示错误信息
            form.add_error("username", "用户名或者密码错误")
            return render(request, "login.html", {"form": form})

        # 网站生成随机字符串:写到用户浏览器的cookie中 再写入到session中
        request.session["info"] = admin_object.username
        # 用户名 密码正确 登录成功
        return redirect("/admin/list/")
    # 空的时候的处理
    return render(request, "login.html", {"form": form})

相应的页面和样式如下:

{% load static %}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Login</title>
    <link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
    <link rel="stylesheet" type="text/css" href="{% static 'css/login.css' %}"/>
</head>
<body>
<div>
    <!--设置居中-->
    <div class="container">
        <div class="panel panel-default">
            <div class="panel-heading">
                <h3 class="panel-title">Login</h3>
            </div>
            <div class="panel-body">
                <!--novalidate去除浏览器默认验证-->
                <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>
</div>
</body>
<script src="{% static 'js/jquery.min.js' %}"></script>
<script src="{% static 'js/bootstrap.min.js' %}"></script>
</html>
body{
    /* 设置背景图片url */
    background-image:url(../img/flowers.jpg);
    /* 设置背景图片全屏 */
    background-size:100%;
}
.container{
    width:300px;
    margin-top:150px;
}
.panel-title{
    text-align:center;
}
.btn-primary{
    width:70px;
    height:35px;
    margin-left:80px;
    margin-top:10px;
}

在这里插入图片描述
同样有一点,那就是如果你不登陆,那么相应的什么列表是不会让访问的,所以对于每一个函数,都要加上这样的验证信息。

在这里插入图片描述
所以就有了中间件!

中间件

在这里插入图片描述
注意是在app对应目录下新建一个文件夹middleware,然后再创建一个文件,编辑一个中间件(也就是一个类)后,然后要去settings.py下注册该中间件!

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
如果在process_request中,没有返回值,那就是可以继续走下去,如果有返回值,那就回来了。

中间件执行的顺序,是按照注册的顺序来的。

那么问题来啦,怎么使用中间件实现登录验证呢?

class AuthMiddleware(MiddlewareMixin):
    def process_request(self, request):

        # 排除那些不需要登录就可以访问的页面
        # request.path_info表示获取当前用户请求的url
        if request.path_info == "/login/":
            return

        # 1、读取当前访问的用户的session值
        info_dict = request.session.get("info")
        if info_dict:
            return

        # 2、没有登录过
        return redirect("/login/")

在这里插入图片描述

注销

那么怎么清除session呢?

在这里插入图片描述

def logout(request):
    request.session.clear()
    return redirect("/login/")

这样当我们没有登录的时候,是不能访问任何其他页面的。

于是我又把页面大致逻辑改的相对合理一点了!

在这里插入图片描述
当登录进去后,这里就是当前登录用户的用户名,当点击注销后就又变成了登录页面。

图片验证码

只有用户名和密码,存在暴力破解!

 <div class="form-group">
                        <label for="id_code">图片验证码</label>
                        <div class="row">
                            <div class="col-xs-7">
                                <input type="text" name="code" class="form-control" placeholder="请输入验证码" id="id_code">
                                <span style="color:red;"></span>
                            </div>
                            <div class="col-xs-5">
                                <img id="image_code" style="width:88px;height:32px;" src="/image/code/">
                            </div>
                        </div>
                    </div>

在这里插入图片描述

def login(request):
    if request.method == "GET":
        form = LoginForm()
        return render(request, "login.html", {"form": form})
    form = LoginForm(data=request.POST)
    if form.is_valid():
        # 验证码校验

        # 因为要在数据库中搜索 需要使用pop 注意区分session中的字段和cleaned_data中的字段!
        user_input_code = form.cleaned_data.pop("code")
        # 60秒超时就可能为空 故为了比较设置一个默认空值
        code = request.session.get("image_code", "")
        if code.upper() != user_input_code.upper():
            form.add_error("code", "验证码错误")
            return render(request, "login.html", {"form": form})

        # 用户名和密码校验

        # form.cleaned_data拿到的数据 是一个字典
        admin_object = models.Admin.objects.filter(**form.cleaned_data).first()
        if not admin_object:
            # 显示错误信息
            form.add_error("username", "用户名或者密码错误")
            return render(request, "login.html", {"form": form})

        # 网站生成随机字符串:写到用户浏览器的cookie中 再写入到session中
        request.session["info"] = admin_object.username

        # 7天免登录 否则60秒失效
        request.session.set_expiry(60*60*24*7)

        # 用户名 密码正确 登录成功
        return redirect("/admin/list/")
    # 空的时候的处理
    return render(request, "login.html", {"form": form})

生成随机码图片:

def image_code(request):
    # 调用pillow函数 生成图片
    img, code_string = check_code()
    print(code_string)

    # 写入到自己的session中 以便于后续获取验证码再进行校验
    request.session["image_code"] = code_string
    # 给session设置60s超时
    request.session.set_expiry(60)

    stream = BytesIO()
    img.save(stream, 'png')

    return HttpResponse(stream.getvalue())

任务管理

Jquery的Ajax请求!

class Task(models.Model):
    """任务"""
    level_choices = (
        (1, "紧急"),
        (2, "重要"),
        (3, "临时"),
    )
    level = models.SmallIntegerField(verbose_name="级别", choices=level_choices, default=1)
    title = models.CharField(verbose_name="标题", max_length=64)
    detail = models.TextField(verbose_name="详细信息")

    user = models.ForeignKey(verbose_name="负责人", to="Admin", on_delete=models.CASCADE)

在这里插入图片描述

task_list

在这里插入图片描述

def task_list(request):
    """任务列表"""
    form = TaskModelForm()
    return render(request, "task_list.html", {"form": form})
# ajax使用post请求时需要加上csrf免除
@csrf_exempt
def task_add(request):
    data_dict = {"status": True}
    return HttpResponse(json.dumps(data_dict))

{% extends 'layout.html' %}

{% block content %}
<div class="panel panel-default">
    <div class="panel-heading">
        <h3 class="panel-title">任务表单</h3>
    </div>
    <div class="panel-body">
        <!--novalidate去除浏览器默认验证-->
        <form id="formAdd">
            {% for field in form %}
            <div class="form-group">
                <label>{{field.label}}</label>
                {{field}}
                <span style="color:red;">{{field.errors.0}}</span>
            </div>
            {% endfor %}
            <!--不能写成submit了 要写成button ajax就是在js中绑定事件-->
            <button type="button" id="btnAdd" class="btn btn-primary">提交</button>
        </form>
    </div>
</div>
{% endblock %}

{% block js %}
    <script type="text/javascript">
        $(function(){
            bindBtnAddEvent();
        })

        function bindBtnAddEvent(){
            $("#btnAdd").click(function(){
                $.ajax({
                    url: "/task/add/",
                    type: "post",
                    data: $("#formAdd").serialize(),
                    dataType: "JSON",
                    success: function(res){
                        console.log(res);
                        console.log(res.status);
                        console.log(res.data);
                    }
                })
            })
        }
    </script>
{% endblock %}

在这里插入图片描述

task_add

$.ajax({
                    url: 请求的url,
                    type: 请求的方式,
                    data: 请求的数据,
                    dataType: 请求的数据格式,
                    success: function(成功时返回的结果){
                        console.log(res);
                        console.log(res.status);
                        console.log(res.data);
                    }
                })

如下:

# ajax使用post请求时需要加上csrf免除
@csrf_exempt
def task_add(request):
    # 用户发送过来的数据进行校验(ModelForm进行校验)
    form = TaskModelForm(data=request.POST)
    if form.is_valid():
        form.save()
        data_dict = {"status": True}
        return HttpResponse(json.dumps(data_dict))
    data_dict = {"status": False, "error": form.errors}
    return HttpResponse(json.dumps(data_dict))

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

在这里插入图片描述
下一篇文章见!!!

  • 6
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值