通过QuerySet

时间:2018-02-14 05:31:38

标签: django django-queryset

前言
让我们假设我们正在研究存储杂志问题的数据库 这些问题本身通常没有“名称”;相反,一大堆属性(发布年份,序列号,发布月份等)将有助于用户稍后可以识别问题的名称。 根据每期可用的属性,此名称将根据模式计算 例如:来自2017年号码01的问题将获得名称:2017-01。多年20002001以及月JanFeb中的问题将获得名称2000/01-Jan/Feb。 可以随时更改属性。

预计用户也可以根据此名称进行查询 - 因此仅显示计算值(通过__str__)是不够的。

到目前为止我做了什么
很长一段时间,我实际上计算了在问题实例上调用的每个时间__str__的名称​​。这是快速而肮脏(和缓慢)的方式。 查询名称非常缓慢而且非常复杂且不可靠,因为它需要“逆向工程”__str__方法并猜测用户试图搜索的内容。

然后我尝试了一种混合方法,使用_name模型字段,如果设置_changed_flag(f.ex。通过信号)并实例化或保存实例,则更新该模型字段。除非我实例化每个需要先更新的实例,否则这仍然没有让我在数据库表上找到最新的名称。再次,慢。在调用refresh_from_db(在后台重新创建当前实例)时,必须注意不要以无限递归结束。

TL:DR
现在,我使用自定义QuerySet作为具有计算字段的模型的管理器:

class ComputedNameModel(BaseModel):   

    _name = models.CharField(max_length=200, editable=False, default=gettext_lazy("No data."))
    _changed_flag = models.BooleanField(editable=False, default=False)

    name_composing_fields = []

    objects = CNQuerySet.as_manager()

    # ... some more methods ...

    def __str__(self):
        return self._name

    class Meta(BaseModel.Meta):
        abstract = True

查询集:

class CNQuerySet(MIZQuerySet):

    def bulk_create(self, objs, batch_size=None):
        # Set the _changed_flag on the objects to be created
        for obj in objs:
            obj._changed_flag = True
        return super().bulk_create(objs, batch_size)

    def filter(self, *args, **kwargs):
        if any(k.startswith('_name')  for k in kwargs):
            self._update_names()
        return super().filter(*args, **kwargs)

    def update(self, **kwargs):
        # it is save to assume that a name update will be required after this update
        # if _changed_flag is not already part of the update, add it with the value True
        if '_changed_flag' not in kwargs:
           kwargs['_changed_flag'] = True
        return super().update(**kwargs)
    update.alters_data = True

    def values(self, *fields, **expressions):
        if '_name' in fields:
            self._update_names()
        return super().values(*fields, **expressions)

    def values_list(self, *fields, **kwargs):
        if '_name' in fields:
            self._update_names()
        return super().values_list(*fields, **kwargs)

    def _update_names(self):
        if self.filter(_changed_flag=True).exists():    
            with transaction.atomic():
                for pk, val_dict in self.filter(_changed_flag=True).values_dict(*self.model.name_composing_fields).items():
                    new_name = self.model._get_name(**val_dict)
                    self.filter(pk=pk).update(_name=new_name, _changed_flag=False)
    _update_names.alters_data = True

正如你所看到的,样板是真实的。我只有樱桃选择了我现在使用的QuerySet方法 通过信号(用于关系)或QuerySet方法,当有关它的任何内容发生变化时,会设置记录_changed_flag。然后在下次以任何方式请求_name字段时更新记录 它速度非常快,因为它不需要模型实例(只有模型的classmethod _get_name()),并且完全依赖于查询集和内存数据。

问题
在何处调用_update_names(),以便在需要时更新名称而不覆盖每个查询集方法?

我试过把它放进去:

  • _clone:发生了不好的事情。为了不以递归地狱结束,您必须跟踪哪个克隆正在尝试更新,以及哪些克隆只是为更新获取数据。克隆也是在初始化你的应用程序时创建的,它有一个好的(表格总是最新的)和坏的一面(更新而不需要它,花费时间)。并非所有查询集方法都会创建克隆,通常情况下,将更新检查置于_clone感觉太深
  • __repr__:保持shell输出是最新的,但不多。默认实现需要查询集的一部分,禁用筛选功能,因此必须在__repr__之前完成更新。
  • _fetch_all:与_clone一样:当您可能不需要时,它会运行更新,并且需要保留内部“您可以尝试更新”的内容。

0 个答案:

没有答案