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. 总结
主要完成了:
- 熟练运用ModelForm的基本操作,巩固了之前的知识
- 添加了校验功能,网页更稳定
- 加入了搜索功能,更人性化
- 加入分页功能,还采取了类封装的形式