【问题标题】:Django Rest Framework: Updating / creating multiple objects dynamically, without giving the pkDjango Rest Framework:动态更新/创建多个对象,无需提供 pk
【发布时间】:2018-08-01 03:06:15
【问题描述】:

我刚刚偶然发现了 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. 接下来需要检查请求用户是否有邮票卡 带有相应的戳卡格式

    a) 如果有,则需要检查邮票卡是否已满

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

    ii) 如果未满,需要更新stamps stampcard-foreignkey 到找到的邮票卡

    b) 如果用户没有给定的邮票卡 stampcardformat,它需要创建一个新的stampcard并更新 stamps stampcard-foreignkey 到创建的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 或任何东西。 所以网址应该是这样的:

/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)的原因。不幸的是,一张被填满的邮票卡已经完成了=True!如何将过程中填满的邮票卡添加到返回值中?

【问题讨论】:

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


    【解决方案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)
    

    这样,返回的邮票卡是否已经完成都没有关系。

    另请注意,我将代码中的限制检查行向下移动到保存标记之后,因为如果限制设置为 1,则必须在添加标记后立即将标记卡设置为已完成。

    【讨论】:

    • 感谢您的改进!我会尽快试试这个。感谢您向我保证,调用 stampcard.save() 确实没问题!
    猜你喜欢
    • 1970-01-01
    • 2018-01-08
    • 1970-01-01
    • 2021-12-04
    • 2021-08-01
    • 2014-04-03
    • 1970-01-01
    • 2015-06-23
    • 2020-11-15
    相关资源
    最近更新 更多