在我的应用中,我对所有模型都使用通用的Base
模型。我遇到了Campus
和Hall
模型无法解释的行为:
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__
...
起初,我认为这是Hall
和Campus
之间的某种循环指向问题,但事实并非如此(调试时,我看不到任何指向Campus
到Hall
)。奇怪的是,如果我首先删除所有Hall
实例,则Campus
的删除很顺利。
我正在使用python 3.7
和Django 3.0.6
答案 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