μ΄κ²μ Google ν λ‘ κ·Έλ£Ήμμ μμλμμ΅λλ€.
https://groups.google.com/forum/#!topic/django -filter/RwNfoWsdeLQ
django-filterλ₯Ό μ¬μ©νμ¬ contrib.postgres JSONFieldsλ₯Ό νν°λ§νλ λ° κ΄μ¬μ΄ μμ΅λλ€.
λͺ κ°μ§ μμμ μλνλ νν°κ° μμ΅λλ€. μ΄κ²μ IntegerFieldμ κ°μ μμ μ μννλ λ°©μμΌλ‘ JSONμ λ°μ΄ν° μ νμ 미리 μμ§ λͺ»νλ€λ μ μμ μκ°λ³΄λ€ 볡μ‘ν©λλ€. λ무 볡μ‘νκ² λ§λ€ μλ μμ΅λλ€.
λ€μμ λ΄ JSONFieldλ₯Ό μ‘°ννλ νν°μ μμ
λλ€.
http://127.0.0.1 :8000/api/v1/craters?data= latitude:float :-57:lte~!@!~ age:str :PC
λͺ¨λΈ λ° νν° μ½λλ λ€μκ³Ό κ°μ΅λλ€.
https://gist.github.com/jzmiller1/627071f555186cd1a58bb8f065205ff7
λλ κ·Έκ²μ κ³μν΄μ μ΄μ§λ½κ² ν κ²μ λλ€. μκ°μ΄λ νΌλλ°±μ΄ μλ μ¬λμ΄ μμΌλ©΄ μλ €μ£ΌμΈμ...
μλ νμΈμ @jzmiller1μ λλ€. μ νν 무μμ λ¬μ±νλ €κ³ ν©λκΉ? λ€μκ³Ό κ°μ κ²½μ° μ μ μμ΅λλ€.
data
λΆνꡬμ 곡ν΅μ μΈ νΉμ μμ±(μλ, μ°λ Ή)μ λ
ΈμΆνλ €κ³ ν©λλ€. μ΄λ¬ν μμ±μ λ³Έμ§μ μΌλ‘ data
μ μ€ν€λ§μ
λλ€.μ μλ ν₯λ―Έλ‘μ§ λ§ JSONFieldκ° λ³Έμ§μ μΌλ‘ μ€ν€λ§κ° μλ€λ 볡μ‘μ±μ΄ μλ€λ κ²μ μμμ΅λλ€. μ€ν€λ§κ° μμΌλ©΄ νν°λ₯Ό μλμΌλ‘ μμ±νλ μ½λλ₯Ό μμ±ν μ μμ΅λλ€. MethodFilterλ μμμ μμ± μ‘°νλ₯Ό νμ©νμ§λ§ μ΄λ¬ν μ‘°νμ μ ν¨μ± μ κ²μ¬ν μ μλ€λ μ μμ μλν©λλ€. μλ₯Ό λ€μ΄, ?data=latitude:char:PC:isnull
λ κ°λ₯νμ§λ§ μλ―Έκ° μμ΅λλ€. μ¬κΈ°μ μλ μ루μ
μ΄ λ¬΄μμ΄λ νΈλ μ΄λμ€νκ° νμν©λλ€. μμ ν μμμ νν°λ μ‘°νμ μ ν¨μ±μ κ²μ¬ν μ μμΌλ©° μ ν¨μ± κ²μ¬ νν°μλ μ€ν€λ§λ₯Ό μ 곡νλ λͺ κ°μ§ λ°©λ²μ΄ νμν©λλ€.
λ λ²μ§Έ κ²½μ° μ루μ μ μ₯ν©νκ³ μ§λ£¨νμ§λ§ κ°λ¨ν©λλ€.
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')
...
κ·Έλ¬λ©΄ 쿼리λ λ€μκ³Ό κ°μ΅λλ€.
http://127.0.0.1:8000/api/v1/craters?latitude__lte=-57&age=PC
λ΄ λͺ©νλ JSONField λ΄μ μμμ μμ±μ λν 쿼리λ₯Ό νμ©νλ μΌλ° JSONFilterλ₯Ό λ§λλ κ²μ λλ€. λ΄κ° μμ νκ³ μλ κ²μ λν΄μλ νΉμ λΆνꡬμ λν λ°μ΄ν° λ΄λΆμ 무μμ΄ μλμ§ μμ§ λͺ»νμ§λ§ κ±°κΈ°μ λ΄κ° μ°Ύκ³ μλ ν€κ° μλ€λ©΄ 쿼리ν μ μκΈ°λ₯Ό λ°λλλ€.
μ‘°ν μ νμ μ ν¨μ±μ κ²μ¬ν μ μλ ν λλ 쿼리λ₯Ό λ§λλ μ¬μ©μμ λ°λΌ μΏΌλ¦¬κ° λ¬΄μλ―Ένκ³ μ²μλΆν° μμνμ§ μλλ€λ κ²μ κΉ¨λ«κ² λ κ²μ΄λΌκ³ μκ°ν©λλ€.
λ΄κ° νλ €λ μΌμ΄ μκ° λλΉμΈμ§ μλμ§ μ λͺ¨λ₯΄κ² μ΅λλ€. λ΄κ° λ¬μ±νλ €λ κ²μ λν λ λμ μ루μ μ΄μμ μ μμ΅λλ€. μ΄κ²μ΄ κ°λ₯νμ§ λͺ»νκ² νλ μ£Όμ λ¬Έμ λ₯Ό λ³Έ μ¬λμ΄ μλμ§ λλ μ΄κ²μ΄ μ μ©ν μ¬μ© μ¬λ‘κ° μλ μ¬λμ΄ μλμ§ κΆκΈνμ΅λλ€. λ΄μ£Όμ μ κ°μ¬ν©λλ€!
λ΄ μ²« λ²μ§Έ μκ°μ @rpkilby μ μμ μ λ°λΌ μ ν¨μ± κ²μ¬μ λν κ²μ λλ€. Schema-lessλ κ°λ°μμ κ΄μ μμ λ³Ό λ νλ₯νμ§λ§ μ£Όμ μ§μ κ°λ₯ν URLμ μ§μ μ°κ²°λκΈ°λ₯Ό μνλμ§ μ λͺ¨λ₯΄κ² μ΅λλ€.
μ§κΈμ μ΄κ²μ μ΄μ΄ λλλ‘ ν©μλ€. μΈκΈ°μλ μμ²μμ μ μ μμ΅λλ€. (λ°λΌμ _"μ¬κΈ°μ MethodFilter
μμ κ° μμ΅λλ€. "_ μμ€μμ λ€λ£¨λλΌλ κ°μΉκ° μμ κ²μ
λλ€.)
λ΄κ° μμ νκ³ μλ κ²μ λν΄μλ νΉμ λΆνꡬμ λν λ°μ΄ν° λ΄λΆμ 무μμ΄ μλμ§ μμ§ λͺ»νμ§λ§ κ±°κΈ°μ λ΄κ° μ°Ύκ³ μλ ν€κ° μλ€λ©΄ 쿼리ν μ μκΈ°λ₯Ό λ°λλλ€.
μ΄κ²μ... μΌμ’ μ νν€ν κ² κ°μ΅λλ€. λΆνꡬ λ°μ΄ν°μ© APIλ₯Ό μ 곡νκ³ μμ§λ§ μ 곡νλ λ°μ΄ν°μ 무μμ΄ λ€μ΄ μλμ§ λͺ¨λ₯΄μλκΉ? μΌλΆ λ μ½λμ κ³΅ν΅ μ€ν€λ§μ μμ±μ΄ λλ½λκ±°λ κ°λ³ λ μ½λκ° μμ ν μμμ μ΄λΌλ κ²μ μλ―Έν©λκΉ?
λΉλΆκ°μ λ²μλ₯Ό λ²μ΄λ¨μΌλ‘ μ’ λ£νκ² μ΅λλ€. λ¬Έμνλκ³ ν μ€νΈλ pull μμ²μ κ³ λ €νκ² λμ΄ κΈ°μ©λλ€. μ°λ¦¬λ λ―Έλμ μ¬κ³ ν μ μλ λ₯λ ₯μ κ°μ§ μ μμ΅λλ€.
jsonfield__a_random_key=value
μ κ°μ 쿼리λ₯Ό κ°λ₯νκ² νλ JSONFilterκ° μλ€λ κ²μ μ λ§ λλΌμ΄ μΌμ΄λΌκ³ μκ°ν©λλ€. objects.filter
λ°©λ²μΌλ‘ ν μ μλ€λ κ²μ μκ³ μμ΅λλ€. μλ§λ νΈλ μ΄λ μ€νκ° νν° μ ν¨μ± κ²μ¬κ° λ μ μμ΅λκΉ?
Q κ°μ²΄λ₯Ό μ¬μ©νμ¬ QuerySet νν°μ λν 'μμ°' 쿼리 ꡬνμ μλ£νμ΅λλ€. JsonFieldλ₯Ό μ¬μ©νμ¬ ~1000κ°μ λ μ½λκ° μλ 쿼리 μΈνΈμ λν΄ λ¨μ ν
μ€νΈλ₯Ό κ±°μ³€μ΅λλ€. ꡬν μμΉ:
https://github.com/shallquist/DJangoQuerySetFilter/blob/master/queryparser.py
@shallquist django-filter 컨ν
μ€νΈμμ QuerySetFilter
λ₯Ό μ¬μ©νλ λ°©λ²μ μ λͺ¨λ₯΄κ² μ΅λλ€. μ΄λκ°μ μ¬μ©λ²μ λ¬Έμν νμ΅λκΉ?
githubμ readmeμ λμ μλ κ²μ²λΌ μ¬μ©νκΈ°κ° λ§€μ° κ°λ¨ν©λλ€. μΌλ° μΏΌλ¦¬κ° μ§μλμ΄μΌ ν©λλ€.
QuerySetFilter('friends').get_Query((person__address__city = Denver | person__address__city = Boulder) & person__address_state ~= CO)
Dever λλ Boulder μ½λ‘λΌλμ μ¬λ λͺ¨λ μΉκ΅¬λ₯Ό κ²μνλ 쿼리λ₯Ό μμ±ν©λλ€. μ¬κΈ°μ μΉκ΅¬λ jsonfieldμ
λλ€.
BTW μ΄κ²μ λ§μ΄ ν μ€νΈλμ§ μμμΌλ©° Django νν°λ ν¬ν¨λ λ°°μ΄ κ°μ²΄ 쿼리λ₯Ό μ§μνμ§ μκΈ° λλ¬Έμ μ΄ μ κ·Ό λ°©μμ ν¬κΈ°νμ΅λλ€.
https://github.com/carltongibson/django-filter/issues/426#issuecomment -380224133
jsonfield__a_random_key=valueμ κ°μ 쿼리λ₯Ό κ°λ₯νκ² νλ JSONFilterκ° μλ€λ κ²μ μ λ§ λλΌμ΄ μΌμ΄λΌκ³ μκ°ν©λλ€. λλ λΉμ μ΄ objects.filter λ©μλλ‘ κ·Έκ²μ ν μ μλ€λ κ²μ μκ³ μμ΅λλ€. μλ§λ νΈλ μ΄λ μ€νκ° νν° μ ν¨μ± κ²μ¬κ° λ μ μμ΅λκΉ?
μλ
νμΈμ @carltongibson , @rpkilby μ΄μ λν κ·νμ μκ°μ λ£κ³ μΆμ΅λλ€. my_field
κ° postgres JSONField
μ΄κ³ λ€μμ μνλ€κ³ κ°μ ν΄ λ³΄κ² μ΅λλ€.
my_field__etc=value
λͺ¨μμ REST νν°λ₯Ό μΆκ°ν©λλ€. μ¬κΈ°μ etc
λ JSONField
λ° value
μμ μ§μνλ 쿼리 μ€ νλμ΄λ©° REST μ¬μ©μκ° μ 곡νλ λͺ¨λ κ²μ
λλ€.etc
λ° value
λ₯Ό MyModel.objects.filter(my_field__etc=value)
νμμΌλ‘ λͺ¨λΈ κ°μ²΄ κ΄λ¦¬μμ μ λ¬νκ³ μΆμ΅λλ€.μμ£Ό μ¬μν κ²μ²λΌ 보μ΄μ§λ§ λλ μ΄μ κ°μ μΌμ λν΄ μκ°νμ§ λͺ»νμ΅λλ€. λν¬λ€μ΄ λμκ² μ½κ°μ μ€λ§λ¦¬λ₯Ό μ€λ€λ©΄ λλ κ·Έκ²μ ꡬννλ €κ³ ν μ μλ€.
μ΄λ€ μκ°μ΄λΌλ λλ¨ν κ°μ¬νκ² μ΅λλ€!
λ€μκ³Ό κ°μ κ²μ΄ μλνμ§ μμ΅λκΉ?
class MyFilter(FilterSet):
my_field__etc = filters.NumberFilter(field_name='my_field', lookup_expr='etc')
μΌλ°μ μΌλ‘ field_name
λ κΈ°λ³Έ λͺ¨λΈ νλ μ΄λ¦κ³Ό μΌμΉν΄μΌ νλ©° λ³ν λ° μ‘°ν(μ΄ κ²½μ° ν€ λ³ν)λ lookup_expr
μ ν¬ν¨λμ΄μΌ ν©λλ€.
@rpkilby λΉ λ₯Έ μλ΅μ κ°μ¬λ립λλ€ - λ€ λ§μ΅λλ€. νμ§λ§ μμ²μμ μ¬μ©μκ° μ 곡ν etc
μ(λ₯Ό) μν©λλ€... κ·Έλμ νν°μ νλμ½λ©ν μ μμμ΅λλ€ π
νν°λ λ€μκ³Ό κ°μμΌ ν©λλ€.
class MyFilter(FilterSet):
my_field = JSONFieldFilter(field_name='my_field')
λ°λΌμ ?my_field__etc=value
μ κ°μ μμμ 쿼리 맀κ°λ³μλ₯Ό μ²λ¦¬νλ λ¨μΌ JSON νν°μ
λλ€.
λ κ°μ§ λ¬Έμ κ° μμ΅λλ€. 첫째, django-filter κ°μ μΌλΆλ 쿼리 맀κ°λ³μμ μ ν¨μ±μ κ²μ¬νλ€λ κ²μ
λλ€. JSONField
μλ μ€ν€λ§κ° μκΈ° λλ¬Έμ μΈλ°μ΄λ λ°μ΄ν°λ₯Ό μ μ νκ² κ²μ¦νλ νν°λ₯Ό μμ±ν μ μμ΅λλ€. μλ₯Ό λ€μ΄ JSON νλμ "count" ν€κ° μλ κ²½μ° μμλ§ μ ν¨νλ€λ κ²μ μ§κ΄ν μ μμ΅λλ€. ν μ μλ μ΅μ μ κ°μ΄ μ ν¨ν JSONμμ 보μ₯νλ κ²μ
λλ€. λ°λΌμ 쿼리λ μ΅μν μ ν¨νμ§λ§ μλ―Έκ° μμ μλ μμ΅λλ€(μ: data__count__gt='cat'
).
λ λ²μ§Έλ μ΄ νν°κ° MultiWidget
κΈ°λ° νν°μ λμΌν μ νμ κ°λλ€λ κ²μ
λλ€. μλ₯Ό λ€μ΄, μ¬λ°λ₯Έ 맀κ°λ³μ μ΄λ¦μ λν μ ν¨μ± κ²μ¬ μ€λ₯λ₯Ό μμ±νμ§ μμ΅λλ€. νμ§λ§ μ΄μ λν΄ μμ보기 μ μ νν°λ₯Ό ꡬννλ λ°©λ²μ λ€μκ³Ό κ°μ΅λλ€. λ€μμ΄ νμν©λλ€.
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)
μμ ν μ€νΈλ ν΄λ³΄μ§ μμμ§λ§ λλ΅μ μΌλ‘ μ νν κ²μ λλ€. κ·Έλ¬λ λ€μκ³Ό κ°μ μ ν μ¬νμ΄ μμ΅λλ€.
ValidationError
sλ₯Ό μ¬λ°λ₯΄κ² μ²λ¦¬ν λ°©λ²μ΄ μμ΅λλ€.MultiWidget
μ νΈνλμ§ μμ΅λλ€ . μ΄ νν°/μμ ―μ λμΌν μ΄μ λ‘ λμΌν λ¬Έμ μ μ§λ©΄ν©λλ€.@rpkilby μ² μ ν λ΅λ³ κ°μ¬ν©λλ€.
JSON νλμ "count" ν€κ° μλ κ²½μ° μμλ§ μ ν¨νλ€λ κ²μ μ§κ΄ν μ μμ΅λλ€.
MyModel.objects.filter(data__count="1")
κ° MyModel.objects.filter(data__count=1)
μ κ°μ κ°μ λ°ννμ§ μκΈ° λλ¬Έμ 쿼리 κ°μ μ νμ νμΈν μ μλ€λ μ¬μ€μ΄ λ§€μ° μ€μν©λλ€. λ§μνμ λλ‘ μΏΌλ¦¬ 맀κ°λ³μμμ κ°μ μ νμ μΆμΈ‘ν μ μλ λ°©λ²μ μμ΅λλ€.
λ°λΌμ 쿼리 κ°μ μ ν μ 보λ₯Ό ν¬ν¨νλ μ΅μ
λ§ λ¨κ²¨λκ³ ?data__count=1:int
μ κ°μ μμ
μ μννμ¬ μ μλ₯Ό κ²μνκ³ ?data__count=1:str
λ¬Έμμ΄μ κ²μνλ λ±μ μμ
μ μνν©λλ€. κ·Έλ¬λ μ¬κΈ° μ μ μλ λλ‘ μ΄κ²μ κΆμ₯λμ§ μμ΅λλ€.
μ΄μ νν°λ₯Ό λͺ μμ μΌλ‘ μ μνλ κ²μ΄ μ κ·Έλ κ² μ€μνμ§ μ΄ν΄ν©λλ€. κ·ΈλΌμλ λΆκ΅¬νκ³ , λλ λΉμ μ μ μμ μλν κ²μ λλ€! λ€μ ν λ² κ°μ¬ν©λλ€
@rpkilby , λλ λΉμ·ν νμκ° μμ΅λλ€.
λ κ°μ μ΄μ΄ μλ μ΄μ κ°μ κ΅¬μ± ν μ΄λΈμ΄ μμ΅λλ€.
meta_structure of type jsonb (This column has info like key1 of type string, key2 of type integer)
3κ°μ μ΄μ΄ μλ config_dataλΌλ λ€λ₯Έ ν μ΄λΈμ΄ μμ΅λλ€.
config_id -> Foreign key to config table
meta_info -> jsonb type
μ°Έκ³ : μμ μΈκΈλ νλ μ νν νκ° μλλλ€. λ©μμ§λ₯Ό μ λ¬νκΈ° μν λνμ μΈ λ²μ μΌ λΏμ λλ€.
νμ¬ κ΅¬μ± ν μ΄λΈμμ μΌμΉνλμ§ νμΈνμ¬ μ μ₯νκΈ° μ μ meta_info ν μ΄λΈμ νλλ₯Ό κ²μ¦νκ³ μμ΅λλ€.
νμν κ²μ config_data ν μ΄λΈμ meta_info μ΄μ μ¬μ©νμ¬ νν°λ§νλ €λ κ²μ λλ€. μ. meta_info__key1='abc'. (key1μ 무μμ΄λ λ μ μμ)
μμμ μ 곡ν μ κ·Ό λ°©μμ μ¬μ©νλ €κ³ μλνμ§λ§ λ¬Έμ λ μμμ μμ±ν JSONFilter ν΄λμ€λ₯Ό μ΄λ»κ² μ¬μ©νλμ§μ λλ€.
μ.
class ConfigDataFilterSet(django_filters.FilterSet):
meta_info = JSONFilter(field_name='meta_info')
pp = ConfigDataFilterSet(data={'meta_info__key1': 'abc'})
μ΄μ pp.qs
λλ pp.filter_queryset()
λ₯Ό μ€ννλ©΄ ConfigDataFilterSet ν΄λμ€μ ν λΉλ νλ μ΄λ¦μ΄ meta_infoμ΄κΈ° λλ¬Έμ μ€μ λ‘ meta_info νλμ νν°λ₯Ό μ μ©νμ§ μμ΅λλ€. μ΄ λκ΄μ 극볡ν μ μλλ‘ λμμ£Όμκ² μ΅λκΉ?
κ°μ₯ μ μ©ν λκΈ
jsonfield__a_random_key=value
μ κ°μ 쿼리λ₯Ό κ°λ₯νκ² νλ JSONFilterκ° μλ€λ κ²μ μ λ§ λλΌμ΄ μΌμ΄λΌκ³ μκ°ν©λλ€.objects.filter
λ°©λ²μΌλ‘ ν μ μλ€λ κ²μ μκ³ μμ΅λλ€. μλ§λ νΈλ μ΄λ μ€νκ° νν° μ ν¨μ± κ²μ¬κ° λ μ μμ΅λκΉ?