You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用post_save信号创建含通用外键的Notification模型实例时遭遇AttributeError问题求助

post_save信号创建含通用外键的Notification模型实例时遭遇AttributeError问题求助

大家好,我最近在Django项目里遇到了一个通用外键结合信号的问题,折腾了好久没搞定,想请各位帮忙看看:

我现在在处理三个模型:SurveyQuestion、Update和Notification。我的需求很明确:每当SurveyQuestion或者Update的实例被创建时,通过post_save信号自动生成一个Notification实例,并且让Notification通过通用外键关联到触发它的那个源模型实例。

我在Notification模型里定义了GenericForeignKey,还想在__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")
    # 其他字段...

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

现在的问题是:我能从后台正常创建SurveyQuestion和Update的实例,看起来Notification也被同步创建了,但当我在shell里查询Notification实例时,直接抛出了AttributeError:

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

# 报错信息:
AttributeError: 'NoneType' object has no attribute 'title'

更奇怪的是,当我尝试打印notification.source的类名时,结果全都是NoneType:

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

# 输出:
Notification for NoneType
Notification for NoneType
Notification for NoneType

我之前还踩过一个坑:一开始在信号函数里创建Notification时,我直接传了source_object=instance,结果保存时直接报错:

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

后来我改成了source_object=ContentType.objects.get_for_model(instance),这个错误倒是解决了,但现在又出现了源对象为空的问题。

下面是我的完整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)

备注:内容来源于stack exchange,提问作者Kovy Jacob

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 12:18:01