13

フォームを送信すると、タグがデータベースに保存されない理由を理解しようとしています。django-rest-framework と Django-taggit もかなり新しく、私は何か間違ったことをしていると思います:)

まず、残りのフレームワークで API を作成する前に、汎用ビュー (CreateView および UpdateView) を使用してイベントを登録/検証していました。正常に動作していましたが、Angularjs を使用しているため、さらに進んで API を構築することにしました。

これで、モデル イベントが作成されましたが、タグがなく、いくつかのエラーがあります。私はいくつかのコードを入れて、後でエラーについて説明します。

イベント/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/シリアライザー.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/ビュー/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/ビュー/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 メソッドは正常に動作しています。

rest-framework はクエリセットに従うと考えていました。すべてのタグでリソースを取得できる場合、POST を使用するとタグが登録されないのはなぜですか?

実際、POST メソッドには 2 つの問題があります。

  • 最初に、すでに作成したタグを送信すると、タグは一意でなければならないというエラーが送信されます。新しいものを作成するのではなく、オブジェクトとリンクさせたいだけであることは理解しています。汎用ビューを使用する場合、この問題は発生しません (魔法によって行われます:)、すべて正常に動作しています)
  • 次に、新しいタグを作成しようとすると、新しいイベントは保存されますが、タグがありません。私のタグに対してangularjsが受け取った応答を見ることができます...彼は私にタグの名前を送りますが、ID、URL(ハイパーリンク)はありません。データベースを確認したところ、タグが作成されていませんでした。 API レスポンス

tags_views でカスタム get_queryset(self) を作成する必要があると思いますが、よくわかりません。引き続き調査していきます。誰かがすでにそれを行っていて、アドバイスがあれば、私は非常にAPIになります。ありがとう。

4

3 に答える 3

17

同じ質問に答えます。しかし、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)

タグデータの投稿データは ['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
于 2014-04-14T09:02:01.947 に答える
7

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 を使用するようになりました。

于 2015-03-31T16:59:00.840 に答える