Skip to content

Commit 590ddf6

Browse files
committed
Add example app to demonstrate the use of drf-url-filters (#1)
* add example app to demonstrate the use of drf-url-filters * update README.md * update README.md * update docstrings for queryset
1 parent b88cc9b commit 590ddf6

File tree

11 files changed

+308
-48
lines changed

11 files changed

+308
-48
lines changed

README.md

Lines changed: 47 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ on incoming query params and their values. A beautiful python package voluptouos
77
# Quick start
88
---
99
**Installation**
10-
10+
1111
1. Download `drf-url-filters` app package from this git repo or can be isnatlled using python-pip like `pip install drf-url-filters`.
1212

1313
2. Add `filters` in INSTALLED_APPS in settings.py file of django project.
@@ -16,73 +16,96 @@ on incoming query params and their values. A beautiful python package voluptouos
1616

1717
1. Your View or ModelViewSet should inherit `FiltersMixin` from filters.mixins.FiltersMixin .
1818

19-
2. To apply filters using `drf-url-filters` we need to configure our view to have a dict mapping `filter_mappings` which converts incoming query parameters to query you want to make on the column name on the queryset.
19+
2. To apply filters using `drf-url-filters` we need to configure our view to have a dict mapping `filter_mappings` which converts incoming query parameters to query you want to make on the column name on the queryset.
2020

21-
```python
2221
# validations.py
22+
23+
```python
24+
25+
from filters.schema import base_query_param_schema
2326
from filters.validations import (
2427
CSVofIntegers,
2528
IntegerLike,
2629
DatetimeWithTZ
2730
)
2831

29-
from filters.schema import base_query_param_schema
30-
32+
# make a validation schema for players filter query params
3133
players_query_schema = base_query_param_schema.extend(
3234
{
33-
"id": IntegerLike()
35+
"id": IntegerLike(),
3436
"name": unicode,
35-
"team_id": CSVofIntegers(),
36-
"install_ts" :DatetimeWithTZ(),
37+
"team_id": CSVofIntegers(), # /?team_id=1,2,3
38+
"install_ts": DatetimeWithTZ(),
3739
"update_ts": DatetimeWithTZ(),
3840
}
3941
)
42+
```
4043

4144
# views.py
42-
from rest_framework import viewsets
43-
from filters.mixins import FiltersMixin
44-
from .models import Players
45-
from .serializers import PlayersViewSet
46-
from .validations import players_query_schema
45+
46+
```python
47+
48+
from rest_framework import (
49+
viewsets,
50+
filters,
51+
)
52+
53+
from .models import Player, Team
54+
from .serializers import PlayerSerializer, TeamSerializer
55+
from .pagination import ResultSetPagination
56+
from .validations import teams_query_schema, players_query_schema
57+
from filters.mixins import (
58+
FiltersMixin,
59+
)
4760

4861

4962
class PlayersViewSet(FiltersMixin, viewsets.ModelViewSet):
5063
"""
51-
This viewset automatically provides `list`, `create`,
52-
`retrieve`, `update` and `destroy` actions.
64+
This viewset automatically provides `list`, `create`, `retrieve`,
65+
`update` and `destroy` actions.
5366
"""
54-
serializer_class = PlayersSerializer
55-
pagination_class = PlayersPagination
56-
57-
# add a mapping of query_params to db_columns(queries)
67+
serializer_class = PlayerSerializer
68+
pagination_class = ResultSetPagination
69+
filter_backends = (filters.OrderingFilter,)
70+
ordering_fields = ('id', 'name', 'update_ts')
71+
ordering = ('id',)
72+
73+
# add a mapping of query_params to db_columns(queries)
5874
filter_mappings = {
5975
'id': 'id',
6076
'name': 'name__icontains',
61-
'team_id': 'teams', # considering a many-to-many related field
77+
'team_id': 'teams',
6278
'install_ts': 'install_ts',
6379
'update_ts': 'update_ts',
6480
'update_ts__gte': 'update_ts__gte',
6581
'update_ts__lte': 'update_ts__lte',
6682
}
83+
6784
# add validation on filters
6885
filter_validation_schema = players_query_schema
69-
86+
7087
def get_queryset(self):
7188
"""
7289
Optionally restricts the queryset by filtering against
7390
query parameters in the URL.
7491
"""
7592
query_params = self.request.query_params
76-
queryset = Players.objects.all()
93+
queryset = Player.objects.prefetch_related(
94+
'teams' # use prefetch_related to minimize db hits.
95+
).all()
96+
7797
# This dict will hold filter kwargs to pass in to Django ORM calls.
7898
db_filters = {}
79-
# update filters on players queryset using FiltersMixin.get_queryset_filters
80-
db_filters.update(
99+
100+
# update filters dict with incoming query params and then pass as
101+
# **kwargs to queryset.filter()
102+
db_filters.update(
81103
self.get_queryset_filters(
82104
query_params
83105
)
84106
)
85107
return queryset.filter(**db_filters)
108+
86109
```
87110

88111
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 a key value mapping from the `filter_mappings` dict.

docs/README.md

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ on incoming query params and their values. A beautiful python package voluptouos
77
# Quick start
88
---
99
**Installation**
10-
10+
1111
1. Download `drf-url-filters` app package from this git repo or can be isnatlled using python-pip like `pip install drf-url-filters`.
1212

1313
2. Add `filters` in INSTALLED_APPS in settings.py file of django project.
@@ -16,73 +16,96 @@ on incoming query params and their values. A beautiful python package voluptouos
1616

1717
1. Your View or ModelViewSet should inherit `FiltersMixin` from filters.mixins.FiltersMixin .
1818

19-
2. To apply filters using `drf-url-filters` we need to configure our view to have a dict mapping `filter_mappings` which converts incoming query parameters to query you want to make on the column name on the queryset.
19+
2. To apply filters using `drf-url-filters` we need to configure our view to have a dict mapping `filter_mappings` which converts incoming query parameters to query you want to make on the column name on the queryset.
2020

21-
```python
2221
# validations.py
22+
23+
```python
24+
25+
from filters.schema import base_query_param_schema
2326
from filters.validations import (
2427
CSVofIntegers,
2528
IntegerLike,
2629
DatetimeWithTZ
2730
)
2831

29-
from filters.schema import base_query_param_schema
30-
32+
# make a validation schema for players filter query params
3133
players_query_schema = base_query_param_schema.extend(
3234
{
33-
"id": IntegerLike()
35+
"id": IntegerLike(),
3436
"name": unicode,
35-
"team_id": CSVofIntegers(),
36-
"install_ts" :DatetimeWithTZ(),
37+
"team_id": CSVofIntegers(), # /?team_id=1,2,3
38+
"install_ts": DatetimeWithTZ(),
3739
"update_ts": DatetimeWithTZ(),
3840
}
3941
)
42+
```
4043

4144
# views.py
42-
from rest_framework import viewsets
43-
from filters.mixins import FiltersMixin
44-
from .models import Players
45-
from .serializers import PlayersViewSet
46-
from .validations import players_query_schema
45+
46+
```python
47+
48+
from rest_framework import (
49+
viewsets,
50+
filters,
51+
)
52+
53+
from .models import Player, Team
54+
from .serializers import PlayerSerializer, TeamSerializer
55+
from .pagination import ResultSetPagination
56+
from .validations import teams_query_schema, players_query_schema
57+
from filters.mixins import (
58+
FiltersMixin,
59+
)
4760

4861

4962
class PlayersViewSet(FiltersMixin, viewsets.ModelViewSet):
5063
"""
51-
This viewset automatically provides `list`, `create`,
52-
`retrieve`, `update` and `destroy` actions.
64+
This viewset automatically provides `list`, `create`, `retrieve`,
65+
`update` and `destroy` actions.
5366
"""
54-
serializer_class = PlayersSerializer
55-
pagination_class = PlayersPagination
56-
57-
# add a mapping of query_params to db_columns(queries)
67+
serializer_class = PlayerSerializer
68+
pagination_class = ResultSetPagination
69+
filter_backends = (filters.OrderingFilter,)
70+
ordering_fields = ('id', 'name', 'update_ts')
71+
ordering = ('id',)
72+
73+
# add a mapping of query_params to db_columns(queries)
5874
filter_mappings = {
5975
'id': 'id',
6076
'name': 'name__icontains',
61-
'team_id': 'teams', # considering a many-to-many related field
77+
'team_id': 'teams',
6278
'install_ts': 'install_ts',
6379
'update_ts': 'update_ts',
6480
'update_ts__gte': 'update_ts__gte',
6581
'update_ts__lte': 'update_ts__lte',
6682
}
83+
6784
# add validation on filters
6885
filter_validation_schema = players_query_schema
69-
86+
7087
def get_queryset(self):
7188
"""
7289
Optionally restricts the queryset by filtering against
7390
query parameters in the URL.
7491
"""
7592
query_params = self.request.query_params
76-
queryset = Players.objects.all()
93+
queryset = Player.objects.prefetch_related(
94+
'teams' # use prefetch_related to minimize db hits.
95+
).all()
96+
7797
# This dict will hold filter kwargs to pass in to Django ORM calls.
7898
db_filters = {}
79-
# update filters on players queryset using FiltersMixin.get_queryset_filters
99+
100+
# update filters dict with incoming query params and then pass as
101+
# **kwargs to queryset.filter()
80102
db_filters.update(
81103
self.get_queryset_filters(
82104
query_params
83105
)
84106
)
85107
return queryset.filter(**db_filters)
108+
86109
```
87110

88111
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 a key value mapping from the `filter_mappings` dict.

example_app/__init__.py

Whitespace-only changes.

example_app/migrations/__init__.py

Whitespace-only changes.

example_app/models.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from django.db import models
2+
3+
4+
class Player(models.Model):
5+
name = models.CharField(max_length=254)
6+
rating = models.FloatField(null=True)
7+
teams = models.ManyToManyField(
8+
'Team',
9+
through='Membership',
10+
through_fields=('team', 'player')
11+
)
12+
install_ts = models.DateTimeField(auto_now_add=True, blank=True)
13+
update_ts = models.DateTimeField(auto_now_add=True, blank=True)
14+
15+
16+
class Team(models.Model):
17+
name = models.CharField(max_length=254)
18+
rating = models.FloatField(null=True)
19+
players = models.ManyToManyField(
20+
'Player',
21+
through='Membership',
22+
through_fields=('team', 'player')
23+
)
24+
install_ts = models.DateTimeField(auto_now_add=True, blank=True)
25+
update_ts = models.DateTimeField(auto_now_add=True, blank=True)
26+
27+
28+
class Membership(models.Model):
29+
team = models.ForeignKey('Team')
30+
player = models.ForeignKey('Player')
31+
is_active = models.BooleanField(default=True)
32+
date_of_joining = models.DateTimeField()
33+
install_ts = models.DateTimeField(auto_now_add=True, blank=True)
34+
update_ts = models.DateTimeField(auto_now_add=True, blank=True)

example_app/pagination.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from rest_framework.pagination import LimitOffsetPagination
2+
3+
4+
class ResultSetPagination(LimitOffsetPagination):
5+
default_limit = 10
6+
max_limit = 100

example_app/serializers.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
2+
# django-drf imports
3+
from rest_framework import serializers
4+
5+
# app level imports
6+
from .models import Player, Team
7+
8+
9+
class PlayerSerializer(serializers.ModelSerializer):
10+
class Meta:
11+
model = Player
12+
fields = (
13+
'id', 'name', 'rating', 'teams',
14+
'install_ts', 'update_ts'
15+
)
16+
17+
18+
class TeamSerializer(serializers.ModelSerializer):
19+
class Meta:
20+
model = Team
21+
fields = (
22+
'id', 'name', 'rating', 'players',
23+
'install_ts', 'update_ts'
24+
)

example_app/urls.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from django.conf.urls import include, url
2+
3+
from rest_framework.routers import DefaultRouter
4+
from example_app.views import PlayersViewSet, TeamsViewSet
5+
6+
7+
# Create a router and register our viewsets with it.
8+
router = DefaultRouter()
9+
10+
router.register(r'players', PlayersViewSet, base_name='players')
11+
router.register(r'teams', TeamsViewSet, base_name='teams')
12+
13+
urlpatterns = [
14+
url(r'^api/', include(router.urls)),
15+
]

example_app/validations.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
2+
from filters.schema import base_query_param_schema
3+
from filters.validations import (
4+
CSVofIntegers,
5+
IntegerLike,
6+
DatetimeWithTZ
7+
)
8+
9+
# make a validation schema for players filter query params
10+
players_query_schema = base_query_param_schema.extend(
11+
{
12+
"id": IntegerLike(),
13+
"name": unicode,
14+
"team_id": CSVofIntegers(), # /?team_id=1,2,3
15+
"install_ts": DatetimeWithTZ(),
16+
"update_ts": DatetimeWithTZ(),
17+
}
18+
)
19+
20+
teams_query_schema = base_query_param_schema.extend(
21+
{
22+
"id": IntegerLike(),
23+
"name": unicode,
24+
"player_id": CSVofIntegers(), # /?player_id=1,2,3
25+
"install_ts": DatetimeWithTZ(),
26+
"update_ts": DatetimeWithTZ(),
27+
}
28+
)

0 commit comments

Comments
 (0)