Django не откатывается с атомами c транзакций - PullRequest
3 голосов
/ 07 марта 2020

У меня есть следующие модели:

class InventoryAction(CustomModel):
    action_content_type = models.ForeignKey(ContentType, on_delete=models.PROTECT,
        limit_choices_to={'model__in': ('inventoryinput', 'inventorytransfer', 'inventoryadjustment', 'physicalinventory', 'requisition', 'sale')}, related_name='inventory_actions', verbose_name=_("Tipo de Acción"))
    action_object_id = models.PositiveIntegerField(verbose_name=_("ID de la acción"))
    action_object = GenericForeignKey('action_content_type', 'action_object_id')
    timestamp = models.DateTimeField(auto_now=True, verbose_name=_("Fecha y hora"))

    class Meta:
        verbose_name = _("Acción de Inventario")
        verbose_name_plural = _("Acciones de Inventario")

    def __str__(self):
        return "{}-{}/{}/{}".format(self.id, self.action_content_type.model, self.action_object_id, self.timestamp)

class InventoryActionProduct(CustomModel):
    inventory_action = models.ForeignKey(InventoryAction, on_delete=models.PROTECT, related_name='products', verbose_name=_("Acción de Inventario"))
    product = models.ForeignKey(Product, on_delete=models.PROTECT, related_name='actions', verbose_name=_("Producto"))
    amount = models.FloatField(verbose_name=_("Cantidad"))

    class Meta:
        verbose_name = _("Producto de Acción de Inventario")
        verbose_name_plural = _("Productos de Acciones de Inventario")

    def __str__(self):
        return "[{}] {}/{}/{}".format(self.id, self.inventory_action.action_content_type.model, self.product.name, self.inventory_action.timestamp)

class InventoryActionItem(CustomModel):
    inventory_action_product = models.ForeignKey(InventoryActionProduct, on_delete=models.PROTECT, related_name='items', verbose_name=_("Producto de Acción de Inventario"))
    product_item = models.ForeignKey(ProductItem, on_delete=models.PROTECT, related_name='invetory_action', verbose_name=_("Artículo"))

    class Meta:
        verbose_name = _("Artículo de Acción de Inventario")
        verbose_name_plural = _("Artícuos de Acciones de Inventario")

    def __str__(self):
        return "[{}] {}/{}".format(self.id, self.inventory_action_product.product.name, self.product_item.serial_number)

class InventoryInput(CustomModel):
    repository = models.ForeignKey(Repository, on_delete=models.PROTECT, related_name='inputs', verbose_name=_("Almacén"))

    class Meta:
        verbose_name = _("Entrada de Inventario")
        verbose_name_plural = _("Entradas de Inventario")

    def __str__(self):
        return "{}-{}".format(self.id, self.repository)
KARDEX_INPUT = 'INPUT'
KARDEX_OUTPUT = 'OUTPUT'
KARDEX_CHOICES = (
    (KARDEX_INPUT, _("Entrada")),
    (KARDEX_OUTPUT, _("Salida")),
)
class Kardex(CustomModel):
    type = models.CharField(max_length=6, choices=KARDEX_CHOICES, verbose_name=_("Tipo de Movimiento"))
    repository = models.ForeignKey(Repository, on_delete=models.PROTECT, related_name='kardex', verbose_name=_("Almacén"))
    product = models.ForeignKey(Product, on_delete=models.PROTECT, related_name='kardex', verbose_name=_("Producto"))
    amount = models.FloatField(verbose_name=_("Cantidad"))
    stock = models.FloatField(null=True, blank=True, verbose_name=_("Existencia"))
    timestamp = models.DateTimeField(auto_now=True, verbose_name=_("Fecha y Hora"))
    reference = models.ForeignKey(InventoryActionProduct, null=True, blank=True, on_delete=models.PROTECT, related_name='kardex', verbose_name=_("Referencie"))

    class Meta:
        verbose_name = _("Cardex")
        verbose_name_plural = _("Cardex")

    def __str__(self):
        return "{}-{}/{}/{}/{}/{}".format(self.id, self.type, self.repository.name, self.product.name, self.amount, self.timestamp)

    def save(self, *args, **kwargs):
        last_kardex_entry = Kardex.objects.filter(repository=self.repository, product=self.product).order_by('timestamp').last()
        if not last_kardex_entry:
            last_kardex_stock = 0
        else:
            last_kardex_stock = last_kardex_entry.stock

        if self.type == KARDEX_OUTPUT:
            if last_kardex_stock < self.amount:
                raise CustomValidation(_("La cantidad existente del producto [{}]{} en la tienda [{}]{} es de {}, y se están intentando vender {}". format(self.product.id, self.product.name, self.repository.store.id, self.repository.store.name, last_kardex_stock, self.amount)), 'amount', status.HTTP_400_BAD_REQUEST)

            self.stock = last_kardex_stock - self.amount
        else:
            self.stock = last_kardex_stock + self.amount

        super().save(*args, **kwargs)

И у меня есть следующее представление:

class InventoryExecuteActionViewSet(viewsets.ViewSet):
    permission_classes = (permissions.IsAuthenticated,)

    def post(self, request):
        action_type = request.data['action']

        try:
            self.repository = request.data['repository']['id']
        except:
            raise CustomValidation(_("Debe proporcionar el id del almacén"), 'repository', status.HTTP_400_BAD_REQUEST)

        try:
            self.repository = models.Repository.objects.get(id=self.repository)
        except ObjectDoesNotExist:
            raise CustomValidation(_("No existe un almacén con id [{}]".format(self.repository)), 'repository', status.HTTP_400_BAD_REQUEST)

        action_controller = get_action_controller(action_type)
        result = action_controller(request.data)
        result_to_return = result.data

        return Response(result_to_return)

У меня также есть следующие классы в другом файле (utils.py):

class InventoryActionController:
    def __init__(self, data=None):
        from inventory.models import InventoryAction
        self.data = {}

        self.inventory_action = InventoryAction()
        self.inventory_action_products = []
        self.inventory_action_items = []

        self.action = None

        self.origin_data = data

        self.save_data()

    def save_data(self):
        self.save_action()
        self.save_inventory_action()
        self.save_action_products()

    def save_action(self):
        pass

    def save_inventory_action(self):
        self.inventory_action.action_content_type = ContentType.objects.get(model=self.action.__class__.__name__.lower())
        self.inventory_action.action_object_id = self.action.id
        self.inventory_action.timestamp = datetime.today()
        self.inventory_action.save()

    def save_action_products(self):
        for action_product in self.origin_data['entries']:
            new_action_product = self.save_action_product(action_product)
            self.inventory_action_products.append(new_action_product)

    def save_action_product(self, action_product):
        from inventory.models import InventoryActionProduct
        from inventory.serializers import InventoryActionProductSerializer, InventoryActionItemSerializer

        new_action_product = InventoryActionProduct()
        new_action_product.inventory_action = self.inventory_action
        new_action_product.product_id = action_product['product']['id']
        new_action_product.amount = action_product.get('amountCurr', None)

        new_action_product.save()
        new_action_product_data = InventoryActionProductSerializer(new_action_product).data

        if new_action_product.product.is_seriable:
            self.save_product_items(new_action_product, action_product['items'])
            new_action_product_data['items'] = InventoryActionItemSerializer(new_action_product.items, many=True).data

        self.data['products'].append(new_action_product_data)

        self.save_kardex(new_action_product)

        return new_action_product

    def save_product_items(self, action_product, action_items):
        from inventory.models import InventoryActionItem

        new_action_items = []

        for action_item in action_items:
            new_product_item = self.save_product_item(action_product.product, action_item)

            new_action_item = InventoryActionItem()
            new_action_item.action_product = action_product
            new_action_item.product_item = new_product_item

            new_action_item.save()

            return new_action_items

    def save_product_item(self, product, item):
        from inventory.models import ProductItem

        new_product_item = ProductItem()
        new_product_item.product = product
        new_product_item.serial_number = item

        return new_product_item

    def save_kardex(self, action_product):
        pass

class InventoryInputController(InventoryActionController):
    def save_action(self):
        from inventory.models import InventoryInput
        from inventory.serializers import InventoryInputSerializer

        self.action = InventoryInput()
        self.action.repository_id = self.origin_data['repository']['id']

        self.action.save()
        self.data = InventoryInputSerializer(self.action).data
        self.data['products'] = []

        self.save_inventory_action()

        self.save_action_products()

    def save_kardex(self, action_product):
        from inventory.models import KARDEX_INPUT

        KardexController().save(KARDEX_INPUT, action_product)

class KardexController:
    def save(self, action_type, action_product):
        from inventory.models import Kardex

        new_kardex_entry = Kardex()
        new_kardex_entry.type = action_type
        new_kardex_entry.repository_id = action_product.inventory_action.action_object.repository_id
        new_kardex_entry.product_id = action_product.product_id
        new_kardex_entry.amount = action_product.amount
        new_kardex_entry.reference = action_product

        new_kardex_entry.save()

        return new_kardex_entry

Я установил базу данных для транзакций atomi c:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'database_name',
        'USER': 'postgres',
        'PASSWORD': 'secret',
        'HOST': 'localhost',
        'PORT': '',
        'ATOMIC_REQUEST': True,
    }
}

Я ожидал, что, когда запрос завершится с исключением, все транзакции базы данных будут откатываться, но я вижу, что в модели InventoryAction и Kardex есть несколько записей, даже когда только один запрос заканчивался return Response(), а все остальные заканчивались исключением.

Что я недоразумение об атомах c Транзакции?

1 Ответ

1 голос
/ 07 марта 2020

Я обнаружил, что атомы c транзакции не работают с Django Rest Framework при установке в базу данных с ATOMIC_REQUEST (https://github.com/encode/django-rest-framework/issues/2034).

Но я также обнаружил, что это работает, когда вы явно устанавливаете его в представление:

from django.db import transaction

class InventoryExecuteActionViewSet(viewsets.ViewSet):
    permission_classes = (permissions.IsAuthenticated,)

    @transaction.atomic
    def post(self, request):
        action_type = request.data['action']

        try:
            self.repository = request.data['repository']['id']
        except:
            raise CustomValidation(_("Debe proporcionar el id del almacén"), 'repository', status.HTTP_400_BAD_REQUEST)

        try:
            self.repository = models.Repository.objects.get(id=self.repository)
        except ObjectDoesNotExist:
            raise CustomValidation(_("No existe un almacén con id [{}]".format(self.repository)), 'repository', status.HTTP_400_BAD_REQUEST)

        action_controller = get_action_controller(action_type)
        result = action_controller(request.data)
        result_to_return = result.data

        return Response(result_to_return)
...