我知道这篇文章很长,但我保证它值得一读:有用的解决方案,并了解 Django 的内部工作原理!
我在使用与另一个记录模型具有外键关系的记录模型时遇到了这个问题。反序列化实例时,我想包含其相关实例。
请注意,我使用
django_rest_framework
序列化器并在 Meta.fields
选项中指定相关字段。这不是很相关,所以我不会包含代码,但如果需要的话可以。
小例子:
models.py
class A(models.Model):
...
class B(models.Model):
a = models.models.ForeignKey(A, on_delete=models.CASCADE)
...
反序列化
A
的实例应返回如下内容:{..., 'b_set': [3, 6, 7], ...}
。我们得到一个可能为空的相关 ID 数组。
添加自定义管理器时出现问题:
LoggedModelManager
,它所做的只是过滤掉当前实例中的日志。A
及其日志ALog
。 A
获取自定义管理器->以确保A.objects.all()
仅返回A
的实例,而不是ALog
,然后我们确保ALog
具有默认管理器(django负责过滤掉非日志实例) ).B
做同样的事情。请注意,B
有一个指向 A
的外键。# Defining the Manager
class LoggedModelManager(models.Manager):
# Save the name of the logging model, which are `ALog` and `BLog` in our example.
def __init__(self, *args, **kwargs):
logging_model = kwargs.pop('logging_model')
self.logging_model_name = logging_model.lower()
super().__init__(*args, **kwargs)
# Here we filter out the logs from the logged instances.
def get_queryset(self) -> models.QuerySet:
return super().get_queryset().filter(**{self.logging_model_name+"__isnull": True})
# Define A and its log
class A(models.Model):
objects = LoggedModelManager(logging_model='ALog')
...
class ALog(A):
objects = models.Manager()
instance = models.ForeignKey(A, on_delete=models.CASCADE)
# Define B and its log
class B(models.Model):
objects = LoggedModelManager(logging_model='BLog')
...
class BLog(B):
objects = models.Manager()
instance = models.ForeignKey(B, on_delete=models.CASCADE)
结果: A 将反序列化为:
{..., 'alog_set': [...], ...}
。但请注意,缺少 b_set
!
经过几个小时的挫折和阅读文档后,
B
的以下重写解决了这个问题:
请注意,
BLog
也必须覆盖这些修改,但我省略了这一点。
class B(models.Model):
objects = LoggedModelManager(logging_model='BLog')
objects_as_related = models.Manager()
...
class Meta:
base_manager_name = 'objects'
default_manager_name = 'objects_as_related'
这有效:
A
现在反序列化为{..., 'alog_set': [...], 'b_set': [...] ...}
。
请注意,default_manager_name
管理器不能是我的自定义管理器。
为什么添加自定义管理器会破坏这个功能?它返回一个 QuerySet,那么为什么不返回一个空的
'b_set': []
而不是它不存在呢?
关于默认管理器的文档非常令人困惑,它说:
“默认情况下,Django 在访问相关对象时使用
管理器类的实例,而不是相关对象上的Model._base_manager
。”_default_manager
但我的例子似乎表明相反?我是否漏读了文档?
感谢您的宝贵时间!
经过一番调试,我发现了问题!
此代码未按预期工作,因为
LoggedModelManager.__init__
在未提供参数时会失败。
我假设 mycustom 管理器仅在定义模型 B 时实例化:
class B(models.Model):
objects = LoggedModelManager(logging_model='BLog') # I thought this was the only place the constructor was called.
然而,事实并非如此。当解析相关模型集时,Django Rest Framework 库中的
ModelSerializer
类将实例化相关模型的管理器。此实例化是在没有任何我预期的参数的情况下完成的,因此引发了异常并忽略了相关集。遗憾的是,序列化器不会报告此错误,这使得调试非常困难。
确保您的自定义管理器稍后可以在不带参数的情况下实例化。如果是这种情况,我通过返回默认值
QuerySet
来做到这一点:
# Defining the Manager
class LoggedModelManager(models.Manager):
# Save the name of the logging model, which are `ALog` and `BLog` in our example.
def __init__(self, *args, **kwargs):
if 'logging_model' in kwargs:
logging_model = kwargs.pop('logging_model')
self.logging_model_name = logging_model.lower()
else:
self.logging_model_name = None
super().__init__(*args, **kwargs)
# Here we filter out the logs from the logged instances.
def get_queryset(self) -> models.QuerySet:
if self.logging_model_name is None:
return super().get_queryset()
return super().get_queryset().filter(**{self.logging_model_name+"__isnull": True})
希望这对您有帮助!我想知道这是否值得一张票来确保 Django Rest Framework 序列化器实际上引发错误而不是沉默它。