2016-10-19 11 views
1

私はこのAPIを設定しようとしていますので、 "PUT"を使ってモデル "MOVIE"のアイテムの1つまたは多くの "TAG"を更新できます。タグはMOVIEのM2Mです。私は映画のアイテムのPKに投稿しています。DjangoのRestでManyToManyフィールドを更新する

私のhttpieの仕事(200OKを返します)は何も作成されません。 JSON全体(フェッチを使用して)を投稿すると、MOVIE(link)にはTAGは作成されますが、M2Mの関係は作成されません。

httpie

http -f PUT http://localhost:8000/api/Edit/3/ tag:='{"name": "TEST"}' 

Models.py

class Tag(models.Model): 
    name = models.CharField("Name", max_length=5000, blank=True) 
    taglevel = models.IntegerField("Tag level", null=True, blank=True) 

class Movie(models.Model): 
    title = models.CharField("Whats happening?", max_length=10000, blank=True) 
    tag = models.ManyToManyField('Tag', blank=True) 

Serializers.py

class Tag1Serializer(serializers.ModelSerializer): 
    class Meta: 
     model = Tag 
     fields = ('name',) 

class EditSerializer(serializers.ModelSerializer): 
    tag = Tag1Serializer(many=True, read_only=True) 
    class Meta: 
      model = Movie 
      fields = ('title', 'tag', 'info', 'created', 'status') 

    def update(self, instance, validated_data): 
     import pdb; pdb.set_trace() 
     tags_data = validated_data.pop('tag') 
     for tag_data in tags_data: 
      tag_qs = Tag.objects.filter(name__iexact=tag_data['name']) 
      if tag_qs.exists(): 
       tag = tag_qs.first() 
      else: 
       tag = Tag.objects.get(**tag_data) 
      instance.tag.add(tag) 
     return movie 

Views.py

class MovieViewSet(viewsets.ModelViewSet): 
    queryset = Movie.objects.all() 
    serializer_class = MovieSerializer 

エラー:何もput(self, validated_data)を呼び出していないので、

Traceback 
    tags_data = validated_data.pop('tag') 
KeyError: 'tag' 
+0

djangoはブートストラップのようにPythonをCSSに変換しますか? –

+1

いいえ、djangoは、モデル、URL、テンプレートなどを設定する方法の設定構造を持つPythonのフレームワークです。 – Ycon

答えて

1

わかりました。私はそれを理解したときに戻ってくることを約束した。これはおそらく、データの安全性を完全に保証するものではないかもしれません。だから、私はPythonとdjangoの相対的な無知でいくつかの仮定をしています。私より賢い人がこの答えを広げることができるなら、私を打つようにしてください。

注:私は、書き込みソフトウェアのクリーンコード標準に固執しています。それは何年にもわたって私に役立ってきました。私はそれがPythonコードのためのメタではないことを知っていますが、小さくて緊密に集中したメソッドがなければ、それはちょっと感じました。

Views.py

あなたがdupesを持つことができない場合は、新しいものを追加することができます前に、関連するオブジェクトを自分でクリアする必要があります。 m2mを確実に削除するための唯一の方法は、私の使用例はです。重複がないようにする必要があり、原子モデルが必要です。あなたのマイレージは異なる場合があります。

class MovieViewSet(viewsets.ModelViewSet): 
    queryset = Movie.objects.all() 
    serializer_class = MovieSerializer 

    def update(self, requiest, *args, **kwargs): 
     movie = self.get_object() 
     movie.tags.clear() 
     return super().update(request, *args, **kwargs) 

Serializers.py

あなたはバリデータがM2Mフィールドを無視するので、必要なデータを取得するのにto_internal_valueシリアライザメソッドをフックする必要があります。

class Tag1Serializer(serializers.ModelSerializer): 
    class Meta: 
     model = Tag 
     fields = ('name',) 

class EditSerializer(serializers.ModelSerializer): 
    tag = Tag1Serializer(many=True, read_only=True) 
    class Meta: 
     model = Movie 
     fields = ('title', 'tag', 'info', 'created', 'status') 

    def to_internal_value(self, data): 
     movie_id = data.get('id') 
     #if it's new, we can safely assume there's no related objects. 
     #you can skip this bit if you can't make that assumption. 
     if self.check_is_new_movie(movie_id): 
      return super().to_internal_value(data) 
     #it's not new, handle relations and then let the default do its thing 
     self.save_data(movie_id, data) 
     return super().to_internal_value(data) 

    def check_is_new_movie(self, movie_id): 
     if not movie_id: 
      return True 
     return False 

    def save_data(self, movie_id, data): 
     movie = Movie.objects.filter(id=movie_id).first() 
     #the data we have is raw json (string). Listify converts it to python primitives. 
     tags_data = Utils.listify(data.get('tags')) 

     for tag_data in tags_data: 
      tag_qs = Tag.objects.filter(name__iexact=tag_data['name']) 
      #I am also assuming that the tag already exists. 
      #If it doesn't, you have to handle that. 
      if tag_qs.exists(): 
       tag = tag_qs.first() 
       movie.tags.add(tag) 

Utils.py

from types import * 
class Utils: 
#python treats strings as iterables; this utility casts a string as a list and ignores iterables 
def listify(arg): 
    if Utils.is_sequence(arg) and not isinstance(arg, dict): 
     return arg 
    return [arg,] 

def is_sequence(arg): 
    if isinstance(arg, str): 
     return False 
    if hasattr(arg, "__iter__"): 
     return True 

Test.py

これが機能するために必要なURLを調整します。ロジックは正しいはずですが、モデルとシリアライザが正しく反映されるように調整が必要な場合があります。 APIClientがput要求で送信するためのjsonデータを作成する必要があるため、より複雑です。

class MovieAPITest(APITestCase): 
    def setUp(self): 
     self.url = '/movies/' 

    def test_add_tag(self): 
     movie = Movie.objects.create(name="add_tag_movie") 
     tag = Tag.objects.create(name="add_tag") 
     movie_id = str(movie.id) 
     url = self.url + movie_id + '/' 

     data = EditSerializer(movie).data 
     data.update({'tags': Tag1Serializer(tag).data}) 
     json_data = json.dumps(data) 

     self.client.put(url, json_data, content_type='application/json') 
     self.assertEqual(movie.tags.count(), 1) 
2

DRFモデルシリアライザクラスにはput方法はありません。代わりにupdate(self, instance, validated_data)を使用してください。インスタンス保存時のドキュメント:http://www.django-rest-framework.org/api-guide/serializers/#saving-instances

また、Djangoモデルクエリーセットには、Movie.objects.putTag.objects.putもありません。既にinstanceの映画の引数があります。タグを照会する場合は、おそらくTag.objects.getまたはTag.objects.filterが必要ですか?クエリセットAPIリファレンス:https://docs.djangoproject.com/en/1.10/ref/models/querysets/#queryset-api

シリアライザメソッドが呼び出されることを確認したら、多分あなたはそれが簡単にエラーを見つけることができるようにDRFテストAPIクライアントを使用するためのテストを書く必要があります。http://www.django-rest-framework.org/api-guide/testing/#apiclient

serializers.py

class TagSerializer(serializers.ModelSerializer): 
    class Meta: 
     model = Tag 
     fields = ('name', 'taglevel', 'id') 


class MovieSerializer(serializers.ModelSerializer): 
    tag = TagSerializer(many=True, read_only=False) 

    class Meta: 
     model = Movie 
     ordering = ('-created',) 
     fields = ('title', 'pk', 'tag') 

    def update(self, instance, validated_data): 
     tags_data = validated_data.pop('tag') 
     instance = super(MovieSerializer, self).update(instance, validated_data) 

     for tag_data in tags_data: 
      tag_qs = Tag.objects.filter(name__iexact=tag_data['name']) 

      if tag_qs.exists(): 
       tag = tag_qs.first() 
      else: 
       tag = Tag.objects.create(**tag_data) 

      instance.tag.add(tag) 

     return instance 

tests.py

class TestMovies(TestCase): 
    def test_movies(self): 
     movie = Movie.objects.create(title='original title') 

     client = APIClient() 
     response = client.put('/movies/{}/'.format(movie.id), { 
      'title': 'TEST title', 
      'tag': [ 
       {'name': 'Test item', 'taglevel': 1} 
      ] 
     }, format='json') 

     self.assertEqual(response.status_code, 200, response.content) 
     # ...add more specific asserts 
+0

私はこれらの調整を行ったが、今はエラー " int 'オブジェクトには属性' tag 'がありません。ここで私のトラブバックは:http://dpaste.com/13CWNB2 – Ycon

+0

ですので、 'movie.tag.add(tag)'の 'movie'は整数です。 'instance'は編集しているムービーですので、' instance.tag.add(tag) 'を実行できます。最新のコードであなたの投稿を更新することができます。 – fips

+0

'instance.tag.add(tag)'と同じエラーが発生します。私は最新のコードで私のポストを更新しました – Ycon

関連する問題