-
Notifications
You must be signed in to change notification settings - Fork 771
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Deprecate ordering options, rework into filter #472
Conversation
1b119be
to
0db2252
Compare
class UserFilter(FilterSet): | ||
account = CharFilter(name='username') | ||
|
||
o = AutoOrderingFilter( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the filter class called?
OK. This looks good.
|
So the names were I think I'll revert back to |
@rpkilby Sounds good!
I'm happy with that — perhaps a decent doc string — and then a decent entry in the actual docs for |
Oh yeah, was fixing some of those inconsistencies when my laptop crashed - called it an evening. I'll patch it up soon. |
90ef2e2
to
352b7d4
Compare
Hey @carltongibson - I reworked the PR and think it's a bit cleaner. Merged the two filter classes into one as there wasn't any real reason to distinguish between the two and was potentially a point of confusion. Also cleaned up the arguments to the filters to make a little more sense. |
352b7d4
to
b82ec73
Compare
for APIs. | ||
|
||
""" | ||
descending_fmt = '%s (descending)' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this probably be translatable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep!
b82ec73
to
6bf9293
Compare
6bf9293
to
a18b016
Compare
Hey - this has been re-rebased. |
|
||
This filter is also CSV-based, and accepts multiple ordering params. The | ||
default select widget does not enable the use of this, but it is useful | ||
for APIs. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rpkilby Was your intention to have the "default select widget" in play? (It's not, but should be I think...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm thinking BaseCSVField needs to handle that it won't get a list from e.g. a Select
widget.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was your intention to have the "default select widget" in play?
The select widget needs to be augmented by the CSVWidget
(similar to how the CSV field and filter work). I'm not sure if this can be handled generically or if this needs to be a one off CSVSelect
widget.
I'm thinking BaseCSVField needs to handle that it won't get a list from e.g. a Select widget.
Off the cuff I'd agree - no reason the field couldn't handle both values. That said, a different widget class won't know how to render the list of values it will get from the CSV field. At worst, it raises an exception. At best, it will force the list as a text value.
I think the correct thing to do here is have conditional rendering in the base CSV widget. If it has a single value (eg, from a select dropdown), use the main widget to render. If it has multiple values, force a text input.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That all makes sense. #501 looks good. I'll review over the weekend. Thanks!
Hi, what is the supposed way of setting the initial ordering in OrderingFilter? In the docs (migration guide) it is stated, that the queryset method I tried to pass the kwarg I saw that the Am I missing sth here? Should the FilterSet class be extended maybe, to filter data keys by existing filter fields to check if it is bound or unbound? |
Hi @rubengrill. Trying to distill/understand your comment - it seems the problem is that you want to apply a default ordering to the queryset, but the rendered form is not aware of the default value, instead rendering some other value (probably just the first choice in the option list). For context, part of the odd behavior is the lack of an 'empty' option for
Yep - initial values are not default values. The unbound form simply preselects that options during rendering. It's still up to the user to submit a final value. Anyway, you should be able to do something like the following: class UserFilter(FilterSet):
ordering = OrderingFilter(fields=(...), initial='username')
class Meta:
model = User
fields = ['username', 'full_name']
# in your view:
f = UserFilter(queryset=User.objects.order_by('username'), data=request.GET)
... Filters pass any unrecognized options to its underlying field. This allows you to take full advantage of the |
Hi @rpkilby, Thanks for your answer.
Exactly, it renders the first choice in the option list, although another choice was passed to
The empty option is correct, if my view does not at all use But in my case, I always want to sort by a default sort field. The user should not be able to choose an empty option, because no sorting at all would not make sense to the user. I'll show my concrete use case to clarify that: The user requests The But: So the problem is that I have a search view only for Shouldn't the FilterSet be bound only if query parameters are given that are actually filter fields? I wrote a class ProductFilter(django_filters.FilterSet):
price__lte = django_filters.NumberFilter(name='price', lookup_expr='lte')
price__gte = django_filters.NumberFilter(name='price', lookup_expr='gte')
o = django_filters.OrderingFilter(
choices=(
('-rating', 'Rating'),
('price', 'Price (ascending)'),
('-price', 'Price (descending)'),
),
initial=['-rating'],
)
def __init__(self, data=None, queryset=None, prefix=None, strict=None):
# Filter data dict by keys that are actually filter fields
# If resulting dict is empty, pass None to super constructor,
# so that this filter is unbound
# Required to get initial value for OrderingFilter working
#
# https://github.com/carltongibson/django-filter/pull/472#issuecomment-252511903
keys = self.base_filters.keys()
data = data or {}
data = {key: value for key, value in data.iteritems() if key in keys}
data = data or None
super(ProductFilter, self).__init__(data, queryset, prefix, strict)
class Meta:
model = models.Product
fields = [] This way, all initial values would be applied (not just for OrderingField), even when other query params are given (that are not related to the |
The user selecting an empty option here should be equivalent to saying "I'm letting the server chose how to sort the results".
Remember - Also, this approach has a subtle bug. Filters that use Either way, the real problem here is that class FilterView(django_filters.views.FilterView):
"""
Use POST data instead of GET query params
"""
def get_filterset_kwargs(self, filterset_class):
kwargs = super(FilterView, self).get_filterset_kwargs(filterset_class)
kwargs['data'] = self.request.POST or None
return kwargs Don't forget to update your form method in the template: <form method="POST">
...
</form> |
As an addendum (since #519 is basically ready) - if you really want to enforce filtering, you should be able to do it via the following: CHOICES = [...]
class ProductFilter(django_filters.FilterSet):
o = django_filters.OrderingFilter(
choices=CHOICES
required=True,
empty_label=None,
)
|
Fixes #438. The objective is to completely deprecate the ordering features of the FilterSet itself, instead encouraging the use of an explicitly declared ordering filter. In theory,
order_by
and the refactoredget_ordering_filter
could be kept, however I think it's better to remove it. The existing behavior oforder_by
is just not compatible with too many use cases. It would be possible to re-addorder_by
, but with a simpler, more restricted set of rules.Details:
This completely reworks ordering into a filter, while retaining some deprecation compatibility.
fields
argument allows users to alias the underlying field names. eg, account => usernamefield_labels
, which is a map of field names to display textschoices
argument. e.g., to disable sorting by a descending option or something.Changes:
OrderingFilter
Meta.order_by
andMeta.order_by_field
.get_ordering_field
intoget_ordering_filter
. While now deprecated, the behavior forMeta.order_by
is completely backwards compatible with the caveat that it only works for theMeta.fields
list syntax.get_ordering_filter
that prevents undefined behavior from occurring when using theMeta.fields
dict syntax withMeta.order_by
.get_order_by
andget_ordering_field
.Notes:
get_order_by
andget_ordering_field
, assertions were chosen over deprecation warnings because those changes are forwards incompatible. The best that can be done is to warn the user that the behavior has changed. Otherwise, the behavior would silently change after upgrading, as those methods are no longer invoked.TODO:
strict
andorder_by_field
moves.Meta.order_by
deprecationget_order_by
,get_ordering_field
, etc...)test_filterset.FilterSetOrderingTests.test_ordering_when_unbound
)