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