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
, и столбец, хранящий первичный ключ (или другой уникальный столбец) этого объекта.