Django通用关联报错:'GenericRelatedObjectManager'无'texttable'属性
Django通用关联属性错误及信号优化方案
错误详情
在UniqueDbSource的create_datum方法中尝试通过self.presenter.texttable访问关联对象时触发属性错误:
File "/usr/src/app/apps/tableau/models/source.py", line 191, in create_datum backend | if self.presenter.texttable is not None: backend | AttributeError: 'GenericRelatedObjectManager' object has no attribute 'texttable'
相关模型代码
UniqueSourcePresenter
class UniqueSourcePresenter(models.Model): """ Generic Relation Manager of all types of source """ # 通用关联必填字段 limit = models.Q(app_label = 'tableau', model = 'UniqueDbSource'.lower()) | models.Q(app_label = 'tableau', model = 'UniqueAPISource'.lower()) content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, limit_choices_to = limit) object_id = models.PositiveIntegerField() content_object = GenericForeignKey()
DataSource & UniqueSource
class DataSource(models.Model): pass class UniqueSource(DataSource): presenter = GenericRelation(UniqueSourcePresenter) class Meta: abstract = True
UniqueDbSource
class UniqueDbSource(UniqueSource): """ 已注册到ContentType的类 """ def create_datum(self): """ 由信号触发调用 """ d = DatumDb() d.title = f"{self.title}_datum" if self.presenter.texttable is not None: ## 报错行 d.connector = self.presenter.texttable.init_source.data.connector query = self.presenter.texttable.init_source.data.query elif self.presenter.charttable is not None: d.connector = self.charttable.presenter.init_source.data.connector query = self.charttable.presenter.init_source.data.query query.pk = None query.save() d.query = query d.save() self.data = d def create_presenter(self): p = UniqueSourcePresenter() p.content_type = ContentType.objects.get(app_label='tableau', model='UniqueDbSource'.lower()) p.object_id = self.id p.save()
Table & TextTable
class Table(models.Model): unique_source = models.OneToOneField(UniqueSourcePresenter, on_delete=models.CASCADE, null=True, blank=True, related_name="%(class)s") class Meta: abstract = True class TextTable(Table): def create_unique_source(self): """ 创建UniqueSource实例并关联到当前对象 """ if self.init_source.content_type.model == 'InitialDbSource'.lower(): us = UniqueDbSource() us.title = f"{self.title}_unique_source" us.save() self.unique_source = us.presenter elif self.init_source.content_type.model == 'InitialAPISource'.lower(): us = UniqueAPISource() us.title = f"{self.title}_unique_source" us.save() self.unique_source = us.presenter
信号代码
@receiver(post_save, sender=UniqueDbSource) @receiver(post_save, sender=UniqueAPISource) def after_unique_source_save(sender, instance, created, **kwargs): if created: instance.create_datum() instance.create_presenter() instance.__class__.objects.filter(id=instance.id).update( data=DatumDb.objects.all().last()) else: pass @receiver(post_save, sender=TextTable) def after_table_save(sender, instance, created, **kwargs): if created: instance.create_unique_source() if created: TextTable.objects.filter(id=instance.id).update( unique_source=UniqueSourcePresenter.objects.all().last() ) # 需优化此行
问题解决
1. 属性错误修复
self.presenter是GenericRelation生成的反向关联管理器,不是单个UniqueSourcePresenter实例,直接访问.texttable会报错。需先获取关联的单个实例:
修改UniqueDbSource的create_datum方法:
def create_datum(self): """ 由信号触发调用 """ d = DatumDb() d.title = f"{self.title}_datum" # 获取关联的presenter实例(一对一关系用first()或get()) presenter = self.presenter.first() query = None if presenter: if presenter.texttable is not None: d.connector = presenter.texttable.init_source.data.connector query = presenter.texttable.init_source.data.query elif presenter.charttable is not None: d.connector = presenter.charttable.init_source.data.connector query = presenter.charttable.init_source.data.query # 避免query为None时触发报错 if query: query.pk = None query.save() d.query = query d.save() self.data = d
2. 信号代码优化
TextTable信号优化
原代码中UniqueSourcePresenter.objects.all().last()存在并发风险(多实例同时创建时可能取到错误对象),直接复用create_unique_source中已设置的关联即可:
@receiver(post_save, sender=TextTable) def after_table_save(sender, instance, created, **kwargs): if created: instance.create_unique_source() # 直接保存当前实例,无需用update和查询last() instance.save()
UniqueSource信号优化
原代码中DatumDb.objects.all().last()同样有并发问题,建议直接使用刚创建的对象,同时调整方法执行顺序(先创建presenter再创建datum,确保关联存在):
@receiver(post_save, sender=UniqueDbSource) @receiver(post_save, sender=UniqueAPISource) def after_unique_source_save(sender, instance, created, **kwargs): if created: # 先创建presenter,确保后续datum能找到关联 instance.create_presenter() instance.create_datum() # 直接保存实例,无需查询更新 instance.save()
内容的提问来源于stack exchange,提问作者user20060726
相关产品推荐
相关产品推荐

