Модель кампании продаж Django со списком внешних ключей, которые могут появляться только в одной кампании продаж?

Я хочу создать кампанию продаж, в которой хранится список книг (Items). Но книга должна применяться только к одной кампании продаж - т.е. она не должна появляться в двух кампаниях.

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

class Campaign(models.Model):

    campaign_name = models.CharField(max_length=50, null=False, blank=False, default='Special Offer')
    included_items = models.ManyToManyField(Item, blank=True)
    active = models.BooleanField(default=True)
    fixed_price = models.DecimalField(max_digits=6, blank=True, null=True, decimal_places=2)

Но я думаю, что поле included_items может иметь неправильный тип поля. Читая другие вопросы и руководство Django, я думаю, что, возможно, я подхожу к этому спереди назад. Возможно, мне следует решать эту проблему в модели Item? (Показано ниже для справки)

class Item(models.Model):

    sku = models.CharField(
        max_length=10, null=True, blank=True, 
        default=create_new_sku)
    title = models.CharField(max_length=254)
    genre = models.ManyToManyField('Genre', blank=True)
    author = models.ManyToManyField('Author', blank=True)
    description = models.TextField()
    age_range = models.ManyToManyField(
        'Age_range')
    image_url = models.URLField(max_length=1024, null=True, blank=True)
    image = models.ImageField(null=True, blank=True)
    price = models.DecimalField(max_digits=6, decimal_places=2, default=0.00)
    discount = models.DecimalField(max_digits=2, decimal_places=0, default=0)
    set_sale_price = models.DecimalField(max_digits=6, decimal_places=2, default=0.00)
    original_sale_price = models.DecimalField(max_digits=6, decimal_places=2, null=True, blank=True)
    final_price = models.DecimalField(max_digits=6, decimal_places=2, null=True, blank=False, editable=False)


Если каждый элемент может принадлежать только одной кампании, вы должны использовать ForeignKey к кампании в модели Item для создания отношения "многие-к-одному"

На самом деле ваш дизайн в порядке.

Использование ForeignKey из ответа @Henty в модели Item все еще сталкивается с проблемами, когда если кампания становится неактивной (active=False), модель Item должна аннулировать ForeignKey, чтобы позволить другим кампаниям предлагать ее. Кроме того, данные модели Item должны содержать информацию только об этом экземпляре. Мне кажется странным наличие внешнего ключа кампании в модели Item, поскольку эти данные не имеют отношения к книге. Возможно, вам стоит почитать о нормализации данных.

При этом вы должны вручную указать вашу промежуточную таблицу many-to-many, так как вам нужно будет применить уникальное ограничение в ней, чтобы только одна активная кампания могла предложить книгу.

В этой промежуточной таблице также будут такие поля, как discount или sale_price книги, поскольку разные кампании могут предлагать разные скидки/цены. Я предполагаю, что price в вашей модели Item - это базовая розничная цена книги, поэтому discount, set_sale_price, original_sale_price и final_price не нужны - опять же, обратитесь к нормализации данных.

Кроме того, вам нужно скопировать поле active из экземпляра Campaign в промежуточную таблицу, поскольку оно будет необходимо для ограничения уникальности. Это делается путем переопределения метода save в вашей промежуточной таблице для копирования значения active из экземпляра Campaign.

Вам также придется переопределить метод save в вашей модели Campaign, чтобы обновить поле active в промежуточных экземплярах при активации/деактивации кампании.

class Campaign(models.Model):
    campaign_name = ...
    included_items = models.ManyToManyField(
        Item,
        through='CampaignItem',
        through_fields=('campaign', 'item')
    )
    active = ...
    fixed_price = ...

    def save(self, *args, **kwargs):
        # pre-save, update associated campaign-item instances
        CampaignItem.objects.filter(campaign=self).update(active=self.active)

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


class CampaignItem(models.Model):
    campaign = models.ForeignKey(Campaign, on_delete=models.CASCADE)
    item = models.ForeignKey(Item, on_delete=models.CASCADE)
    active = models.BooleanField(editable=False)
    discount = ...
    sale_price = ...
    # add any other relevant fields related to this campaign item

    class Meta:
        # add constraint where item can only be in one active campaign
        constraints = [
            UniqueConstraint(fields=['item'],
                condition=Q(active=True),
                name='unique_active_item')
        ]

    def save(self, *args, **kwargs):
        # copy over the `active` value from campaign
        self.active = self.campaign.active
        super().save(*args, **kwargs)
    
Вернуться на верх