大家好,这是皮爷给大家带来的最新的学习Python能干啥?之Django教程,从零开始,到最后成功部署上线的项目。这一节,来点硬货内容。
上一节,我们开发了文章应用,创建了Post,Category还有Tag这三个类。这一节,我们就要在CMS页面里面,来实现Category还有Tag的管理功能。
在第12讲,我们重构了首页页面,这一节,我们的重点是CMS的Dashboard页面,所以,我们需要将Dashboard重构。重构的思路还是和之前Index.html重构思路一样,即通过使用extend,block还有include,来讲整个页面拆分成几个模块,每一个页面都是通过模块模块之间的组合,所以,我们要在CMS下面创建一个base文件夹,重构完之后,整个目录结构应该是这个样子的:
这里base目录下,有三个东西,dashboard_base
则是dashboard最基本的,navbar
则是顶部的Navbar导航栏,sidebar
则是左侧的Sidebar菜单列。
这个dashboard_base
文件结构就成了下面这样:
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Peekpa | Dashboard | {% block title %}
{% endblock %}title>
{% block head %}
{% endblock %}
head>
<body class="hold-transition sidebar-mini layout-fixed">
<div class="wrapper">
{% include 'cms/base/navbar.html' %}
{% include 'cms/base/sidebar.html' %}
<div class="content-wrapper">
{% block main %}
{% endblock %}
div>
div>
body>
html>
可以看到这里我们使用了三个blcok,分别是title,head还有main。这一点和首页是一样的。同时,还用了两个include标签,分别将navbar还有sidebar引入进来。
我们把原来的Dashboard.html抽象到了home.html文件中:
{% extends 'cms/base/dashboard_base.html' %}
{% block title %}
Home
{% endblock %}
{% block head %}
{% endblock %}
{% block main %}
<section class="content">
<div class="container-fluid">
<div class="row d-flex justify-content-around pt-4 mb-4">
<p class="h3">《用Django全栈开发——14. CMS中添加Category和Tag管理》p>
<p class="h5">公众号『皮爷撸码』,连载更新此系统的开发教程,敬请关注p>
div>
<div class="row d-flex justify-content-around">
<img class="img-thumbnail" src="https://www.peekpa.tech/asserts/img/qrcode.jpg" alt="">
div>
div>
section>
{% endblock %}
可以看到,这里首先使用extend来继承dashboard_base
文件,然后填充里面的block内容。最后,我们继承完成的首页,就长这个样子:
我们开发Category的思路也很简单,就只有两个页面:管理页面还有发布页面。
管理页面就是一张表格,里面显示的Category的基本信息;发布页面则是要发布Category。所以,我们创建Category目录,并在目录下创建两个文件,manage.html
和publish.html
文件,分别对应的管理和发布。当然,这两个文件的开发思路和home是一样的,都需要继承dashboard_base
文件。
所以,我们先把manage.html
和publish.html
两个文件的结构先创建起来:
{% extends 'cms/base/dashboard_base.html' %}
{% block head %}
{% endblock %}
{% block title %}
Category Management
{% endblock %}
{% block main %}
<section class="content">
<div class="container-fluid pt-4">
<div class="row">
<div class="col-sm-12">
<div class="card">
<div class="card-body">
<div class="row p-2 d-flex justify-content-between">
<p class="h3">Categoriesp>
<div class="float-right">
<a class="btn btn-primary text-right" href=""><i class="mr-2 fas fa-plus">i>Adda>
div>
div>
<table class="table table-bordered table-hover">
<thead class="thead-light">
<tr>
<th style="width: 10%;">#th>
<th>tag_nameth>
<th>create_timeth>
<th class="w-25">actionsth>
tr>
thead>
<tbody>
<tr>
tr>
tbody>
table>
div>
div>
div>
div>
div>
section>
{% endblock %}
{% extends 'cms/base/dashboard_base.html' %}
{% block title %}
Category Publish
{% endblock %}
{% block main %}
<section class="content">
<div class="container-fluid">
<div class="row d-flex justify-content-center pt-4">
<div class="col-sm-6">
<div class="card card-primary">
<div class="card-header">
<h3 class="card-title">Tag Publishh3>
div>
<form class="form-horizontal" method="post">
{% csrf_token %}
<div class="card-body">
<div class="form-group row mb-0">
<label for="inputEmail3" class="col-form-label col-sm-2 text-center">Namelabel>
<div class="col-sm-10">
<input type="text" class="form-control" id="name" name="name">
div>
div>
div>
<div class="card-footer">
<button type="submit" class="btn btn-info" name="submit">Submitbutton>
<button type="submit" class="btn btn-danger float-right" name="cancel">Cancelbutton>
div>
form>
div>
div>
div>
div>
section>
{% endblock %}
可以看到,两个html文件的格式都非常简单:一个是里面添加了一个table,用来展示数据;另一个则是只有一个form表单,用来提交数据。
接下来,我们还需要在CMS应用中,来创建这两个视图文件的映射,很简单,在CMS目录下的views.py文件,添加两个函数:
def category_manage_view(request):
return render(request, 'cms/category/manage.html')
def category_publish_view(request):
return render(request, 'cms/category/publish.html')
在urls.py文件中,将这两个视图函数映射到URL上:
urlpatterns = [
#前面的内容省略,下面两个是新家的内容
path("dashboard/category/manage", views.category_manage_view, name="category_manage_view"),
path("dashboard/category/publish", views.category_publish_view, name="category_publish_view"),
]
这样,我们就完成了映射:http://localhost:8000/cms/dashboard/category/manage
对应的是Category Manage页面;http://localhost:8000/cms/dashboard/category/publish
对应的是Category Publish页面。
如果想要方便的看我们的页面,我们还得修改一下左侧的Sidebar,分别将这两个URL对应进去,这个简单,只需要在sidebar.html
文件中修改即可:
<li class="nav-header">CATEGORYli>
<li class="nav-item">
<a href="{% url 'cms:category_manage_view' %}" class="nav-link">
<i class="nav-icon fas fa-list">i>
<p>
Category Management
p>
a>
li>
<li class="nav-item">
<a href="{% url 'cms:category_publish_view' %}" class="nav-link">
<i class="nav-icon far fa-plus-square">i>
<p>
Category Publish
p>
a>
li>
此时,我们启动服务,来查看一下我们的页面。Manage页面:
Publish页面;
目前只是样子搭建完成,功能还没有做。接下来我们来说功能的事儿。
关于Category的功能,其实很简单,目前就这么几条:
这几条其实也就是传说中的CRUD。我们首先来看新增功能。
新增功能的页面,自然是在Category Publish中,函数的编写地方则应该是在CMS应用中。首先明确一点,我们的Category Publish页面中的form表单,是要发送POST请求。所以,我们应该在CMS目录下创建一个forms.py
文件,在里面填写表单,然后再创建一个category_view.py
文件,在这个里面写视图函数。为啥要单独在创建一个Category View文件?是因为这样我们方便管理。
Forms.py
文件里面的CategoryForm:
class CategoryForm(forms.ModelForm, FormMixin):
class Meta:
model=Category
fields = "__all__"
Category_view.py
文件里面的内容:
class CategoryView(View):
def post(self, request):
form = CategoryForm(request.POST)
if form.is_valid():
name = form.cleaned_data.get('name')
Category.objects.create(name=name)
return redirect(reverse("cms:category_publish_view"))
可以看到,这里我们直接调用的Category.objects.create(name=name)方法来保存数据,接下来,我们尝试一下:
我们在publish这里输入123,然后点击submit:
看到POST请求已经发送出去了,接下来我们去数据库里面看一下:
数据库里面成功的有了name为123的数据,说明我们的添加功能已经完成了。
接下来我们去完成一下读取的过程,即将数据库里面的数据都读取出来,放到Manage页面里:
编写逻辑即在我们的manage.html的加载时候,做一下数据库的读取操作,然后将读取的到的Category全部返回到页面中。所以我们就要修改cms目录下的views.py文件里的category_manage_view(request)
函数了,让他读取所有的Category数据:
def category_manage_view(request):
context = {
"list_data": Category.objects.all()
}
return render(request, 'cms/category/manage.html', context=context)
这里我们使用context来传递给前端,当然,前端的publish.html页面也得修改,即在tbody标签里面,添加Django模板的for循环:
<tbody>
{% for item in list_data %}
<tr>
<td>{
{ item.id }}td>
<td>{
{ item.name }}td>
<td>{
{ item.create_time }}td>
<td>
<a href="#" class="btn btn-info btn-xs">Modifya>
<button class="btn btn-danger btn-xs delete-btn" data-pk-id="{
{ javcode.pk }}">
Delete
button>
td>
tr>
{% endfor %}
tbody>
我们在数据库里面分别添加了Python开发和关注皮爷撸码两条数据,我们再来看一下前端的显示:
完美显示,说明读取工作也做完了。
这里的所说的修改,是指在Manage页面,我们如果要修改比如第一条数据的name,我们需要系统提供改名字的服务。
为了更好的偷懒,不对,是重复利用,我们可以试用publish页面的内容来提供修改。做法只需要:
首先我们要修改publish.html文件的form结构:
<form class="form-horizontal" action="{% url 'cms:category_add' %}" method="post">
{% csrf_token %}
{% if item_data %}
<input type="text" class="form-control" id="pk" name="pk" value="{
{ item_data.id }}" hidden>
{% endif %}
<div class="card-body">
<div class="form-group row mb-0">
<label for="inputEmail3" class="col-form-label col-sm-2 text-center">Namelabel>
<div class="col-sm-10">
{% if item_data %}
<input type="text" class="form-control" id="name" name="name" value="{
{ item_data.name }}">
{% else %}
<input type="text" class="form-control" id="name" name="name">
{% endif %}
div>
div>
div>
<div class="card-footer">
{% if item_data %}
<button type="submit" class="btn btn-info" name="modify">Submitbutton>
<button type="submit" class="btn btn-danger float-right" name="cancel">Cancelbutton>
{% else %}
<button type="submit" class="btn btn-info" name="submit">Submitbutton>
<button type="submit" class="btn btn-danger float-right" name="back">Backbutton>
{% endif %}
div>
form>
我们看到这里为了复用publish.html,我们使用的Django模板的if标签,用来判断后台是否传入了Category data,如果传入了,就将Category的ID还有name显示到表格中,如果没有,一切如初。
还有一点就是最后的submit按钮也做了修改,根据是否有数据, 做了4个按钮,分别对应不同的数据处理逻辑,可以看到这4个button的name不一样,分别是modify, cancel, submit和back。为啥要分成4个?是因为在request.post请求的时候,我们会将按钮的name传入给后台,通过name不一样,我们就能处理不同的逻辑:
这个时候,我们再重新组织一下CategoryView的post逻辑:
class CategoryView(View):
def post(self, request):
# 新建提交
if 'submit' in request.POST:
form = CategoryForm(request.POST)
if form.is_valid():
name = form.cleaned_data.get('name')
Category.objects.create(name=name)
return redirect(reverse("cms:category_publish_view"))
else:
return restful.method_error("Form is error", form.get_errors())
# 修改Category
elif 'modify' in request.POST:
form = CategoryEditForm(request.POST)
if form.is_valid():
pk = form.cleaned_data.get('pk')
name = form.cleaned_data.get('name')
Category.objects.filter(id=pk).update(name=name)
return redirect(reverse("cms:category_manage_view"))
else:
return restful.method_error("Form is error", form.get_errors())
# 修改状态返回
elif 'back':
return redirect(reverse("cms:category_manage_view"))
# 新建状态的取消
else:
return redirect(reverse("cms:category_publish_view"))
这里要说明一下,由于我们的Category的修改和创建,前端的表单传过来数据是不一样的:修改有id,创建没有id。所以这里我们又创建了另外的一个CategoryEditForm,里面就多了一个pk值而已:
class CategoryEditForm(forms.ModelForm, FormMixin):
pk = forms.CharField(max_length=100)
class Meta:
model=Category
fields = "__all__"
在上面的代码中,就能很清楚的看到,通过不同的request.POST里面的button name,做出了不同的处理。这时候,我们编写完成,再来前端实验一下,我们将第一条数据的name的值123修改为1234:
点击modify,将会来到修改页面:
点击提交,就会回到list页面:
这个时候看到,数值变成了1234,说明成功了!
删除很好操作,只需要完成一个删除函数就可以,同时配置好url就行。我们直接写一个删除的函数:
class CategoryDeleteView(View):
def post(self,request):
category_id = request.POST.get('category_id')
print("category_id :", category_id)
Category.objects.filter(id=category_id).delete()
return redirect(reverse("cms:category_manage_view"))
然后将它绑定到urls.py文件中:
path("dashboard/category/delete", CategoryDeleteView.as_view(), name="category_delete"),
这个时候就会发现,我们的方法是写到了reqest.POST中了,这个时候,我们就要修改一下前端的代码,将删除按键绑定到JavaScript上了,这个时候就要引入一下js文件了:
function CMSCategory() {
}
CMSCategory.prototype.listenDeleteEvent = function () {
var deleteBtns = $(".delete-btn");
deleteBtns.click(function () {
var btn = $(this);
var category_id = btn.attr('data-category-id');
peekpaajax.post({
'url': '/cms/dashboard/category/delete',
'data': {
'category_id': category_id
},
'success': function (result) {
if(result['code'] === 200){
window.location = window.location.href;
// window.location.reload()
}
}
});
});
};
CMSCategory.prototype.run = function () {
this.listenDeleteEvent();
};
$(function () {
var category = new CMSCategory();
category.run();
});
别忘了最后在manage.html顶部的head block里面,把js文件引入:
<script src="{% static 'js/category.min.js' %}">script>
这里有一点注意:
$ is not defined
错误发生,是应该把dashboard_base.html
文件里面,body最后的script中的jQuery的引入,提前到head block前。具体不懂的,可以去看我的dashboard_base.html
的head部分源码还有category/manage.html
的源码
这里我们使用了一个peekpaajax,这个其实是我封装的一层Ajax,这里的逻辑是如果返回200,即成功,就刷新页面。
然后我们编写delete的视图函数:
class CategoryDeleteView(View):
def post(self,request):
category_id = request.POST.get('category_id')
Category.objects.filter(id=category_id).delete()
return restful.ok()
在urls.py里面配置路径:
urlpatterns = [
path("dashboard/category/delete", CategoryDeleteView.as_view(), name="category_delete"),
]
这个时候我们来试验一下,我们删除第一条数据:
然后我们点击第一条数据的delete:
发现数据被删除了,可以看到ID没有了,我们再来数据库里面看一下:
确实是删除了。
至此,我们的Category的增删改查都开发完成。
剩下的Tag的增删改查,因为和Category一模一样,所以我们就不说了。
最后总结一下,
Category和Tag的管理开发:
整套教程源码获取,可以关注『皮爷撸码』,回复『peekpa.com』