【问题标题】:Using Django-taggit with django-rest-framework, i'm not able to save my tags将 Django-taggit 与 django-rest-framework 一起使用,我无法保存我的标签
【发布时间】:2014-03-01 02:03:30
【问题描述】:

我试图弄清楚为什么当我提交表单时,我的标签没有保存在我的数据库中。 django-rest-framework 和 Django-taggit 也很新,我想我做错了什么:)

首先,在使用rest-framework 制作我的API 之前,我使用通用视图(CreateView 和UpdateView)来注册/验证我的事件。它工作正常,但我决定更进一步并尝试构建一个 API,因为我现在使用的是 Angularjs。

现在我的模型事件已创建,但没有我的标签,我有一些错误。我放了一些代码,然后我会描述我的错误。

events/models.py

class Event(models.Model):
[...]

    title = models.CharField(max_length=245, blank=False)
    description = models.TextField(max_length=750, null=True, blank=True)
    start = models.DateTimeField()
    end = models.DateTimeField()
    created_at = models.DateTimeField(editable=False)
    updated_at = models.DateTimeField(editable=False)
    slug = AutoSlugField(populate_from='title', unique=True, editable=False)
    expert = models.BooleanField(choices=MODE_EXPERT, default=0)
    home = models.BooleanField(choices=HOME, default=0)
    nb_participant = models.PositiveSmallIntegerField(default=1)
    price = models.PositiveSmallIntegerField(default=0)
    cancelled = models.BooleanField(default=0)

    user = models.ForeignKey(User, editable=False, related_name='author')
    address = models.ForeignKey('Address', editable=False, related_name='events')
    participants = models.ManyToManyField(User, related_name='participants', blank=True, editable=False,
                                      through='Participants')
    theme_category = models.ForeignKey('EventThemeCategory', unique=True, editable=False)

    tags = TaggableManager(blank=True)

    class Meta:
        db_table = 'event'

    def save(self, *args, **kwargs):
        if not self.pk:
            self.created_at = timezone.now()
        self.updated_at = timezone.now()
        super(Event, self).save(*args, **kwargs)
    [...]

我正在使用 serializers.HyperlinkedModelSerializer。

api/serializer.py

from taggit.models import Tag

class TagListSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Tag
        fields = ('url', 'id', 'name')


class EventSerializer(serializers.HyperlinkedModelSerializer):
    address = AddressSerializer()
    user = UserSerializer(required=False)
    tags = TagListSerializer(blank=True)

    class Meta:
        model = Event
        fields = ('url', 'id', 'title', 'description', 'start', 'end', 'created_at', 'updated_at', 'slug', 'expert','home', 'nb_participant', 'price', 'address', 'user', 'theme_category', 'tags')
        depth = 1

api/views/tags_views.py

from rest_framework import generics
from api.serializers import TagListSerializer
from taggit.models import Tag


class TagsListAPIView(generics.ListCreateAPIView):
    queryset = Tag.objects.all()
    model = Tag
    serializer_class = TagListSerializer


class TagsDetailAPIView(generics.RetrieveUpdateDestroyAPIView):
    queryset = Tag.objects.all()
    model = Tag
    serializer_class = TagListSerializer

api/views/events_views.py

class EventListAPIView(generics.ListCreateAPIView):
    queryset = Event.objects.all()
    model = Event
    serializer_class = EventSerializer
    paginate_by = 100

    def pre_save(self, obj):
        """
        Set the object's owner, based on the incoming request.
        """
        obj.user = self.request.user
        return super(EventListAPIView, self).pre_save(obj)

api/urls.py

    url(r'^events/(?P<slug>[0-9a-zA-Z_-]+)/$', EventDetailAPIView.as_view(), name='event-detail'),

首先,当我调用 /api/events/name-of-my-event 时,API 会向我发送带有我的标签的好资源。 GET 方法工作正常。

我在想休息框架遵循查询集。因此,如果我可以使用我的所有标签获取资源,为什么当我使用 POST 时我的标签没有注册?

其实我的POST方法有两个问题:

  • 第一个如果我发送一个我已经创建的标签,他给我一个错误,说标签必须是唯一的。我明白,我不想创建一个新的,我只是希望它与我的对象链接。当我使用通用视图时我没有这个问题(它是通过魔法完成的:)并且一切正常)
  • 其次,当我尝试创建新标签时,我的新事件被保存但没有我的标签。 您可以看到 angularjs 收到的对我的标签的响应......他向我发送了标签的名称,但没有 id、url(超链接)。当我检查我的数据库时,标签尚未创建。

我想我必须在我的 tags_views 中创建一个自定义 get_queryset(self) 但我不确定。 我会继续调查。如果有人已经对此并有一些建议,我会非常API。谢谢。

【问题讨论】:

标签: django django-rest-framework django-taggit


【解决方案1】:

我遇到了很多错误,但我找到了解决问题的方法。也许不是最好的,因为我对所有这些都很陌生,但现在它可以工作。

我会尝试描述我所有的错误,也许它会对某人有所帮助。

首先我的 angularjs 发送一个与查询集完全匹配的 json

例如,对于下面我的模型事件,angularjs 发送到 API:

现在让我们从我的所有错误开始:

  • “同名标签已存在”

当我重新使用一个标签时,我遇到了这个错误。不知道为什么,因为使用没有 API 的经典验证,一切正常。

  • 使用新标签也不会保存任何内容。

当我尝试在我的事件事件模型上使用新标签时,数据库中没有保存任何内容。 Angularjs 收到一个带有标签名称但 id 为 null 的响应(请参阅我原来问题的图片)

  • "AttributeError: 'RelationsList' 对象没有属性 'add'"

现在我想注册我的标签我需要有一个已经创建的事件实例。多亏了这一点,我才能像文档中描述的那样在上面添加我的标签。

apple.tags.add("red", "green", "fruit")

所以我决定在我的 events_views.py 中添加一个 post_save:

class EventListAPIView(generics.ListCreateAPIView):
    queryset = Event.objects.all()
    model = Event
    serializer_class = EventSerializer
    paginate_by = 100

    def pre_save(self, obj):
        """
        Set the object's owner, based on the incoming request.
        """
        obj.user = self.request.user
        return super(EventListAPIView, self).pre_save(obj)

    def post_save(self, obj, created=False):
        print 'tags', self.request.DATA
        obj.tags.add(self.request.DATA['tags'])
        return super(EventListAPIView, self).post_save(obj)

但现在据说我有这个错误AttributeError:'RelationsList'对象没有属性'add'。 实际上,这很明显,因为 obj.tags 是一个对象列表,而不是 TaggableManager。

所以我决定重新开始,而不是在“标签”中发送我的标签,而是在另一个自定义属性“标签”中发送,以避免与 TaggableManager 冲突。

  • “TypeError:不可散列的类型:'list'”

新错误 :) 我找到了这个django-taggit-unhashable-type-list的解决方案

def post_save(self, obj, created=False):
    map(obj.tags.add, self.request.DATA['tagged'])
    return super(EventListAPIView, self).post_save(obj)
  • “TypeError:不可散列的类型:'dict'”

现在,我发现我发送的标签格式不正确。我改变了它(在angularjs方面)发送一个像这样的数组['jazz','rock']而不是[object,object]。初学者犯的愚蠢错误。

现在奇迹发生了,angularjs 收到的响应很好:

对不起我的英语。我知道这可能不是最好的解决方案,当我找到另一个解决方案时,我会尝试更新它。

【讨论】:

    【解决方案2】:

    遇到同样的问题。但是我只想通过 TaggableManager 直接保存标签列表(没有 TagListSerializer 和 TagsListAPIView)。我的解决方案是:

    class MyModel(models.Model):
        ...
        tags = TaggableManager(blank=True)
    
        def get_tags_display(self):
            return self.tags.values_list('name', flat=True)
    
    class MyModelSerializer(serializers.HyperlinkedModelSerializer):
        ...
        tags = serializers.Field(source='get_tags_display') # more about: http://www.django-rest-framework.org/api-guide/fields#generic-fields
        ...
    
    class MyModelViewSet(viewsets.ModelViewSet):
        ...
        def post_save(self, *args, **kwargs):
            if 'tags' in self.request.DATA:
                self.object.tags.set(*self.request.DATA['tags']) # type(self.object.tags) == <taggit.managers._TaggableManager>
            return super(MyModelViewSet, self).post_save(*args, **kwargs)
    

    tags数据的post数据会是['tagA', 'tagB',...],TaggableManager会处理。谢谢。

    对于 DRF>3.1,您只需要在 ModelSerializer 类中覆盖 create 和 update:

    class StringListField(serializers.ListField): # get from http://www.django-rest-framework.org/api-guide/fields/#listfield
        child = serializers.CharField()
    
        def to_representation(self, data):
            return ' '.join(data.values_list('name', flat=True)) # you change the representation style here.
    
    
    class MyModelSerializer(serializers.ModelSerializer):
        tags = StringListField()
    
        class Meta:
            model = models.MyModel
    
        def create(self, validated_data):
            tags = validated_data.pop('tags')
            instance = super(MyModelSerializer, self).create(validated_data)
            instance.tags.set(*tags)
            return instance
    
        def update(self, instance, validated_data):
            # looks same as create method
    

    【讨论】:

    • 聪明的解决方案,唯一要记住的是使用最新版本的 Django-Rest-Framework(截至本文 v3.0),您将用 serializers.ReadOnlyField 替换 serializers.Field
    • @Chris,感谢您的 cmets。实际上,DRF3.0 变化很大,我的补丁代码现在无法使用(ModelViewSet.post_save() 已被删除)。但是重写 ModelSerializer.save() 或 create() 和 update() 更方便。标签可以在那里设置。如果有人需要 v3.0 的示例代码,请告诉我。谢谢。
    • 附上,希望能有所帮助。
    • 感谢@gzerone,我遇到了异常——“只能加入一个可迭代对象”
    • 我的错误,已修复,如果仍然无效,请告诉我。
    【解决方案3】:

    我曾经按照以下方式序列化 taggit 对象,但目前 django-taggit 提供了一个内置的序列化器https://github.com/jazzband/django-taggit/blob/master/taggit/serializers.py,它是我之前提到的包中的供应商。

    """
    Django-taggit serializer support
    Originally vendored from https://github.com/glemmaPaul/django-taggit-serializer
    """
    import json
    
    # Third party
    from django.utils.translation import gettext_lazy
    from rest_framework import serializers
    
    
    class TagList(list):
        def __init__(self, *args, **kwargs):
            pretty_print = kwargs.pop("pretty_print", True)
            super().__init__(*args, **kwargs)
            self.pretty_print = pretty_print
    
        def __add__(self, rhs):
            return TagList(super().__add__(rhs))
    
        def __getitem__(self, item):
            result = super().__getitem__(item)
            try:
                return TagList(result)
            except TypeError:
                return result
    
        def __str__(self):
            if self.pretty_print:
                return json.dumps(self, sort_keys=True, indent=4, separators=(",", ": "))
            else:
                return json.dumps(self)
    
    
    class TagListSerializerField(serializers.Field):
        child = serializers.CharField()
        default_error_messages = {
            "not_a_list": gettext_lazy(
                'Expected a list of items but got type "{input_type}".'
            ),
            "invalid_json": gettext_lazy(
                "Invalid json list. A tag list submitted in string"
                " form must be valid json."
            ),
            "not_a_str": gettext_lazy("All list items must be of string type."),
        }
        order_by = None
    
        def __init__(self, **kwargs):
            pretty_print = kwargs.pop("pretty_print", True)
    
            style = kwargs.pop("style", {})
            kwargs["style"] = {"base_template": "textarea.html"}
            kwargs["style"].update(style)
    
            super().__init__(**kwargs)
    
            self.pretty_print = pretty_print
    
        def to_internal_value(self, value):
            if isinstance(value, str):
                if not value:
                    value = "[]"
                try:
                    value = json.loads(value)
                except ValueError:
                    self.fail("invalid_json")
    
            if not isinstance(value, list):
                self.fail("not_a_list", input_type=type(value).__name__)
    
            for s in value:
                if not isinstance(s, str):
                    self.fail("not_a_str")
    
                self.child.run_validation(s)
    
            return value
    
        def to_representation(self, value):
            if not isinstance(value, TagList):
                if not isinstance(value, list):
                    if self.order_by:
                        tags = value.all().order_by(*self.order_by)
                    else:
                        tags = value.all()
                    value = [tag.name for tag in tags]
                value = TagList(value, pretty_print=self.pretty_print)
    
            return value
    
    
    class TaggitSerializer(serializers.Serializer):
        def create(self, validated_data):
            to_be_tagged, validated_data = self._pop_tags(validated_data)
    
            tag_object = super().create(validated_data)
    
            return self._save_tags(tag_object, to_be_tagged)
    
        def update(self, instance, validated_data):
            to_be_tagged, validated_data = self._pop_tags(validated_data)
    
            tag_object = super().update(instance, validated_data)
    
            return self._save_tags(tag_object, to_be_tagged)
    
        def _save_tags(self, tag_object, tags):
            for key in tags.keys():
                tag_values = tags.get(key)
                getattr(tag_object, key).set(tag_values)
    
            return tag_object
    
        def _pop_tags(self, validated_data):
            to_be_tagged = {}
    
            for key in self.fields.keys():
                field = self.fields[key]
                if isinstance(field, TagListSerializerField):
                    if key in validated_data:
                        to_be_tagged[key] = validated_data.pop(key)
    
            return (to_be_tagged, validated_data)
    

    http://blog.pedesen.de/2013/07/06/Using-django-rest-framework-with-tagged-items-django-taggit/

    随着 Django Rest Framework 3.0 的发布,TagListSerializer 的代码略有变化。 serializers.WritableField 被贬低,取而代之的是 serializers.Field 用于创建自定义序列化程序字段,例如这样。下面是 Django Rest Framework 3.0 的更正代码。

    class TagListSerializer(serializers.Field):
        def to_internal_value(self, data):
            if type(data) is not list:
                raise ParseError("expected a list of data")
            return data
    
        def to_representation(self, obj):
            if type(obj) is not list:
                return [tag.name for tag in obj.all()]
            return obj
    

    我现在使用来自https://github.com/glemmaPaul/django-taggit-serializer 库的taggit 序列化程序中的bulit。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-03-29
      • 2012-08-02
      • 2013-06-30
      • 2018-03-23
      • 2014-12-08
      • 1970-01-01
      相关资源
      最近更新 更多