Link Django models together via GenericKey?
i have the following models:
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):
...
A team has one subscription and it can activate one or more package and the same package could be activated more than one time. (number of times specified with "max_ativation_number")
Example:
A team has a subscription called Suite and the available packages are: EmailAccount and Calendar The team choose to activate 3 EmailAccount and 2 Calendar (packages are not tied to each other)
For that reason the team could activate the same package more times.
For every activation i need to create a new instance on PackageA or PackageB (it depends on the choice a team made) and then i should "link" to that instance somehow.
Should i use GenericKey field inside Activation model? I not only need the name of the chosen package but I also need to figure out which instance.
Yes, I also think GenericForeignKey is a very good idea in this case, so that we can link the Activation model to either PackageA or PackageB dynamically.
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')
Here's an example of how you could create an instance of 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()
)
This can be solved in multiple ways as you want to create a new package instance one solution is using Django signals to create the new instance of packages by using post_save signal 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
....
As far as I understand, you are trying to make a Many-To-Many relationship actually https://docs.djangoproject.com/en/dev/topics/db/models/#extra-fields-on-many-to-many-relationships:
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()
So in order to create Subscription Suite
with 3 EmailAccount and 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)
Now you have suite_subscription
with 3 max email packages and 2 max calendar packages. Similarly to this, you can create extra packages and subscriptions...
And you can add as many custom attributes as you want to that "pivot" Activation table.