Django模型到dict堆栈溢出崩溃

2024-09-29 22:22:23 发布

您现在位置:Python中文网/ 问答频道 /正文

在我的应用程序中,我对所有模型都使用一个公共Base模型。我遇到了无法用CampusHall模型解释的行为:

class Base(models.Model):
    id: int = models.AutoField(primary_key=True, editable=False)

    class Meta:
        abstract = True

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._initial = self.as_dict

    ...
    @property
    def as_dict(self) -> dict:
        fields = [field.name for field in self._meta.fields]
        res = model_to_dict(self, fields=fields)
        return res
    ...

class Campus(Base):
    name: str = models.CharField(max_length=100)

    class Meta:
        verbose_name_plural = "campuses"

    def __str__(self):
        return self.name

class Hall(Base):
    name: str = models.CharField(max_length=100)
    campus: Campus = models.ForeignKey(Campus, on_delete=models.SET_NULL, null=True)

    def save(self, *args, **kwargs):
        if self.campus is not None and isinstance(self.campus, str):
            self.campus = Campus.objects.get_or_create(name=self.campus)[0]
        super().save(*args, **kwargs)

    def __str__(self):
        return f"{self.name}{f' ({self.campus})' if self.campus else ''}"

在管理控制台上,当我试图删除某个Campus实例时,服务器崩溃。这是(很长)轨迹的一小部分:

Fatal Python error: Cannot recover from stack overflow.
...
Current thread 0x00000bdc (most recent call first):
...
  File "P:\Python\Coursist\academic_helper\models\base.py", line 142 in as_dict
  File "P:\Python\Coursist\academic_helper\models\base.py", line 98 in __init__
  File "C:\Venvs\Coursist\lib\site-packages\django\db\models\base.py", line 512 in from_db
  File "C:\Venvs\Coursist\lib\site-packages\django\db\models\query.py", line 75 in __iter__
  File "C:\Venvs\Coursist\lib\site-packages\django\db\models\query.py", line 1261 in _fetch_all
  File "C:\Venvs\Coursist\lib\site-packages\django\db\models\query.py", line 258 in __len__
  File "C:\Venvs\Coursist\lib\site-packages\django\db\models\query.py", line 411 in get
  File "C:\Venvs\Coursist\lib\site-packages\django\db\models\base.py", line 627 in refresh_from_db
  File "C:\Venvs\Coursist\lib\site-packages\django\db\models\query_utils.py", line 139 in __get__
  File "C:\Venvs\Coursist\lib\site-packages\django\db\models\fields\__init__.py", line 931 in value_from_object
  File "C:\Venvs\Coursist\lib\site-packages\django\forms\models.py", line 93 in model_to_dict
  File "P:\Python\Coursist\academic_helper\models\base.py", line 142 in as_dict
  File "P:\Python\Coursist\academic_helper\models\base.py", line 98 in __init__
  File "C:\Venvs\Coursist\lib\site-packages\django\db\models\base.py", line 512 in from_db
  File "C:\Venvs\Coursist\lib\site-packages\django\db\models\query.py", line 75 in __iter__
  File "C:\Venvs\Coursist\lib\site-packages\django\db\models\query.py", line 1261 in _fetch_all
  File "C:\Venvs\Coursist\lib\site-packages\django\db\models\query.py", line 258 in __len__
  File "C:\Venvs\Coursist\lib\site-packages\django\db\models\query.py", line 411 in get
  File "C:\Venvs\Coursist\lib\site-packages\django\db\models\base.py", line 627 in refresh_from_db
  File "C:\Venvs\Coursist\lib\site-packages\django\db\models\query_utils.py", line 139 in __get__
  File "C:\Venvs\Coursist\lib\site-packages\django\db\models\fields\__init__.py", line 931 in value_from_object
  File "C:\Venvs\Coursist\lib\site-packages\django\forms\models.py", line 93 in model_to_dict
  File "P:\Python\Coursist\academic_helper\models\base.py", line 142 in as_dict
  File "P:\Python\Coursist\academic_helper\models\base.py", line 98 in __init__
  File "C:\Venvs\Coursist\lib\site-packages\django\db\models\base.py", line 512 in from_db
  File "C:\Venvs\Coursist\lib\site-packages\django\db\models\query.py", line 75 in __iter__
...

起初我认为这是HallCampus之间的某种循环指向问题,但情况似乎并非如此(在调试时,我看不到从CampusHall的任何指针)。奇怪的是,如果我首先删除所有Hall实例,那么Campus的删除就会顺利进行。
我正在使用python 3.7Django 3.0.6


Tags: djangoinpyselfdbbasevenvsmodels
2条回答

BaseModel的设计导致了这个问题,因为model_to_dict将解决*到许多关系,并创建可能巨大的列表。管理员创建了自己的内存列表,向您显示了所有要删除的相关模型,这一点被放大了

您基本上是在模型的init方法中放置一个序列化表示,您已经将其作为属性。如果这是缓存值的方式,那么我建议您使用django.utils.functions.cached_property,并将机制更改为“首次访问时缓存”,而不是“初始化时缓存”:

from django.utils.functional import cached_property


class Base(models.Model):
    id: int = models.AutoField(primary_key=True, editable=False)

    class Meta:
        abstract = True

    ...
    @cached_property
    def as_dict(self) -> dict:
        fields = [field.name for field in self._meta.fields]
        res = model_to_dict(self, fields=fields)
        return res

    @property
    def _initial(self) -> dict:
        # if you're set on the _initial name
        return self.as_dict

经过一些调试,我提出了以下修补程序:

class Base(models.Model):
    id: int = models.AutoField(primary_key=True, editable=False)
...
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if models.DEFERRED in args:
            # This means we delete it now, not interested in dict logic
            self._initial = dict()
        else:
            self._initial = self.as_dict
...

不确定这是一个优雅/正确的解决方案,但它解决了这个bug,其他一切似乎都很好。
如果还有其他人有更好的想法,我很想听听

相关问题 更多 >

    热门问题