diff --git a/docs/README.md b/docs/README.md index c992fd6..54992d4 100644 --- a/docs/README.md +++ b/docs/README.md @@ -90,22 +90,23 @@ class PlayersViewSet(FiltersMixin, viewsets.ModelViewSet): query parameters in the URL. """ query_params = self.request.query_params - queryset = Player.objects.prefetch_related( - 'teams' # use prefetch_related to minimize db hits. - ).all() + url_params = self.kwargs + + # get queryset_filters from FilterMixin + queryset_filters = self.get_db_filters(url_params, query_params) # This dict will hold filter kwargs to pass in to Django ORM calls. - db_filters = {} + db_filters = queryset_filters['db_filters'] - # update filters dict with incoming query params and then pass as - # **kwargs to queryset.filter() - db_filters.update( - self.get_queryset_filters( - query_params - ) - ) - return queryset.filter(**db_filters) + # This dict will hold exclude kwargs to pass in to Django ORM calls. + db_excludes = queryset_filters['db_excludes'] + + # fetch queryset from Players model + queryset = Player.objects.prefetch_related( + 'teams' # use prefetch_related to minimize db hits. + ).all() + return queryset.filter(**db_filters).exclude(**db_excludes) ``` With the use of `drf-url-filters` adding a new filter on a new column is as simple as adding a new key in the dict. Prohibitting a filter on particular column is same as removing a key value mapping from the `filter_mappings` dict. @@ -117,7 +118,7 @@ Copyright (c) 2016 Manjit Kumar Read more about it in LICENSE file available in repo. # Credits -Special thanks to authors of [voluptouos](https://github.com/alecthomas/voluptuous) and friends [*](https://github.com/cdax) [**](https://github.com/SaurabhJha) who encourage people to contribute into open source community. +Special thanks to authors of [voluptouos](https://github.com/alecthomas/voluptuous) and friends [saurabhjha](https://github.com/cdax) [cdax](https://github.com/SaurabhJha) who encourage people to contribute into open source community. # Support Please [open an issue](https://github.com/manjitkumar/drf-url-filters/issues/new) for support. diff --git a/example_app/views.py b/example_app/views.py index 8a5a145..6731314 100644 --- a/example_app/views.py +++ b/example_app/views.py @@ -43,21 +43,23 @@ def get_queryset(self): query parameters in the URL. """ query_params = self.request.query_params + url_params = self.kwargs + + # get queryset_filters from FilterMixin + queryset_filters = self.get_db_filters(url_params, query_params) + + # This dict will hold filter kwargs to pass in to Django ORM calls. + db_filters = queryset_filters['db_filters'] + + # This dict will hold exclude kwargs to pass in to Django ORM calls. + db_excludes = queryset_filters['db_excludes'] + + # fetch queryset from Players model queryset = Player.objects.prefetch_related( 'teams' # use prefetch_related to minimize db hits. ).all() - # This dict will hold filter kwargs to pass in to Django ORM calls. - db_filters = {} - - # update filters dict with incoming query params and then pass as - # **kwargs to queryset.filter() - db_filters.update( - self.get_queryset_filters( - query_params - ) - ) - return queryset.filter(**db_filters) + return queryset.filter(**db_filters).exclude(**db_excludes) class TeamsViewSet(FiltersMixin, viewsets.ModelViewSet): @@ -90,18 +92,21 @@ def get_queryset(self): Optionally restricts the queryset by filtering against query parameters in the URL. """ + query_params = self.request.query_params + url_params = self.kwargs + + # get queryset_filters from FilterMixin + queryset_filters = self.get_db_filters(url_params, query_params) + + # This dict will hold filter kwargs to pass in to Django ORM calls. + db_filters = queryset_filters['db_filters'] + + # This dict will hold exclude kwargs to pass in to Django ORM calls. + db_excludes = queryset_filters['db_excludes'] + queryset = Team.objects.prefetch_related( 'players' ).all() - # This dict will hold filter kwargs to pass in to Django ORM calls. - db_filters = {} - - # filters on mercant queryset - db_filters.update( - self.get_queryset_filters( - query_params - ) - ) - return queryset.filter(**db_filters) + return queryset.filter(**db_filters).exclude(**db_excludes) diff --git a/filters/mixins.py b/filters/mixins.py index 020f8da..79b13b0 100644 --- a/filters/mixins.py +++ b/filters/mixins.py @@ -9,38 +9,72 @@ class FiltersMixin(object): queryset. ''' - def get_queryset_filters(self, query_params, *args, **kwargs): + def __get_queryset_filters(self, query_params, *args, **kwargs): ''' get url_params and query_params and make db_filters to filter the queryset to the finest. - - [1] when a CSV is passed as value to a query params make a filter + [1] ~ sign is used to negated / exclude a filter. + [2] when a CSV is passed as value to a query params make a filter with 'IN' query. ''' filters = [] + excludes = [] + if getattr(self, 'filter_mappings', None) and query_params: filter_mappings = self.filter_mappings try: # check and raise 400_BAD_REQUEST for invalid query params - if getattr(self, 'filter_validation_schema', None): - query_params = self.filter_validation_schema( - self.request.query_params - ) - else: - raise Invalid('Validation is not configured for filters.') + filter_validation_schema = getattr( + self, + 'filter_validation_schema', + base_query_params_schema + ) + query_params = filter_validation_schema(query_params) except Invalid as inst: raise ParseError(detail=inst) - filters = [] - for query, value in query_params.items(): + for query, value in query_params.iteritems(): + # [1] ~ sign is used to exclude a filter. + is_exclude = '~' in query if query in self.filter_mappings and value: query = filter_mappings[query] - # [1] multiple options is filter values will execute as in query + # [2] multiple options is filter values will execute as `IN` query if isinstance(value, list): query += '__in' if value: - filters.append((query, value)) - filters = dict(filters) - return filters + if is_exclude: + excludes.append((query, value)) + else: + filters.append((query, value)) + + return dict(filters), dict(excludes) + + def __merge_query_params(url_params, query_params): + ''' + merges the url_params dict with query_params query dict and returns + the merged dict. + ''' + url_params = {} + for key in query_params: + url_params[key] = query_params.get(key) # get method on query-dict works differently than on dict. + return url_params + + def get_db_filters(self, url_params, query_params): + ''' + returns a dict with db_filters and db_excludes values which can be + used to apply on viewsets querysets. + ''' + + # merge url and query params + query_params = self.__merge_query_params(url_params, query_params) + + # get queryset filters + db_filters = self.__get_queryset_filters(query_params)[0] + db_excludes = self.__get_queryset_filters(query_params)[1] + + return { + 'db_filters': db_filters, + 'db_excludes': db_excludes, + } diff --git a/setup.py b/setup.py index 1a2e0ca..e138598 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir))) __name__ = 'drf-url-filters' -__version__ = '0.1.4' +__version__ = '0.2.0' __author__ = 'Manjit Kumar' __author_email__ = 'manjit1727@gmail.com' __url__ = 'https://github.com/manjitkumar/drf-url-filters'