Django Rest Framework:动态更新/创建多个对象,无需提供 pk
Django Rest Framework: Updating / creating multiple objects dynamically, without giving the 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
)
这些模型描述了一个简单的邮票卡片模型。当邮票卡通过外键关联的邮票数量与邮票卡格式的限制数量相同时,邮票卡被认为是满的。
我需要编写执行以下操作的视图:
- 该视图包含邮票列表(见下文),其中包括
uuid 的。
- 然后需要为每个给定的印章卡片格式找到正确的格式
戳.
接下来需要检查请求用户是否有印章卡
与相应的印章格式.
a) 如果有,需要检查印章卡是否已满。
i) 如果已满,则需要创建给定格式的新印章卡
并将 stamps stampcard-foreignkey 更新为创建的 stampcard。
ii) 如果未满,需要更新邮票 stampcard-foreignkey
到找到的邮票卡片
b) 如果用户没有给定的印章卡
stampcardformat,它需要创建一个新的stampcard并更新
将 stampcard-foreignkey 标记到创建的 stampcard。
邮票请求正文列表如下:
[
{
"stamp_uuid": "62c4070f-926a-41dd-a5b1-1ddc2afc01b2"
},
{
"stamp_uuid": "4ad6513f-5171-4684-8377-1b00de4d6c87"
},
...
]
基于 class 的视图似乎不支持这种行为。我尝试修改基于 class 的视图,但无济于事。除了很多点我都失败了,因为视图抛出错误:
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/
并对其执行 put(或任何具有正文且有效的请求)。
我写的路线是:
url(r'^stamps/$', StampUpdate.as_view(), name='stamp-api-update'),
编辑:
巨大的更新。所以我设法将一个有效的观点融合在一起。
首先,我像这样更新了邮票卡模型(我确实添加了一个新字段 'done' 来跟踪它是否已满):
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 来说非常不安全。我无法让它首先序列化数据。我的问题是:这样的观点可以吗?或者我可以改进什么吗?例如,它不使用通用的 class,但我不知道如何在这里使用它们...
- 我也很想 return 邮票卡,如果它用这种方法填满的话。但我也想排除所有不相关的邮票卡,这就是我调用
.exclude(done=True)
的原因。不幸的是,一张被填满的邮票卡已经完成了=真的!如何将在此过程中填满的印章卡添加到 return 值?
我不认为在 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,则必须在添加邮票后立即将邮票卡设置为已完成。
我刚刚偶然发现了 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 的。
- 然后需要为每个给定的印章卡片格式找到正确的格式 戳.
接下来需要检查请求用户是否有印章卡 与相应的印章格式.
a) 如果有,需要检查印章卡是否已满。
i) 如果已满,则需要创建给定格式的新印章卡 并将 stamps stampcard-foreignkey 更新为创建的 stampcard。
ii) 如果未满,需要更新邮票 stampcard-foreignkey 到找到的邮票卡片
b) 如果用户没有给定的印章卡 stampcardformat,它需要创建一个新的stampcard并更新 将 stampcard-foreignkey 标记到创建的 stampcard。
邮票请求正文列表如下:
[
{
"stamp_uuid": "62c4070f-926a-41dd-a5b1-1ddc2afc01b2"
},
{
"stamp_uuid": "4ad6513f-5171-4684-8377-1b00de4d6c87"
},
...
]
基于 class 的视图似乎不支持这种行为。我尝试修改基于 class 的视图,但无济于事。除了很多点我都失败了,因为视图抛出错误:
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/
并对其执行 put(或任何具有正文且有效的请求)。 我写的路线是:
url(r'^stamps/$', StampUpdate.as_view(), name='stamp-api-update'),
编辑: 巨大的更新。所以我设法将一个有效的观点融合在一起。 首先,我像这样更新了邮票卡模型(我确实添加了一个新字段 'done' 来跟踪它是否已满):
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 来说非常不安全。我无法让它首先序列化数据。我的问题是:这样的观点可以吗?或者我可以改进什么吗?例如,它不使用通用的 class,但我不知道如何在这里使用它们... - 我也很想 return 邮票卡,如果它用这种方法填满的话。但我也想排除所有不相关的邮票卡,这就是我调用
.exclude(done=True)
的原因。不幸的是,一张被填满的邮票卡已经完成了=真的!如何将在此过程中填满的印章卡添加到 return 值?
我不认为在 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,则必须在添加邮票后立即将邮票卡设置为已完成。