Django не позволяет создавать объекты в параллельном режиме - PullRequest
0 голосов
/ 13 марта 2019

моделей:

class CouponUsage(models.Model):
    coupon = models.ForeignKey('Coupon', on_delete=models.CASCADE, related_name="usage")
    date = models.DateTimeField(auto_now_add=True)    

class Coupon(models.Model):
    name = models.CharField(max_length=255)
    capacity = models.IntegerField()

    @property
    def remaining(self):
        usage = self.usage.all().count()
        return self.capacity - usage

просмотров:

def use_coupon(request):
    coupon = Coupon.objects.get(condition)

    if coupon.remaining > 0:
        # do something

Я не знаю, как обрабатывать проблемы параллелизма в приведенном выше коде, я считаю, что одной из возможных ошибок является то, что еслипредложение в представлении выполняет другой объект CouponUsage может быть создан ... как я могу справиться с этим?

как предотвратить создание объектов CouponUsage, если они находятся внутри предложения if в представлении

1 Ответ

0 голосов
/ 13 марта 2019

Один из способов сделать это - полагаться на проверки целостности базы данных и транзакции.Предполагая, что ваша емкость всегда должна быть в диапазоне [0, + бесконечность), вы можете изменить модель Coupon на PositiveIntegerField вместо IntegerField:

class Coupon(models.Model):
    name = models.CharField(max_length=255)
    capacity = models.PositiveIntegerField()

Затем необходимо обновитьваша Coupon емкость каждый раз, когда создается CouponUsage.Вы можете переопределить метод save(), чтобы отразить это изменение:

from django.db import models, transaction

class CouponUsage(models.Model):
    coupon = models.ForeignKey('Coupon', on_delete=models.CASCADE, related_name="usage")
    date = models.DateTimeField(auto_now_add=True) 

    @transaction.atomic()
    def save(self, ...):  # Arguments missing
        if not self.pk:  # This is an insert, you may want to raise an error otherwise
            self.coupon.capacity = models.F('capacity') - 1  # The magic is here, this is executed at the database level so no problem with old in memory values
            self.coupon.save()
        super().save(...)

Теперь, когда создается CuponUsage, вы обновляете емкость для связанного экземпляра Coupon.Ключевым моментом здесь является то, что вместо чтения значения из базы данных в память Python, обновления и сохранения, что может привести к противоречивым результатам, обновление до capacity выполняется на уровне базы данных с использованием выражения F ,Это гарантирует, что никакие две транзакции не будут использовать одно и то же значение.

Теперь обратите внимание, что при использовании поля PositiveInteger вместо IntegerField база данных также гарантирует, что capacity не может упасть ниже 0. Поэтому еслиТеперь вы пытаетесь создать экземпляр CuponUsage таким образом, чтобы емкость Cupon получала отрицательное значение, возникло исключение, что препятствовало созданию такого CuponUsage.

Теперь вам нужно воспользоватьсяоб этом в вашем коде, выполнив что-то вроде следующего:

def use_coupon(request):
    coupon = Coupon.objects.get(condition)

    try:
        usage = CuponUsage.objects.create(coupon=coupon)
        # Do whatever you want here, you already 'consumed' a coupon
    except IntegrityError:  # Check for the specific exception
        # Sorry no capacity left
        pass

Если в случае получения купона вам нужно сделать что-то, что может потерпеть неудачу, и в таком случае вам нужно «вернуть»Использование, вы можете заключить всю вашу функцию use_coupon внутри транзакции.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...