我有一个票证模型及其票证序列号。故障单模型有bought
和booked_at
字段。还有show和seat的unique_together
属性。
class Ticket(models.Model):
show = models.ForeignKey(Show, on_delete=models.CASCADE)
seat = models.ForeignKey(Seat, on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE)
booked_at = models.DateTimeField(default=timezone.now)
bought = models.BooleanField(default=False)
class Meta:
unique_together = ('show', 'seat')
TicketSerializer:
class TicketSerializer(serializers.Serializer):
seat = serializers.PrimaryKeyRelatedField(queryset=Seat.objects.all())
show = serializers.PrimaryKeyRelatedField(queryset=Show.objects.all())
user = serializers.PrimaryKeyRelatedField(queryset=User.objects.all())
bought = serializers.BooleanField(default=False)
def validate(self, attrs):
if attrs['seat']:
try:
ticket = Ticket.objects.get(show=attrs['show'], seat=seat)
if not ticket.bought:
if ticket.booked_at < timezone.now() - datetime.timedelta(minutes=5):
# ticket booked crossed the deadline
ticket.delete()
return attrs
else:
# ticket in 5 mins range
raise serializers.ValidationError("Ticket with same show and seat exists.")
else:
raise serializers.ValidationError("Ticket with same show and seat exists.")
except Ticket.DoesNotExist:
return attrs
else:
raise serializers.ValidationError("No seat value provided.")
在视图中,我使用@transaction.atomic()
确保仅在所有票证有效时创建票证,或者如果无效则不创建任何票证。
@transaction.atomic()
@list_route(
methods=['POST'],
permission_classes=[IsAuthenticated],
url_path='book-tickets-by-show/(?P<show_id>[0-9]+)'
)
def book_tickets_by_show(self, request, show_id=None):
try:
show = Show.objects.get(id=show_id)
user = request.user
...
...
data_list = [...]
with transaction.atomic():
try:
serializer = TicketSerializer(data=data_list, many=True)
if serializer.is_valid():
serializer.save()
....
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
except (Seat.DoesNotExist, ValueError, ConnectionError) as e:
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
except (Show.DoesNotExist, IntegrityError) as e:
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
我想知道的是,当有多个请求被用于创建相同席位的票证时,它是否会有所帮助?
假设,用户A想要预订座位5,6的票。用户B想要预订座位3,6的票,而另一个用户C想要预订座位2,3,4,5,6的票。
上述方法是否会阻止为所有用户预订各自座位的票证,并且只为一个用户创建票证(可能是谁的交易是第一个)?或者,如果有更好的方法,那么请你告诉我如何。我希望我很清楚。如果没有,请问。
答案 0 :(得分:3)
这有助于防止在为同一个席位创建票证时调用多个请求。
是的,它会的。 unique_together
约束加transaction.atomic()
将确保您无法为同一个座位/节目创建两张票。
也就是说,您目前的方法存在一些问题:
我认为没有必要将整个视图以及在atomic()
中保存的位进行包装 - 您不需要同时执行这两个操作并包装整个视图交易中的性能成本。在事务中包装serializer.save()
就足够了。
不建议在事务中捕获异常 - 请参阅the warning in the documentation。通常最好尽可能地捕获可以生成它们的代码的异常,以避免混淆。我建议将代码重构为类似的东西。
try:
show = Show.objects.get(id=show_id)
# Catch this specific exception where it happens, rather than at the bottom.
except Show.DoesNotExist as e:
return Response({'detail': str(e)}
user = request.user
...
...
data_list = [...]
try:
serializer = TicketSerializer(data=data_list, many=True)
if serializer.is_valid():
try:
# Note - this is now *inside* a try block, not outside
with transaction.atomic():
serializer.save()
....
except IntegrityError as e:
return Response({'detail': str(e), status=status.HTTP_400_BAD_REQUEST}
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# Retained from your code - althought I am not sure how you would
# end up with ever get a Seat.DoesNotExist or ValueError error here
# Would be better to catch them in the place they can occur.
except (Seat.DoesNotExist, ValueError, ConnectionError) as e:
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
答案 1 :(得分:0)
您应该使用显式的分布式锁来同步请求,而不是依赖 @When("^When I create a new student by providing the information studentCollege (.*) studentList (.*)$")
public void generatestudent(String studentOwner, List<Student> listOfstudent) {
// need to fetch values in here from whatever is given in feature file
}
这不是一个锁定。
有various ways来实现锁定,但在我们的Django / Gunicorn项目中,我们使用Python自己的transaction.atomic
来确保请求输入一个代码块时间。这是一个对我们有用的相对简单的解决方案。
multiprocessing.Lock
答案 2 :(得分:0)
以下内容如何? 创建一个间歇性的表
class showAndSeat(models.Model):
show = models.ForeignKey(Show, on_delete=models.CASCADE)
seat = models.ForeignKey(Seat, on_delete=models.CASCADE)
showtime = models.DateTimeField(default=timezone.now)
...
class Meta:
unique_together = ('show', 'seat', 'showtime')
你现有的类Ticket将有一个showAndSeat的外键(唯一的限制是你必须使用一些cron创建showAndSeat)
将现有视图更改为
def book_tickets_by_show(self, request, show_id=None):
....
...
...
try:
with transaction.atomic():
seat_list_from_user = [1,2,3,4] # get the list from the request
lock_ticket = showAndSeat.objects.select_for_update(nowait=True).filter(seat__number__in=seat_list_from_user,show = selected_show_timings)
serializer = TicketSerializer(data=data_list, many=True)
if serializer.is_valid():
serializer.save()
return GOOD_Response()
except DatabaseError :
# Tickets are locked by some one else
except (Show.DoesNotExist, IntegrityError) as e:
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
except :
# some other unhandled error
return BAD_RESPONSE()