Is a GenericForeignKey a compound key?

I am trying to wrap my head around the usage of a GenericForeignKey, and I am looking for better understanding (or confirmation I am understanding it correctly).

Context
I have three models: Enterprise, Establishment and Activity.
One can quickly reason that Enterprise - Establishment are a one-to-many relation. Activities however, are bound to a specific Establishment, if a Establishment exists. If not, then to an Enterprise (that has only one establishment and thus is not mentioned in Establishment).

Quick example:
Company A has one establishment that sells food, thus this activity entry with label “food” should have a ForeignKey to Enterprise.
Company B has two branches/establishments, one sells tools, another sells wood. The activity labels in this case will have a ForeignKey to the Establishment, and not (directly) to the Enterprise.

With this in mind

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

class Enterprise(models.Model):
    """An active enterprise."""
    enterprise_id = models.CharField(
        primary_key=True,
        max_length=14,
        help_text="Enterprise ID."
    )
    status = models.CharField(
        max_length=2,
        help_text="Status."
    )

class Establishment(models.Model):
    """A local establishment of an existing enterprise."""
    establishment_id = models.CharField(
        primary_key=True,
        max_length=14,
        help_text="Establishment ID"
    )
    enterprise_id = models.ForeignKey(
        Enterprise, on_delete=models.CASCADE
    )
class Activity(models.Model):
    """Description of activity."""
    limit = models.Q(app_label='api_nbb', model="enterprise") | models.Q(app_label='api_nbb', model="establishment")
    entity = models.ForeignKey(
        ContentType, 
        on_delete=models.CASCADE, 
        limit_choices_to=limit
    )
    object_id = models.CharField()
    content_object = GenericForeignKey("entity", "object_id")
    
    activity_tag = models.CharField(
        max_length=10,
        help_text="Short description for activity."
    )

Let us pretend I do:

try:
    x = Enterprise.objects.get(enterprise_id="1234")
except Enterprise.DoesNotExist:
    x = Establishment.objects.get(establishment_id="1234")

activity = Activity(content_object=x, activity_tag="wood")
activity.save()

To me it seems that:

  • An activity does not actually refer, in the DB at least, to the instance/row in table Enterprise/Establishment, but is rather a compound key of a class instance of either Enterprise/Establishment, and said instances attribute, in this case the activity_tag valued wood.
  • The on_delete=models.CASCADE is unnecessary, since it is not linked to an entry in tables Enterprise/Establishment but to the existence of the (Django) models Enterprise/Establishment under django_content_type.

Are both mine assumptions correct here?

And (I know open question but I don’t expect an explanation, rather a nudge or pointer) considering the context, is this a legitimate use of the GenericForeignKey or should I use another strategy when later, I want to identify all information of one Enterprise? (in which case, I’ll have to write a custom query for the activities, I assume).

Thank you for any help and I have to say, new to Django but already a fan!

A GFK allows you to essentially ForeignKey your Activity model to ANY model in your Django project. User, Group, Site, anything…

It is not a database level foreign key constraint and you’re correct it’s just two fields that can be resolved to pointed to object_id of table whatever.

Personaly, I would restructure this data model a bit. I would have a Many2Many field on Establishment to Activity. This way you have a single “Food” or “Sells Tools” across all Establishments and then what an “Enterprise” has is imply the aggregation of all Activities across all an Enterprise’s Establishments.

I like that idea. And it would definitely be beneficial.

I haven’t actually used a GFK in my code yet, that is, to get information rather than storing it. But my assumption is that it works like a regular FK once it is loaded into Django’s framework?