Связывать модели Django вместе с помощью GenericKey?

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

class Team(models.Model):
    users = models.ManyToManyField("User")
    
class User(AbstractUser):
     ...
    
class Subscription(models.Model):
    team = models.ForeignKey("Team", on_delete=models.CASCADE)
    name = models.CharField(max_length=64)
    
class Package(models.Model):
    name = models.CharField(max_length=64) # packageA, packageB
    max_activation_number = models.PositiveIntegerField(default=1)
    
class Activation(models.Model):
    subscription = models.ForeignKey("Subscription", on_delete=models.CASCADE)
    package = models.ForeignKey("Package", on_delete=models.CASCADE)
    created = models.DatetimeField()
    
class PackageA(models.Model):
    ... 
    
    
class PackageB(models.Model):
    ... 
    

У команды одна подписка, и она может активировать один или несколько пакетов, причем один и тот же пакет может быть активирован более одного раза. (количество раз задается параметром "max_ativation_number")

Пример:

У команды есть подписка под названием Suite и доступными пакетами являются: EmailAccount и Calendar. Команда выбирает активировать 3 EmailAccount и 2 Calendar (пакеты не связаны друг с другом)

По этой причине команда может активировать один и тот же пакет большее количество раз.

Для каждой активации мне нужно создать новый экземпляр на PackageA или PackageB (это зависит от выбора команды), а затем я должен как-то "привязать" его к этому экземпляру.

Должен ли я использовать поле GenericKey внутри модели Activation? Мне нужно не только имя выбранного пакета, но и выяснить, какой экземпляр.

Да, я также думаю, что GenericForeignKey - очень хорошая идея в этом случае, так что мы можем динамически связать модель активации с PackageA или PackageB.

from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType

class Activation(models.Model):
    subscription = models.ForeignKey("Subscription", on_delete=models.CASCADE)
    package = models.ForeignKey("Package", on_delete=models.CASCADE)
    created = models.DatetimeField()
    
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')

Вот пример того, как можно создать экземпляр Activation:

subscription = Subscription.objects.get(...)
package = Package.objects.get(...)
content_type = ContentType.objects.get_for_model(PackageA)
package_a = PackageA.objects.get(...)

activation = Activation.objects.create(
    subscription=subscription,
    package=package,
    content_type=content_type,
    object_id=package_a.id,
    created=datetime.now()
)

Это можно решить несколькими способами, поскольку вы хотите создать новый экземпляр пакета. Одно из решений - использовать сигналы Django для создания нового экземпляра пакетов с помощью post_save сигнала from django.db.models.signals import post_save.

from django.db.models.signals import post_save

@receiver(post_save, sender= Activation)
def post_activation_save(sender, instance, **kwargs):
    if kwargs.get('created'):
        # create new package instance
        ....

Насколько я понимаю, вы пытаетесь создать отношение "многие-ко-многим" https://docs.djangoproject.com/en/dev/topics/db/models/#extra-fields-on-many-to-many-relationships:

enter image description here


class Team(models.Model):
    users = models.ManyToManyField("User")
    
class User(AbstractUser):
     ...
    
class Subscription(models.Model):
    team = models.ForeignKey("Team", on_delete=models.CASCADE)
    name = models.CharField(max_length=64)
    packages = models.ManyToManyField(Package, through=Activation)
    
class Package(models.Model):
    name = models.CharField(max_length=64) # packageA, packageB
    
class Activation(models.Model):
    subscription = models.ForeignKey("Subscription", on_delete=models.CASCADE)
    package = models.ForeignKey("Package", on_delete=models.CASCADE)
    max_activation_number = models.PositiveIntegerField(default=1)
    created = models.DatetimeField()

Итак, для того чтобы создать подписку Suite с 3 EmailAccount и 2 Calendar

suite_subscription = Subscription.objects.create(name='Suite', team=team_1)
email_package = Package.objects.create(name='EmailAccount')
calendar_package = Package.objects.create(name='Calendar')
suite_subscription.activation_set.create(package=email_package, max_activation_number=3)
suite_subscription.activation_set.create(package=calendar_package, max_activation_number=2)

Теперь у вас есть suite_subscription с 3 максимальными пакетами электронной почты и 2 максимальными пакетами календаря. Аналогично этому, вы можете создавать дополнительные пакеты и подписки...

И вы можете добавить столько пользовательских атрибутов, сколько захотите, к этой "поворотной" таблице Активации.

Вернуться на верх