Django Rest Framework: динамическое обновление/создание нескольких объектов без предоставления pk

Я только что наткнулся на самую сложную проблему, с которой я когда-либо сталкивался в Django Rest Framework. Позвольте мне сначала дать вам мои модели, а затем объяснить:

class Stampcardformat(models.Model):
    workunit    = models.ForeignKey(
        Workunit,
        on_delete=models.CASCADE
    )
    uuid        = models.UUIDField(
        default=uuid.uuid4,
        editable=False,
        unique=True
    )
    limit       = models.PositiveSmallIntegerField(
        default=10
    )
    category    = models.CharField(
        max_length=255
    )


class Stampcard(models.Model):
    stampcardformat = models.ForeignKey(
        Stampcardformat,
        on_delete=models.CASCADE
    )
    user        = models.ForeignKey(
        User,
        on_delete=models.CASCADE
    )
    uuid        = models.UUIDField(
        default=uuid.uuid4,
        editable=False,
        unique=True
    )


class Stamp(models.Model):
    stampcardformat = models.ForeignKey(
        Stampcardformat,
        on_delete=models.CASCADE
    )
    stampcard = models.ForeignKey(
        Stampcard,
        on_delete=models.CASCADE,
        blank=True,
        null=True
    )
    uuid        = models.UUIDField(
        default=uuid.uuid4,
        editable=False,
        unique=True
    )

Эти модели описывают простую модель штамповой карты. Штамповая карта считается заполненной, когда с ней связано столько штампов через внешний ключ, сколько диктует ее лимитное число в формате штамповой карты. Мне нужно написать представление, которое делает следующее:

  1. Представление принимает список марок (см. ниже), состоящий из их uuid.
  2. Затем необходимо найти правильный формат штампа для каждого заданного штампа.
  3. Затем необходимо проверить, есть ли у запрашивающего пользователя карта штампа с соответствующим форматом карты штампа.

    а) Если да, необходимо проверить, заполнена ли штамповая карточка.

    i) Если он заполнен, ему необходимо создать новую карту штампа заданного формата и обновить внешний ключ штампа штампа на созданный штамп.

    ii) Если он не заполнен, необходимо обновить внешний ключ Stampcard Stampcard до найденного Stampcard.

    б) Если у пользователя нет штамповой карты данного формата штамповой карты, ему необходимо создать новую штамповую карту и обновить штамповый штамп-внешний ключ на созданный штамповый ключ.

Вот список штампов в теле запроса:

[
    {
        "stamp_uuid": "62c4070f-926a-41dd-a5b1-1ddc2afc01b2"
    },
    {
        "stamp_uuid": "4ad6513f-5171-4684-8377-1b00de4d6c87"
    },
    ...
]

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

AssertionError: Expected view StampUpdate to be called with a URL keyword argument named "pk". Fix your URL conf, or set the `.lookup_field` attribute on the view correctly.

Редактировать

Для дополнительного контекста: мне нужно, чтобы URL-адрес был без pk, slug или чего-то еще. Таким образом, URL-адрес должен быть примерно таким:

/api/stampcards/stamps/ 

и выполните для него пут (или любой другой запрос, который имеет тело и работает). Маршрут, который я написал:

url(r'^stamps/$', StampUpdate.as_view(), name='stamp-api-update'),

Редактировать: ОГРОМНОЕ обновление. Так что мне удалось создать представление, которое работает. Сначала я обновил модель штамповой карты следующим образом (я добавил новое поле «Готово», чтобы отслеживать, заполнено ли оно):

class Stampcard(models.Model):
    stampcardformat = models.ForeignKey(
        Stampcardformat,
        on_delete=models.CASCADE
    )
    user        = models.ForeignKey(
        User,
        on_delete=models.CASCADE
    )
    uuid        = models.UUIDField(
        default=uuid.uuid4,
        editable=False,
        unique=True
    )
    done        = models.BooleanField(default=False)

Затем я написал представление следующим образом:

class StampUpdate(APIView):
    permission_classes = (IsAuthenticated,)

    def get_object(self, uuid):
        try:
            return Stamp.objects.get(uuid=uuid)
        except Stamp.DoesNotExist():
            raise Http404

    def put(self, request, format=None):
    for stamp_data in request.data:
        stamp = self.get_object(stamp_data['stamp_uuid'])
        if stamp.stampcard==None:
            user_stampcard = self.request.user.stampcard_set.exclude(done=True).filter(stampcardformat=stamp.stampcardformat)
            if user_stampcard.exists():
                earliest_stampcard = user_stampcard.earliest('timestamp')
                stamp.stampcard = earliest_stampcard
                stamp.save()
                if earliest_stampcard.stamp_set.count() == earliest_stampcard.stampcardformat.limit:
                    earliest_stampcard.done=True
                    earliest_stampcard.save()
            else:
                new_stampcard = Stampcard(stampcardformat=stamp.stampcardformat, user=self.request.user)
                new_stampcard.save()
                stamp.stampcard = new_stampcard
                stamp.save()
    new_stampcards = Stampcard.objects.exclude(done=True).filter(user=self.request.user)
    last_full_stampcard = Stampcard.objects.filter(user=self.request.user).filter(done=True)
    if last_full_stampcard.exists():
        last_full_stampcard_uuid=last_full_stampcard.latest('updated').uuid
        last_full_stampcard = Stampcard.objects.filter(uuid=last_full_stampcard_uuid)
        stampcards = new_stampcards | last_full_stampcard
    else:
        stampcards = new_stampcards
    print(stampcards)
    stampcard_serializer = StampcardSerializer(stampcards, many=True)
    return Response(stampcard_serializer.data)

Но у меня есть две проблемы с этим кодом:

  1. Моя интуиция подсказывает мне, что части, где просто вызывается save() для экземпляра модели (например, stamp.save()), очень небезопасны для API. Я не мог заставить его работать, чтобы сначала сериализовать данные. Мой вопрос: это представление нормально, как это? Или можно что-то улучшить? Например, он не использует общий класс, но я не знаю, как их использовать здесь...
  2. Я также хотел бы вернуть штамп-карту, если она была заполнена таким способом. Но я также хочу исключить все нерелевантные марки, поэтому я назвал .exclude(done=True). Штампкарта, которая, к сожалению, была заполнена, сделала = Правда! Как я могу добавить к возвращаемому значению карточки с марками, которые были заполнены в процессе?

person J. Hesters    schedule 21.02.2018    source источник


Ответы (1)


Я не думаю, что небезопасно иметь stamp.save() в методе PUT, потому что по определению он предполагает изменение значения объекта.

Чтобы вернуть только соответствующие марки, вы можете просто добавить штамп в набор, подобный этому.

class StampUpdateView(APIView):
    def get_object(self, uuid):
        try:
            return Stamp.objects.get(uuid=uuid)
        except Stamp.DoesNotExist():
            raise Http404

    def put(self, request, *args, **kwargs):
        stampcard_set = set()
        for stamp_data in request.data:
            stamp = self.get_object(stamp_data['stamp_uuid'])

            user_stampcard = request.user.stampcard_set.exclude(done=True).filter(stampcardformat=stamp.stampcardformat)
            if user_stampcard.exists():
                stampcard = user_stampcard.earliest('timestamp')
            else:
                stampcard = Stampcard(stampcardformat=stamp.stampcardformat, user=request.user)
                stampcard.save()

            stamp.stampcard = stampcard
            stamp.save()

            if stampcard.stamp_set.count() == stampcard.stampcardformat.limit:
                stampcard.done = True
                stampcard.save()

            stampcard_set.add(stampcard)

        stampcard_serializer = StampcardSerializer(stampcard_set, many=True)
        return Response(stampcard_serializer.data)

Таким образом, не имеет значения, были ли уже возвращены штамповые карточки или нет.

Также обратите внимание, что я опускаю строки проверки лимита в вашем коде, чтобы они были после сохранения штампа, потому что, если предел был установлен на 1, штамп-карту нужно будет установить сразу после добавления штампа.

person Panu Tangchalermkul    schedule 23.02.2018
comment
Спасибо за ваши улучшения! Я попробую это как можно скорее. И спасибо, что заверили меня, что вызов штампкарты.save() действительно в порядке! - person J. Hesters; 24.02.2018