Django:我如何在Django .db.models. QuerySet中重载Q,以便在我的管理器中用于特殊目的



首先,由于这是我的第一个问题/帖子,我想感谢所有为这个伟大的社区,和惊人的服务,作为-像世界各地的许多开发人员- stackoverflow -是我的主要资源,当涉及到代码问题。

注意:

这篇文章有点长(抱歉),涵盖了两个不同但相关的方面,并按以下方式组织:

  1. Background/Context
  2. 一个设计问题,我想听听你的建议。
  3. 解决方案,我试图实现解决2.
  4. 与3.
  5. 相关的实际问题和疑问
<标题> 1。一些上下文:

我有两个不同的Model对象(ReportJobs)从现有的实现,有一个糟糕的设计。事实上,这两个对象的目的非常相似,但可能是在两个不同的时间框架内实现的。

许多处理发生在这些对象上,由于系统必须发展,我开始编写元类/接口,两者都将成为子类。目前Models都使用不同的Fields名称来表示相同的目的,比如authorjuser来表示User(这是非常愚蠢的)等等。

由于我不能仅仅更改数据库中的列名称,然后遍历数千行代码来更改对这些字段的每个引用(尽管由于现代ide的Find uses 特性,我可以这样做),也因为这些对象可能在其他地方使用,所以我使用了db_column=技术。能够在每个模型中具有相同的字段名,并最终以相同的方式处理两个对象(而不是使用数千行重复的代码来做相同的事情)。

所以,我有这样的东西:

from django.db import models
class Report(Runnable):
    _name = models.CharField(max_length=55, db_column='name')
    _description = models.CharField(max_length=350, blank=True, db_column='description')
    _author = ForeignKey(User, db_column='author_id')
    # and so on
class Jobs(Runnable):
    _name = models.CharField(max_length=55, db_column='jname')
    _description = models.CharField(max_length=4900, blank=True, db_column='jdetails')
    _author = ForeignKey(User, db_column='juser_id')
    # and so on

如前所述,为了避免重写对象的客户端代码,我使用了遮蔽字段的属性:

from django.db import models
class Runnable(models.Model):   
    objects = managers.WorkersManager() # The default manager.
    @property # Report
    def type(self):
        return self._type
    @type.setter # Report
    def type(self, value):
        self._type = value
    # for backward compatibility, TODO remove both
    @property # Jobs
    def script(self):
        return self._type
    @script.setter # Jobs
    def script(self, value):
        self._type = value
    # and so on


<标题> 2。设计问题:

这很好,这是我想要的,除了现在使用Report.objects.filter(name='something')Jobs.objects.filter(jname='something')不会工作,显然是由于Django的设计(等与.get(), .exclude()等…),和客户端代码是可悲的的那些。
我当然计划用我新创建的WorkersManager

的方法来替换它们

Aparte:
等待……怎么啦?"新建 WorkersManager " ??
是的,经过两年和数千行代码,这里没有Manager,疯了吧?
但是你猜怎么着?这是我最不关心的;振作起来,因为大部分代码仍然位于view.py和相关文件中(而不是在它应该操作的对象中),基本上有点"纯粹"命数式python


<标题> 3。我的解决方案:

经过大量阅读(这里和那里)和研究,我发现:

  1. 试图子类化Field,不是一个解决方案
  2. 我可以重载QuerySet

所以我做了:

from django.db.models.query_utils import Q as __originalQ
class WorkersManager(models.Manager):   
    def get_queryset(self):
        class QuerySet(__original_QS):
            """
            Overloads original QuerySet class
            """
            __translate = _translate # an external fonction that changes the name of the keys in kwargs
            def filter(self, *args, **kwargs):
                args, kwargs = self.__translate(*args, **kwargs)
                super(QuerySet, self).filter(args, kwargs)
            # and many more others [...]
        return QuerySet(self.model, using=self._db)

这很好。

<标题> 4。怎么了?

问题是Django在db.model内部使用了Q。查询,使用自己的导入,并且Q没有被公开或引用,因此它可能被重载。

>>> a =Report.objects.filter(name='something')
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "/venv/local/lib/python2.7/site-packages/django/db/models/manager.py", line 143, in filter
    return self.get_query_set().filter(*args, **kwargs)
  File "/venv/local/lib/python2.7/site-packages/django/db/models/query.py", line 624, in filter
    return self._filter_or_exclude(False, *args, **kwargs)
  File "/venv/local/lib/python2.7/site-packages/django/db/models/query.py", line 642, in _filter_or_exclude
    clone.query.add_q(Q(*args, **kwargs))
  File "/venv/local/lib/python2.7/site-packages/django/db/models/sql/query.py", line 1250, in add_q
    can_reuse=used_aliases, force_having=force_having)
  File "/venv/local/lib/python2.7/site-packages/django/db/models/sql/query.py", line 1122, in add_filter
    process_extras=process_extras)
  File "/venv/local/lib/python2.7/site-packages/django/db/models/sql/query.py", line 1316, in setup_joins
    "Choices are: %s" % (name, ", ".join(names)))
FieldError: Cannot resolve keyword 'name' into field. Choices are: _author, _description, _name, # and many more

但是我记得读过一些关于Django如何只加载Model的第一次出现,以及如何通过在使用import之前重新定义这样的Model来欺骗它(很明显这不适用于python)
因此,最终我试图重载Q,在导入相关类之前或之后重新定义它,但我不可能弄清楚。

这是我尝试的:

from django.db.models.query_utils import Q as __originalQ
__translation = {'name': '_name',} # has much more, just for exemple
def _translate(*args, **kwargs):
    for key in kwargs:
        if key in __translation.keys():
            kwargs[__translation[key]] = kwargs[key]
            del kwargs[key]
    return args, kwargs
class Q(__originalQ):
    """
    Overloads original Q class
    """
    def __init__(self, *args, **kwargs):
        super(Q, self).__init__(_translate(*args, **kwargs))
# now import QuerySet which should use the new Q class
from django.db.models.query import QuerySet as __original_QS
class QuerySet(__original_QS):
    """
    Overloads original QuerySet class
    """
    __translate = _translate # writing shortcut
    def filter(self, *args, **kwargs):
        args, kwargs = self.__translate(*args, **kwargs)
        super(QuerySet, self).filter(args, kwargs)
    # and some others
# now import QuerySet which should use the new QuerySet class
from django.db import models
class WorkersManager(models.Manager):
    def get_queryset(self):
        # might not even be required if above code was successful
        return QuerySet(self.model, using=self._db)

这当然没有效果,因为Q在_filter_or_exclude的定义中被从django.db.model.query重新导入。
当然,直观的解决方案是超载_filter_or_exclude,和复制原来的代码调用super
但这是赶上:我使用Django的旧版本,这可能是更新的总有一天,我不想惹Django实现细节,我已经与get_queryset,但我想这是好的因为它是(据我理解)一个占位符重载,它也是唯一的方法。

所以我在这里,我的问题是:
没有别的办法了吗?

在Django模块中重载Q是没有办法的吗?

非常感谢你一路阅读:)

这是一个土豆(哎呀,错误的网站,对不起:))

<标题>编辑:

因此,在尝试重载_filter_or_exclude之后,它似乎没有效果。
我可能错过了一些关于调用堆栈顺序或类似的东西…

耶!我找到解决办法了。

原来,首先,忘记有一个return在我的函数,如:

def filter(self, *args, **kwargs):
    args, kwargs = self.__translate(*args, **kwargs)
    super(QuerySet, self).filter(args, kwargs)

代替:

def filter(self, *args, **kwargs):
    args, kwargs = self.__translate(*args, **kwargs)
    return super(QuerySet, self).filter(args, kwargs)

还有:

args, kwargs = self.__translate(*args, **kwargs)

代替:

args, kwargs = self.__translate(args, kwargs)

在函数调用时导致unpack,因此原始kwargs的所有内容都结束在args中,从而阻止translate产生任何影响。

但更糟糕的是,我不明白我可以直接重载filter, get等,直接从我的自定义管理器…
这节省了我处理QuerySetQ的精力。

最后,下面的代码按预期工作:

def _translate(args, kwargs):
    for key in kwargs.keys():
        if key in __translation.keys():
            kwargs[__translation[key]] = kwargs[key]
            del kwargs[key]
    return args, kwargs
class WorkersManager(models.Manager):
    def filter(self, *args, **kwargs):
        args, kwargs = _translate(args, kwargs)
        return super(WorkersManager, self).filter(*args, **kwargs)
    # etc...

相关内容

  • 没有找到相关文章

最新更新