Skip to content
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

Add QuerySetRequestMixin #494

Merged
merged 3 commits into from
Nov 2, 2016

Conversation

rpkilby
Copy link
Collaborator

@rpkilby rpkilby commented Sep 22, 2016

Exposes the request to be used during filtering. See DRF #3766, #3905, #3977.

  • Add FilterSet.request, which is generally accessible through the Filter.parent.
  • Add queryset callable handling to ModelChoiceFilter and ModelMultipleChoiceFilter through QuerySetRequestMixin.
def departments(request):
    company = request.user.company
    return company.department_set.all()

class EmployeeFilter(filters.FilterSet):
    department = filters.ModelChoiceFilter(queryset=departments)
    ...
class Filter(filters.FilterSet):
    relation = django_filters.CharFilter(method='filter_relation')

    def filter_relation(self, queryset, name, value):
        return queryset.filter(id__in=some_search_involving(self.user, value))

TODO:

  • docs
  • tests

@sassanh
Copy link

sassanh commented Sep 23, 2016

Thanks

@carltongibson
Copy link
Owner

This is on my list — except I want to make the request available, not just the user... @rpkilby Fancy adjusting?

@rpkilby
Copy link
Collaborator Author

rpkilby commented Sep 23, 2016

Hi @carltongibson & @sassanh - out of curiosity what's the benefit to passing the full request instead of just the request user? My initial reaction is 👎, but I'm happy to change it since it's so straightforward to do.

Some thoughts on request:

  • Testing becomes a little more annoying - you have to use the request factory and authenticate the user instead of just passing a user directly.
  • It seems unnecessary? Not sure what else can be meaningfully used from the request object. (I'll just chalk that up to my lack of imagination 😄)
  • The api feels a little weird. We pass the request.query_params as the first argument, but then optionally the request?

@carltongibson
Copy link
Owner

OK. I'll elaborate more later but... I've needed to filter on Accept-Languages header, and have had to do something like @kevin-brown's workaround before.

Bottom line, whole request is context, we want the whole request, but it's optional, most of what we do is on the QueryDict.

@rpkilby
Copy link
Collaborator Author

rpkilby commented Sep 23, 2016

I've needed to filter on Accept-Languages header

Oh cool - that makes sense. I'll update the PR.

@rpkilby rpkilby changed the title [WIP] Expose request user to filters [WIP] Expose request/user to filters Sep 23, 2016
Copy link
Owner

@carltongibson carltongibson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Needs docs.

I'm inclined to add something like a RequestFilter — which would look like the old MethodFilter. This would always run — if self.parent.request is not None — and would enable users ot define their own filtering logic in a method on the FilterSet. (How best to aid/encourage validation...?)

@@ -307,11 +307,34 @@ class DurationFilter(Filter):
field_class = forms.DurationField


class ModelChoiceFilter(Filter):
class QuerySetMixin(object):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class needs a docstring. What's it for?

queryset = self.extra.get('queryset')

if callable(queryset):
return queryset(user=user)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not following the API usage here — what's the intent?

It looks like you want to automatically filter to the user if provided... but queryset is a django.db QuerySet instance right — so filter(...)... ???

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was thinking the API would be similar to a few of django's model field options, such as default. You either pass the value (qs in this case) or a callable. In this case, the callable is passed the request or user (tbd, but leaning towards passing the request now).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually - it would make the most sense to pass the request.

@@ -288,6 +288,9 @@ def __init__(self, data=None, queryset=None, prefix=None, strict=None):
# What to do on on validation errors
self.strict = self._meta.strict if strict is None else strict

self.request = request
self.user = getattr(request, 'user', None)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we want to hold both references here. request.user is fine.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, especially if the callable takes the request instead of the user.

@blueyed
Copy link
Contributor

blueyed commented Oct 5, 2016

@rpkilby
Just a btw:

Testing becomes a little more annoying - you have to use the request factory and authenticate the user instead of just passing a user directly.

You can just pass in a dict with only the user, instead of a whole (faked) request, if you're only using the user in your tests.

@rpkilby
Copy link
Collaborator Author

rpkilby commented Oct 22, 2016

Hi @carltongibson - updated the PR to be based on the request instead of the request.user. Also added a doc string.

I'm inclined to add something like a RequestFilter — which would look like the old MethodFilter. This would always run — if self.parent.request is not None — and would enable users ot define their own filtering logic in a method on the FilterSet. (How best to aid/encourage validation...?)

Could you give an example of possible RequestFilter usage? I don't think it's possible to always run a specific filter since filters are only invoked if the corresponding query params are passed.

Possible alternatives:

  • Users can override the .qs property to filter based on the passed in request.
  • Users can override the .get_queryset() method on CBVs and DRF viewsets.

@carltongibson
Copy link
Owner

Users can override the .qs property to filter based on the passed in request.

That should be the documented approach — it does away with the need for an extra type of weird Filter.

(The whole point of this is to keep the filtering logic in the one-place, the FilterSet so overriding the get_queryset method in the view doesn't help.)

@carltongibson
Copy link
Owner

@rpkilby I like QuerySetRequestMixin. If you can rebase (after #520) and doc how the mixin allows you to not override __init__ we'll have this.

Good work! 👍

@carltongibson carltongibson added this to the 1.0 milestone Oct 22, 2016
@carltongibson carltongibson changed the title [WIP] Expose request/user to filters Add QuerySetRequestMixin Oct 22, 2016
@rpkilby
Copy link
Collaborator Author

rpkilby commented Oct 29, 2016

Hi @carltongibson - docs/tests have been added. Do they look sufficient?

@rpkilby rpkilby mentioned this pull request Oct 30, 2016
Copy link
Owner

@carltongibson carltongibson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice.

@carltongibson carltongibson merged commit 130fc00 into carltongibson:develop Nov 2, 2016
@rpkilby rpkilby deleted the user-filtering branch November 2, 2016 12:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants