Я только что наткнулся на самую сложную проблему, с которой я когда-либо сталкивался в 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
)
Эти модели описывают простую модель штамповой карты. Штамповая карта считается заполненной, когда с ней связано столько штампов через внешний ключ, сколько диктует ее лимитное число в формате штамповой карты. Мне нужно написать представление, которое делает следующее:
- Представление принимает список марок (см. ниже), состоящий из их uuid.
- Затем необходимо найти правильный формат штампа для каждого заданного штампа.
Затем необходимо проверить, есть ли у запрашивающего пользователя карта штампа с соответствующим форматом карты штампа.
а) Если да, необходимо проверить, заполнена ли штамповая карточка.
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)
Но у меня есть две проблемы с этим кодом:
- Моя интуиция подсказывает мне, что части, где просто вызывается save() для экземпляра модели (например,
stamp.save()
), очень небезопасны для API. Я не мог заставить его работать, чтобы сначала сериализовать данные. Мой вопрос: это представление нормально, как это? Или можно что-то улучшить? Например, он не использует общий класс, но я не знаю, как их использовать здесь... - Я также хотел бы вернуть штамп-карту, если она была заполнена таким способом. Но я также хочу исключить все нерелевантные марки, поэтому я назвал
.exclude(done=True)
. Штампкарта, которая, к сожалению, была заполнена, сделала = Правда! Как я могу добавить к возвращаемому значению карточки с марками, которые были заполнены в процессе?