django-Vue搭建博客 :文章分类

教程来源杜塞-django-vue系列
博客链接 传送门

博客文章通常需要分类,方便用户快速识别文章的类型,或者进行某种关联操作。

本章就来学习实现对文章的分类。

增加分类模型

首先在article/models.py里增加一个分类的模型,并将其和文章进行建立一对一的外键:

# article/models.py

# 文章分类
class Category(models.Model):
    """文章分类"""
    title = models.CharField(max_length=100)
    created = models.DateTimeField(default=timezone.now)

    class Meta:
        ordering = ['-created']

    def __str__(self):
        return self.title

# 博客文章
class Article(models.Model):
	···
    # 分类
    category = models.ForeignKey(
        Category,
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='articles'
    )
	···

字段简单,大体使用title字段就可以。

注意:

需要进行数据迁移,另外Category类要放在Article上面,因为python执行顺序是自上而下。另外分类也可以新建一个app,但是我感觉没必要

视图与路由

试图还是用视图集的形式,因为简单:

from .models import Category
# article/views.py

class CategoryViewSet(viewsets.ModelViewSet):
    """分类视图集"""
    queryset = Category.objects.all()
    serializer_class = CategorySerializer
    permission_classes = [IsAdminUserOrReadOnly]

同样我们创建分类的序列化器(没有新知识与之前都是一样的),我们一会在写他

将路由注册好:

# drf_vue_blog/urls.py
# 其他都不改,就增加这行
router.register(r'category', views.CategoryViewSet)

这就是自动注册的魅力。

序列化器

接下来我们把 article/serializers.py修改一下:

from rest_framework import serializers
from .models import Article, Category
from user_info.serializers import UserDescSerializer


class CategorySerializer(serializers.HyperlinkedModelSerializer):
    """分类的序列化器"""
    url = serializers.HyperlinkedIdentityField(view_name='category-detail')

    class Meta:
        model = Category
        fields = '__all__'
        read_only_fields = ['created']


class ArticleSerializer(serializers.HyperlinkedModelSerializer):
    """文章序列化器"""

    author = UserDescSerializer(read_only=True)
    # category 的嵌套序列化字段
    category = CategorySerializer(read_only=True)
    # category 的id字段,用于创建、更新 category 外键
    category_id = serializers.IntegerField(write_only=True,allow_null=True,required=False)

    # category_id 字段的验证器

    def vaildate_category_id(self, value):
        if not Category.objects.filter(id=value).exists() and value is not None:
            raise serializers.ValidationError("Category with id {} not exists.".format(value))
        return value

    class Meta:
        model = Article
        fields = '__all__'

稍微开始有点复杂了,让我们来拆分一下代码。
CategorySerializer还是原来的那些东西,不再赘述。

我们看ArticleSerializer:

  • 由于我们希望文章接口不仅仅只返回费雷id,所以我们要显示指定 Category,将其变成一个嵌套数据,与之前 author一样.
  • DRF 框架原生没有实现可写的嵌套数据(是因为其逻辑操作没有统一化标准),那么我们想创建/更新文章和文章分类的外键关系怎么办?一种方式
    就是自己去实现序列化器的create()/update()方法;另一种就是DRF框架提供的修改外键的快捷方式,即显示指定的category_id字段,
    则此字段会自动连接到category外键,以便你更新外键关系。
  • 再看 category内部。writr_only表示字段仅可以写;allow_null表示允许为空;required表示在创建/更新时可以不设置此字段。

讲过以上设置,实际上序列化器已经可以正常工作了。但有个小问题时如果用户提交了一个不存在的分类外键,后端返回外键不存在的500错判五,不太友好。解决方法就是先对数据进行预先验证。

验证的方法有如下几种:

  • 覆写序列化器的.validate(...)方法。这个是全局的验证器,其接受的唯一参数是所有字段值的字典。当你需要同时对多个字段进行验证时,这是个很好的选择。
  • 另一种就是教程用到的,即.validate_{field_name}(...)方法,它会将验证某个特定的字段,比如category_id

.validate_category_id检查了两样东西:

  • 数据库中是否包含了对应的id数值。
  • 传入的值是否为None。他是为了能够将已有的外键置空。

如果没通过上述检查,后端抛出400错误(代替之前的500错误),并且返回产生的提示,这就友好一些了。

这就基本完成了对分类的开发。接下来就是实际的测试了。

测试

打开命令行,首先创建分类:

>http -a xianwei:admin123456 http://127.0.0.1:8000/api/category/ title=Django
HTTP/1.1 201 Created
···
{
    "created": "2021-06-17T20:46:25.667557",
    "title": "Django",
    "url": "http://127.0.0.1:8000/api/category/1/"
}

更新已有的分类:

>http -a xianwei:admin123456 PUT http://127.0.0.1:8000/api/category/1/ title=Flask
HTTP/1.1 200 OK
···
{
    "created": "2021-06-17T20:46:25.667557",
    "title": "Flask",
    "url": "http://127.0.0.1:8000/api/category/1/"
}

细心的网友会发现,他并没有返回id,而是返回的url中我们看到了id,这就是我们前面学习使用的超链接起的作用发。

创建文章并指定分类:

>http -a xianwei:admin123456 POST http://127.0.0.1:8000/api/article/ title=category_11 body=bbb category_id=1
HTTP/1.1 201 Created
···
{
    "author": {
        "date_joined": "2021-06-13T14:58:00",
        "id": 3,
        "last_login": null,
        "username": "xianwei"
    },
    "body": "bbb",
    "category": {
        "created": "2021-06-17T20:46:25.667557",
        "title": "Flask",
        "url": "http://127.0.0.1:8000/api/category/1/"
    },
    "created": "2021-06-17T20:52:34.325383",
    "title": "category_11",
    "updated": "2021-06-17T20:52:34.325383",
    "url": "http://127.0.0.1:8000/api/article/16/"
}

注意里时category_id=1,而不是category=1。

置空现有的文章的分类:

>http -a xianwei:admin123456 PATCH http://127.0.0.1:8000/api/article/16/ category_id:=null
HTTP/1.1 200 OK

{
    "author": {
        "date_joined": "2021-06-13T14:58:00",
        "id": 3,
        "last_login": null,
        "username": "xianwei"
    },
    "body": "bbb",
    "category": null,
    "created": "2021-06-17T20:52:34.325383",
    "title": "category_11",
    "updated": "2021-06-17T20:54:35.888087",
    "url": "http://127.0.0.1:8000/api/article/16/"
}

这里需要注意一下,在更新资源时用到了POST,PUT,PATCH三种请求方法,它们的区别时:

  • POST:创建新的资源。
  • PUT:整体高峰新特定资源,默认情况下,要完整给出所有必须的字段。
  • PATCH:部分字段更新,进更闹心需要的字段,非给出的字段默认不更改。

另外一个小问题就是更新分类用了:=符号,这是httpie非字符串数据的方法,详情见文档

删除以及权限等功能就不试了,读者们可以自行尝试。

完善分类详情

上面写的分类接口,我们希望分类的泪飙页面不再显示其链接的文章以及保持数据清爽,但是详情页则展示出分类的所有文章,方便接口使用。因此就需要同一个视图集用到
两个不同的序列化器,及前面文章末尾提到的覆写get_serializer_class

修改序列化器:

# article/models.py

class ArticleCategoryDetailSerializer(serializers.ModelSerializer):
    """给分类详情的嵌套序列化器"""
    url = serializers.HyperlinkedIdentityField(view_name='article-detail')
    
    class Meta:
        model = Article
        fields=[
            'url',
            'title',
        ]
        
class CategoryDetailSerializer(serializers.ModelSerializer):
    """分类详情"""
    
    article = ArticleCategoryDetailSerializer(many=True, read_only=True)
    
    class Meta:
        model = Category
        fields=[
            'id',
            'title',
            'created',
            'articles'
        ]

然后修改试图:

# article/views.py

from .serializers import ArticleSerializer,CategorySerializer,CategoryDetailSerializer
class CategoryViewSet(viewsets.ModelViewSet):
    """分类视图集"""
    queryset = Category.objects.all()
    serializer_class = CategorySerializer
    permission_classes = [IsAdminUserOrReadOnly]
    
    def get_serializer_class(self):
        if self.action == 'list':
            return CategorySerializer
        else:
            return CategoryDetailSerializer

除此之外没有新的魔法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值