Django model foreign key to whichever model calls it

I am getting back into Django after a few years, and am running into the following problem. I am making a system where there are 2 models; a survey, and an update. I want to make a notification model that would automatically have an object added when I add a survey object or update object, and the notification object would have a foreign key to the model object which caused it to be added.

However I am running into a brick wall figuring out how I would do this, to have a model with a foreign key which can be to one of two models, which would be automatically set to the model object which creates it. Any help with this would be appreciated.

I am trying to make a model that looks something like this (psuedocode):

class notification(models.model):
     source = models.ForeignKey(to model that created it) #this is what I need help with
     start_date = models.DateTimeField(inherited from model that created it)
     end_date = models.DateTimeField(inherited from model that created it)

Also, just to add some context to the question and in case I am looking at this from the wrong angle, I am wanting to do this because both surveys and updates will be displayed on the same page, so my plan is to query the notification model, and then have the view do something like this:

from .models import notification

notifications = notification.objects.filter(start_date__lte=now, end_date__gte=now).order_by('-start_date')

for notification in notifications:
     if notification.__class__.__name__ == "survey_question":
          survey = notification.survey_question.all()
          question = survey.question()
     elif notification.__class__.__name__ == "update":
          update = notification.update.all()
          update = update.update()

I am also doing this instead of combining the 2 queries and then sorting them by date as I want to have notifications for each specific user anyways, so my plan is (down the road) to have a notification created for each user.

Here are my models (that I reference in the question):

from django.db import models
from datetime import timedelta
from django.utils import timezone

def tmrw():
    return timezone.now() + timedelta(days=1)

class update(models.Model):
    update = models.TextField()
    start_date = models.DateTimeField(default=timezone.now, null=True, blank=True)
    end_date = models.DateTimeField(default=tmrw, null=True, blank=True)

    class Meta:
        verbose_name = 'Update'
        verbose_name_plural = f'{verbose_name}s'

class survey_question(models.Model):
    question = models.TextField()
    start_date = models.DateTimeField(default=timezone.now, null=True, blank=True)
    end_date = models.DateTimeField(default=tmrw, null=True, blank=True)

    class Meta:
        verbose_name = 'Survey'
        verbose_name_plural = f'{verbose_name}s'

Perhaps there is some context where this might be needed, and a way to accomplish this, however, I don't see either. The source = models.ForeignKey(to model that created it) will be the pk of the model that created it, but then that means that source could have a duplicate value, one when a notification instance is created by an update, and possibly the same value when a notification instance is created by the survey_question. After all the actual pk value, just an integer in your case, could be the same for both an update instance and a survey_question instance.

I'm not sure why you can't just add another field to the survey indicating whether it was an update or a survey. That would also eliminate the need for a separate notification model.

class SurveyQuestion(models.Model):    # Note use of PascalCase for class name

    SURVEY = "SV"
    UPDATE = "UP"

    SURVEY_TYPE_CHOICES = {
        SURVEY: "Survey",
        UPDATE: "Update",
    }
    survey_type = models.CharField(
        max_length=2,
        choices=SURVEY_TYPE_CHOICES,
        default=SURVEY,
    )
    question = models.TextField()
    start_date = models.DateTimeField(default=timezone.now, null=True, blank=True)
    end_date = models.DateTimeField(default=tmrw, null=True, blank=True)

Then, you can easily filter by survey_type.

GenericForeignKey to the rescue:

A normal ForeignKey can only “point to” one other model, which means that if the TaggedItem model used a ForeignKey it would have to choose one and only one model to store tags for. The contenttypes application provides a special field type (GenericForeignKey) which works around this and allows the relationship to be with any model

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

class notification(models.model):
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id    = models.PositiveIntegerField()
    source       = GenericForeignKey("content_type", "object_id")
Вернуться на верх