更改数据的 Django 自定义管理器方法无法与其他管理器方法(过滤器、获取等)一起使用
Django custom manager`s method that changes data works incorrectly with other manager`s methods (filter, get, etc.)
我创建了一个自定义 Manager
,但在我的例子中,它的方法将一些字符串添加到模型中特定字段的末尾,而不是像通常情况下那样仅过滤查询集。
我的目标是 return 在调用 SomeModel.objects
时已经改变的对象。 Django 的文档说:
You can override a Manager’s base QuerySet by overriding the Manager.get_queryset() method. get_queryset() should return a QuerySet with the properties you require.
我的方法有效,当我调用 SomeModel.objects.all()
时,但是如果我为 objects
或在 .all()
之后应用一些过滤器,我可以看到数据变得正常。
models.py:
class BaseModelQuerySet(models.QuerySet):
def edit_desc(self, string):
if self.exists():
for obj in self:
if 'description' in obj.__dict__:
obj.__dict__['description'] += string
return self
class BaseModelManager(models.Manager):
def get_queryset(self):
return BaseModelQuerySet(self.model, using=self._db).edit_desc('...extra text')
class BaseModel(models.Model):
objects = BaseModelManager()
class Meta:
abstract = True
Shell 输出:
>>> Macro.objects.all()[0].description
'Test text...extra text'
>>> Macro.objects.all().filter(id=1)[0].description
'Test text'
这让我很困惑。这样的印象是其他方法调用常规查询集而不是一个 returned with custom objects
.
查询集方法 return 查询集实际上 return 一个 new 查询集(它们不会就地更改当前查询集)。并不是说它们是 returning "regular" 查询集,它们是您的自定义查询集子类的实例(您可以通过检查您的查询集类型自行检查),但是 edit_desc()
方法没有被召唤到他们那里。
从技术上讲,您可以 "solve" 通过重写所有 exclude/filter/ect 方法来重新应用您的 edit_desc()
方法,但这会非常低效(甚至比实际情况还要低),所以您可能需要重新考虑您的 真正的 问题是什么,以及如何以更有效和更少干扰的方式解决它。也许解释您的具体用例可以更好地解决问题
?
编辑:鉴于您的评论,一个可能的(并且更有效的)解决方案是覆盖 QuerySet
产生的部分或 return 模型实例(或只是原始值)所以此时您可以处理您的模型实例/数据。您可能想看看 django/db/query.py,特别是 ModelIterable
和 ValueIterable
类。这比您当前的解决方案要多得多,但是...
如果您只关心模型实例(而不是原始数据),另一个可能更简单的解决方案是使用 ModelProxy
,重写它的 __init__
方法以在此时添加您的处理...
最后我意识到,queryset 方法实际上不适用于 queryset 对象。相反,他们添加了一些额外的 SQL 语法并再次对数据库执行查询。
因此,更改对象的数据而不保存它,然后尝试执行一些过滤等将导致再次查询数据库并检索初始数据。
如果有人在我的问题中有类似的用例,不幸的是需要另一种方法。可能的是:
- 重新定义查询集方法,使它们在本地查询集上工作,就像列表一样(不是首选,可能会导致默认 django 行为中的各种问题)
- 创建额外的模型,这将在逻辑上与主模型相关(可能没问题)
无论如何,非常感谢@bruno desthuilliers 和他的建议,如果有人有更好的想法,将很高兴在评论中看到它们。
我创建了一个自定义 Manager
,但在我的例子中,它的方法将一些字符串添加到模型中特定字段的末尾,而不是像通常情况下那样仅过滤查询集。
我的目标是 return 在调用 SomeModel.objects
时已经改变的对象。 Django 的文档说:
You can override a Manager’s base QuerySet by overriding the Manager.get_queryset() method. get_queryset() should return a QuerySet with the properties you require.
我的方法有效,当我调用 SomeModel.objects.all()
时,但是如果我为 objects
或在 .all()
之后应用一些过滤器,我可以看到数据变得正常。
models.py:
class BaseModelQuerySet(models.QuerySet):
def edit_desc(self, string):
if self.exists():
for obj in self:
if 'description' in obj.__dict__:
obj.__dict__['description'] += string
return self
class BaseModelManager(models.Manager):
def get_queryset(self):
return BaseModelQuerySet(self.model, using=self._db).edit_desc('...extra text')
class BaseModel(models.Model):
objects = BaseModelManager()
class Meta:
abstract = True
Shell 输出:
>>> Macro.objects.all()[0].description
'Test text...extra text'
>>> Macro.objects.all().filter(id=1)[0].description
'Test text'
这让我很困惑。这样的印象是其他方法调用常规查询集而不是一个 returned with custom objects
.
查询集方法 return 查询集实际上 return 一个 new 查询集(它们不会就地更改当前查询集)。并不是说它们是 returning "regular" 查询集,它们是您的自定义查询集子类的实例(您可以通过检查您的查询集类型自行检查),但是 edit_desc()
方法没有被召唤到他们那里。
从技术上讲,您可以 "solve" 通过重写所有 exclude/filter/ect 方法来重新应用您的 edit_desc()
方法,但这会非常低效(甚至比实际情况还要低),所以您可能需要重新考虑您的 真正的 问题是什么,以及如何以更有效和更少干扰的方式解决它。也许解释您的具体用例可以更好地解决问题
?
编辑:鉴于您的评论,一个可能的(并且更有效的)解决方案是覆盖 QuerySet
产生的部分或 return 模型实例(或只是原始值)所以此时您可以处理您的模型实例/数据。您可能想看看 django/db/query.py,特别是 ModelIterable
和 ValueIterable
类。这比您当前的解决方案要多得多,但是...
如果您只关心模型实例(而不是原始数据),另一个可能更简单的解决方案是使用 ModelProxy
,重写它的 __init__
方法以在此时添加您的处理...
最后我意识到,queryset 方法实际上不适用于 queryset 对象。相反,他们添加了一些额外的 SQL 语法并再次对数据库执行查询。
因此,更改对象的数据而不保存它,然后尝试执行一些过滤等将导致再次查询数据库并检索初始数据。
如果有人在我的问题中有类似的用例,不幸的是需要另一种方法。可能的是:
- 重新定义查询集方法,使它们在本地查询集上工作,就像列表一样(不是首选,可能会导致默认 django 行为中的各种问题)
- 创建额外的模型,这将在逻辑上与主模型相关(可能没问题)
无论如何,非常感谢@bruno desthuilliers 和他的建议,如果有人有更好的想法,将很高兴在评论中看到它们。