Django JSONField过滤查询集,其中过滤器值为带注释的和值

时间:2019-02-19 12:47:36

标签: python django postgresql

如何正确编写过滤器代码,使其仅返回未售罄的动物。

我正在使用POSTGRES db,python3.6和Django 2.1.7(当前有v2.2a1,v2.2b1预发行版本)

我的问题是Django JSONField filtering的扩展 会根据过滤器中的硬编码值进行过滤。

“我的案件”要求过滤器中有带注释的值。

models.py我知道可以对模型进行优化,但是自3年多以来,我已经拥有大量记录。

from django.db import models
from django.contrib.postgres.fields import JSONField

class Animal(models.Model):
    data = models.JSONField(verbose_name=_('data'), blank=True)

class Sell(models.Model):
    count = models.IntegerField(verbose_name=_('data'), blank=True)
    animal = models.ForeignKey('Animal', 
                               on_delete=models.CASCADE,
                               related_name="sales_set",
                               related_query_name="sold"
   )

在我的api中,我只想退还仍有待出售物品的动物

animal = Animal(data={'type':'dog', 'bread':'Husky', 'count':20})

我要过滤的内容应类似于animal.data ['count']> sum(animal.sales_set__count

Animal.objects.annotate(animals_sold=Sum('sales_set__count'))
.filter(data__contains=[{'count__gt': F('animals_sold')}])

上面的代码我得到builtins.TypeError TypeError: Object of type 'F' is not JSON serializable

如果我删除了F,它将不会根据animals_sold的值进行过滤,但是会在文本“ animals_sold”上进行过滤,并且没有任何帮助。

Animal.objects.annotate(animals_sold=Sum('sales_set__count'))
.filter(data__contains=[{'count__gt': F('animals_sold')}])

编辑1: 这里还有一个可以链接的主题: Postgres: values query on json key with django

编辑2: 这是related django ticket

中建议的一些带有自定义转换类的其他代码
from django.db.models.constants import LOOKUP_SEP
from django.db.models import F, Q, Prefetch, Sum
from django.db.models import IntegerField, FloatField, ExpressionWrapper
from django.db.models.functions import Cast
from django.contrib.postgres.fields import JSONField
from django.contrib.postgres.fields.jsonb import KeyTransform, KeyTextTransform

class KeyIntegerTransform(KeyTransform):  # similar to KeyTextTransform
    """ trasnform the data.count to integer """
    operator = '->>'
    nested_operator = '#>>'
    output_field = IntegerField()

class KeyIntTransformFactory:
    """ helper class for the JSONF() """

    def __init__(self, key_name):
        self.key_name = key_name

    def __call__(self, *args, **kwargs):
        return KeyIntegerTransform(self.key_name, *args, **kwargs)


class JSONF(F):
    """ for filtering on JSON Fields """

    def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False):
        rhs = super().resolve_expression(query, allow_joins, reuse, summarize, for_save)
        field_list = self.name.split(LOOKUP_SEP)
        for name in field_list[1:]:
            rhs = KeyIntegerTransform(name)(rhs)
        return rhs


到目前为止,我尝试过的

queryset过滤:

q = q.filter(data__contains={'count__gt':JSONF('sold_count_sum')})
# err: Object of type 'JSONF' is not JSON serializable

q = q.filter(sold_count_sum__lt=Cast(JSONF('data_count'), IntegerField()))
# err: operator does not exist: text ->> unknown

q = q.filter(sold_count_sum__lt=Cast(JSONF('data__count'), IntegerField()))
# err: 'KeyIntegerTransform' takes exactly 1 argument (0 given)

q = q.filter(sold_count_sum__lt=KeyIntegerTransform('count', 'data'))
# err: operator does not exist: text ->> unknown

q = q.filter(sold_count_sum__lt=F('data__count'))
# err: operator does not exist: text ->> unknown

q = q.filter(sold_count_sum__lt=F('data_count'))
# err: operator does not exist: text ->> unknown

q = q.filter(sold_count_sum__lt=JSONF('data_count'))
# err: operator does not exist: text ->> unknown

q = q.filter(sold_count_sum__lt=JSONF('data__count'))
# err: 'KeyIntegerTransform' takes exactly 1 argument (0 given)

q = q.filter(sold_count_sum__lt=JSONF('data', 'count'))
# err: JSONF.__init__() takes 2 params


3 个答案:

答案 0 :(得分:3)

        queryset = Animal.objects.annotate(
            json=Cast(F('data'), JSONField()),
            sold_count_sum = Sum('sold__count'),
            sold_times = Count('sold'),
        ).filter(
            Q(sold_times=0) | Q(sold_count_sum__lt=Cast(
                 KeyTextTransform('count', 'json'), IntegerField())
            ),
            # keyword filtering here ...
            # client = client
        )

这对我来说很有效,但是可以使用一个不错的JSONF字段对其进行优化

我们还可以(重新)移动json批注并使用data的强制转换版本(可能会改善性能)

        queryset = Animal.objects.annotate(
            sold_count_sum = Sum('sold__count'),
            sold_times = Count('sold'),
        ).filter(
            Q(sold_times=0) | Q(sold_count_sum__lt=Cast(
                 KeyTextTransform('count', Cast(
                     F('data'), JSONField())), IntegerField()
                 )
            ),
            # keyword filtering here ...
            # client = client
        )

答案 1 :(得分:1)

F类目前不支持JSONField,但是您可以尝试按照https://hub.docker.com中的说明制作自己的自定义表达式。

答案 2 :(得分:1)

怎么样呢?

from django.db.models import Sum, F
from django.contrib.postgres.fields.jsonb import KeyTransform

Animal.objects.annotate(animals_sold=Sum('sales_set__count'), data_count=KeyTransform('count', 'data')).filter(data_count__gt=F('animals_sold'))