django-Vue搭建博客 :文章标签

今天对文章分类进行补充,做一期文章标签

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

模型和视图

老规矩,创建标签的模型:

# article/models.py

# 文章标签
class Tag(models.Model):
    text = models.CharField(max_length=30)

    class Meta:
        ordering = ['-id']
    def __str__(self):
        return self.text
···
# 博客文章
class Article(models.Model):
	···
	# 标签
    tags = models.ManyToManyField(
        Tag,
        blank=True,
        related_name='articles'
    )
	···

一篇文章不可能只有一个标签,所以我们选用多对多关系。

注意数据迁移

接下来完成视图函数:

# article/views.py

from .models import Tag
from .serializers import TagSerializer
class TagViewSet(viewsets.ModelViewSet):
    queryset = Tag.objects.all()
    serializer_class = TagSerializer
    permission_classes

视图函数还是如出一辙,同样接下来我们注册路由,序列化器我们放在后面写。

# drf_vue_blog/urls.py

···
router.register(r'tag', views.TagViewSet)
···

序列化器

接下来就是最重要的 TagSerializer

# article/serializers.py

# 新增标签序列化器
class TagSerializer(serializers.HyperlinkedModelSerializer):
    """标签序列化器"""

    class Meta:
        model = Tag
        fields='__all__'

# 修改文章序列化器,添加tags字段
class ArticleSerializer(serializers.HyperlinkedModelSerializer):
    """文章序列化器"""
    # tag 字段
    tags = serializers.SlugRelatedField(
        queryset=Tag.objects.all(),
        many=True,
        required=False,
        slug_field='text'
    )

通过前面章节已经知道,默认的嵌套序列化器只显示外链的 id,需要改得更友好一些。但似乎又没必要改为超链接或者字段嵌套,因为标签就 text 字段有用。因此就用 SlugRelatedField 直接显示其 text 字段的内容就足够了。

让我们给已有的文章新增一个叫 java 的标签试试:

>http -a xianwei:admin123456 PATCH http://127.0.0.1:8000/api/article/15/ tags:="[\"java\"]"
HTTP/1.1 400 Bad Request
···
{
    "tags": [
        "Object with text=java does not exist."
    ]
}

指令中的反斜线时windows的通病,需要注意。

修改失败了,原因是标签java不存在。多对多的关系中,DRF默认你必须得有这个外键对象,才能指定其关系。虽然合情合理,但我们更希望在创建、更新文章时,程序会自动检查数据库中是否存在此标签。如果存在就指向,不存在就创建并指向。

要实现这个效果,你可能想到覆写.validate_{field_name}()或者.validate()还或者.create()/.update()方法。但很遗憾他们都不行。

原因是DRF执行默认的字段有效性检查比上述的方法都要早,程序还执行不到上述方法,框架就抛出错误了。

正确的是覆写to_internal_value()方法(新知识点):

# article/serializers.py
class ArticleSerializer(serializers.HyperlinkedModelSerializer):
···
# 覆写方法,如果如数的不存在就自动创建
    def to_internal_value(self, data):
        tags_data = data.get('tags')
        if isinstance(tags_data,list):
            for text in tags_data:
                if not Tag.objects.filter(text=text).exists():
                    Tag.objects.create(text=text)
        return super().to_internal_value(data)

to_internal_value() 方法原本作用是将请求中的原始 Json 数据转化为 Python 表示形式(期间还会对字段有效性做初步检查)。它的执行时间比默认验证器的字段检查更早,因此有机会在此方法中将需要的数据创建好,然后等待检查的降临。isinstance() 确定标签数据是列表,才会循环并创建新数据。

再请求试试:

>http -a xianwei:admin123456 PATCH http://127.0.0.1:8000/api/article/15/ tags:="[\"java\",\"python\"]"
HTTP/1.1 200 OK
···
{
    "author": {
        "date_joined": "2021-06-13T14:58:00",
        "id": 3,
        "last_login": null,
        "username": "xianwei"
    },
    "body": "aaa",
    "category": null,
    "created": "2021-06-17T20:51:53.437601",
    "tags": [
        "python",
        "java"
    ],
    "title": "category_1",
    "updated": "2021-06-18T17:16:01.303651",
    "url": "http://127.0.0.1:8000/api/article/15/"
}

这次成功了,当然同时赋值多个标签也是可以的,指控也是可以的(给空列表).

除此之外,因为标签仅有text字段是有用的,两个id不同但是text相同的标签是没有任何意义的。更重要的是,SlugRelatedField是不允许有重复的slug_field
因此我们还要覆写TagSerializercreate()/update()方法。、

class TagSerializer(serializers.HyperlinkedModelSerializer):
    """标签序列化器"""

    def check_tag_exists(self, validated_data):
        text = validated_data.get('text')
        if Tag.objects.filter(text=text).exists():
            raise serializers.ValidationError('Tag with text {} exists.'.format(text))

    def create(self, validated_data):
        self.check_tag_exists(validated_data)
        return super().create(validated_data)

    def update(self, instance, validated_data):
        self.check_tag_exists(validated_data)
        return super().update(instance, validated_data)
···

注意这里是添加了这几行代码,原有的还在。

这样就防止了重复的text的标签对象现象。

两个序列化器的完整代码是这个样的:

# article/serializers.py

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

    author = UserDescSerializer(read_only=True)

    # tag 字段
    tags = serializers.SlugRelatedField(
        queryset=Tag.objects.all(),
        many=True,
        required=False,
        slug_field='text'
    )

    # 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

    # 覆写方法,如果如数的不存在就自动创建
    def to_internal_value(self, data):
        tags_data = data.get('tags')
        if isinstance(tags_data, list):
            for text in tags_data:
                if not Tag.objects.filter(text=text).exists():
                    Tag.objects.create(text=text)
        return super().to_internal_value(data)

    class Meta:
        model = Article
        fields = '__all__'
		
class TagSerializer(serializers.HyperlinkedModelSerializer):
    """标签序列化器"""

    def check_tag_exists(self, validated_data):
        text = validated_data.get('text')
        if Tag.objects.filter(text=text).exists():
            raise serializers.ValidationError('Tag with text {} exists.'.format(text))

    def create(self, validated_data):
        self.check_tag_exists(validated_data)
        return super().create(validated_data)

    def update(self, instance, validated_data):
        self.check_tag_exists(validated_data)
        return super().update(instance, validated_data)

    class Meta:
        model = Tag
        fields = '__all__'

标签的增删改查,就请读者自行测试吧。

无论是通过文章接口还是标签自己的接口,创建新标签应该都是 OK 的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值