Django model_to_dict堆栈崩溃

时间:2020-05-31 08:23:17

标签: python django python-3.x django-models stack-overflow

在我的应用中,我对所有模型都使用通用的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

2 个答案:

答案 0 :(得分:0)

经过调试,我想出了这个补丁:

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
...

不确定这是否是一种优雅/正确的解决方案,但它解决了该错误,其他所有功能似乎都可以正常工作。
如果还有其他更好的想法,我很想听听。

答案 1 :(得分:0)

您的BaseModel设计导致了此问题,因为model_to_dict将解决* ToMany关系并可能创建庞大的列表。管理员会创建一个自己的内存列表,以向您显示您将要删除的所有相关模型。

您基本上是在模型的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
相关问题