Improve REST API with filters

This commit is contained in:
Yohann D'ANELLO 2020-03-11 11:15:03 +01:00
parent bc97eb1eb4
commit 417cd5da04
15 changed files with 78 additions and 36 deletions

View File

@ -1,7 +1,8 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import viewsets from rest_framework import viewsets
from rest_framework.filters import SearchFilter
from .serializers import ActivityTypeSerializer, ActivitySerializer, GuestSerializer from .serializers import ActivityTypeSerializer, ActivitySerializer, GuestSerializer
from ..models import ActivityType, Activity, Guest from ..models import ActivityType, Activity, Guest
@ -15,6 +16,8 @@ class ActivityTypeViewSet(viewsets.ModelViewSet):
""" """
queryset = ActivityType.objects.all() queryset = ActivityType.objects.all()
serializer_class = ActivityTypeSerializer serializer_class = ActivityTypeSerializer
filter_backends = [DjangoFilterBackend]
filterset_fields = ['name', 'can_invite', ]
class ActivityViewSet(viewsets.ModelViewSet): class ActivityViewSet(viewsets.ModelViewSet):
@ -25,6 +28,8 @@ class ActivityViewSet(viewsets.ModelViewSet):
""" """
queryset = Activity.objects.all() queryset = Activity.objects.all()
serializer_class = ActivitySerializer serializer_class = ActivitySerializer
filter_backends = [DjangoFilterBackend]
filterset_fields = ['name', 'description', 'activity_type', ]
class GuestViewSet(viewsets.ModelViewSet): class GuestViewSet(viewsets.ModelViewSet):
@ -35,3 +40,5 @@ class GuestViewSet(viewsets.ModelViewSet):
""" """
queryset = Guest.objects.all() queryset = Guest.objects.all()
serializer_class = GuestSerializer serializer_class = GuestSerializer
filter_backends = [SearchFilter]
search_fields = ['$name', ]

View File

@ -3,7 +3,9 @@
from django.conf.urls import url, include from django.conf.urls import url, include
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import routers, serializers, viewsets from rest_framework import routers, serializers, viewsets
from rest_framework.filters import SearchFilter
from activity.api.urls import register_activity_urls from activity.api.urls import register_activity_urls
from member.api.urls import register_members_urls from member.api.urls import register_members_urls
from note.api.urls import register_note_urls from note.api.urls import register_note_urls
@ -33,6 +35,9 @@ class UserViewSet(viewsets.ModelViewSet):
""" """
queryset = User.objects.all() queryset = User.objects.all()
serializer_class = UserSerializer serializer_class = UserSerializer
filter_backends = [DjangoFilterBackend, SearchFilter]
filterset_fields = ['id', 'username', 'first_name', 'last_name', 'email', 'is_superuser', 'is_staff', 'is_active', ]
search_fields = ['$username', '$first_name', '$last_name', ]
# Routers provide an easy way of automatically determining the URL conf. # Routers provide an easy way of automatically determining the URL conf.

View File

@ -1,6 +1,7 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import viewsets from rest_framework import viewsets
from .serializers import ChangelogSerializer from .serializers import ChangelogSerializer
@ -15,3 +16,5 @@ class ChangelogViewSet(viewsets.ModelViewSet):
""" """
queryset = Changelog.objects.all() queryset = Changelog.objects.all()
serializer_class = ChangelogSerializer serializer_class = ChangelogSerializer
filter_backends = [DjangoFilterBackend]
filterset_fields = ['model', 'action', "instance_pk", 'user', 'ip',]

View File

@ -81,7 +81,7 @@ def save_object(sender, instance, **kwargs):
model = instance.__class__ model = instance.__class__
fields = '__all__' fields = '__all__'
previous_json = JSONRenderer().render(CustomSerializer(previous).data).decode("UTF-8") previous_json = JSONRenderer().render(CustomSerializer(previous).data).decode("UTF-8") if previous else None
instance_json = JSONRenderer().render(CustomSerializer(instance).data).decode("UTF-8") instance_json = JSONRenderer().render(CustomSerializer(instance).data).decode("UTF-8")
if previous_json == instance_json: if previous_json == instance_json:

View File

@ -1,8 +0,0 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
app_name = 'logs'
# TODO User interface
urlpatterns = [
]

View File

@ -2,6 +2,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from rest_framework import viewsets from rest_framework import viewsets
from rest_framework.filters import SearchFilter
from .serializers import ProfileSerializer, ClubSerializer, RoleSerializer, MembershipSerializer from .serializers import ProfileSerializer, ClubSerializer, RoleSerializer, MembershipSerializer
from ..models import Profile, Club, Role, Membership from ..models import Profile, Club, Role, Membership
@ -25,6 +26,8 @@ class ClubViewSet(viewsets.ModelViewSet):
""" """
queryset = Club.objects.all() queryset = Club.objects.all()
serializer_class = ClubSerializer serializer_class = ClubSerializer
filter_backends = [SearchFilter]
search_fields = ['$name', ]
class RoleViewSet(viewsets.ModelViewSet): class RoleViewSet(viewsets.ModelViewSet):
@ -35,6 +38,8 @@ class RoleViewSet(viewsets.ModelViewSet):
""" """
queryset = Role.objects.all() queryset = Role.objects.all()
serializer_class = RoleSerializer serializer_class = RoleSerializer
filter_backends = [SearchFilter]
search_fields = ['$name', ]
class MembershipViewSet(viewsets.ModelViewSet): class MembershipViewSet(viewsets.ModelViewSet):

View File

@ -5,7 +5,8 @@ from rest_framework import serializers
from rest_polymorphic.serializers import PolymorphicSerializer from rest_polymorphic.serializers import PolymorphicSerializer
from ..models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias from ..models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias
from ..models.transactions import TransactionTemplate, Transaction, MembershipTransaction, TemplateCategory from ..models.transactions import TransactionTemplate, Transaction, MembershipTransaction, TemplateCategory, \
TemplateTransaction
class NoteSerializer(serializers.ModelSerializer): class NoteSerializer(serializers.ModelSerializer):
@ -111,6 +112,17 @@ class TransactionSerializer(serializers.ModelSerializer):
fields = '__all__' fields = '__all__'
class TemplateTransactionSerializer(serializers.ModelSerializer):
"""
REST API Serializer for Transactions.
The djangorestframework plugin will analyse the model `TemplateTransaction` and parse all fields in the API.
"""
class Meta:
model = TemplateTransaction
fields = '__all__'
class MembershipTransactionSerializer(serializers.ModelSerializer): class MembershipTransactionSerializer(serializers.ModelSerializer):
""" """
REST API Serializer for Membership transactions. REST API Serializer for Membership transactions.
@ -120,3 +132,11 @@ class MembershipTransactionSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = MembershipTransaction model = MembershipTransaction
fields = '__all__' fields = '__all__'
class TransactionPolymorphicSerializer(PolymorphicSerializer):
model_serializer_mapping = {
Transaction: TransactionSerializer,
TemplateTransaction: TemplateTransactionSerializer,
MembershipTransaction: MembershipTransactionSerializer,
}

View File

@ -2,7 +2,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from .views import NotePolymorphicViewSet, AliasViewSet, \ from .views import NotePolymorphicViewSet, AliasViewSet, \
TemplateCategoryViewSet, TransactionViewSet, TransactionTemplateViewSet, MembershipTransactionViewSet TemplateCategoryViewSet, TransactionViewSet, TransactionTemplateViewSet
def register_note_urls(router, path): def register_note_urls(router, path):
@ -15,4 +15,3 @@ def register_note_urls(router, path):
router.register(path + '/transaction/category', TemplateCategoryViewSet) router.register(path + '/transaction/category', TemplateCategoryViewSet)
router.register(path + '/transaction/transaction', TransactionViewSet) router.register(path + '/transaction/transaction', TransactionViewSet)
router.register(path + '/transaction/template', TransactionTemplateViewSet) router.register(path + '/transaction/template', TransactionTemplateViewSet)
router.register(path + '/transaction/membership', MembershipTransactionViewSet)

View File

@ -2,13 +2,15 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django.db.models import Q from django.db.models import Q
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import viewsets from rest_framework import viewsets
from rest_framework.filters import SearchFilter
from .serializers import NoteSerializer, NotePolymorphicSerializer, NoteClubSerializer, NoteSpecialSerializer, \ from .serializers import NoteSerializer, NotePolymorphicSerializer, NoteClubSerializer, NoteSpecialSerializer, \
NoteUserSerializer, AliasSerializer, \ NoteUserSerializer, AliasSerializer, \
TemplateCategorySerializer, TransactionTemplateSerializer, TransactionSerializer, MembershipTransactionSerializer TemplateCategorySerializer, TransactionTemplateSerializer, TransactionPolymorphicSerializer
from ..models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias from ..models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias
from ..models.transactions import TransactionTemplate, Transaction, MembershipTransaction, TemplateCategory from ..models.transactions import TransactionTemplate, Transaction, TemplateCategory
class NoteViewSet(viewsets.ModelViewSet): class NoteViewSet(viewsets.ModelViewSet):
@ -139,6 +141,8 @@ class TemplateCategoryViewSet(viewsets.ModelViewSet):
""" """
queryset = TemplateCategory.objects.all() queryset = TemplateCategory.objects.all()
serializer_class = TemplateCategorySerializer serializer_class = TemplateCategorySerializer
filter_backends = [SearchFilter]
search_fields = ['$name', ]
class TransactionTemplateViewSet(viewsets.ModelViewSet): class TransactionTemplateViewSet(viewsets.ModelViewSet):
@ -149,6 +153,8 @@ class TransactionTemplateViewSet(viewsets.ModelViewSet):
""" """
queryset = TransactionTemplate.objects.all() queryset = TransactionTemplate.objects.all()
serializer_class = TransactionTemplateSerializer serializer_class = TransactionTemplateSerializer
filter_backends = [DjangoFilterBackend]
filterset_fields = ['name', 'amount', 'display', 'category', ]
class TransactionViewSet(viewsets.ModelViewSet): class TransactionViewSet(viewsets.ModelViewSet):
@ -158,14 +164,6 @@ class TransactionViewSet(viewsets.ModelViewSet):
then render it on /api/note/transaction/transaction/ then render it on /api/note/transaction/transaction/
""" """
queryset = Transaction.objects.all() queryset = Transaction.objects.all()
serializer_class = TransactionSerializer serializer_class = TransactionPolymorphicSerializer
filter_backends = [SearchFilter]
search_fields = ['$reason', ]
class MembershipTransactionViewSet(viewsets.ModelViewSet):
"""
REST API View set.
The djangorestframework plugin will get all `MembershipTransaction` objects, serialize it to JSON with the given serializer,
then render it on /api/note/transaction/membership/
"""
queryset = MembershipTransaction.objects.all()
serializer_class = MembershipTransactionSerializer

View File

@ -3,7 +3,7 @@
"model": "note.note", "model": "note.note",
"pk": 1, "pk": 1,
"fields": { "fields": {
"polymorphic_ctype": 37, "polymorphic_ctype": 40,
"balance": 0, "balance": 0,
"is_active": true, "is_active": true,
"display_image": "", "display_image": "",
@ -14,7 +14,7 @@
"model": "note.note", "model": "note.note",
"pk": 2, "pk": 2,
"fields": { "fields": {
"polymorphic_ctype": 37, "polymorphic_ctype": 40,
"balance": 0, "balance": 0,
"is_active": true, "is_active": true,
"display_image": "", "display_image": "",
@ -25,7 +25,7 @@
"model": "note.note", "model": "note.note",
"pk": 3, "pk": 3,
"fields": { "fields": {
"polymorphic_ctype": 37, "polymorphic_ctype": 40,
"balance": 0, "balance": 0,
"is_active": true, "is_active": true,
"display_image": "", "display_image": "",
@ -36,7 +36,7 @@
"model": "note.note", "model": "note.note",
"pk": 4, "pk": 4,
"fields": { "fields": {
"polymorphic_ctype": 37, "polymorphic_ctype": 40,
"balance": 0, "balance": 0,
"is_active": true, "is_active": true,
"display_image": "", "display_image": "",
@ -47,7 +47,7 @@
"model": "note.note", "model": "note.note",
"pk": 5, "pk": 5,
"fields": { "fields": {
"polymorphic_ctype": 36, "polymorphic_ctype": 39,
"balance": 0, "balance": 0,
"is_active": true, "is_active": true,
"display_image": "", "display_image": "",
@ -58,7 +58,7 @@
"model": "note.note", "model": "note.note",
"pk": 6, "pk": 6,
"fields": { "fields": {
"polymorphic_ctype": 36, "polymorphic_ctype": 39,
"balance": 0, "balance": 0,
"is_active": true, "is_active": true,
"display_image": "", "display_image": "",

View File

@ -137,12 +137,14 @@ REST_FRAMEWORK = {
# or allow read-only access for unauthenticated users. # or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': [ 'DEFAULT_PERMISSION_CLASSES': [
# TODO Maybe replace it with our custom permissions system # TODO Maybe replace it with our custom permissions system
'rest_framework.permissions.DjangoModelPermissions' 'rest_framework.permissions.DjangoModelPermissions',
], ],
'DEFAULT_AUTHENTICATION_CLASSES': [ 'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication', 'rest_framework.authentication.TokenAuthentication',
] ],
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 20,
} }
# Internationalization # Internationalization

View File

@ -20,8 +20,7 @@ urlpatterns = [
path('accounts/', include('django.contrib.auth.urls')), path('accounts/', include('django.contrib.auth.urls')),
path('admin/doc/', include('django.contrib.admindocs.urls')), path('admin/doc/', include('django.contrib.admindocs.urls')),
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
path('logs/', include('logs.urls')), path('api/', include('api.urls')),
path('api/', include('api.urls')),
] ]
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View File

@ -0,0 +1,5 @@
{% load crispy_forms_tags %}
{% load i18n %}
<h2>{% trans "Field filters" %}</h2>
{% crispy filter.form %}

View File

@ -0,0 +1,6 @@
{% load i18n %}
<h2>{% trans "Field filters" %}</h2>
<form class="form" action="" method="get">
{{ filter.form.as_p }}
<button type="submit" class="btn btn-primary">{% trans "Submit" %}</button>
</form>

View File

@ -0,0 +1 @@
{% for widget in widget.subwidgets %}{% include widget.template_name %}{% if forloop.first %}-{% endif %}{% endfor %}