首先,由于这是我的第一个问题/帖子,我想感谢所有为这个伟大的社区,和惊人的服务,作为-像世界各地的许多开发人员- stackoverflow -是我的主要资源,当涉及到代码问题。
注意:
这篇文章有点长(抱歉),涵盖了两个不同但相关的方面,并按以下方式组织:
- Background/Context
- 一个设计问题,我想听听你的建议。
- 解决方案,我试图实现解决2.
- 与3. 相关的实际问题和疑问
我有两个不同的Model
对象(Report
和Jobs
)从现有的实现,有一个糟糕的设计。事实上,这两个对象的目的非常相似,但可能是在两个不同的时间框架内实现的。
许多处理发生在这些对象上,由于系统必须发展,我开始编写元类/接口,两者都将成为子类。目前Models
都使用不同的Fields
名称来表示相同的目的,比如author
和juser
来表示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
这很好,这是我想要的,除了现在使用Report.objects.filter(name='something')
或Jobs.objects.filter(jname='something')
不会工作,显然是由于Django的设计(等与.get()
, .exclude()
等…),和客户端代码是可悲的满的那些。
我当然计划用我新创建的WorkersManager
Aparte:
等待……怎么啦?"新建WorkersManager
" ??
是的,经过两年和数千行代码,这里没有Manager
,疯了吧?
但是你猜怎么着?这是我最不关心的;振作起来,因为大部分代码仍然位于view.py和相关文件中(而不是在它应该操作的对象中),基本上有点"纯粹"命数式python…
经过大量阅读(这里和那里)和研究,我发现:
- 试图子类化
Field
,不是一个解决方案 - 我可以重载
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)
这很好。
问题是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
,但我想这是好的因为它是(据我理解)一个占位符重载,它也是唯一的方法。
所以我在这里,我的问题是:
没有别的办法了吗?
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
等,直接从我的自定义管理器…
这节省了我处理QuerySet
和Q
的精力。
最后,下面的代码按预期工作:
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...