Модель кампании продаж 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)