Django的ORM Model.objects.raw()查询导致无限递归错误

Django's ORM Model.objects.raw() query causes an infinite recursion error

我有以下(简化)模型:

class Common(models.Model):

    id = models.BigAutoField(primary_key=True, editable=False)
    date_created = models.DateTimeField()

    class Meta:
        abstract = True

    def __init__(self, *args, **kwargs):
        super(Common, self).__init__(*args, **kwargs)
        self._initial_data = {}
        self.track_fields(
            'date_created',
        )

    def track_fields(self, *args):
        for field_name in args:
            self._initial_data[field_name] = getattr(self, field_name)


class Directory(Common):

    directory_path = models.TextField()
    parent_directory = models.ForeignKey('self')

class File(Common):

    removed = models.BooleanField()
    parent_directory = models.ForeignKey(Directory)

现在,我正在尝试查询这样的一些对象(一个简化的示例,不要注意为什么首先使用 sql):

sql_select_dirs_of_deleted_files = '''
    select d.id, directory_path
    from directory d
    left join file f on f.parent_directory_id = d.id
    where f.removed = true and f.id in %s
    group by d.id, directory_path
    order by directory_path asc
    '''
dirs_of_deleted_files = Directory.objects.raw(sql_select_dirs_of_deleted_files, [tuple(file_ids)])
parent_of_top_dir = dirs_of_deleted_files[0].parent_directory

访问dirs_of_deleted_files[0]导致行

出现无限递归错误
self._initial_data[field_name] = getattr(self, field_name)

在通用模型中。我知道继承和使用 getattr 中的递归问题,但是使用 models.Model.__getattribute__(self, field_name) 在这里似乎没有什么不同。然而,真正起作用的是:

dirs_of_deleted_files = Directory.objects \
    .filter(files__in=file_ids, files__removed=True) \
    .distinct('id', 'directory_path') \
    .order_by('directory_path')

现在,访问 dirs_of_deleted_files[0] 不会导致任何无限递归错误。

Common 模型被其他几个模型继承,显然在不同的地方实例化了很多次,并且 getattr() 从来没有引起任何问题,直到使用这个 Directory.objects.raw() 方法。为什么不?我会怀疑它是 Django 中的错误,但我会保留我的判断。

是的,问题在 Django ticket #22858 中已知。

If you access a field in __init__(), you should include it in all of your only() calls to avoid hitting the database again for each item in the queryset.

这是 defer() 文档的常见结果:

Each deferred field will be retrieved from the database if you access that field (one at a time, not all the deferred fields at once).

如果方法 __init__ 确实访问任何两个或更多延迟字段(即由 .raw().only().defer() 方法延迟的字段),则可以重现问题然后每次访问都会触发数据库查询,并创建一个新的临时实例,再次需要相同的字段。

问题的最小化示例

class SomeModel(models.Model):
    a = models.IntegerField()
    b = models.IntegerField()

    def __init__(self, *args, **kwargs):
        super(SomeModel, self).__init__(*args, **kwargs)
        (self.a, self.b)

class Test(TestCase):
    def test(self):
        SomeModel.objects.create(a=0, b=0)
        SomeModel.objects.only('id')[0]
        # RuntimeError: maximum recursion depth exceeded

FIX

你可以通过条件field_name in self.__dict__来修复它,因为如果你不需要加载它,你可能有时不需要跟踪字段。

def track_fields(self, *args):
    for field_name in args:
        if field_name in self.__dict__:
            self._initial_data[field_name] = getattr(self, field_name)