用Django全栈开发——15. 开发文章管理

232 阅读8分钟

大家好,这是皮爷给大家带来的最新的学习Python能干啥?之Django教程,从零开始,到最后成功部署上线的项目。这一节,我们来开发文章的管理功能,包括发布,查看,修改和删除。

Peekpa.com的官方地址:peekpa.com

上一节我们开发了Category和Tag的管理,这一节我们就开发一下文章的管理。

修改SideBar

首先是在sidebar引入文章管理的两个页面:发布文章和管理页面。同样,我们还要在cms目录下,创建一个post目录,在里面同样有个manage.html和publish.html:

我们的思路还是和之前开发一样的,复用publish.html页面,即发布和修改是同一个页面,通过是否传入文章内容来确认功能。

Post的新增

关于文章的模型,我们在第13节详细的说过,这里就简单的来看一下Post的model有哪些东西,下面的代码在app/post目录下的models.py文件里:

class Post(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey('peekpauser.User', on_delete=models.SET_NULL, null=True)
    description = models.CharField(max_length=200)
    thumbnail = models.URLField()

    content = models.TextField()
    content_html = models.TextField(blank=True, editable=False)
    is_md = models.BooleanField(default=True)

    category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True)
    tag = models.ManyToManyField(Tag)
    
    priority = models.IntegerField(default=-1)
    is_hot = models.BooleanField(default=False)
    is_top = models.BooleanField(default=False)
    is_main_page = models.BooleanField(default=False)

    status = models.PositiveIntegerField(default=STATUS_DRAFT, choices=STATUS_ITEMS)
    publish_time = models.DateTimeField(auto_now_add=True)
    publish_time_show = models.DateTimeField(default=datetime.datetime.now)
    time_id = models.CharField(blank=True, max_length=30)
    read_num = models.PositiveIntegerField(default=0)

文章类里面属性较多,所以,我们在开发文章发布页面的时候,理应把这些属性都应该带进去。

我们通过Bootstrap来写好文章的发布页面的表格,最终样子应该长下面这样:

可以看到中间有些部分采用的是Droplist,比如Author,Category, Status,最下面的Tag还是一个区域的checkbox。

这里的publish.html页面就会和之前的页面不一样,因为我们在这个页面里面,需要显示以下数据:

  • Author;
  • Category;
  • Tag

这些数据都是从数据库里面读取的。所以,我们的post_publish_view方法里面,就要读取出来这些数据,然后通过context的形势返回到前端页面。

def post_publish_view(request):
    context = {
        'list_data_category': Category.objects.all(),
        'list_data_tag': Tag.objects.all(),
        'list_data_user': User.objects.all(),
        'list_data_status': Post.STATUS_ITEMS,
        'form': PostForm()
    }
    return render(request, 'cms/post/publish.html', context=context)

同样,我们还是要使用Post请求来发送整个表单数据, 所以,我们要和Tag还有Category一样,写一个PostView,还需要一个PostForm。但是这里的PostForm有些不一样:

class PostForm(forms.ModelForm, FormMixin):
    tag_id = forms.ModelMultipleChoiceField(widget=forms.CheckboxSelectMultiple, queryset=Tag.objects.all())

    class Meta:
        model = Post
        exclude = ('tag',)

这里我们要单独创建一个tag_id变量,是因为在我们的Post模型中,tag是一个ManyToMany的类型,这里我们需要在表单中,使用ModelMultipleChoiceField类型将前端返回的多选的Tag封装起来,然后再创建Post实例,保存起来。

所以,这个时候,我们的后端更新文章的代码就是这样:

class PostView(View):
    def post(self, request):
        # 新建提交
        if 'submit' in request.POST:
            form = PostForm(request.POST)
            if form.is_valid():
                title = form.cleaned_data.get('title')
                description = form.cleaned_data.get('description')
                author = form.cleaned_data.get('author')
                thumbnail = form.cleaned_data.get('thumbnail')
                status = form.cleaned_data.get('status')
                content = form.cleaned_data.get('content')
                is_md = form.cleaned_data.get('is_md')
                category = form.cleaned_data.get('category')
                priority = form.cleaned_data.get('priority')
                is_hot = form.cleaned_data.get('is_hot')
                is_top = form.cleaned_data.get('is_top')
                is_main_page = form.cleaned_data.get('is_main_page')
                publish_time_show = form.cleaned_data.get('publish_time_show')
                time_id = form.cleaned_data.get('time_id')
                read_num = form.cleaned_data.get('read_num')
                tags = form.cleaned_data.get('tag_id')
                instance = Post.objects.create(title=title, description=description, author=author,
                                    thumbnail=thumbnail, status=status, content=content,
                                    is_md=is_md, category=category, priority=priority,
                                    is_hot=is_hot, is_top=is_top, is_main_page=is_main_page,
                                    publish_time_show=publish_time_show, time_id=time_id,read_num=read_num
                                    )
                instance.tag.set(tags)
                return redirect(reverse("cms:post_publish_view"))

这里只需要注意倒数第二行的instance.tag.set(tags),为什么要这么写?是因为,我们从PostForm里面拿到的tags,是一个QuerySet,但是Django的Object在创建的时候,对于ManyToMany的类型,是不能直接创建的,推荐的方法就是创建一个instance,然后调用instance.变量名字.set(内容)的方法来实现ManyToMany。

这个时候,别忘了在CMS目录下的urls.py文件里面配置PistView的映射:

urlpatterns = [
    path("dashboard/post/add", PostView.as_view(), name="post_add"),
]

现在,发布文章的基本功能就配置完成了,我们在前端来实验一下启动我们的服务器,来到发布页面,我们试着把所有的东西都填写进去:

点击发布,页面又跳回到了Publish页面,这个时候,我们在数据库里面看一下刚才发布的那篇文章:

发现是有的。还能看到,当is_md为true的时候,我们的content会自动转换成html格式的文件。而且数据完全正确,那么新增文章的功能就完成了。

Post的查询

关于文章的查询,我们还是将文章放到Manage.html页面,里面有个table,然后将文章放进去,做法和Category还有Tag类似:

  • 编写Html页面;
  • 编写视图函数;
  • 在视图函数里把数据通过context的格式传给前端;
  • 在urls.py里面映射;

Manage.html大致结构如下:

<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">Post</p>
                                <div class="float-right">
                                    <a class="btn btn-primary text-right" href="{% url 'cms:post_publish_view' %}"><i class="mr-2 fas fa-plus"></i>Add</a>
                                </div>
                            </div>
                            <table class="table table-bordered table-hover">
                                <thead class="thead-light">
                                    <tr>
                                        <th style="width: 10%;">#</th>
                                        <th>Post_Time_ID</th>
                                        <th>Post_title</th>
                                        <th>Post_show_time</th>
                                        <th class="w-25">actions</th>
                                    </tr>
                                </thead>
                                <tbody>
                                    {% for item in list_data %}
                                        <tr>
                                            <td>{{ item.id }}</td>
                                            <td>{{ item.time_id }}</td>
                                            <td>{{ item.title }}</td>
                                            <td>{{ item.publish_time_show }}</td>
                                            <td>
                                                <a href="{% url 'cms:tag_edit' %}?tag_id={{ item.id }}" class="btn btn-info btn-xs">Modify</a>
                                                <button class="btn btn-danger btn-xs delete-btn" data-tag-id="{{ item.id}}">
                                                    Delete
                                                </button>
                                            </td>
                                        </tr>
                                    {% endfor %}
                                </tbody>
                            </table>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </section>

文件的manage视图函数:

def post_manage_view(request):
    context = {
        "list_data": Post.objects.all()
    }
    return render(request, 'cms/post/manage.html', context=context)

最后效果图:

Post的修改

Post的修改,和Category还有Tag的思路是一样的:

  • 写一个修改文章的视图函数,并将放置到modify按钮上;
  • 将文章传到publisht.html页面,进行修改;
  • 修改底部提交按钮的逻辑。

所以,视图函数很简单,通过id来取文章:

class PostEditView(View):
    def get(self, request):
        post_id = request.GET.get('post_id')
        post = Post.objects.get(pk=post_id)
        tag_list = list()
        for item in post.tag.all():
            tag_list.append(item.id)
        context = {
            'item_data': post,
            'list_data_category': Category.objects.all(),
            'list_data_tag': Tag.objects.all(),
            'list_data_user': User.objects.all(),
            'list_data_status': Post.STATUS_ITEMS,
            'item_data_tag_list': tag_list
        }
        return render(request, 'cms/post/publish.html', context=context)

这里要简单说明一下,我们将Post, Category, Tag, User还有Status传送到前端,这个是因为编辑页面需要这些数据;还有一个就是tag list,这个传送到前端是为了在前端展示出来我们已经勾选的tag。

和之前Category与Tag一样,我们在publish.html页面需要修改一下结构,因为我们传入了item_data数据:

<form class="form-horizontal" action="{% url 'cms:post_add' %}" method="post">
{% if item_data %}
    <input type="text" class="form-control" id="id" name="id" value="{{ item_data.id }}" hidden>
{% endif %}

....

<div class="form-group">
    <label for="title" class="">Title</label>
    {% if item_data %}
        <input type="text" class="form-control" id="title" name="title" value="{{ item_data.title }}">
    {% else %}
        <input type="text" class="form-control" id="title" name="title">
    {% endif %}
</div>

....

<div class="form-group">
    <label for="is_md" class="">Is MarkDown</label>
    <select name="is_md" id="is_md" class="custom-select">
        {% if item_data %}
            <option value="True" {% if True == item_data.is_md %} selected {% endif %}>Yes</option>
            <option value="False" {% if False == item_data.is_md %} selected {% endif %}>No</option>
        {% else %}
            <option value="True">Yes</option>
            <option value="False">No</option>
        {% endif %}
    </select>
</div>

....

<div class="form-group">
    <label for="tag_id" class="">Tag</label>
    <div class="d-flex flex-wrap">
        {% if item_data %}
            {% for item in list_data_tag %}
                <div class="form-check mr-4 mb-2">
                  <input class="form-check-input" type="checkbox" value="{{ item.id }}" name="tag_id" id="tags_id_{{ item.id }}" {% if item.id in item_data_tag_list %} checked {% endif %}>
                  <label class="form-check-label">{{ item.name }}</label>
                </div>
            {% endfor %}
        {% else %}
            {% for item in list_data_tag %}
                <div class="form-check mr-4 mb-2">
                  <input class="form-check-input" type="checkbox" value="{{ item.id }}" name="tag_id" id="tags_id_{{ item.id }}">
                  <label class="form-check-label">{{ item.name }}</label>
                </div>
            {% endfor %}
        {% endif %}
    </div>
</div>

</form>

可以看到,这里我们还是通过Django的DTL来来做判断,是否传入了item_data来判断是否显示相对应的数值。

此时我们点击点击一下文章的modify按钮,发现页面如下:

发现和刚才的文章一模一样,接下里就要修改修改过程的后半截了,即处理提交逻辑,这个处理的逻辑主要是在之前的PostView里面,因为我们的button的name不一样,所以通过name来区分是否是修改还是创建,从而处理不同的逻辑:

class PostView(View):
    def post(self, request):
        # 修改Post
        elif 'modify' in request.POST:
        form = PostEditForm(request.POST)
        if form.is_valid():
            id = form.cleaned_data.get('id')
            title = form.cleaned_data.get('title')
            description = form.cleaned_data.get('description')
            author = form.cleaned_data.get('author')
            thumbnail = form.cleaned_data.get('thumbnail')
            status = form.cleaned_data.get('status')
            content = form.cleaned_data.get('content')
            is_md = form.cleaned_data.get('is_md')
            category = form.cleaned_data.get('category')
            priority = form.cleaned_data.get('priority')
            is_hot = form.cleaned_data.get('is_hot')
            is_top = form.cleaned_data.get('is_top')
            is_main_page = form.cleaned_data.get('is_main_page')
            publish_time_show = form.cleaned_data.get('publish_time_show')
            time_id = form.cleaned_data.get('time_id')
            read_num = form.cleaned_data.get('read_num')
            tags = form.cleaned_data.get('tag_id')
            instance = Post.objects.filter(id=id)
            instance.update(title=title, description=description, author=author,
                                thumbnail=thumbnail, status=status, content=content,
                                is_md=is_md, category=category, priority=priority,
                                is_hot=is_hot, is_top=is_top, is_main_page=is_main_page,
                                publish_time_show=publish_time_show, time_id=time_id,read_num=read_num
                                )
            instance.first().tag.set(tags)
            return redirect(reverse("cms:post_manage_view"))
        else:
            return restful.method_error("Form is error", form.get_errors())

这个时候,我们就去前端测试一下,把各个数据都修改一下,看看能不能成功:

然后点击提交,页面跳回到了管理页面:

发现我们的标题确实改了,我们再去数据库里面核对一下:

看到数据确实是正确的,那么我们的修改就完成了。

Post的删除

Post的删除还是和Category还有Tag的一致,通过JavaScript的Ajax发送POST请求,来删除,具体做法就是写JavaScript代码,视图函数,当然,这里只负责处理逻辑,同样别忘了在urls.py文件里面配置视图函数。

class PostDeleteView(View):
    def post(self, request):
        post_id = request.POST.get('post_id')
        Post.objects.filter(id=post_id).delete()
        return restful.ok()

在manag.html页面的按钮,别忘了修改传入post_id

<button class="btn btn-danger btn-xs delete-btn" data-post-id="{{ item.id}}">
    Delete
</button>

写好之后,我们来前端测试一下,在唯一的文章上面点击delete,就会看到页面跳转到了manage.html页面,数据没有哦了。

说明数据删除成功。

技术总结

最后总结一下,

文章管理开发,基本和Category还有Tag的开发一样:

  1. 增加:写视图函数,发送post请求,使用form表单处理,最后保存到数据库中;
  2. 删除:使用到了JavaScript,通过Ajax发送Post请求;
  3. 修改:复用Publish.html,是否传入item_data来判断是否是修改还是添加;
  4. 查询:读取数据看,然后将结果放到context中,返回给页面;
  5. 完毕。

获取代码的唯一途径:关注『皮爷撸码』,回复『代码』即可获得。

长按下图二维码关注,如文章对你有启发,欢迎在看与转发。