Advertisement

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

阅读量:

你好呀!皮爷爷为我们带来了最新的一期学习Python能干啥?之Django教程!从零开始到入门到精通的学习过程一直到项目的成功部署和上线。这一节里我们来开发文章管理系统,并涵盖文章发布、查看、修改以及删除等功能。

在这里插入图片描述

上一节我们实现了Category和Tag的处理与维护工作;这一节我们将着手处理与维护文章的相关管理。

修改SideBar

首先,在 sidebar 引入了两个管理页面:一个是发布文章页面(publish.html),另一个是管理页面(manage.html)。此外,在 cms 目录下还需要创建 post 目录,并在其中包含 manage.html 和 publish.html 文件。

在这里插入图片描述

我们采用的方法与之前的开发方案一致,并基于publish.html页面实现。即发布与修改操作集成在同一页面中,并通过传入或不传入文章内容来判断其功能。

Post的新增

关于文章中的模型部分,在第13节中已经进行了深入阐述,请简要介绍Post模块中的模型包含哪些内容;其中涉及的应用路径为: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部分则是一个位于页面底部的区域化选择框。

这里的publish.html页面会呈现为和其他页面不同,在此页面内我们需要展示以下数据

  • Author;
  • Category;
  • Tag

这些数据源自数据库中的记录。因此,在我们的post_publish_view方法中,就必须要提取这些数据信息,并随后通过特定的途径将这些信息传递到前端页面。

复制代码
    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模型中采用的是ManyToMany关系模式。为此,在表单设计阶段就需要考虑如何接收前端提交的多个Tag标签,并通过使用ModelMultipleChoiceField字段来接收这些多选值。接着生成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获取数据时得到的是一个QuerySet对象,在Django中,默认情况下普通对象(如Model)无法直接支持ManyToMany关系。然而,在Django中,默认情况下普通对象(如Model)无法直接支持ManyToMany关系。推荐的做法是先创建一个实例对象(如my_instance),然后调用该实例上的设置字段值方法来完成这一过程。

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

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

当前完成搭建了文章的基本功能模块。 在开发环境中测试一下启动我们的服务器。 访问发布页面 尝试着将所有的设置都填入表单中.

在这里插入图片描述

当用户点击发布时, 页面被自动重定向到Publish页面; 此时, 请查看一下之前发布的那篇文章:

在这里插入图片描述

确实存在。另外能够观察到的是,在参数is_md设置为true时,我们的content会生成HTML格式的文件。数据完全正确的情况下,则实现了新增文章的功能。

Post的查询

有关文章查询的问题,在本项目中我们选择在Manage.html页面上放置相关文章信息。其中包含一个表格区域,在这个区域中我们可以填入这些文章信息的具体内容,并将其与分类(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的行为保持一致,在后端通过编写相应的HTTP客户端代码实现这一目标,并在View函数中完成具体的逻辑处理。为了实现这一目标,在后端编写相应的HTTP客户端代码的同时,请确保在urls.py文件中适当配置View函数以支持该功能的实现

复制代码
    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>

完成后,在唯一的一篇文章上点击删除按钮后即可看到页面跳转至manage.html页面,并处于数据为空的状态

在这里插入图片描述

说明数据删除成功。

技术总结

最后总结一下,

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

  1. 实现视图功能时,请确保发送POST请求数组,并利用form表单接收和处理数据最后将数据存入数据库。
  2. 在删除操作中,请确保所有JavaScript脚本均需提前关闭并正确执行AJAX提交POST请求数组。
  3. 建议在原有页面基础上进行复制粘贴操作,并根据是否传递item_data参数决定是更新还是新增内容。
  4. 系统会自动读取数据库中的数据内容并将其传递给页面。
  5. 操作完成。

整套教程源码获取,可以关注『皮爷撸码』,回复『peekpa.com

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

在这里插入图片描述

全部评论 (0)

还没有任何评论哟~