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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 04:10:30