Django: NameError с экземпляром модели с Generic Foreign Field in, созданным по сигналу post_save

У меня есть 3 модели, с которыми я имею дело: SurveyQuestion, Update и Notification. Я использую сигнал post_save для создания экземпляра модели Notification всякий раз, когда был создан экземпляр SurveyQuestion или Update.

У модели Notification есть GenericForeignKey, который обращается к той модели, которая его создала. Внутри модели Notification я пытаюсь использовать ForeignKey, чтобы установить __str__ в качестве title поля экземпляра модели, которая его создала. Например, так:

class Notification(models.Model):
    source_object = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    source = GenericForeignKey("source_object", "object_id")
    #more stuff

    def __str__(self):
        return f'{self.source.title} notification'

Я могу создать экземпляры SurveyQuestion и Update из панели администратора, которые затем (как предполагается) создают экземпляр Notification. Однако, когда я запрашиваю экземпляры Notification в оболочке:

from hotline.models import Notification
notifications = Notification.objects.all()
for notification in notifications:
    print (f"Notification object: {notification}")

NoneType
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
      1 for notification in notifications:
----> 2     print (notification)
File ~/ygzsey/hotline/models.py:27, in Notification.__str__(self)
     26 def __str__(self):
---> 27     return f'{self.source.title} notification'
AttributeError: 'NoneType' object has no attribute 'title'

Когда я запрашиваю экземпляры SurveyQuestion:

from hotline.models import SurveyQuestion
surveys = SurveyQuestion.objects.all()
for survey in surveys:
    print (f"Model: {survey.__class__.__name__}")

Model: SurveyQuestion

Когда я запрашиваю экземпляры Notification и пытаюсь вывести имя класса их поля ForeignKey (я обозначил его source), я получаю следующее:

for notification in notifications:
    print (f"Notification for {notification.source.__class__.__name__}")

Notification for NoneType
Notification for NoneType
Notification for NoneType

Похоже, что экземпляры SurveyQuestion, Update и Notification сохраняются правильно, но с GenericForeignKey возникла какая-то проблема. Любая помощь будет оценена по достоинству.

Примечание: Если это имеет значение, то я post_save создавал экземпляр Notification с помощью Notification(source_object=instance, start_date=instance.start_date, end_date=instance.end_date), но при попытке сохранить экземпляр SurveyQuestion или Update в панели администратора возникала ошибка:

ValueError at /admin/hotline/update/add/
Cannot assign "<Update: Update - ad>": "Notification.source_object" must be a "ContentType" instance.

Так что я изменил его на Notification(source_object=ContentType.objects.get_for_model(instance), start_date=instance.start_date, end_date=instance.end_date), я не помню вопрос+ответ здесь, на котором я основывался, но это было где-то здесь.

Спасибо!

Мой полный models.py:

from django.db import models
from datetime import timedelta
from django.utils import timezone
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.contrib.contenttypes.models import ContentType
from django.db.models.signals import post_save
from django.dispatch import receiver

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

class Notification(models.Model):
    source_object = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    source = GenericForeignKey("source_object", "object_id")
    start_date = models.DateTimeField(default=timezone.now)
    end_date = models.DateTimeField(default=tmrw)

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

    def __str__(self):
        return f'{self.source.title} notification'

class Update(models.Model):
    title = models.CharField(max_length=25)
    update = models.TextField()
    start_date = models.DateTimeField(default=timezone.now)
    end_date = models.DateTimeField(default=tmrw)
    #notification = GenericRelation(Notification, related_query_name='notification')

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

    def __str__(self):
        return f'{self.__class__.__name__} - {self.title}'

class SurveyQuestion(models.Model):
    title = models.CharField(max_length=25)
    question = models.TextField()
    start_date = models.DateTimeField(default=timezone.now)
    end_date = models.DateTimeField(default=tmrw)
    #notification = GenericRelation(Notification, related_query_name='notification')

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

    def __str__(self):
        return f'{self.__class__.__name__} - {self.title}'

class SurveyOption(models.Model):
    survey = models.ForeignKey(SurveyQuestion, on_delete=models.CASCADE, related_name='options')
    option = models.TextField()
    id = models.AutoField(primary_key=True)

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

    def __str__(self):
        return f'{self.survey.title} option #{self.id}'

@receiver(post_save)
def create_notification(instance, **kwargs):
    #"""
    print (f"instance: {instance}")
    print (f"instance.__class__: {instance.__class__}")
    print (f"instance.__class__.__name__: {instance.__class__.__name__}")
    #"""
    senders = ['SurveyQuestion', 'Update']
    if instance.__class__.__name__ in senders:
        notification = Notification(source_object=ContentType.objects.get_for_model(instance), start_date=instance.start_date, end_date=instance.end_date)
        notification.save()

post_save.connect(create_notification)

Вы должны использовать source, а не source_object:

Notification(
    source=instance, start_date=instance.start_date, end_date=instance.end_date
)

А GenericForeignKey по сути объединяет два столбца: source_object (очень неудачное название), который указывает на тип объекта, на который ссылается GenericForeignKey, и столбец, хранящий первичный ключ (или другой уникальный столбец) этого объекта.

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