2016-10-19 4 views
1

Я пытаюсь настроить этот API, чтобы я мог использовать «PUT» для обновления одного или нескольких «TAG» на элементе в модели «MOVIE». Теги - M2M на MOVIE. Я размещаю на ПК номер в фильме.Обновление поля ManyToMany с отдыхом Django

Моя работа httpie (возвращает 200OK), но ничего не создается. Когда я отправляю весь JSON (используя выборку), он просто создает TAG, но отношения M2M на MOVIE (link).

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 

Ошибка:

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

Является ли django для python, как бутстрап, чтобы css? –

+1

Нет, django - это фреймворк на вершине Python с установленной структурой, как настроить свои модели, URL-адреса, шаблоны и т. Д. Давайте продолжим тему :) – Ycon

ответ

1

Хорошо. Я обещал вернуться, когда понял. Вероятно, это не полностью безопасно для данных, поскольку django еще не проверила входящие данные, поэтому я делаю некоторые предположения в моем относительном незнании python и django. Если кто-то умнее меня может расширить этот ответ, пожалуйста, удари меня.

примечание: я твердо придерживаюсь стандарта чистого кода для написания программного обеспечения. С тех пор я хорошо себя зарекомендовал. Я знаю, что это не мета для кода Python, но без малейших, тщательно сфокусированных методов он чувствовал себя неаккуратным.

Views.py

Вы должны очистить связанные объекты самостоятельно, прежде чем добавлять новые, если вы не можете иметь простофили. Это единственный способ найти, чтобы удалить 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

Вы должны подключить метод to_internal_value сериализатора, чтобы получить данные, необходимые, так как валидатор игнорирует m2m поля.

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

Adjust URLs по мере необходимости для этой работы. Логика должна быть правильной, но может потребоваться некоторая настройка, чтобы правильно отражать ваши модели и сериализаторы. Это сложнее, потому что мы должны создать json-данные для APIClient для отправки с помощью запроса put.

2

Там нет put метод на классе сериализатора ФПИ модели, поэтому ничего не вызывает put(self, validated_data). Вместо этого используйте: update(self, instance, validated_data). Документы о сберегательных экземплярах: http://www.django-rest-framework.org/api-guide/serializers/#saving-instances

Также не существует заданий для запроса модели django: Movie.objects.put и Tag.objects.put. У вас есть аргумент instance для фильма уже, и если вы запрашиваете теги, возможно, вам нужны Tag.objects.get или Tag.objects.filter? QuerySet API Reference: https://docs.djangoproject.com/en/1.10/ref/models/querysets/#queryset-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 '. Вот мой tracback: http://dpaste.com/13CWNB2 – Ycon

+0

поэтому 'movie' of' movie.tag.add (tag) 'является целым числом. 'instance' - это фильм, который вы редактируете, поэтому вы можете сделать' instance.tag.add (tag) '. или вы можете обновить свой пост с помощью последнего кода. – fips

+0

По-прежнему получает ту же ошибку с 'instance.tag.add (tag)'. Я обновил свой пост с помощью последнего кода – Ycon

Смежные вопросы