Django Rest Framework:动态更新/创建多个对象,而不提供pk

时间:2018-02-21 13:44:43

标签: python django django-rest-framework django-views django-rest-viewsets

我偶然发现了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' S
  2. 然后需要找到每个给定的正确的stampcardformat 邮票。
  3. 接下来需要检查请求用户是否有标记卡 使用相应的stampcardformat

    a)如果,则需要检查,如果邮票卡已满或

    i)如果已满,则需要创建给定格式的新标记卡 并将邮票stampcard-foreignkey更新为创建的标记卡。

    ii)如果它不完整,则需要更新邮票stampcard-foreignkey 到找到的卡片

    b)如果用户没有获得给定的标记卡 stampcardformat ,它需要创建一个新的印章卡并更新 将stampcard-foreignkey邮戳到创建的标记卡上。

  4. 以下是邮票的请求正文清单:

    [
        {
            "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或任何东西。 所以网址应该是这样的:

    /api/stampcards/stamps/ 
    

    并执行put(或任何具有正文和工作的请求)。 我写的路线是:

    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)的原因。不幸的是,一张已经填满的邮票已经完成=但是真的!如何将在流程中填写的标记卡添加到返回值?

1 个答案:

答案 0 :(得分:1)

我不认为在PUT方法中使用stamp.save()是不安全的,因为根据定义,它假设改变了对象的值。

要仅返回相关的标记卡,您只需将标记卡添加到此类

中即可
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)

这样,返回的标记卡是否已经完成并不重要。

另请注意,我将代码中的限制检查行向下移动,以便在保存图章时将其删除,因为如果将limit设置为1,则必须在添加图章后立即将标记设置为已完成。