前言
目前我们已经有了一些静态的页面,接下来我们就进行数据的交互。
首先我们要学习模板的抽离,因为每个没有有很多相同的代码,我们可以把公共的部分抽离出来,比如导航。
抽离基础模板
新增base.html作为基础模板。
{% load static %}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>学生教学计划系统</title>
<link href="{% static 'django33/django33.css' %}" rel="stylesheet">
<link href="{% static 'django33/django33.js' %}" rel="stylesheet">
</head>
<body class="container-full-dark">
<div class="navbar-blue">
<a href="#" class="logo">Python私教</a>
<div class="nav-links">
<a href="#">首页</a>
<a href="#">课程</a>
<a href="#">关于我们</a>
<a href="#">联系我们</a>
</div>
<div class="search-box">
<input type="text" placeholder="搜索课程..."/>
<button>🔍</button>
</div>
</div>
{% block content %}
{% endblock %}
</body>
</html>
修改student.html页面。
{% extends 'base.html' %}
{% load static %}
{% block content %}
<div class="container-blue mt1">
<h1 class="title-white-center">学生列表</h1>
<div class="flex-center">
<div class="col4-card">
<div class="flex-column">
<h3 class="content-white">张三</h3>
<p class="intro-date-gray">2023-10-10 14:30:00</p>
</div>
<div class="flex">
<button class="btn-blue">未开始</button>
<button class="btn-green">进行中</button>
<button class="btn-red">已完成</button>
</div>
</div>
<div class="col4-card">
<div class="flex-column">
<h3 class="content-white">李四</h3>
<p class="intro-date-gray">2023-10-10 14:30:00</p>
</div>
<div class="flex">
<button class="btn-blue">未开始</button>
<button class="btn-green">进行中</button>
<button class="btn-red">已完成</button>
</div>
</div>
<div class="col4-card">
<div class="flex-column">
<h3 class="content-white">王五</h3>
<p class="intro-date-gray">2023-10-10 14:30:00</p>
</div>
<div class="flex">
<button class="btn-blue">未开始</button>
<button class="btn-green">进行中</button>
<button class="btn-red">已完成</button>
</div>
</div>
<div class="col4-card">
<div class="flex-column">
<h3 class="content-white">赵六</h3>
<p class="intro-date-gray">2023-10-10 14:30:00</p>
</div>
<div class="flex">
<button class="btn-blue">未开始</button>
<button class="btn-green">进行中</button>
<button class="btn-red">已完成</button>
</div>
</div>
</div>
</div>
{% endblock %}
修改student_add.html页面。
{% extends 'base.html' %}
{% load static %}
{% block content %}
<div class="form-blue-container-sm">
<form class="form">
<div class="input-group-blue">
<label for="image-title">姓名</label>
<input type="text"
id="image-title"
placeholder="请输入姓名" required>
</div>
<div class="input-group-blue">
<label for="image-title">电话</label>
<input type="text"
id="image-title"
placeholder="请输入电话" required>
</div>
<button type="submit" class="form-blue-btn">添加</button>
</form>
</div>
{% endblock %}
修改plan.html页面。
{% extends 'base.html' %}
{% load static %}
{% block content %}
<div class="container-blue mt1">
<h1 class="title-white-center">张三进行中的计划</h1>
<div class="flex-center">
<div class="col4-card">
<div class="flex-column">
<h3 class="content-white">部门表改造以后, 权限会跟着变动, 上级部门的权限,
应该自动包含下级的权限</h3>
<div class="status2">一般</div>
<p class="intro-date-gray">2023-10-10 14:30:00</p>
</div>
<div class="flex">
<button class="btn-green">执行</button>
<button class="btn-red">删除</button>
</div>
</div>
<div class="col4-card">
<div class="flex-column">
<h3>localstorage存储了用户id和用户名,要考虑存储在pinia中,还要考虑刷新的问题</h3>
<div class="status2">一般</div>
<p class="intro-date-gray">2023-10-10 14:30:00</p>
</div>
<div class="flex">
<button class="btn-green">执行</button>
<button class="btn-red">删除</button>
</div>
</div>
<div class="col4-card">
<div class="flex-column">
<h3 class="content-white">注册页面优化</h3>
<div class="status2">一般</div>
<p class="intro-date-gray">2023-10-10 14:30:00</p>
</div>
<div class="flex">
<button class="btn-green">执行</button>
<button class="btn-red">删除</button>
</div>
</div>
<div class="col4-card">
<div class="flex-column">
<h3 class="content-white">注册页面优化</h3>
<div class="status2">一般</div>
<p class="intro-date-gray">2023-10-10 14:30:00</p>
</div>
<div class="flex">
<button class="btn-green">执行</button>
<button class="btn-red">删除</button>
</div>
</div>
</div>
</div>
{% endblock %}
修改plan_add.html页面。
{% extends 'base.html' %}
{% load static %}
{% block content %}
<div class="form-blue-container-sm">
<form class="form">
<div class="input-group-blue">
<label for="image-description">计划</label>
<textarea id="image-description"
placeholder="请输入计划内容"
rows="6"></textarea>
</div>
<button type="submit" class="form-blue-btn">添加</button>
</form>
</div>
{% endblock %}
效果预览
此时通过浏览器访问相关的页面,确保页面还是正常的。
http://127.0.0.1:8000/plan/
http://127.0.0.1:8000/plan/add/
http://127.0.0.1:8000/student/
http://127.0.0.1:8000/student/add/
考虑为空的情况
修改student.html页面,如果没有学生,就显示空空如也。
{% extends 'base.html' %}
{% load static %}
{% block content %}
{% if students %}
<div class="container-blue mt1">
<h1 class="title-white-center">学生列表</h1>
<div class="flex-center">
<div class="col4-card">
<div class="flex-column">
<h3 class="content-white">张三</h3>
<p class="intro-date-gray">2023-10-10 14:30:00</p>
</div>
<div class="flex">
<button class="btn-blue">未开始</button>
<button class="btn-green">进行中</button>
<button class="btn-red">已完成</button>
</div>
</div>
<div class="col4-card">
<div class="flex-column">
<h3 class="content-white">李四</h3>
<p class="intro-date-gray">2023-10-10 14:30:00</p>
</div>
<div class="flex">
<button class="btn-blue">未开始</button>
<button class="btn-green">进行中</button>
<button class="btn-red">已完成</button>
</div>
</div>
<div class="col4-card">
<div class="flex-column">
<h3 class="content-white">王五</h3>
<p class="intro-date-gray">2023-10-10 14:30:00</p>
</div>
<div class="flex">
<button class="btn-blue">未开始</button>
<button class="btn-green">进行中</button>
<button class="btn-red">已完成</button>
</div>
</div>
<div class="col4-card">
<div class="flex-column">
<h3 class="content-white">赵六</h3>
<p class="intro-date-gray">2023-10-10 14:30:00</p>
</div>
<div class="flex">
<button class="btn-blue">未开始</button>
<button class="btn-green">进行中</button>
<button class="btn-red">已完成</button>
</div>
</div>
</div>
</div>
{% else %}
<div class="container-blue-empty">
<div class="empty-blue">
空空如也
</div>
</div>
{% endif %}
{% endblock %}
设计模型
模型就是数据库表,和咱们之前学面向对象的时候的类是差不多的概念,模型就是一种可以关联数据库表的特殊类。
我们来创建学生模型和计划模型,每个学生有自己的计划。
from django33.db import models
# 在这里创建你的模型
class Student(models.Model):
name = models.CharField(max_length=20, verbose_name='姓名')
add_time = models.DateTimeField(auto_now_add=True, verbose_name='添加时间')
class Meta:
verbose_name = '学生'
verbose_name_plural = verbose_name
db_table = 'student'
def __str__(self):
return self.name
def __repr__(self):
return self.__str__()
class Plan(models.Model):
name = models.CharField(max_length=20, verbose_name='计划名称')
# 1未开始 2进行中 3已完成
status = models.SmallIntegerField(default=1, verbose_name='计划状态')
add_time = models.DateTimeField(auto_now_add=True, verbose_name='添加时间')
student = models.ForeignKey(Student, on_delete=models.CASCADE, verbose_name='学生')
class Meta:
verbose_name = '计划'
verbose_name_plural = verbose_name
db_table = 'plan'
def __str__(self):
return self.name
def __repr__(self):
return self.__str__()
迁移模型:
python manage.py makemigrations
python manage.py migrate
迁移模型就是生成数据库表的过程,迁移之后,我们就可以通过模型类来操作数据库表了。
查询所有学生
修改学生列表的视图方法,我们查询所有学生并传递到模板中。
先导入模型。
from .models import Student, Plan
然后再编写视图函数。
def student(request):
students = Student.objects.all()
context = {'students': students}
return render(request, 'student.html', context)
不过此时我们是没有学生数据的,所以页面上应该会显示空空如也。
浏览器访问 http://127.0.0.1:8000/student/ 测试一下。
添加学生
首先,在导航中,添加一个链接,点击链接跳转到添加学生界面。
<a href="{% url 'index:student_add' %}">新学生</a>
然后,修改一下首页,首页默认就是学生列表界面。
from django33.urls import path
from . import views
app_name = 'index'
urlpatterns = [
path('plan/', views.plan, name='plan'),
path('plan/add/', views.plan_add, name='plan_add'),
path('', views.student, name='student'),
path('student/add/', views.student_add, name='student_add'),
]
此时,点击导航上的新学生,会跳转到添加学生页面。
修改添加学生的表单。
<form class="form" method="post" action="{% url 'index:student_add' %}">
{% csrf_token %}
<div class="input-group-blue">
<label for="name">姓名</label>
<input type="text"
id="name"
name="name"
placeholder="请输入姓名" required>
</div>
<button type="submit" class="form-blue-btn">添加</button>
</form>
这里的{% csrf_token %}
是用来防止CSRF跨站攻击的,是django内置的功能,如果是表单,一定要加。
这里的action="{% url 'index:student_add' %}"
表示会把请求发送到添加学生的视图函数里面去。
接着我们修改添加学生的视图函数,实现添加的逻辑。
def student_add(request):
if request.method == 'POST':
name = request.POST.get('name')
student = Student(name=name)
student.save()
return redirect(reverse('index:student'))
return render(request, 'student_add.html')
此时我们去添加学生,就能够自动跳转到学生列表页面了,也就是首页。
动态渲染学生列表
我们添加了学生以后,就可以动态渲染学生列表了,我们使用for循环进行渲染。
{% for student in students %}
<div class="col4-card">
<div class="flex-column">
<h3 class="content-white">{{ student.name }}</h3>
<p class="intro-date-gray">{{ student.add_time }}</p>
</div>
<div class="flex">
<button class="btn-blue">未开始</button>
<button class="btn-green">进行中</button>
<button class="btn-red">已完成</button>
<button class="btn-yellow">新计划</button>
<button class="btn-green">编辑</button>
<button class="btn-red">删除</button>
</div>
</div>
{% endfor %}
此时,首页就会动态渲染我们添加的学生列表信息了。
修改学生信息
如果学生的信息不小心填错了,该怎么修改呢?
我们可以通过超链接重定向到添加学生的页面,并携带学生的id,后端根据id查询学生的信息并渲染添加页面,添加页面中,渲染学生的默认值。
接下来在学生列表页面中,添加编辑的超链接。
<a
class="btn-green"
href="{% url 'index:student_add' %}?id={{ student.id }}"
>
编辑
</a>
然后我们去修改添加学生的视图函数,如果传递了学生id过来,就根据id查询学生。
def student_add(request):
if request.method == 'POST':
name = request.POST.get('name')
student = Student(name=name)
student.save()
return redirect(reverse('index:student'))
sid = request.GET.get('id')
student = Student()
if sid:
student = Student.objects.filter(id=sid).first()
context = {'student': student}
return render(request, 'student_add.html', context)
然后我们还要去修改添加学生的表单,如果有id,要把id作为隐藏框,如果有name,需要把name的值显示出来。
<form class="form" method="post" action="{% url 'index:student_add' %}">
{% csrf_token %}
<div class="input-group-blue">
<label for="name">姓名</label>
{% if student.id %}
<input type="hidden" name="id" value="{{ student.id }}">
<input type="text"
id="name"
name="name"
value="{{ student.name }}"
placeholder="请输入姓名" required>
{% else %}
<input type="text"
id="name"
name="name"
placeholder="请输入姓名" required>
{% endif %}
</div>
<button type="submit" class="form-blue-btn">添加</button>
</form>
如果有学生信息,这填充id和学生姓名,如果没有,则使用空数据。
最后,我们还得修改添加学生的视图函数,如果获取到了id,说明是修改,此时执行修改的逻辑。
def student_add(request):
if request.method == 'POST':
id = request.POST.get('id')
name = request.POST.get('name')
if id:
student = Student.objects.filter(id=id).first()
student.name = name
student.save()
else:
student = Student(name=name)
student.save()
return redirect(reverse('index:student'))
sid = request.GET.get('id')
student = Student()
if sid:
student = Student.objects.filter(id=sid).first()
context = {'student': student}
return render(request, 'student_add.html', context)
这样我们添加和修改学生的逻辑就完成了。
关于删除学生的功能我们不在前端实现,因为比较危险,可以通过自带的后台管理系统或者数据库实现删除的功能。
添加计划
点击每个学生的“新计划”按钮,这跳转到添加计划的页面,同时要携带这个学生的信息。
首先,我们修改新计划的链接。
<a class="btn-yellow" href="{% url 'index:plan_add' %}?id={{ student.id }}">
新计划
</a>
然后我们修改添加计划的视图函数。
def plan_add(request):
student_id = request.GET.get('id')
if not student_id:
return redirect(reverse('index:not_found'))
context= {"student_id": student_id}
return render(request, 'plan_add.html', context)
这个not_found我们现在还没有待会儿再实现,现在先继续实现添加计划的逻辑。接下来修改一下添加计划的表单。
<form class="form" method="post" action="{% url 'index:plan_add' %}">
{% csrf_token %}
<input type="hidden" name="student_id" value="{{ student_id }}">
<div class="input-group-blue">
<label for="plan">计划</label>
<textarea id="plan"
placeholder="请输入计划内容"
name="name"
rows="6"></textarea>
</div>
<button type="submit" class="form-blue-btn">添加</button>
</form>
接着我们再继续修改添加计划的视图函数,如果是POST请求,这实现添加计划的逻辑。
def plan_add(request):
if request.method == 'POST':
id = request.POST.get('id')
name = request.POST.get('name')
student_id = request.POST.get('student_id')
if id:
plan = Plan.objects.filter(id=id).first()
plan.name = name
plan.student_id = student_id
plan.save()
else:
plan = Plan(name=name, student_id=student_id)
plan.save()
return redirect(reverse('index:plan'))
student_id = request.GET.get('id')
if not student_id:
return redirect(reverse('index:not_found'))
context= {"student_id": student_id}
return render(request, 'plan_add.html', context)
这段代码和添加学生的代码差不多,也是如果能获取到id则为修改,否则就是新增。
添加或者修改完毕以后,重定向到计划列表页面。
错误404页面
之前我们有一个找不到学生就重定向到404页面的逻辑,现在我们来实现一下该视图函数和页面。
添加error404.html作为页面模板。
{% extends 'base.html' %}
{% load static %}
{% block content %}
<div class="error404-blue">
<h1 class="title-white-center">资源不存在</h1>
</div>
{% endblock %}
添加视图函数:
def error404(request):
return render(request, 'error404.html')
添加路由:
path('404/', views.error404, name='not_found'),
浏览器访问: http://127.0.0.1:8000/404/
调整导航
在继续开发之前,我们先调整一下导航,这样方便在页面之间跳转。
<a href="{% url 'index:student' %}" class="logo">Python私教</a>
<div class="nav-links">
<a href="{% url 'index:student' %}">首页</a>
<a href="{% url 'index:student_add' %}">新学生</a>
</div>
动态渲染计划列表
接下来,我们就像动态渲染学生列表一样,动态渲染计划列表,默认显示状态为1的未开始的计划。
首先也是先添加模板。
{% extends 'base.html' %}
{% load static %}
{% block content %}
{% if plans %}
<div class="container-blue mt1">
<h1 class="title-white-center">{{ student.name }}{{ status_text }}的计划</h1>
<div class="flex-center">
{% for plan in plans %}
<div class="col4-card">
<div class="flex-column">
<h3 class="content-white">{{ plan.name }}</h3>
<p class="intro-date-gray">{{ plan.add_time }}</p>
</div>
<div class="flex">
<button class="btn-green">执行</button>
<button class="btn-red">删除</button>
</div>
</div>
{% endfor %}
</div>
</div>
{% else %}
<div class="container-blue-empty">
<div class="empty-blue">
空空如也
</div>
</div>
{% endif %}
{% endblock %}
然后是修改视图函数。
def plan(request):
student_id = request.GET.get('sid')
if not student_id:
return redirect('index:not_found')
student = Student.objects.filter(id=student_id).first()
if not student:
return redirect('index:not_found')
status = 1
status_text = '未开始'
plans = Plan.objects.filter(status=status).all()
context = {
'student': student,
'plans': plans,
'status_text': status_text,
}
return render(request, 'plan.html', context)
同时,添加计划的视图函数也要改一下,要把学生的id传过来。
def plan_add(request):
if request.method == 'POST':
id = request.POST.get('id')
name = request.POST.get('name')
student_id = request.POST.get('student_id')
if id:
plan = Plan.objects.filter(id=id).first()
plan.name = name
plan.student_id = student_id
plan.save()
else:
plan = Plan(name=name, student_id=student_id)
plan.save()
return redirect(f"{reverse('index:plan')}?sid={student_id}")
student_id = request.GET.get('id')
if not student_id:
return redirect(reverse('index:not_found'))
context = {"student_id": student_id}
return render(request, 'plan_add.html', context)
此时给李四添加一个计划,你就会发现自动跳转了: http://127.0.0.1:8000/plan/?sid=2
删除计划
点击删除按钮的时候,我们发送一个删除请求,为了安全,我们用form包裹,传递post请求。
删除一行,重新回到当前页面。
首先修改模板。
<form action="{% url 'index:plan' %}?did={{ plan.id }}&sid={{ student.id }}"
method="post">
{% csrf_token %}
<button class="btn-red" type="submit">删除</button>
</form>
接着修改视图函数。
def plan(request):
# 执行删除
if request.method == 'POST':
id = request.GET.get('did')
if id:
plan = Plan.objects.filter(id=id).first()
plan.delete()
# 查询学生
student_id = request.GET.get('sid')
if not student_id:
return redirect('index:not_found')
student = Student.objects.filter(id=student_id).first()
if not student:
return redirect('index:not_found')
# 查询计划
status = 1
status_text = '未开始'
plans = Plan.objects.filter(status=status).all()
context = {
'student': student,
'plans': plans,
'status_text': status_text,
}
return render(request, 'plan.html', context)
执行计划
点击执行按钮,将计划修改为2,也就是进行中,然后渲染进行中的计划。
修改模板,将执行按钮改为:
<form action="{% url 'index:plan' %}?uid={{ plan.id }}&sid={{ student.id }}"
method="post">
{% csrf_token %}
<input type="hidden" name="status" value="2">
<button type="submit" class="btn-green">执行</button>
</form>
此时我们还是使用post请求,不过传递的是uid,u表示update,更新。
然后我们修改视图函数。
def plan(request):
# 查询计划
status = 1
status_text = '未开始'
# 执行删除
if request.method == 'POST':
did = request.GET.get('did')
uid = request.GET.get('uid')
# 删除
if did:
plan = Plan.objects.filter(id=did).first()
plan.delete()
# 修改
elif uid:
plan = Plan.objects.filter(id=uid).first()
plan.status = 2
plan.save()
# 查询进行中的计划
status = 2
status_text = '进行中'
# 查询学生
student_id = request.GET.get('sid')
if not student_id:
return redirect('index:not_found')
student = Student.objects.filter(id=student_id).first()
if not student:
return redirect('index:not_found')
plans = Plan.objects.filter(status=status).all()
context = {
'student': student,
'plans': plans,
'status': status,
'status_text': status_text,
}
return render(request, 'plan.html', context)
这里我们把status向模板传递,模板根据status的值,动态的渲染一个按钮是“执行”还是“完成”,或者是否加一个撤销?
完成计划
我们修改一下模板,添加一个完成按钮,如果status为2,也就是进行中,则渲染这个按钮,点击按钮,将状态改为已完成。
{% if status == 1 %}
<form action="{% url 'index:plan' %}?uid={{ plan.id }}&sid={{ student.id }}"
method="post">
{% csrf_token %}
<input type="hidden" name="status" value="2">
<button type="submit" class="btn-green">执行</button>
</form>
{% elif status == 2 %}
<form action="{% url 'index:plan' %}?uid={{ plan.id }}&sid={{ student.id }}"
method="post">
{% csrf_token %}
<input type="hidden" name="status" value="3">
<button type="submit" class="btn-green">完成</button>
</form>
<form action="{% url 'index:plan' %}?uid={{ plan.id }}&sid={{ student.id }}"
method="post">
{% csrf_token %}
<input type="hidden" name="status" value="1">
<button type="submit" class="btn-yellow">暂停</button>
</form>
{% endif %}
我们根据状态值动态的切换,动态的渲染不同的按钮。然后加了一个status的隐藏输入框,通过模板主动传递状态值。
此时我们修改视图函数。
status_dict = {
1: '未开始',
2: '进行中',
3: '已完成',
4: '已取消',
5: '已删除',
}
def plan(request):
# 查询计划
status = 1
status_text = status_dict.get(status)
# 执行删除
if request.method == 'POST':
did = request.GET.get('did')
uid = request.GET.get('uid')
# 删除
if did:
plan = Plan.objects.filter(id=did).first()
plan.delete()
# 修改
elif uid:
try:
status = int(request.POST.get('status'))
status_text = status_dict.get(status)
except:
pass
plan = Plan.objects.filter(id=uid).first()
plan.status = status
plan.save()
# 查询学生
student_id = request.GET.get('sid')
if not student_id:
return redirect('index:not_found')
student = Student.objects.filter(id=student_id).first()
if not student:
return redirect('index:not_found')
plans = Plan.objects.filter(status=status).all()
context = {
'student': student,
'plans': plans,
'status': status,
'status_text': status_text,
}
return render(request, 'plan.html', context)
查看学生计划
在学生列表页面,通过按钮的点击,能够快速跳转到不同状态下的计划。
<a href="{% url 'index:plan' %}?sid={{ student.id }}&status=1" class="btn-blue">未开始</a>
<a href="{% url 'index:plan' %}?sid={{ student.id }}&status=2" class="btn-green">进行中</a>
<a href="{% url 'index:plan' %}?sid={{ student.id }}&status=3" class="btn-red">已完成</a>
然后修改计划视图函数,让status支持通过查询参数传递过来。
def plan(request):
# 查询计划
status = None
try:
status = int(request.GET.get('status'))
except:
status = 1
status_text = status_dict.get(status)
此时重新访问学生列表页面,你就可以点击各种状态按钮跳转到不同状态下的计划列表页面了。
总结
本课主要讲解了一个计划看板的实战项目,这是可以投入真实使用的项目,也是我们学习django33全栈开发第一个能够正式使用的项目。
完整的代码我已经打包发布到星球,感兴趣的同学可以去星球的源代码专栏进行下载。
最近一直在上直播课,喜欢学编程的同学欢迎来试听一下。