Comment écrire correctement le code du filtre pour qu'il ne renvoie que les animaux qui ne sont pas épuisés.
J'utilise POSTGRES db, python3.6 et Django 2.1.7 (actuellement il y a v2.2a1, v2 .2b1 pré-versions)
Ma quête est une extension du Filtrage Django JSONField qui filtre sur une valeur codée en dur dans le filtre.
My Case nécessite une valeur annotée dans le filtre.
models.py Je sais que les modèles peuvent être optimisés, mais j'ai déjà énormément de records depuis plus de 3 ans
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
dans mon api je souhaite renvoyer uniquement les animaux qui ont encore quelque chose à vendre
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
Ce que je veux filtrer doit être similaire à 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')}])
avec le code ci-dessus, j'obtiens builtins.TypeError
TypeError: L'objet de type 'F' n'est pas sérialisable JSON
si je supprime le F il ne filtrera pas sur la valeur de animals_sold, mais sur le envoyez "animals_sold" et cela n'aide pas.
Animal.objects.annotate(animals_sold=Sum('sales_set__count'))
.filter(data__contains=[{'count__gt': F('animals_sold')}])
Édition 1: Il y a un autre sujet ici qui peut être lié: Postgres: requête de valeurs sur la clé json avec django p >
Modifier 2: voici un code supplémentaire avec des classes de transformation personnalisées comme suggéré dans le ticket django associé
animal = Animal(data={'type':'dog', 'bread':'Husky', 'count':20})
filtrage des ensembles de requêtes que j'ai essayé jusqu'à présent:
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"
)
3 Réponses :
La classe F ne prend pas en charge un JSONField pour le moment, mais vous pouvez essayer de créer votre propre expression personnalisée comme décrit dans le ticket associé .
Est-ce que je vous comprends bien? Voulez-vous dire que je ne peux utiliser que la requête SQL pure?
Non, vous pouvez également créer votre propre expression personnalisée comme expliqué dans le commentaire d'alst dans le code du ticket . djangoproject.com/ticket/29769#comment:5
Je ne connais pas assez bien le fonctionnement de cette partie de django, donc je n'arriverai pas à créer ma propre fonction JSONF. J'envisage de refactoriser les modèles et de transférer les données du champ de données vers les champs de modèle. btw, quelle est la valeur du const LOOKUP_SEP ? dans le commentaire là-bas?
githubjango.com/django/blango/ …
J'ai ajouté plus de code dans ma question. il semble qu'un petit changement dans le JSONF devrait faire l'affaire, pouvez-vous s'il vous plaît vérifier ce qui ne va pas?
Que diriez-vous de quelque chose comme ceci:
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'))
J'ai déjà essayé et je viens de le réessayer, mais j'obtiens ProgrammingError : l'opérateur n'existe pas: jsonb> bigint LIGNE 1: ... unt ') HAVING ("my_secret_app_animal". "data" -> 'count')> (SUM ("fi ...
C'est presque la même chose si le data.count est float. pouvez-vous suggérer un Cast fonctionnel?
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
)
Oui, j'ai aussi brièvement joué avec et j'ai trouvé à peu près la même chose, ce n'est pas une mauvaise solution dont vous avez toujours besoin pour convertir les valeurs du JSON. Vous n'avez probablement pas besoin d'annoter le json si vous utilisez KeyTextTransform ('count', 'data') .
J'obtiens unhashable type: 'list' si j'utilise KeyTextTransform ('count', 'data') au lieu de 'json' comme dans sold_count_sum__lt = Cast (KeyTextTransform ('count', KeyTextTransform ( 'count', 'data')), IntegerField ()) , mais en utilisant Cast (KeyTextTransform ('count', Cast (F ('data'), JSONField ())), IntegerField () ) fonctionne bien
Je ne sais pas si cela peut être utile pour le billet que vous avez mentionné ici.