Cela a commencé dans le groupe de discussion Google :
https://groups.google.com/forum/#!topic/django-filter/RwNfoWsdeLQ
Je souhaite pouvoir filtrer contrib.postgres JSONFields avec django-filter.
J'ai un filtre qui fonctionne pour quelques exemples. C'est plus compliquĂ© que je ne le pensais dans la mesure oĂč vous ne connaissez pas vraiment le type de donnĂ©es dans votre JSON Ă l'avance comme vous le faites avec quelque chose comme un IntegerField. Je suis peut-ĂȘtre en train de compliquer les choses.
Voici un exemple de filtre qui frappe mon JSONField
http://127.0.0.1 :8000/api/v1/craters?data= latitude:float :-57:lte~!@!~ age:str :PC
Voici les modÚles et le code du filtre :
https://gist.github.com/jzmiller1/627071f555186cd1a58bb8f065205ff7
Je vais continuer à bosser dessus. Si quelqu'un a des idées ou des commentaires s'il vous plaßt faites le moi savoir...
Bonjour @jzmiller1. Qu'essayez-vous exactement d'atteindre ? Je ne peux pas dire si vous ĂȘtes :
data
. Ces attributs seraient essentiellement le schéma de votre data
.Le premier est intéressant, mais comme vous l'avez découvert, la complication réside dans le fait que le JSONField est intrinsÚquement sans schéma. Sans schéma, vous ne pouvez pas écrire de code pour générer automatiquement des filtres. Votre MethodFilter fonctionne en ce sens qu'il autorise toute recherche d'attribut arbitraire, mais vous ne pouvez pas valider ces recherches. par exemple, ?data=latitude:char:PC:isnull
est possible, mais absurde. Quelle que soit la solution ici, il faudra un compromis. Un filtre complÚtement arbitraire ne pourra pas valider les recherches, un filtre de validation nécessiterait un moyen de fournir un schéma.
Pour le deuxiĂšme cas, les solutions sont verbeuses / fastidieuses, mais simples.
class CratersFilter(filters.FilterSet):
latitude = filters.NumberFilter(name='data__latitude', lookup_expr='exact')
latitude__lt = filters.NumberFilter(name='data__latitude', lookup_expr='lt')
latitude__gt = filters.NumberFilter(name='data__latitude', lookup_expr='gt')
latitude__isnull = filters.BooleanFilter(name='data__latitude', lookup_expr='isnull')
# not sure if 'isnull' is a valid lookup for JSONFields - just demonstrating that
# different lookups expect different value types.
age = filters.CharFilter(name='data__age', lookup_expr='exact')
...
Votre requĂȘte ressemblerait alors Ă Â :
http://127.0.0.1:8000/api/v1/craters?latitude__lte=-57&age=PC
Mon objectif est de crĂ©er un JSONFilter gĂ©nĂ©rique qui permettra des requĂȘtes sur n'importe quel attribut arbitraire Ă l'intĂ©rieur d'un JSONField. Pour ce sur quoi je travaille, je ne saurai pas vraiment ce qu'il y a dans les donnĂ©es d'un cratĂšre particulier, mais s'il y a une clĂ© lĂ -dedans que je recherche, j'aimerais pouvoir l'interroger.
En ce qui concerne l'incapacitĂ© de valider les types de recherche, je pense que je dĂ©pendrais de l'utilisateur qui effectue la requĂȘte pour se rendre compte que la requĂȘte est absurde et ne la fait tout simplement pas pour commencer.
Je ne sais pas si ce que j'essaie de faire est une perte de temps ou non. Il existe peut-ĂȘtre une meilleure solution pour ce que j'essaie d'accomplir. J'Ă©tais curieux de savoir si quelqu'un voyait des problĂšmes majeurs qui empĂȘcheraient cela d'ĂȘtre possible ou si quelqu'un avait un cas d'utilisation oĂč cela serait utile. Merci d'y avoir jetĂ© un Ćil !
Ma premiÚre réflexion concerne la validation, selon le point de @rpkilby . L'absence de schéma est agréable du point de vue du développeur, mais je ne suis pas sûr que vous souhaitiez qu'il soit directement connecté aux URL adressables.
Laissons cela ouvert pour l'instant. Je vois que c'est une demande populaire. (Donc, mĂȘme l'aborder au niveau de _"Voici un exemple MethodFilter
"_ dans la documentation en vaudrait la peine.)
Pour ce sur quoi je travaille, je ne saurai pas vraiment ce qu'il y a dans les données d'un cratÚre particulier, mais s'il y a une clé là -dedans que je recherche, j'aimerais pouvoir l'interroger.
Ăa a l'air... un peu funky. Vous fournissez une API pour les donnĂ©es sur les cratĂšres, mais vous ne savez pas ce qu'il y a dans les donnĂ©es que vous fournissez ? Voulez-vous dire que certains enregistrements seront dĂ©pourvus des attributs d'un schĂ©ma commun, ou que les enregistrements individuels sont complĂštement arbitraires ?
Je vais fermer ceci comme hors de portée pour le moment. Heureux d'examiner les demandes d'extraction documentées et testées. Nous pourrions avoir la capacité de reconsidérer à l'avenir.
Je pense que ce serait vraiment incroyable d'avoir un JSONFilter permettant des requĂȘtes telles que jsonfield__a_random_key=value
. Je sais que vous pouvez le faire avec la méthode objects.filter
. Peut-ĂȘtre que le compromis pourrait ĂȘtre la validation du filtre ?
Je viens de terminer une implĂ©mentation d'une requĂȘte "naturelle" pour le filtre QuerySet Ă l'aide de l'objet Q. Il a Ă©tĂ© testĂ© unitairement par rapport Ă un ensemble de requĂȘtes avec ~ 1000 enregistrements Ă l'aide d'un JsonField. La mise en Ćuvre est Ă Â :
https://github.com/shallquist/DJangoQuerySetFilter/blob/master/queryparser.py
HĂ© @shallquist, je ne sais pas comment utiliser QuerySetFilter
dans le contexte de django-filter. Avez-vous documenté l'utilisation quelque part?
C'est assez simple Ă utiliser comme le montre le readme sur github. Les requĂȘtes normales doivent ĂȘtre prises en charge, c'est-Ă -dire.
QuerySetFilter('friends').get_Query((person__address__city = Denver | person__address__city = Boulder) & person__address_state ~= CO)
qui construira une requĂȘte pour rĂ©cupĂ©rer tous les amis qui vivent Ă Dever ou Boulder colorado, oĂč friends est un jsonfield.
BTW Cela n'a pas été beaucoup testé et comme les filtres Django ne prennent pas en charge l'interrogation des objets de tableau intégrés, j'ai abandonné cette approche.
https://github.com/carltongibson/django-filter/issues/426#issuecomment -380224133
Je pense que ce serait vraiment incroyable d'avoir un JSONFilter permettant des requĂȘtes telles que jsonfield__a_random_key=value. Je sais que vous pouvez le faire avec la mĂ©thode objects.filter. Peut-ĂȘtre que le compromis pourrait ĂȘtre la validation du filtre ?
HĂ© @carltongibson , @rpkilby , j'aimerais avoir votre avis Ă ce sujet. Disons que my_field
est un postgres JSONField
, et je veux :
my_field__etc=value
oĂč etc
est l'une des requĂȘtes prises en charge par JSONField
et value
tout ce que l'utilisateur REST fournit.etc
et value
au gestionnaire d'objets modĂšles sous la forme de MyModel.objects.filter(my_field__etc=value)
.Cela semble super trivial mais je n'ai pas compris comment faire quelque chose comme ça. Si vous me donnez un petit indice, je pourrais essayer de l'implémenter.
Toutes les pensées seraient super appréciées!
Est-ce que quelque chose comme ce qui suit ne fonctionne pas ?
class MyFilter(FilterSet):
my_field__etc = filters.NumberFilter(field_name='my_field', lookup_expr='etc')
En général, le field_name
doit correspondre au nom du champ de modĂšle sous-jacent, tandis que les transformations et les recherches (une transformation clĂ© dans ce cas) doivent ĂȘtre contenues dans le lookup_expr
.
@rpkilby merci beaucoup pour une réponse aussi rapide - Ouais exactement, mais je veux que etc
fourni par l'utilisateur dans la requĂȘte... Donc je ne pouvais pas vraiment le coder en dur dans le filtre đ
Le filtre devrait plutĂŽt ressembler Ă Â :
class MyFilter(FilterSet):
my_field = JSONFieldFilter(field_name='my_field')
Ainsi, un seul filtre JSON pour gĂ©rer des paramĂštres de requĂȘte arbitraires comme ?my_field__etc=value
.
Je vois deux problĂšmes. PremiĂšrement, une partie de la valeur de django-filter est qu'il valide les paramĂštres de la requĂȘte. Ătant donnĂ© que JSONField
n'ont pas de schĂ©ma, il n'est pas possible de gĂ©nĂ©rer des filtres qui valident de maniĂšre appropriĂ©e les donnĂ©es entrantes. par exemple, si votre champ JSON a une clĂ© "count", il ne serait pas possible de deviner que seuls les nombres positifs sont valides. Le mieux que l'on puisse faire est de garantir que la valeur est valide JSON. Ainsi, les requĂȘtes seraient au moins valides, mais peut-ĂȘtre absurdes (par exemple, data__count__gt='cat'
).
La seconde est que ce filtre va avoir les mĂȘmes limitations que les filtres basĂ©s sur MultiWidget
. par exemple, il ne générera pas d'erreurs de validation pour les noms de paramÚtres corrects. Mais avant de plonger là -dedans, voici comment j'implémenterais probablement le filtre. Nous avons besoin:
my_field__*
.class JSONWidget(widgets.Textarea):
"""A widget that handles multiple parameters prefixed with the field name."""
def value_from_datadict(self, data, files, name):
prefix = f'{name}{LOOKUP_SEP}'
# this is doing two things:
# - matches multiple params for the base field name
# - in addition to returning the value, we also need the full parameter name
# for querying. otherwise, values will be filtered against the base `name`.
return {k: v for k, v in data.items() if k.startswith(prefix)}
def get_context(self, name, value, attrs):
# to support rendering the widget, you would need to generate subwidgets
# similar to MultiWidget.get_context.
pass
class JSONField(postgres.forms.JSONField):
widget = JSONWidget
def clean(self, value):
# note that it's not possible to collect/reraise any validation errors under
# their actual parameter names. `form.add_error` should be used here, however
# the field class does not have access to the form instance. raising
# ValidationError({k: str(original_exc)}) also does not work.
# clean/convert each value
return {k: super().clean(v) for k, v in value.items()}
class JSONFilter(filters.Filter):
field_class = JSONField
def filter(self, qs, value):
if value in EMPTY_VALUES:
return qs
return qs.filter(**value)
Je n'ai pas testĂ© ce qui prĂ©cĂšde, mais cela devrait ĂȘtre Ă peu prĂšs correct. Cependant, il existe des limites :
ValidationError
sMultiWidget
. Ce filtre/widget rencontrerait les mĂȘmes problĂšmes pour les mĂȘmes raisons.@rpkilby merci beaucoup pour cette rĂ©ponse complĂšte.
si votre champ JSON a une clé "count", il ne serait pas possible de deviner que seuls les nombres positifs sont valides.
C'est un excellent point, le fait que nous ne puissions pas valider le type de valeur de la requĂȘte le rend trĂšs difficile car MyModel.objects.filter(data__count="1")
ne renverra pas la mĂȘme chose que MyModel.objects.filter(data__count=1)
. Comme vous le dites, il n'y a aucun moyen de deviner le type de la valeur Ă partir des paramĂštres de la requĂȘte.
Ne laissant donc que l'option d'intĂ©grer les informations de type dans la valeur de la requĂȘte, en faisant quelque chose comme ?data__count=1:int
pour rechercher un entier et ?data__count=1:str
pour les chaßnes, etc. Mais comme il est suggéré ici , ce n'est pas recommandé.
Je comprends maintenant pourquoi il est si précieux de définir explicitement les filtres. Néanmoins, je vais essayer votre suggestion! Merci encore
@rpkilby , j'ai un besoin similaire.
J'ai une table de configuration comme celle-ci avec deux colonnes
meta_structure of type jsonb (This column has info like key1 of type string, key2 of type integer)
J'ai une autre table nommée config_data qui aura 3 colonnes.
config_id -> Foreign key to config table
meta_info -> jsonb type
Remarque : Les tableaux mentionnés ci-dessus ne sont pas les tableaux exacts. Ce ne sont que des versions représentatives pour transmettre le message.
Je valide actuellement les champs de la table meta_info avant de les enregistrer en vérifiant qu'ils correspondent à la table de configuration.
Le besoin est que je veux filtrer en utilisant la colonne meta_info de la table config_data. Par exemple. meta_info__key1='abc'. (key1 peut ĂȘtre n'importe quoi)
J'essayais d'utiliser l'approche que vous aviez donnée ci-dessus, mais le problÚme est de savoir comment utiliser la classe JSONFilter que vous avez créée ci-dessus.
Par exemple.
class ConfigDataFilterSet(django_filters.FilterSet):
meta_info = JSONFilter(field_name='meta_info')
pp = ConfigDataFilterSet(data={'meta_info__key1': 'abc'})
Maintenant, si j'exécute pp.qs
ou pp.filter_queryset()
, il n'appliquera pas réellement le filtre sur le champ meta_info car le nom de champ attribué dans la classe ConfigDataFilterSet est meta_info. Pouvez-vous m'aider à surmonter cet obstacle?
Commentaire le plus utile
Je pense que ce serait vraiment incroyable d'avoir un JSONFilter permettant des requĂȘtes telles que
jsonfield__a_random_key=value
. Je sais que vous pouvez le faire avec la méthodeobjects.filter
. Peut-ĂȘtre que le compromis pourrait ĂȘtre la validation du filtre ?