λλ ...
Traceback (most recent call last):
[..]
File "/usr/local/lib/python3.7/site-packages/django/forms/forms.py", line 393, in _clean_fields
value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
File "/usr/local/lib/python3.7/site-packages/django_filters/widgets.py", line 201, in value_from_datadict
return value.split(',')
AttributeError: 'list' object has no attribute 'split'
κ·Έλ¦¬κ³ κ·Έ μ½λλ ...
https://github.com/carltongibson/django-filter/blob/1f47e36b614724a8735e0457fa511dcaf5448481/django_filters/widgets.py#L195 -L202
... super().value_from_datadict(data, files, name)
κ° λͺ©λ‘μ λ°ννλ κ²κ³Ό κ΄λ ¨νμ¬ κ°λ ₯νμ§ μμ΅λλ€. λ λλ²κΉ
ν΄μΌ νμ§λ§ μ¬κΈ°μμ λ¬΄μ¨ μΌμ΄ μΌμ΄λκ³ μλμ§ μ΄λ―Έ μκ³ μμ μλ μμ΅λλ€.
μλ νμΈμ @mosebμ λλ€. μ¬μ© μ€μΈ FilterSet μ½λλ₯Ό λΆμ¬λ£μ μ μμ΅λκΉ? μμΆμ μ λ§μ§λ§ λͺ μ€μ μ΄ μμΈκ° λ°μν μ΄μ λ₯Ό κ²°μ νκΈ°μ μΆ©λΆν©λλ€.
μ 체 μμΆμ :
Traceback (most recent call last):
File "/usr/local/lib/python3.7/wsgiref/handlers.py", line 137, in run
self.result = application(self.environ, self.start_response)
File "/usr/local/lib/python3.7/site-packages/django/contrib/staticfiles/handlers.py", line 65, in __call__
return self.application(environ, start_response)
File "/usr/local/lib/python3.7/site-packages/django/core/handlers/wsgi.py", line 141, in __call__
response = self.get_response(request)
File "/usr/local/lib/python3.7/site-packages/django/core/handlers/base.py", line 75, in get_response
response = self._middleware_chain(request)
File "/usr/local/lib/python3.7/site-packages/django/core/handlers/exception.py", line 36, in inner
response = response_for_exception(request, exc)
File "/usr/local/lib/python3.7/site-packages/django/core/handlers/exception.py", line 90, in response_for_exception
response = handle_uncaught_exception(request, get_resolver(get_urlconf()), sys.exc_info())
File "/usr/local/lib/python3.7/site-packages/django/core/handlers/exception.py", line 34, in inner
response = get_response(request)
File "/usr/local/lib/python3.7/site-packages/django/utils/deprecation.py", line 94, in __call__
response = response or self.get_response(request)
File "/usr/local/lib/python3.7/site-packages/django/core/handlers/exception.py", line 36, in inner
response = response_for_exception(request, exc)
File "/usr/local/lib/python3.7/site-packages/django/core/handlers/exception.py", line 90, in response_for_exception
response = handle_uncaught_exception(request, get_resolver(get_urlconf()), sys.exc_info())
File "/usr/local/lib/python3.7/site-packages/django/core/handlers/exception.py", line 34, in inner
response = get_response(request)
File "/usr/local/lib/python3.7/site-packages/django/utils/deprecation.py", line 94, in __call__
response = response or self.get_response(request)
File "/usr/local/lib/python3.7/site-packages/django/core/handlers/exception.py", line 36, in inner
response = response_for_exception(request, exc)
File "/usr/local/lib/python3.7/site-packages/django/core/handlers/exception.py", line 90, in response_for_exception
response = handle_uncaught_exception(request, get_resolver(get_urlconf()), sys.exc_info())
File "/usr/local/lib/python3.7/site-packages/django/core/handlers/exception.py", line 34, in inner
response = get_response(request)
File "/usr/local/lib/python3.7/site-packages/django/utils/deprecation.py", line 94, in __call__
response = response or self.get_response(request)
File "/usr/local/lib/python3.7/site-packages/django/core/handlers/exception.py", line 36, in inner
response = response_for_exception(request, exc)
File "/usr/local/lib/python3.7/site-packages/django/core/handlers/exception.py", line 90, in response_for_exception
response = handle_uncaught_exception(request, get_resolver(get_urlconf()), sys.exc_info())
File "/usr/local/lib/python3.7/site-packages/django/core/handlers/exception.py", line 34, in inner
response = get_response(request)
File "/usr/local/lib/python3.7/site-packages/django/utils/deprecation.py", line 94, in __call__
response = response or self.get_response(request)
File "/usr/local/lib/python3.7/site-packages/django/core/handlers/exception.py", line 36, in inner
response = response_for_exception(request, exc)
File "/usr/local/lib/python3.7/site-packages/django/core/handlers/exception.py", line 90, in response_for_exception
response = handle_uncaught_exception(request, get_resolver(get_urlconf()), sys.exc_info())
File "/usr/local/lib/python3.7/site-packages/django/core/handlers/exception.py", line 34, in inner
response = get_response(request)
File "/usr/local/lib/python3.7/site-packages/django/utils/deprecation.py", line 94, in __call__
response = response or self.get_response(request)
File "/usr/local/lib/python3.7/site-packages/django/core/handlers/exception.py", line 36, in inner
response = response_for_exception(request, exc)
File "/usr/local/lib/python3.7/site-packages/django/core/handlers/exception.py", line 90, in response_for_exception
response = handle_uncaught_exception(request, get_resolver(get_urlconf()), sys.exc_info())
File "/usr/local/lib/python3.7/site-packages/django/core/handlers/exception.py", line 34, in inner
response = get_response(request)
File "/usr/local/lib/python3.7/site-packages/django/utils/deprecation.py", line 94, in __call__
response = response or self.get_response(request)
File "/usr/local/lib/python3.7/site-packages/django/core/handlers/exception.py", line 36, in inner
response = response_for_exception(request, exc)
File "/usr/local/lib/python3.7/site-packages/django/core/handlers/exception.py", line 90, in response_for_exception
response = handle_uncaught_exception(request, get_resolver(get_urlconf()), sys.exc_info())
File "/usr/local/lib/python3.7/site-packages/django/core/handlers/exception.py", line 34, in inner
response = get_response(request)
File "/usr/local/lib/python3.7/site-packages/django_global_request/middleware.py", line 15, in __call__
return self.get_response(request)
File "/usr/local/lib/python3.7/site-packages/django/core/handlers/exception.py", line 36, in inner
response = response_for_exception(request, exc)
File "/usr/local/lib/python3.7/site-packages/django/core/handlers/exception.py", line 90, in response_for_exception
response = handle_uncaught_exception(request, get_resolver(get_urlconf()), sys.exc_info())
File "/usr/local/lib/python3.7/site-packages/django/core/handlers/exception.py", line 34, in inner
response = get_response(request)
File "/usr/local/lib/python3.7/site-packages/django/core/handlers/base.py", line 145, in _get_response
response = self.process_exception_by_middleware(e, request)
File "/usr/local/lib/python3.7/site-packages/django/core/handlers/base.py", line 143, in _get_response
response = response.render()
File "/usr/local/lib/python3.7/site-packages/django/template/response.py", line 106, in render
self.content = self.rendered_content
File "/usr/local/lib/python3.7/site-packages/rest_framework/response.py", line 72, in rendered_content
ret = renderer.render(self.data, accepted_media_type, context)
File "/usr/local/lib/python3.7/site-packages/rest_framework/renderers.py", line 733, in render
context = self.get_context(data, accepted_media_type, renderer_context)
File "/usr/local/lib/python3.7/site-packages/rest_framework/renderers.py", line 710, in get_context
'filter_form': self.get_filter_form(data, view, request),
File "/usr/local/lib/python3.7/site-packages/rest_framework/renderers.py", line 642, in get_filter_form
html = backend().to_html(request, queryset, view)
File "/usr/local/lib/python3.7/site-packages/rest_framework_filters/backends.py", line 52, in to_html
return super().to_html(request, queryset, view)
File "/usr/local/lib/python3.7/site-packages/django_filters/rest_framework/backends.py", line 105, in to_html
return template.render(context, request)
File "/usr/local/lib/python3.7/site-packages/django/template/backends/django.py", line 61, in render
return self.template.render(context)
File "/usr/local/lib/python3.7/site-packages/django/template/base.py", line 171, in render
return self._render(context)
File "/usr/local/lib/python3.7/site-packages/django/template/base.py", line 163, in _render
return self.nodelist.render(context)
File "/usr/local/lib/python3.7/site-packages/django/template/base.py", line 937, in render
bit = node.render_annotated(context)
File "/usr/local/lib/python3.7/site-packages/django/template/base.py", line 904, in render_annotated
return self.render(context)
File "/usr/local/lib/python3.7/site-packages/crispy_forms/templatetags/crispy_forms_tags.py", line 199, in render
c = self.get_render(context).flatten()
File "/usr/local/lib/python3.7/site-packages/crispy_forms/templatetags/crispy_forms_tags.py", line 118, in get_render
actual_form.form_html = helper.render_layout(actual_form, node_context, template_pack=self.template_pack)
File "/usr/local/lib/python3.7/site-packages/crispy_forms/helper.py", line 308, in render_layout
template_pack=template_pack
File "/usr/local/lib/python3.7/site-packages/crispy_forms/layout.py", line 140, in render
return self.get_rendered_fields(form, form_style, context, template_pack, **kwargs)
File "/usr/local/lib/python3.7/site-packages/crispy_forms/layout.py", line 104, in get_rendered_fields
for field in self.fields
File "/usr/local/lib/python3.7/site-packages/crispy_forms/layout.py", line 104, in <genexpr>
for field in self.fields
File "/usr/local/lib/python3.7/site-packages/crispy_forms/utils.py", line 148, in render_field
html = template.render(context)
File "/usr/local/lib/python3.7/site-packages/django/template/backends/django.py", line 61, in render
return self.template.render(context)
File "/usr/local/lib/python3.7/site-packages/django/template/base.py", line 171, in render
return self._render(context)
File "/usr/local/lib/python3.7/site-packages/django/template/base.py", line 163, in _render
return self.nodelist.render(context)
File "/usr/local/lib/python3.7/site-packages/django/template/base.py", line 937, in render
bit = node.render_annotated(context)
File "/usr/local/lib/python3.7/site-packages/django/template/base.py", line 904, in render_annotated
return self.render(context)
File "/usr/local/lib/python3.7/site-packages/django/template/defaulttags.py", line 309, in render
return nodelist.render(context)
File "/usr/local/lib/python3.7/site-packages/django/template/base.py", line 937, in render
bit = node.render_annotated(context)
File "/usr/local/lib/python3.7/site-packages/django/template/base.py", line 904, in render_annotated
return self.render(context)
File "/usr/local/lib/python3.7/site-packages/django/template/defaulttags.py", line 309, in render
return nodelist.render(context)
File "/usr/local/lib/python3.7/site-packages/django/template/base.py", line 937, in render
bit = node.render_annotated(context)
File "/usr/local/lib/python3.7/site-packages/django/template/base.py", line 904, in render_annotated
return self.render(context)
File "/usr/local/lib/python3.7/site-packages/django/template/defaulttags.py", line 302, in render
match = condition.eval(context)
File "/usr/local/lib/python3.7/site-packages/django/template/defaulttags.py", line 876, in eval
return self.value.resolve(context, ignore_failures=True)
File "/usr/local/lib/python3.7/site-packages/django/template/base.py", line 671, in resolve
obj = self.var.resolve(context)
File "/usr/local/lib/python3.7/site-packages/django/template/base.py", line 796, in resolve
value = self._resolve_lookup(context)
File "/usr/local/lib/python3.7/site-packages/django/template/base.py", line 837, in _resolve_lookup
current = getattr(current, bit)
File "/usr/local/lib/python3.7/site-packages/django/forms/boundfield.py", line 74, in errors
return self.form.errors.get(self.name, self.form.error_class())
File "/usr/local/lib/python3.7/site-packages/django/forms/forms.py", line 180, in errors
self.full_clean()
File "/usr/local/lib/python3.7/site-packages/django/forms/forms.py", line 381, in full_clean
self._clean_fields()
File "/usr/local/lib/python3.7/site-packages/django/forms/forms.py", line 393, in _clean_fields
value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
File "/usr/local/lib/python3.7/site-packages/django_filters/widgets.py", line 201, in value_from_datadict
return value.split(',')
AttributeError: 'list' object has no attribute 'split'
[12/Jul/2019 13:49:06] "GET /XXXXXXXX/?ordering=first_name HTTP/1.1" 500 59
django-rest-frameworkκ° μ½λλ₯Ό μμ±νλ λͺ©λ‘μ νμνλ κ²μ λλ€.
μλ νμΈμ @mosebμ λλ€. μ¬μ© μ€μΈ FilterSet μ½λλ₯Ό λΆμ¬λ£μ μ μμ΅λκΉ?
μμ§ν λ§ν΄μ, μμ§ μ½λμ ν΄λΉ μμμμ μ무 κ²λ μ°Ύμ§ λͺ»νμ΅λλ€. κ·Έκ²μ λͺ¨λ DRFμ μν κ²μΌ μ μμ΅λλ€. μμ§ νμ€νμ§ μμ΅λλ€.
λ΄ λ²κ·Έ λ³΄κ³ μκ° μ΄μμ μ΄μ§ μλ€λ κ²μ μκ³ μμ§λ§ νμ¬λ‘μλ κ°μ₯ μ’μ΅λλ€. μ£μ‘ν©λλ€!
API 보기μλ filterset_class
λλ filterset_fields
λλ μ΄μ λ²μ μ django-filterλ₯Ό μ¬μ©νλ κ²½μ° filter_class
λ° filter_fields
κ° μμ΄μΌ ν©λλ€.
filter_class
μ¬μ©μ μ°Ύμμ΅λλ€. κ·Έλμ django-filter 2.0μΌλ‘μ λ§μ΄κ·Έλ μ΄μ
μ΄ λκΉμ§ μ λλ‘ μνλμ§ μμλ€λ κ²μ μκ² λ κ² κ°μ΅λλ€. μμν!
κ°λ
μ μΌλ‘ django-filterκ° pre-2.x λ¨μ μμμ κ°μ§νκ³ μ΄μ λν΄ κ²½κ³ νλ κ²μ λ§λ κ²μ΄ μμ΅λκΉ? κ²½κ³ νμ§λ§ λ΄κ° λμ³€μ΅λκΉ? ν 리νμ€νΈμ λν νμ μΆκ° μ¬νμ λν μ½λ κ²½κ³ κ° μμ΅λκΉ?
v2.0μ κΈ°μ‘΄ μ¬μ© μ€λ¨ κ²½κ³ λ₯Ό λ§μ΄ μ κ±°νμΌλ―λ‘ v1.0μ λν΄ ν μ€νΈ μ€μνΈλ₯Ό μ€νν λ€μ django-filterμ v1.1μ λν΄ μ€νν΄ λ³Ό μ μμ΅λλ€.
μ λ°μ΄νΈνλ©΄ μ μ λ‘ ν΄κ²°λλ€κ³ κ°μ ν©λλ€. κ·Έλ μ§ μμΌλ©΄ λ€μ μ€μμμ€.
https://github.com/moseb/django-filter-issue-1103-demoμ μ¬ν κ°λ₯ν λ¬Έμ μ λ°λͺ¨κ° μμ΅λλ€. μ΄ λ¬Έμ λ₯Ό λ€μ μ¬λ κ²μ κ³ λ €νμμμ€. κ°μ¬ν©λλ€!
μ, μ΄κ²μ ModelMultipleChoiceFilter
μ΄ CSV mixinκ³Ό νΈνλμ§ μλ κ²κ³Ό in
μ‘°νμ κ΄λ ¨μ΄ μμ΅λλ€. μ¬κΈ°μ λ κ°μ§ λ³κ°μ κ΄λ ¨ λ¬Έμ κ° μμ΅λλ€.
κ°κ΄μ νν°κ° μ΄λ―Έ μ μ¬ν λμμ μ 곡νλ―λ‘ m2m νλμ λν΄ in
μ‘°νκ° νμνμ§ μμ΅λλ€.
μλ !
μ, μ΄κ²μ
ModelMultipleChoiceFilter
μ΄ CSV mixinκ³Ό νΈνλμ§ μλ κ²κ³Όin
μ‘°νμ κ΄λ ¨μ΄ μμ΅λλ€. μ¬κΈ°μ λ κ°μ§ λ³κ°μ κ΄λ ¨ λ¬Έμ κ° μμ΅λλ€.
GitHubμ μ΄λ¬ν λ¬Έμ μ λν κΈ°μ‘΄ ν°μΌμ΄ μμ΅λκΉ?
μμμ μΈκΈν κ²μ²λΌ BaseCSVWidget.value_from_datadict
λ₯Ό λ κ°λ ₯νκ² λ§λλ κ²λ§μΌλ‘λ λ¬Έμ κ° ν΄κ²°λμ§ μλ κ² κ°μ΅λκΉ? (μμ§ λ¬Έμ λ₯Ό μμ ν μ΄ν΄νμ§ λͺ»νμ΅λλ€.)
κ°κ΄μ νν°κ° μ΄λ―Έ μ μ¬ν λμμ μ 곡νλ―λ‘ m2m νλμ λν΄
in
μ‘°νκ° νμνμ§ μμ΅λλ€.
in
λ₯Ό μ¬μ©νλ©΄ exact
μ΄μμ μ¬λ¬ κ°μ νμΈν μ μμ΅λλ€. Γ¬n
λ₯Ό μ¬μ©ν μ μλ κ²½μ° μ¬λ¬ κ°μ νμΈνλ €λ©΄ μ΄λ»κ² ν΄μΌ ν©λκΉ?
μΆμ : μ΄ ν°μΌμ λ€μ μ΄ μ μμ΅λκΉ?
μμμ μΈκΈν κ²μ²λΌ
BaseCSVWidget.value_from_datadict
λ₯Ό λ κ°λ ₯νκ² λ§λλ κ²λ§μΌλ‘λ λ¬Έμ κ° ν΄κ²°λμ§ μλ κ² κ°μ΅λκΉ? (μμ§ λ¬Έμ λ₯Ό μμ ν μ΄ν΄νμ§ λͺ»νμ΅λλ€.)
νΉμ. κ°μ΄ μ΄λ―Έ λͺ©λ‘μ΄λ©΄ κ³μ μ§ννμ¬ ν΄λΉ λͺ©λ‘μ λ°νν΄μΌ ν©λλ€. μ¦, CSV λμμ SelectMultiple
μμ ―κ³Ό νΌν©νλ κ²μ΄ ν©λ¦¬μ μ΄μ§ μλ€κ³ μκ°ν©λλ€.
in
λ₯Ό μ¬μ©νλ©΄exact
μ΄μμ μ¬λ¬ κ°μ νμΈν μ μμ΅λλ€.Γ¬n
λ₯Ό μ¬μ©ν μ μλ κ²½μ° μ¬λ¬ κ°μ νμΈνλ €λ©΄ μ΄λ»κ² ν΄μΌ ν©λκΉ?
ModelMultipleChoiceFilter
λ Q κ°μ²΄μμ OR 쿼리λ₯Ό ꡬμ±ν©λλ€. λ°λΌμ /api/mymodel?m2m=a&m2m=b
μ κ°μ 쿼리 λ¬Έμμ΄μ΄ μλ κ²½μ° λ€μκ³Ό κ°μ νν° νΈμΆλ‘ λλ©λλ€.
MyModel.objects.filter(Q(m2m='a') | Q(m2m='b'))
2.1 릴리μ€μμλ lookup_expr
κ° μ΄μ κ° Q κ°μ²΄μ μ μ©λλ―λ‘ λ€μκ³Ό κ°μ΄ λ©λλ€.
MyModel.objects.filter(Q(m2m__in='a') | Q(m2m__in='b'))
μμ λ΄μ©μ μ ν¨νμ§ μμΌλ©° μ€λ¨λμ§λ§ contains
λ° λ¨μΌ κ°μΌλ‘ μλνλλ‘ μλλ κΈ°ν μ‘°νμλ μλ―Έκ° μμ΅λλ€.
κ°λ¨ν λ§ν΄μ, μ¬κΈ°μ ν΄μΌ ν μΌμ m2m νλμ λν΄ exact
μ‘°νλ₯Ό μ¬μ©νλ κ²μ
λλ€.
μ¬κΈ°μμ ν μΌμ λ€μκ³Ό κ°μ΅λλ€.
BaseCSVWidget
κ° SelectMultiple
μ νΈνλλλ‘ ν©λλ€. λλ νΈνλμ§ μμμΌ νλ κ²½μ° μ΅μν initμμ μ μ©ν μ€λ₯λ₯Ό μ 곡ν΄μΌ ν©λλ€.Meta.fields
λ₯Ό μμ ν©λλ€. ModelMultipleChoiceFilter
in
μ‘°νλ₯Ό μμ±νλ κ²μ μλ―Έκ° μμ΅λλ€. λν λΆμΈμ μμνλ isnull
μ κ°μ λ€λ₯Έ μ‘°νμ λν΄ μ΄λ€ μΌμ΄ λ°μνλμ§ ν
μ€νΈν΄μΌ ν©λλ€.μ, μ΄κ²μ μ¬μ ν λ²κ·Έ μ²λΌ 보μ΄μ§ μμ§λ§ λͺ¨λ μλ λ°©μμ λ¬Έμνλμ§ μμ κ² κ°μ΅λλ€...
λ°©κΈ μ΄ μ€λ₯κ° λ°μνκ³ μ΄μ λν μ€λͺ μ μ΄ μ€λ λμ 묻ν μμΌλ―λ‘ μμ½νκ³ λ―Έλ λ μλ₯Ό μν΄ κΈ΄ κ²μμ μ μ₯ν©λλ€.
ManyToMany
보기 μΈνΈμ filterset_fields
μ μ μΈλ νλλ in
λ₯Ό ν¬ν¨ ν μ μμ΅λλ€ .
λ°νμ μ κ²½κ³ λ λ§€μ° κ°μ¬νκ² μ΅λλ€.
@marcosox μ’μ μκ°μ λλ€. μΆκ°νλ PRμ 보λ λ°κ°μ΅λλ€!
κ°μ₯ μ μ©ν λκΈ
λ°©κΈ μ΄ μ€λ₯κ° λ°μνκ³ μ΄μ λν μ€λͺ μ μ΄ μ€λ λμ 묻ν μμΌλ―λ‘ μμ½νκ³ λ―Έλ λ μλ₯Ό μν΄ κΈ΄ κ²μμ μ μ₯ν©λλ€.
ManyToMany
보기 μΈνΈμfilterset_fields
μ μ μΈλ νλλin
λ₯Ό ν¬ν¨ ν μ μμ΅λλ€ .λ°νμ μ κ²½κ³ λ λ§€μ° κ°μ¬νκ² μ΅λλ€.