为什么自定义管理器会破坏相关对象查询?

问题描述 投票:0回答:1

我知道这篇文章很长,但我保证它值得一读:有用的解决方案,并了解 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
管理器不能是我的自定义管理器。

我的问题

  1. 为什么添加自定义管理器会破坏这个功能?它返回一个 QuerySet,那么为什么不返回一个空的

    'b_set': []
    而不是它不存在呢?

  2. 关于默认管理器的文档非常令人困惑,它说:

“默认情况下,Django 在访问相关对象时使用

Model._base_manager
管理器类的实例,而不是相关对象上的
_default_manager
。”

但我的例子似乎表明相反?我是否漏读了文档?

  1. 如果您对这种现象有任何见解或对我的陈述进行更正,我将不胜感激!

感谢您的宝贵时间!

python django django-models django-managers
1个回答
0
投票

经过一番调试,我发现了问题!

问题

此代码未按预期工作,因为

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 序列化器实际上引发错误而不是沉默它。

© www.soinside.com 2019 - 2024. All rights reserved.