项目实战一
需求
以 前后端不分离的方式实现学生的增删改查操作
学生列表功能
接口设计
url:/students/
请求方法:get
参数:
- 格式:查询参数
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
page | int | 否 | 页码,默认为1 |
size | init | 否 | 每页数据条数默认为10 |
name | str | 否 | 根据姓名过滤 |
age | int | 否 | 根据年龄过滤 |
sex | int | 否 | 根据性别过滤 |
phone | str | 否 | 根据手机过滤 |
channel_id | int | 否 | 根据渠道过滤 |
响应:html
代码
视图
import math
from urllib.parse import urlencode
def student_list(request):
"""学生列表视图"""
# 1. 获取查询参数
query_params = {key: value for key, value in request.GET.items()}
# 2. 获取分页参数
page = int(query_params.pop('page', 1))
size = int(query_params.pop('size', 10))
# 3. 获取查询集
queryset = Student.objects.all()
for key, value in query_params.items():
try:
queryset = queryset.filter(**{key: value})
except:
pass
# 4. 分页处理
# 数据总条数
total_num = queryset.count()
# 总页数
total_page = math.ceil(total_num/size)
# 下一页
if page < total_page:
query_params.update({'page': page+1, 'size': size})
next_page_query_params = urlencode(query_params)
else:
next_page_query_params = None
# 上一页
if page > 1:
query_params.update({'page': page - 1, 'size': size})
pre_page_query_params = urlencode(query_params)
else:
pre_page_query_params = None
# 分页过滤
queryset = queryset[(page-1)*size:page*size]
# 5. 渲染模板并返回响应
return render(request, 'crm/student_list.html', context={
'queryset': queryset,
'pre_page': pre_page_query_params,
'next_page': next_page_query_params,
'page': page,
'total_num': total_num,
'total_page': total_page
})
路由
path('students/', views.student_list, name='student-list'),
模板
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
<title>学生列表</title>
<!-- Bootstrap -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css"
integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
<!-- HTML5 shim 和 Respond.js 是为了让 IE8 支持 HTML5 元素和媒体查询(media queries)功能 -->
<!-- 警告:通过 file:// 协议(就是直接将 html 页面拖拽到浏览器中)访问页面时 Respond.js 不起作用 -->
<!--[if lt IE 9]>
<script src="https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script>
<![endif]-->
</head>
<body>
<div class="container">
<h1>学生列表</h1>
<div style="width: 800px">
{% if queryset %}
<a class="btn btn-success" style="float: right" href="{% url 'student-create' %}">添加</a>
<table class="table table-hover table-bordered table-condensed">
<thead>
<tr>
<th>序号</th>
<th>姓名</th>
<th>年龄</th>
<th>性别</th>
<th>电话</th>
<th>渠道</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for obj in queryset %}
<tr>
<td>{{ forloop.counter }}</td>
<td>{{ obj.name }}</td>
<td>{{ obj.age | default:"" }}</td>
<td>{% if obj.sex == 1 %}男{% else %}女{% endif %}</td>
<td>{{ obj.phone | default:"" }}</td>
<td>{% if obj.channel %}{{ obj.channel.title }}{% endif %}</td>
<td>{{ obj.c_time }}</td>
<td><a class="btn btn-primary" href="{% url 'student-update' obj.id %}">编辑</a>
<a class="btn btn-danger" href="{% url 'student-delete' obj.id %}">删除</a></td>
</tr>
{% endfor %}
</tbody>
</table>
<nav aria-label="...">
<ul class="pager">
<li class="previous {% if not pre_page %}disabled{% endif %}"><a
href="{% if pre_page %}?{{ pre_page }}{% else %}#{% endif %}"><span
aria-hidden="true">←</span> 上一页</a></li>
<li class="next {% if not next_page %}disabled{% endif %}"><a
href="{% if next_page %}?{{ next_page }}{% else %}#{% endif %}">下一页 <span aria-hidden="true">→</span></a>
</li>
</ul>
</nav>
{% else %}
<p>查不到数据...</p>
{% endif %}
</div>
</div>
<!-- jQuery (Bootstrap 的所有 JavaScript 插件都依赖 jQuery,所以必须放在前边) -->
{# <script src="https://cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js" integrity="sha384-nvAa0+6Qg9clwYCGGPpDQLVpLNn0fRaROjHqs13t4Ggj3Ez50XnGQqc/r8MhnRDZ" crossorigin="anonymous"></script>#}
<!-- 加载 Bootstrap 的所有 JavaScript 插件。你也可以根据需要只加载单个插件。 -->
{# <script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js" integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd" crossorigin="anonymous"></script>#}
</body>
</html>
访问http://127.0.0.1/crm/students/
效果图如下:
学生添加功能
学生添加页面
接口设计
url:/students/create/
请求方法:get
响应:html页面
代码
- 视图
class StudentCreateView(View):
"""
学生添加视图
"""
def get(self, request):
"""学生添加页面"""
# 1. 获取渠道对象
channels = Channel.objects.all()
return render(request, 'crm/student_detail.html', context={'channels': channels})
- 路由
path('students/create/', views.StudentCreateView.as_view(), name='student-create')
- 模板
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
<title>{% if obj %}修改{% else %}添加{% endif %}学生</title>
<!-- Bootstrap -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
<!-- HTML5 shim 和 Respond.js 是为了让 IE8 支持 HTML5 元素和媒体查询(media queries)功能 -->
<!-- 警告:通过 file:// 协议(就是直接将 html 页面拖拽到浏览器中)访问页面时 Respond.js 不起作用 -->
<!--[if lt IE 9]>
<![endif]-->
</head>
<body>
<div class="container">
<div style="width: 800px">
<h1>学生{% if obj %}修改{% else %}添加{% endif %}页面</h1>
<form class="form-horizontal" method="post">
<div class="form-group">
<label for="name" class="col-sm-2 control-label">姓名</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="name" name="name" placeholder="姓名" value="{{ obj.name }}">
</div>
</div>
<div class="form-group">
<label for="age" class="col-sm-2 control-label">年龄</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="age" name="age" placeholder="年龄" value="{{ obj.age|default:"" }}">
</div>
</div>
<div class="form-group">
<label for="sex" class="col-sm-2 control-label">性别</label>
<div class="col-sm-10">
<select name="sex" id="sex" class="form-control">
<option value="1" {% if obj.sex == 1%}selected{% endif %}>男</option>
<option value="0" {% if obj.sex == 0%}selected{% endif %}>女</option>
</select>
</div>
</div>
<div class="form-group">
<label for="phone" class="col-sm-2 control-label">电话号码</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="phone" name="phone" placeholder="phone" value="{{ obj.phone|default:"" }}">
</div>
</div>
<div class="form-group">
<label for="channel" class="col-sm-2 control-label">渠道</label>
<div class="col-sm-10">
<select name="channel" id="channel" class="form-control">
<option value="">--------</option>
{% for ch in channels %}
<option value="{{ ch.id }}" {% if obj.channel.title == ch.title %}selected{% endif %}>{{ ch.title }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default">{% if obj %}修改{% else %}添加{% endif %}</button>
{# <button type="submit" class="btn btn-default">添加</button>#}
</div>
</div>
</form>
</div>
</div>
<!-- jQuery (Bootstrap 的所有 JavaScript 插件都依赖 jQuery,所以必须放在前边) -->
<script src="https://cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js" integrity="sha384-nvAa0+6Qg9clwYCGGPpDQLVpLNn0fRaROjHqs13t4Ggj3Ez50XnGQqc/r8MhnRDZ" crossorigin="anonymous"></script>
<!-- 加载 Bootstrap 的所有 JavaScript 插件。你也可以根据需要只加载单个插件。 -->
<script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js" integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd" crossorigin="anonymous"></script>
</body>
</html>
访问http://127.0.0.1/crm/students/create/
效果图如下:
学生添加
接口设计
url:/students/create/
请求方法:post
参数:
- 格式:form
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
name | str | 是 | 姓名 |
age | int | 否 | 年龄 |
sex | int | 否 | 性别 |
phone | str | 否 | 手机 |
channel_id | int | 否 | 渠道id |
响应:重定向到学生列表页面
代码
- 视图
class StudentCreateView(View):
"""
学生添加视图
"""
...
def post(self, request):
"""添加学生"""
# 1. 获取前端传递的数据
data = {key: value for key, value in request.POST.items() if value}
if data.get('channel', None):
data['channel_id'] = data.pop('channel')
# 2. 创建学生
try:
obj = Student.objects.create(**data)
except Exception as e:
# 粗糙的处理现在就够了
return HttpResponse(str(e), status=400)
# 3. 返回响应
return redirect(reverse('student-list'))
打开F12,鼠标移动到“添加”按钮上面,左下角展示路由路径是在原有的路径下面紧跟aaa
如果a标签里href为以"/"开头的,比如说“/aaa”那就直接从根路由后面紧跟你href的值,如下图
大家看出区别来了吗?
那此时添加按钮我可以直接写成创建学生也就是学生详情那个界面的路由吗?当然可以,但不过这样子写就是硬编码。
我们应该用模板标签
然后我们看渠道是下拉选择框,那这个选择项我们怎么获取?肯定是从数据库查了获取对吧。此时思路就是这样子:
1. 先去数据库查有几个渠道。
2. 查到之后渲染模板。
学生修改功能
学生修改页面
接口设计
url:/students/update/
请求方法:get
响应:html页面
代码
- 视图
from django.shortcuts import get_object_or_404
class StudentUpdateView(View):
"""
学生更新视图
"""
def get_obj(self, pk):
obj = get_object_or_404(Student, pk=pk)
return obj
def get(self, request, pk):
# 1. 获取修改对象
obj = self.get_obj(pk)
# 2. 获取渠道对象
channels = Channel.objects.all()
# 2. 渲染并返回修改页面
return render(request, 'crm/student_detail.html', context={'channels': channels, 'obj': obj})
- 路由
path('students/update/<int:pk>/', views.StudentUpdateView.as_view(), name='student-update')
- 模板
同添加功能模板
学生修改
接口设计
url:/students/update/pk/
请求方法:post
参数:
-
路径参数
-
格式:form
参数名 | 类型 | 是否必传 | 说明 |
---|---|---|---|
name | str | 否 | 姓名 |
age | int | 否 | 年龄 |
sex | int | 否 | 性别 |
phone | str | 否 | 手机 |
channel_id | int | 否 | 渠道id |
响应:重定向到学生列表页面
代码
- 视图
class StudentUpdateView(View):
"""
学生更新视图
"""
...
def post(self, request, pk):
# 1. 获取修改对象
obj = self.get_obj(pk)
# 2. 获取前端传递的数据
data = {key: value for key, value in request.POST.items() if value}
if data.get('channel', None):
data['channel_id'] = data.pop('channel')
# 3. 更新学生
for key, value in data.items():
setattr(obj, key, value)
try:
obj.save(update_fields=data.keys())
except Exception as e:
# 粗糙的处理现在就够了
return HttpResponse(str(e), status=400)
# 4. 返回响应
return redirect(reverse('student-list'))
- 路由
同上
学生删除功能
接口设计
url:/students/delete/pk/
请求方法:get
参数:
- 路径参数
响应:重定向到学生列表页面
代码
- 视图
def student_delete(request, pk):
# 1. 获取对象
obj = get_object_or_404(Student, pk=pk)
# 2. 删除对象
obj.delete()
# 3. 返回响应
return redirect(reverse('student-list'))
- 路由
path('students/delete/<int:pk>', views.student_delete, name='student-delete')