代码之家  ›  专栏  ›  技术社区  ›  Kovy Jacob

Django:AttributeError,其中包含泛型外部字段的模型实例,由post_save信号创建

  •  0
  • Kovy Jacob  · 技术社区  · 5 月前

    我在这里处理的模型有三个: SurveyQuestion , Update ,以及 Notification .我用a post_save 创建实例的信号 通知 模型每当一个实例 调查问题 更新 创建。

    这个 通知 模型有一个 GenericForeignKey 它将转到创建它的任何模型。在通知模型中,我尝试使用 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的实例。但是,当我查询以下实例时 通知 在外壳中:

    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'
    

    当我查询以下实例时 调查问题 :

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

    当我查询以下实例时 通知 并尝试打印他们的类名 外键 我给它贴上了标签 source ),我明白了:

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

    所以看起来 调查问题 , 更新 ,以及 通知 实例保存正常,但存在一些问题 通用外键 .

    我有 post_save 创建一个实例 通知 使用 Notification(source_object=instance, start_date=instance.start_date, end_date=instance.end_date) ,但在尝试保存的实例时,这会给我带来错误 调查问题 更新 在管理面板中:

    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)
    
    1 回复  |  直到 5 月前
        1
  •  1
  •   willeM_ Van Onsem    5 月前

    你应该使用 source ,不 source_object :

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

    A. GenericForeignKey 基本上结合了两列 源对象 (非常糟糕的名字)指向 类型 关于该项目 通用外键 一个对象的主键(或另一个唯一列)。

    推荐文章