From f4a665cb7f6035fda29bab7ab0f1b79c99bb996c Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 6 Feb 2020 23:29:17 +0100 Subject: [PATCH 01/33] API --- apps/api/urls.py | 29 +++++++++++++++++++++++++++++ note_kfet/settings/base.py | 10 ++++++++++ note_kfet/urls.py | 3 +++ requirements.txt | 1 + 4 files changed, 43 insertions(+) create mode 100644 apps/api/urls.py diff --git a/apps/api/urls.py b/apps/api/urls.py new file mode 100644 index 00000000..39d795ea --- /dev/null +++ b/apps/api/urls.py @@ -0,0 +1,29 @@ +# -*- mode: python; coding: utf-8 -*- +# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from django.conf.urls import url, include +from django.contrib.auth.models import User +from rest_framework import routers, serializers, viewsets + +# Serializers define the API representation. +class UserSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = User + fields = ['url', 'username', 'email', 'is_staff'] + +# ViewSets define the view behavior. +class UserViewSet(viewsets.ModelViewSet): + queryset = User.objects.all() + serializer_class = UserSerializer + +# Routers provide an easy way of automatically determining the URL conf. +router = routers.DefaultRouter() +router.register(r'users', UserViewSet) + +# Wire up our API using automatic URL routing. +# Additionally, we include login URLs for the browsable API. +urlpatterns = [ + url(r'^', include(router.urls)), + url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')) +] \ No newline at end of file diff --git a/note_kfet/settings/base.py b/note_kfet/settings/base.py index af1f474b..17283a95 100644 --- a/note_kfet/settings/base.py +++ b/note_kfet/settings/base.py @@ -50,6 +50,8 @@ INSTALLED_APPS = [ 'django.contrib.sites', 'django.contrib.messages', 'django.contrib.staticfiles', + # API + 'rest_framework', # Note apps 'activity', @@ -117,6 +119,14 @@ AUTHENTICATION_BACKENDS = ( 'guardian.backends.ObjectPermissionBackend', ) +REST_FRAMEWORK = { + # Use Django's standard `django.contrib.auth` permissions, + # or allow read-only access for unauthenticated users. + 'DEFAULT_PERMISSION_CLASSES': [ + 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly' + ] +} + ANONYMOUS_USER_NAME = None # Disable guardian anonymous user GUARDIAN_GET_CONTENT_TYPE = 'polymorphic.contrib.guardian.get_polymorphic_base_content_type' diff --git a/note_kfet/urls.py b/note_kfet/urls.py index 88bb6bb9..9fd63eef 100644 --- a/note_kfet/urls.py +++ b/note_kfet/urls.py @@ -19,4 +19,7 @@ urlpatterns = [ path('accounts/', include('django.contrib.auth.urls')), path('admin/doc/', include('django.contrib.admindocs.urls')), path('admin/', admin.site.urls), + + # Include Django REST API + path('api/', include('api.urls')), ] diff --git a/requirements.txt b/requirements.txt index 39b32fdf..2f1aaf34 100644 --- a/requirements.txt +++ b/requirements.txt @@ -21,3 +21,4 @@ requests-oauthlib==1.2.0 six==1.12.0 sqlparse==0.3.0 urllib3==1.25.3 +djangorestframework==3.11.0 From edc428a05e0045c6a449b16db24c72882e18e3e3 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 6 Feb 2020 23:43:56 +0100 Subject: [PATCH 02/33] Add member models in API --- apps/api/urls.py | 9 +++++--- apps/member/serializers.py | 46 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 apps/member/serializers.py diff --git a/apps/api/urls.py b/apps/api/urls.py index 39d795ea..bc7a68c7 100644 --- a/apps/api/urls.py +++ b/apps/api/urls.py @@ -5,14 +5,13 @@ from django.conf.urls import url, include from django.contrib.auth.models import User from rest_framework import routers, serializers, viewsets +from member.serializers import ProfileViewSet, ClubViewSet, RoleViewSet, MembershipViewSet -# Serializers define the API representation. class UserSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = User - fields = ['url', 'username', 'email', 'is_staff'] + fields = ['url', 'username', 'first_name', 'last_name', 'email', 'is_staff'] -# ViewSets define the view behavior. class UserViewSet(viewsets.ModelViewSet): queryset = User.objects.all() serializer_class = UserSerializer @@ -20,6 +19,10 @@ class UserViewSet(viewsets.ModelViewSet): # Routers provide an easy way of automatically determining the URL conf. router = routers.DefaultRouter() router.register(r'users', UserViewSet) +router.register(r'profiles', ProfileViewSet) +router.register(r'clubs', ClubViewSet) +router.register(r'roles', RoleViewSet) +router.register(r'memberships', MembershipViewSet) # Wire up our API using automatic URL routing. # Additionally, we include login URLs for the browsable API. diff --git a/apps/member/serializers.py b/apps/member/serializers.py new file mode 100644 index 00000000..f16f664d --- /dev/null +++ b/apps/member/serializers.py @@ -0,0 +1,46 @@ +from .models import Profile, Club, Role, Membership +from rest_framework import serializers, viewsets + +class ProfileSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Profile + fields = '__all__' + + +class ProfileViewSet(viewsets.ModelViewSet): + queryset = Profile.objects.all() + serializer_class = ProfileSerializer + + +class ClubSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Club + fields = '__all__' + + +class ClubViewSet(viewsets.ModelViewSet): + queryset = Club.objects.all() + serializer_class = ClubSerializer + + +class RoleSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Role + fields = '__all__' + + +class RoleViewSet(viewsets.ModelViewSet): + queryset = Role.objects.all() + serializer_class = RoleSerializer + + +class MembershipSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Membership + fields = '__all__' + + +class MembershipViewSet(viewsets.ModelViewSet): + queryset = Membership.objects.all() + serializer_class = MembershipSerializer + From 8e2b0688b56ace904726ac54ebff4394116693dc Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 6 Feb 2020 23:44:59 +0100 Subject: [PATCH 03/33] Add member models in API --- apps/api/urls.py | 2 +- apps/member/serializers.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/api/urls.py b/apps/api/urls.py index bc7a68c7..f18b7895 100644 --- a/apps/api/urls.py +++ b/apps/api/urls.py @@ -1,5 +1,5 @@ # -*- mode: python; coding: utf-8 -*- -# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later from django.conf.urls import url, include diff --git a/apps/member/serializers.py b/apps/member/serializers.py index f16f664d..cb6bcdce 100644 --- a/apps/member/serializers.py +++ b/apps/member/serializers.py @@ -1,3 +1,7 @@ +# -*- mode: python; coding: utf-8 -*- +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + from .models import Profile, Club, Role, Membership from rest_framework import serializers, viewsets From 5de93066db80b773ceb39d2185bd9dac29bfdc7d Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 6 Feb 2020 23:49:33 +0100 Subject: [PATCH 04/33] Add activity models in API --- apps/activity/serializers.py | 38 ++++++++++++++++++++++++++++++++++++ apps/api/urls.py | 14 +++++++++---- 2 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 apps/activity/serializers.py diff --git a/apps/activity/serializers.py b/apps/activity/serializers.py new file mode 100644 index 00000000..cca55751 --- /dev/null +++ b/apps/activity/serializers.py @@ -0,0 +1,38 @@ +# -*- mode: python; coding: utf-8 -*- +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from .models import ActivityType, Activity, Guest +from rest_framework import serializers, viewsets + +class ActivityTypeSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = ActivityType + fields = '__all__' + + +class ActivityTypeViewSet(viewsets.ModelViewSet): + queryset = ActivityType.objects.all() + serializer_class = ActivityTypeSerializer + + +class ActivitySerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Activity + fields = '__all__' + + +class ActivityViewSet(viewsets.ModelViewSet): + queryset = Activity.objects.all() + serializer_class = ActivitySerializer + + +class GuestSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Guest + fields = '__all__' + + +class GuestViewSet(viewsets.ModelViewSet): + queryset = Guest.objects.all() + serializer_class = GuestSerializer diff --git a/apps/api/urls.py b/apps/api/urls.py index f18b7895..97e4ebe7 100644 --- a/apps/api/urls.py +++ b/apps/api/urls.py @@ -6,6 +6,7 @@ from django.conf.urls import url, include from django.contrib.auth.models import User from rest_framework import routers, serializers, viewsets from member.serializers import ProfileViewSet, ClubViewSet, RoleViewSet, MembershipViewSet +from activity.serializers import ActivityTypeViewSet, ActivityViewSet, GuestViewSet class UserSerializer(serializers.HyperlinkedModelSerializer): class Meta: @@ -19,10 +20,15 @@ class UserViewSet(viewsets.ModelViewSet): # Routers provide an easy way of automatically determining the URL conf. router = routers.DefaultRouter() router.register(r'users', UserViewSet) -router.register(r'profiles', ProfileViewSet) -router.register(r'clubs', ClubViewSet) -router.register(r'roles', RoleViewSet) -router.register(r'memberships', MembershipViewSet) + +router.register(r'members/profiles', ProfileViewSet) +router.register(r'members/clubs', ClubViewSet) +router.register(r'members/roles', RoleViewSet) +router.register(r'members/memberships', MembershipViewSet) + +router.register(r'activity/activity_types', ActivityTypeViewSet) +router.register(r'activity/activities', ActivityViewSet) +router.register(r'activity/guests', GuestViewSet) # Wire up our API using automatic URL routing. # Additionally, we include login URLs for the browsable API. From 92fc92ba4080a806bd3900d03e561b7f7b54e245 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 7 Feb 2020 00:12:00 +0100 Subject: [PATCH 05/33] Add note models in API --- apps/api/urls.py | 27 +++++++++---- apps/note/serializers.py | 83 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 8 deletions(-) create mode 100644 apps/note/serializers.py diff --git a/apps/api/urls.py b/apps/api/urls.py index 97e4ebe7..cca07d80 100644 --- a/apps/api/urls.py +++ b/apps/api/urls.py @@ -7,11 +7,13 @@ from django.contrib.auth.models import User from rest_framework import routers, serializers, viewsets from member.serializers import ProfileViewSet, ClubViewSet, RoleViewSet, MembershipViewSet from activity.serializers import ActivityTypeViewSet, ActivityViewSet, GuestViewSet +from note.serializers import NoteViewSet, NoteClubViewSet, NoteUserViewSet, NoteSpecialViewSet, \ + TransactionViewSet, TransactionTemplateViewSet, MembershipTransactionViewSet class UserSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = User - fields = ['url', 'username', 'first_name', 'last_name', 'email', 'is_staff'] + fields = ('username', 'first_name', 'last_name', 'email', 'is_staff',) class UserViewSet(viewsets.ModelViewSet): queryset = User.objects.all() @@ -21,14 +23,23 @@ class UserViewSet(viewsets.ModelViewSet): router = routers.DefaultRouter() router.register(r'users', UserViewSet) -router.register(r'members/profiles', ProfileViewSet) -router.register(r'members/clubs', ClubViewSet) -router.register(r'members/roles', RoleViewSet) -router.register(r'members/memberships', MembershipViewSet) +router.register(r'members/profile', ProfileViewSet) +router.register(r'members/club', ClubViewSet) +router.register(r'members/role', RoleViewSet) +router.register(r'members/membership', MembershipViewSet) -router.register(r'activity/activity_types', ActivityTypeViewSet) -router.register(r'activity/activities', ActivityViewSet) -router.register(r'activity/guests', GuestViewSet) +router.register(r'activity/activity', ActivityViewSet) +router.register(r'activity/type', ActivityTypeViewSet) +router.register(r'activity/guest', GuestViewSet) + +router.register(r'note/note', NoteViewSet) +router.register(r'note/club', NoteClubViewSet) +router.register(r'note/user', NoteUserViewSet) +router.register(r'note/special', NoteSpecialViewSet) + +router.register(r'note/transaction', TransactionViewSet) +router.register(r'note/transaction/template', TransactionTemplateViewSet) +router.register(r'note/transaction/membership', MembershipTransactionViewSet) # Wire up our API using automatic URL routing. # Additionally, we include login URLs for the browsable API. diff --git a/apps/note/serializers.py b/apps/note/serializers.py new file mode 100644 index 00000000..39ce835a --- /dev/null +++ b/apps/note/serializers.py @@ -0,0 +1,83 @@ +# -*- mode: python; coding: utf-8 -*- +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from .models.notes import Note, NoteClub, NoteSpecial, NoteUser +from .models.transactions import TransactionTemplate, Transaction, MembershipTransaction +from rest_framework import serializers, viewsets + +class NoteSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Note + fields = ('balance', 'is_active', 'display_image', 'created_at',) + + +class NoteViewSet(viewsets.ModelViewSet): + queryset = Note.objects.all() + serializer_class = NoteSerializer + + +class NoteClubSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = NoteClub + fields = ('balance', 'is_active', 'display_image', 'created_at', 'club',) + + +class NoteClubViewSet(viewsets.ModelViewSet): + queryset = NoteClub.objects.all() + serializer_class = NoteClubSerializer + + +class NoteSpecialSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = NoteSpecial + fields = ('balance', 'is_active', 'display_image', 'created_at', 'club', 'special_type',) + + +class NoteSpecialViewSet(viewsets.ModelViewSet): + queryset = NoteSpecial.objects.all() + serializer_class = NoteSpecialSerializer + + +class NoteUserSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = NoteUser + fields = ('balance', 'is_active', 'display_image', 'created_at', 'user',) + + +class NoteUserViewSet(viewsets.ModelViewSet): + queryset = NoteUser.objects.all() + serializer_class = NoteUserSerializer + + +class TransactionTemplateSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = TransactionTemplate + fields = '__all__' + + +class TransactionTemplateViewSet(viewsets.ModelViewSet): + queryset = TransactionTemplate.objects.all() + serializer_class = TransactionTemplateSerializer + + +class TransactionSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Transaction + fields = '__all__' + + +class TransactionViewSet(viewsets.ModelViewSet): + queryset = Transaction.objects.all() + serializer_class = TransactionSerializer + + +class MembershipTransactionSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = MembershipTransaction + fields = '__all__' + + +class MembershipTransactionViewSet(viewsets.ModelViewSet): + queryset = MembershipTransaction.objects.all() + serializer_class = MembershipTransactionSerializer From c28884114c0049ff4d326569bd1ca6274f6451fd Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 7 Feb 2020 00:29:04 +0100 Subject: [PATCH 06/33] Add some doc --- apps/activity/serializers.py | 27 ++++++++++++++++ apps/api/urls.py | 14 +++++++- apps/member/serializers.py | 36 +++++++++++++++++++++ apps/note/serializers.py | 63 ++++++++++++++++++++++++++++++++++++ 4 files changed, 139 insertions(+), 1 deletion(-) diff --git a/apps/activity/serializers.py b/apps/activity/serializers.py index cca55751..4d79426b 100644 --- a/apps/activity/serializers.py +++ b/apps/activity/serializers.py @@ -6,33 +6,60 @@ from .models import ActivityType, Activity, Guest from rest_framework import serializers, viewsets class ActivityTypeSerializer(serializers.HyperlinkedModelSerializer): + """ + REST API Serializer for Activity types. + The djangorestframework plugin will analyse the model `ActivityType` and parse all fields in the API. + """ class Meta: model = ActivityType fields = '__all__' class ActivityTypeViewSet(viewsets.ModelViewSet): + """ + REST API View set. + The djangorestframework plugin will get all `ActivityType` objects, serialize it to JSON with the given serializer, + then render it on /api/activity/type/ + """ queryset = ActivityType.objects.all() serializer_class = ActivityTypeSerializer class ActivitySerializer(serializers.HyperlinkedModelSerializer): + """ + REST API Serializer for Activities. + The djangorestframework plugin will analyse the model `Activity` and parse all fields in the API. + """ class Meta: model = Activity fields = '__all__' class ActivityViewSet(viewsets.ModelViewSet): + """ + REST API View set. + The djangorestframework plugin will get all `Activity` objects, serialize it to JSON with the given serializer, + then render it on /api/activity/activity/ + """ queryset = Activity.objects.all() serializer_class = ActivitySerializer class GuestSerializer(serializers.HyperlinkedModelSerializer): + """ + REST API Serializer for Guests. + The djangorestframework plugin will analyse the model `Guest` and parse all fields in the API. + """ class Meta: model = Guest fields = '__all__' class GuestViewSet(viewsets.ModelViewSet): + """ + REST API View set. + The djangorestframework plugin will get all `Guest` objects, serialize it to JSON with the given serializer, + then render it on /api/activity/guest/ + """ queryset = Guest.objects.all() serializer_class = GuestSerializer diff --git a/apps/api/urls.py b/apps/api/urls.py index cca07d80..c149b3f4 100644 --- a/apps/api/urls.py +++ b/apps/api/urls.py @@ -11,11 +11,20 @@ from note.serializers import NoteViewSet, NoteClubViewSet, NoteUserViewSet, Note TransactionViewSet, TransactionTemplateViewSet, MembershipTransactionViewSet class UserSerializer(serializers.HyperlinkedModelSerializer): + """ + REST API Serializer for Users. + The djangorestframework plugin will analyse the model `User` and parse all fields in the API. + """ class Meta: model = User fields = ('username', 'first_name', 'last_name', 'email', 'is_staff',) class UserViewSet(viewsets.ModelViewSet): + """ + REST API View set. + The djangorestframework plugin will get all `User` objects, serialize it to JSON with the given serializer, + then render it on /api/users/ + """ queryset = User.objects.all() serializer_class = UserSerializer @@ -23,21 +32,24 @@ class UserViewSet(viewsets.ModelViewSet): router = routers.DefaultRouter() router.register(r'users', UserViewSet) +# Routers for members app router.register(r'members/profile', ProfileViewSet) router.register(r'members/club', ClubViewSet) router.register(r'members/role', RoleViewSet) router.register(r'members/membership', MembershipViewSet) +# Routers for activity app router.register(r'activity/activity', ActivityViewSet) router.register(r'activity/type', ActivityTypeViewSet) router.register(r'activity/guest', GuestViewSet) +# Routers for note app router.register(r'note/note', NoteViewSet) router.register(r'note/club', NoteClubViewSet) router.register(r'note/user', NoteUserViewSet) router.register(r'note/special', NoteSpecialViewSet) -router.register(r'note/transaction', TransactionViewSet) +router.register(r'note/transaction/transaction', TransactionViewSet) router.register(r'note/transaction/template', TransactionTemplateViewSet) router.register(r'note/transaction/membership', MembershipTransactionViewSet) diff --git a/apps/member/serializers.py b/apps/member/serializers.py index cb6bcdce..2ead2c97 100644 --- a/apps/member/serializers.py +++ b/apps/member/serializers.py @@ -6,45 +6,81 @@ from .models import Profile, Club, Role, Membership from rest_framework import serializers, viewsets class ProfileSerializer(serializers.HyperlinkedModelSerializer): + """ + REST API Serializer for Profiles. + The djangorestframework plugin will analyse the model `Profile` and parse all fields in the API. + """ class Meta: model = Profile fields = '__all__' class ProfileViewSet(viewsets.ModelViewSet): + """ + REST API View set. + The djangorestframework plugin will get all `Profile` objects, serialize it to JSON with the given serializer, + then render it on /api/members/profile/ + """ queryset = Profile.objects.all() serializer_class = ProfileSerializer class ClubSerializer(serializers.HyperlinkedModelSerializer): + """ + REST API Serializer for Clubs. + The djangorestframework plugin will analyse the model `Club` and parse all fields in the API. + """ class Meta: model = Club fields = '__all__' class ClubViewSet(viewsets.ModelViewSet): + """ + REST API View set. + The djangorestframework plugin will get all `Club` objects, serialize it to JSON with the given serializer, + then render it on /api/members/club/ + """ queryset = Club.objects.all() serializer_class = ClubSerializer class RoleSerializer(serializers.HyperlinkedModelSerializer): + """ + REST API Serializer for Roles. + The djangorestframework plugin will analyse the model `Role` and parse all fields in the API. + """ class Meta: model = Role fields = '__all__' class RoleViewSet(viewsets.ModelViewSet): + """ + REST API View set. + The djangorestframework plugin will get all `Role` objects, serialize it to JSON with the given serializer, + then render it on /api/members/role/ + """ queryset = Role.objects.all() serializer_class = RoleSerializer class MembershipSerializer(serializers.HyperlinkedModelSerializer): + """ + REST API Serializer for Memberships. + The djangorestframework plugin will analyse the model `Memberships` and parse all fields in the API. + """ class Meta: model = Membership fields = '__all__' class MembershipViewSet(viewsets.ModelViewSet): + """ + REST API View set. + The djangorestframework plugin will get all `Membership` objects, serialize it to JSON with the given serializer, + then render it on /api/members/membership/ + """ queryset = Membership.objects.all() serializer_class = MembershipSerializer diff --git a/apps/note/serializers.py b/apps/note/serializers.py index 39ce835a..db63ea6b 100644 --- a/apps/note/serializers.py +++ b/apps/note/serializers.py @@ -7,77 +7,140 @@ from .models.transactions import TransactionTemplate, Transaction, MembershipTra from rest_framework import serializers, viewsets class NoteSerializer(serializers.HyperlinkedModelSerializer): + """ + REST API Serializer for Notes. + The djangorestframework plugin will analyse the model `Note` and parse all fields in the API. + """ class Meta: model = Note fields = ('balance', 'is_active', 'display_image', 'created_at',) class NoteViewSet(viewsets.ModelViewSet): + """ + REST API View set. + The djangorestframework plugin will get all `Note` objects, serialize it to JSON with the given serializer, + then render it on /api/note/note/ + """ queryset = Note.objects.all() serializer_class = NoteSerializer class NoteClubSerializer(serializers.HyperlinkedModelSerializer): + """ + REST API Serializer for Club's notes. + The djangorestframework plugin will analyse the model `NoteClub` and parse all fields in the API. + """ class Meta: model = NoteClub fields = ('balance', 'is_active', 'display_image', 'created_at', 'club',) class NoteClubViewSet(viewsets.ModelViewSet): + """ + REST API View set. + The djangorestframework plugin will get all `NoteClub` objects, serialize it to JSON with the given serializer, + then render it on /api/note/club/ + """ queryset = NoteClub.objects.all() serializer_class = NoteClubSerializer class NoteSpecialSerializer(serializers.HyperlinkedModelSerializer): + """ + REST API Serializer for special notes. + The djangorestframework plugin will analyse the model `NoteSpecial` and parse all fields in the API. + """ class Meta: model = NoteSpecial fields = ('balance', 'is_active', 'display_image', 'created_at', 'club', 'special_type',) class NoteSpecialViewSet(viewsets.ModelViewSet): + """ + REST API View set. + The djangorestframework plugin will get all `NoteSpecial` objects, serialize it to JSON with the given serializer, + then render it on /api/note/special/ + """ queryset = NoteSpecial.objects.all() serializer_class = NoteSpecialSerializer class NoteUserSerializer(serializers.HyperlinkedModelSerializer): + """ + REST API Serializer for User's notes. + The djangorestframework plugin will analyse the model `NoteUser` and parse all fields in the API. + """ class Meta: model = NoteUser fields = ('balance', 'is_active', 'display_image', 'created_at', 'user',) class NoteUserViewSet(viewsets.ModelViewSet): + """ + REST API View set. + The djangorestframework plugin will get all `NoteUser` objects, serialize it to JSON with the given serializer, + then render it on /api/note/user/ + """ queryset = NoteUser.objects.all() serializer_class = NoteUserSerializer class TransactionTemplateSerializer(serializers.HyperlinkedModelSerializer): + """ + REST API Serializer for Transaction templates. + The djangorestframework plugin will analyse the model `TransactionTemplate` and parse all fields in the API. + """ class Meta: model = TransactionTemplate fields = '__all__' class TransactionTemplateViewSet(viewsets.ModelViewSet): + """ + REST API View set. + The djangorestframework plugin will get all `TransactionTemplate` objects, serialize it to JSON with the given serializer, + then render it on /api/note/transaction/template/ + """ queryset = TransactionTemplate.objects.all() serializer_class = TransactionTemplateSerializer class TransactionSerializer(serializers.HyperlinkedModelSerializer): + """ + REST API Serializer for Transactions. + The djangorestframework plugin will analyse the model `Transaction` and parse all fields in the API. + """ class Meta: model = Transaction fields = '__all__' class TransactionViewSet(viewsets.ModelViewSet): + """ + REST API View set. + The djangorestframework plugin will get all `Transaction` objects, serialize it to JSON with the given serializer, + then render it on /api/note/transaction/transaction/ + """ queryset = Transaction.objects.all() serializer_class = TransactionSerializer class MembershipTransactionSerializer(serializers.HyperlinkedModelSerializer): + """ + REST API Serializer for Membership transactions. + The djangorestframework plugin will analyse the model `MembershipTransaction` and parse all fields in the API. + """ class Meta: model = MembershipTransaction fields = '__all__' 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 From 5633f0123d7a881d6f2233a64ea242d0f51e6b93 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 7 Feb 2020 17:02:07 +0100 Subject: [PATCH 07/33] Clean code --- apps/api/activity/serializers.py | 35 ++++++++ apps/api/activity/urls.py | 14 ++++ .../serializers.py => api/activity/views.py} | 37 +-------- apps/api/members/serializers.py | 46 +++++++++++ apps/api/members/urls.py | 15 ++++ .../serializers.py => api/members/views.py} | 45 +---------- apps/{ => api}/note/serializers.py | 67 +--------------- apps/api/note/urls.py | 20 +++++ apps/api/note/views.py | 79 +++++++++++++++++++ apps/api/urls.py | 25 ++---- 10 files changed, 226 insertions(+), 157 deletions(-) create mode 100644 apps/api/activity/serializers.py create mode 100644 apps/api/activity/urls.py rename apps/{activity/serializers.py => api/activity/views.py} (51%) create mode 100644 apps/api/members/serializers.py create mode 100644 apps/api/members/urls.py rename apps/{member/serializers.py => api/members/views.py} (53%) rename apps/{ => api}/note/serializers.py (55%) create mode 100644 apps/api/note/urls.py create mode 100644 apps/api/note/views.py diff --git a/apps/api/activity/serializers.py b/apps/api/activity/serializers.py new file mode 100644 index 00000000..4d1bc449 --- /dev/null +++ b/apps/api/activity/serializers.py @@ -0,0 +1,35 @@ +# -*- mode: python; coding: utf-8 -*- +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from activity.models import ActivityType, Activity, Guest +from rest_framework import serializers + +class ActivityTypeSerializer(serializers.HyperlinkedModelSerializer): + """ + REST API Serializer for Activity types. + The djangorestframework plugin will analyse the model `ActivityType` and parse all fields in the API. + """ + class Meta: + model = ActivityType + fields = '__all__' + + +class ActivitySerializer(serializers.HyperlinkedModelSerializer): + """ + REST API Serializer for Activities. + The djangorestframework plugin will analyse the model `Activity` and parse all fields in the API. + """ + class Meta: + model = Activity + fields = '__all__' + + +class GuestSerializer(serializers.HyperlinkedModelSerializer): + """ + REST API Serializer for Guests. + The djangorestframework plugin will analyse the model `Guest` and parse all fields in the API. + """ + class Meta: + model = Guest + fields = '__all__' diff --git a/apps/api/activity/urls.py b/apps/api/activity/urls.py new file mode 100644 index 00000000..ca0fab43 --- /dev/null +++ b/apps/api/activity/urls.py @@ -0,0 +1,14 @@ +# -*- mode: python; coding: utf-8 -*- +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from .views import ActivityTypeViewSet, ActivityViewSet, GuestViewSet + + +def register_activity_urls(router, path): + """ + Configure router for Activity REST API. + """ + router.register(path + r'activity', ActivityViewSet) + router.register(path + r'type', ActivityTypeViewSet) + router.register(path + r'guest', GuestViewSet) diff --git a/apps/activity/serializers.py b/apps/api/activity/views.py similarity index 51% rename from apps/activity/serializers.py rename to apps/api/activity/views.py index 4d79426b..af2f0fe1 100644 --- a/apps/activity/serializers.py +++ b/apps/api/activity/views.py @@ -2,18 +2,9 @@ # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later -from .models import ActivityType, Activity, Guest -from rest_framework import serializers, viewsets - -class ActivityTypeSerializer(serializers.HyperlinkedModelSerializer): - """ - REST API Serializer for Activity types. - The djangorestframework plugin will analyse the model `ActivityType` and parse all fields in the API. - """ - class Meta: - model = ActivityType - fields = '__all__' - +from activity.models import ActivityType, Activity, Guest +from .serializers import ActivityTypeSerializer, ActivitySerializer, GuestSerializer +from rest_framework import viewsets class ActivityTypeViewSet(viewsets.ModelViewSet): """ @@ -25,16 +16,6 @@ class ActivityTypeViewSet(viewsets.ModelViewSet): serializer_class = ActivityTypeSerializer -class ActivitySerializer(serializers.HyperlinkedModelSerializer): - """ - REST API Serializer for Activities. - The djangorestframework plugin will analyse the model `Activity` and parse all fields in the API. - """ - class Meta: - model = Activity - fields = '__all__' - - class ActivityViewSet(viewsets.ModelViewSet): """ REST API View set. @@ -45,16 +26,6 @@ class ActivityViewSet(viewsets.ModelViewSet): serializer_class = ActivitySerializer -class GuestSerializer(serializers.HyperlinkedModelSerializer): - """ - REST API Serializer for Guests. - The djangorestframework plugin will analyse the model `Guest` and parse all fields in the API. - """ - class Meta: - model = Guest - fields = '__all__' - - class GuestViewSet(viewsets.ModelViewSet): """ REST API View set. @@ -62,4 +33,4 @@ class GuestViewSet(viewsets.ModelViewSet): then render it on /api/activity/guest/ """ queryset = Guest.objects.all() - serializer_class = GuestSerializer + serializer_class = GuestSerializer \ No newline at end of file diff --git a/apps/api/members/serializers.py b/apps/api/members/serializers.py new file mode 100644 index 00000000..98e2ba15 --- /dev/null +++ b/apps/api/members/serializers.py @@ -0,0 +1,46 @@ +# -*- mode: python; coding: utf-8 -*- +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from member.models import Profile, Club, Role, Membership +from rest_framework import serializers, viewsets + + +class ProfileSerializer(serializers.HyperlinkedModelSerializer): + """ + REST API Serializer for Profiles. + The djangorestframework plugin will analyse the model `Profile` and parse all fields in the API. + """ + class Meta: + model = Profile + fields = '__all__' + + +class ClubSerializer(serializers.HyperlinkedModelSerializer): + """ + REST API Serializer for Clubs. + The djangorestframework plugin will analyse the model `Club` and parse all fields in the API. + """ + class Meta: + model = Club + fields = '__all__' + + +class RoleSerializer(serializers.HyperlinkedModelSerializer): + """ + REST API Serializer for Roles. + The djangorestframework plugin will analyse the model `Role` and parse all fields in the API. + """ + class Meta: + model = Role + fields = '__all__' + + +class MembershipSerializer(serializers.HyperlinkedModelSerializer): + """ + REST API Serializer for Memberships. + The djangorestframework plugin will analyse the model `Memberships` and parse all fields in the API. + """ + class Meta: + model = Membership + fields = '__all__' diff --git a/apps/api/members/urls.py b/apps/api/members/urls.py new file mode 100644 index 00000000..ffce1cbd --- /dev/null +++ b/apps/api/members/urls.py @@ -0,0 +1,15 @@ +# -*- mode: python; coding: utf-8 -*- +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from .views import ProfileViewSet, ClubViewSet, RoleViewSet, MembershipViewSet + + +def register_members_urls(router, path): + """ + Configure router for Member REST API. + """ + router.register(path + r'profile', ProfileViewSet) + router.register(path + r'club', ClubViewSet) + router.register(path + r'role', RoleViewSet) + router.register(path + r'membership', MembershipViewSet) diff --git a/apps/member/serializers.py b/apps/api/members/views.py similarity index 53% rename from apps/member/serializers.py rename to apps/api/members/views.py index 2ead2c97..9755151e 100644 --- a/apps/member/serializers.py +++ b/apps/api/members/views.py @@ -2,17 +2,9 @@ # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later -from .models import Profile, Club, Role, Membership -from rest_framework import serializers, viewsets - -class ProfileSerializer(serializers.HyperlinkedModelSerializer): - """ - REST API Serializer for Profiles. - The djangorestframework plugin will analyse the model `Profile` and parse all fields in the API. - """ - class Meta: - model = Profile - fields = '__all__' +from member.models import Profile, Club, Role, Membership +from .serializers import ProfileSerializer, ClubSerializer, RoleSerializer, MembershipSerializer +from rest_framework import viewsets class ProfileViewSet(viewsets.ModelViewSet): @@ -25,16 +17,6 @@ class ProfileViewSet(viewsets.ModelViewSet): serializer_class = ProfileSerializer -class ClubSerializer(serializers.HyperlinkedModelSerializer): - """ - REST API Serializer for Clubs. - The djangorestframework plugin will analyse the model `Club` and parse all fields in the API. - """ - class Meta: - model = Club - fields = '__all__' - - class ClubViewSet(viewsets.ModelViewSet): """ REST API View set. @@ -45,16 +27,6 @@ class ClubViewSet(viewsets.ModelViewSet): serializer_class = ClubSerializer -class RoleSerializer(serializers.HyperlinkedModelSerializer): - """ - REST API Serializer for Roles. - The djangorestframework plugin will analyse the model `Role` and parse all fields in the API. - """ - class Meta: - model = Role - fields = '__all__' - - class RoleViewSet(viewsets.ModelViewSet): """ REST API View set. @@ -65,16 +37,6 @@ class RoleViewSet(viewsets.ModelViewSet): serializer_class = RoleSerializer -class MembershipSerializer(serializers.HyperlinkedModelSerializer): - """ - REST API Serializer for Memberships. - The djangorestframework plugin will analyse the model `Memberships` and parse all fields in the API. - """ - class Meta: - model = Membership - fields = '__all__' - - class MembershipViewSet(viewsets.ModelViewSet): """ REST API View set. @@ -83,4 +45,3 @@ class MembershipViewSet(viewsets.ModelViewSet): """ queryset = Membership.objects.all() serializer_class = MembershipSerializer - diff --git a/apps/note/serializers.py b/apps/api/note/serializers.py similarity index 55% rename from apps/note/serializers.py rename to apps/api/note/serializers.py index db63ea6b..be99eec3 100644 --- a/apps/note/serializers.py +++ b/apps/api/note/serializers.py @@ -2,9 +2,10 @@ # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later -from .models.notes import Note, NoteClub, NoteSpecial, NoteUser -from .models.transactions import TransactionTemplate, Transaction, MembershipTransaction -from rest_framework import serializers, viewsets +from note.models.notes import Note, NoteClub, NoteSpecial, NoteUser +from note.models.transactions import TransactionTemplate, Transaction, MembershipTransaction +from rest_framework import serializers + class NoteSerializer(serializers.HyperlinkedModelSerializer): """ @@ -16,16 +17,6 @@ class NoteSerializer(serializers.HyperlinkedModelSerializer): fields = ('balance', 'is_active', 'display_image', 'created_at',) -class NoteViewSet(viewsets.ModelViewSet): - """ - REST API View set. - The djangorestframework plugin will get all `Note` objects, serialize it to JSON with the given serializer, - then render it on /api/note/note/ - """ - queryset = Note.objects.all() - serializer_class = NoteSerializer - - class NoteClubSerializer(serializers.HyperlinkedModelSerializer): """ REST API Serializer for Club's notes. @@ -56,16 +47,6 @@ class NoteSpecialSerializer(serializers.HyperlinkedModelSerializer): fields = ('balance', 'is_active', 'display_image', 'created_at', 'club', 'special_type',) -class NoteSpecialViewSet(viewsets.ModelViewSet): - """ - REST API View set. - The djangorestframework plugin will get all `NoteSpecial` objects, serialize it to JSON with the given serializer, - then render it on /api/note/special/ - """ - queryset = NoteSpecial.objects.all() - serializer_class = NoteSpecialSerializer - - class NoteUserSerializer(serializers.HyperlinkedModelSerializer): """ REST API Serializer for User's notes. @@ -76,16 +57,6 @@ class NoteUserSerializer(serializers.HyperlinkedModelSerializer): fields = ('balance', 'is_active', 'display_image', 'created_at', 'user',) -class NoteUserViewSet(viewsets.ModelViewSet): - """ - REST API View set. - The djangorestframework plugin will get all `NoteUser` objects, serialize it to JSON with the given serializer, - then render it on /api/note/user/ - """ - queryset = NoteUser.objects.all() - serializer_class = NoteUserSerializer - - class TransactionTemplateSerializer(serializers.HyperlinkedModelSerializer): """ REST API Serializer for Transaction templates. @@ -96,16 +67,6 @@ class TransactionTemplateSerializer(serializers.HyperlinkedModelSerializer): fields = '__all__' -class TransactionTemplateViewSet(viewsets.ModelViewSet): - """ - REST API View set. - The djangorestframework plugin will get all `TransactionTemplate` objects, serialize it to JSON with the given serializer, - then render it on /api/note/transaction/template/ - """ - queryset = TransactionTemplate.objects.all() - serializer_class = TransactionTemplateSerializer - - class TransactionSerializer(serializers.HyperlinkedModelSerializer): """ REST API Serializer for Transactions. @@ -116,16 +77,6 @@ class TransactionSerializer(serializers.HyperlinkedModelSerializer): fields = '__all__' -class TransactionViewSet(viewsets.ModelViewSet): - """ - REST API View set. - The djangorestframework plugin will get all `Transaction` objects, serialize it to JSON with the given serializer, - then render it on /api/note/transaction/transaction/ - """ - queryset = Transaction.objects.all() - serializer_class = TransactionSerializer - - class MembershipTransactionSerializer(serializers.HyperlinkedModelSerializer): """ REST API Serializer for Membership transactions. @@ -134,13 +85,3 @@ class MembershipTransactionSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = MembershipTransaction fields = '__all__' - - -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 diff --git a/apps/api/note/urls.py b/apps/api/note/urls.py new file mode 100644 index 00000000..766bc963 --- /dev/null +++ b/apps/api/note/urls.py @@ -0,0 +1,20 @@ +# -*- mode: python; coding: utf-8 -*- +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from .views import NoteViewSet, NoteClubViewSet, NoteUserViewSet, NoteSpecialViewSet, \ + TransactionViewSet, TransactionTemplateViewSet, MembershipTransactionViewSet + + +def register_note_urls(router, path): + """ + Configure router for Note REST API. + """ + router.register(path + r'note', NoteViewSet) + router.register(path + r'club', NoteClubViewSet) + router.register(path + r'user', NoteUserViewSet) + router.register(path + r'special', NoteSpecialViewSet) + + router.register(path + r'transaction/transaction', TransactionViewSet) + router.register(path + r'transaction/template', TransactionTemplateViewSet) + router.register(path + r'transaction/membership', MembershipTransactionViewSet) diff --git a/apps/api/note/views.py b/apps/api/note/views.py new file mode 100644 index 00000000..eb4a3019 --- /dev/null +++ b/apps/api/note/views.py @@ -0,0 +1,79 @@ +# -*- mode: python; coding: utf-8 -*- +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from note.models.notes import Note, NoteClub, NoteSpecial, NoteUser +from note.models.transactions import TransactionTemplate, Transaction, MembershipTransaction +from .serializers import NoteSerializer, NoteClubSerializer, NoteSpecialSerializer, NoteUserSerializer, \ + TransactionTemplateSerializer, TransactionSerializer, MembershipTransactionSerializer +from rest_framework import viewsets + + +class NoteViewSet(viewsets.ModelViewSet): + """ + REST API View set. + The djangorestframework plugin will get all `Note` objects, serialize it to JSON with the given serializer, + then render it on /api/note/note/ + """ + queryset = Note.objects.all() + serializer_class = NoteSerializer + + +class NoteClubViewSet(viewsets.ModelViewSet): + """ + REST API View set. + The djangorestframework plugin will get all `NoteClub` objects, serialize it to JSON with the given serializer, + then render it on /api/note/club/ + """ + queryset = NoteClub.objects.all() + serializer_class = NoteClubSerializer + + +class NoteSpecialViewSet(viewsets.ModelViewSet): + """ + REST API View set. + The djangorestframework plugin will get all `NoteSpecial` objects, serialize it to JSON with the given serializer, + then render it on /api/note/special/ + """ + queryset = NoteSpecial.objects.all() + serializer_class = NoteSpecialSerializer + + +class NoteUserViewSet(viewsets.ModelViewSet): + """ + REST API View set. + The djangorestframework plugin will get all `NoteUser` objects, serialize it to JSON with the given serializer, + then render it on /api/note/user/ + """ + queryset = NoteUser.objects.all() + serializer_class = NoteUserSerializer + + +class TransactionTemplateViewSet(viewsets.ModelViewSet): + """ + REST API View set. + The djangorestframework plugin will get all `TransactionTemplate` objects, serialize it to JSON with the given serializer, + then render it on /api/note/transaction/template/ + """ + queryset = TransactionTemplate.objects.all() + serializer_class = TransactionTemplateSerializer + + +class TransactionViewSet(viewsets.ModelViewSet): + """ + REST API View set. + The djangorestframework plugin will get all `Transaction` objects, serialize it to JSON with the given serializer, + then render it on /api/note/transaction/transaction/ + """ + queryset = Transaction.objects.all() + serializer_class = TransactionSerializer + + +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 diff --git a/apps/api/urls.py b/apps/api/urls.py index c149b3f4..c9f3002d 100644 --- a/apps/api/urls.py +++ b/apps/api/urls.py @@ -5,10 +5,9 @@ from django.conf.urls import url, include from django.contrib.auth.models import User from rest_framework import routers, serializers, viewsets -from member.serializers import ProfileViewSet, ClubViewSet, RoleViewSet, MembershipViewSet -from activity.serializers import ActivityTypeViewSet, ActivityViewSet, GuestViewSet -from note.serializers import NoteViewSet, NoteClubViewSet, NoteUserViewSet, NoteSpecialViewSet, \ - TransactionViewSet, TransactionTemplateViewSet, MembershipTransactionViewSet +from .activity.urls import register_activity_urls +from .members.urls import register_members_urls +from .note.urls import register_note_urls class UserSerializer(serializers.HyperlinkedModelSerializer): """ @@ -33,25 +32,13 @@ router = routers.DefaultRouter() router.register(r'users', UserViewSet) # Routers for members app -router.register(r'members/profile', ProfileViewSet) -router.register(r'members/club', ClubViewSet) -router.register(r'members/role', RoleViewSet) -router.register(r'members/membership', MembershipViewSet) +register_members_urls(router, r'members/') # Routers for activity app -router.register(r'activity/activity', ActivityViewSet) -router.register(r'activity/type', ActivityTypeViewSet) -router.register(r'activity/guest', GuestViewSet) +register_activity_urls(router, r'activity/') # Routers for note app -router.register(r'note/note', NoteViewSet) -router.register(r'note/club', NoteClubViewSet) -router.register(r'note/user', NoteUserViewSet) -router.register(r'note/special', NoteSpecialViewSet) - -router.register(r'note/transaction/transaction', TransactionViewSet) -router.register(r'note/transaction/template', TransactionTemplateViewSet) -router.register(r'note/transaction/membership', MembershipTransactionViewSet) +register_note_urls(router, r'note/') # Wire up our API using automatic URL routing. # Additionally, we include login URLs for the browsable API. From e8e22541faa2858fe87501ad291117f0b0d8998b Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 7 Feb 2020 17:10:35 +0100 Subject: [PATCH 08/33] Small fix --- apps/api/note/serializers.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/apps/api/note/serializers.py b/apps/api/note/serializers.py index be99eec3..fb8dd771 100644 --- a/apps/api/note/serializers.py +++ b/apps/api/note/serializers.py @@ -27,16 +27,6 @@ class NoteClubSerializer(serializers.HyperlinkedModelSerializer): fields = ('balance', 'is_active', 'display_image', 'created_at', 'club',) -class NoteClubViewSet(viewsets.ModelViewSet): - """ - REST API View set. - The djangorestframework plugin will get all `NoteClub` objects, serialize it to JSON with the given serializer, - then render it on /api/note/club/ - """ - queryset = NoteClub.objects.all() - serializer_class = NoteClubSerializer - - class NoteSpecialSerializer(serializers.HyperlinkedModelSerializer): """ REST API Serializer for special notes. From f52a89637cace5b7230d07b26452bb1936965ebd Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 7 Feb 2020 20:47:49 +0100 Subject: [PATCH 09/33] Polymorphic types --- apps/api/activity/serializers.py | 6 +++--- apps/api/members/serializers.py | 10 +++++----- apps/api/note/serializers.py | 33 +++++++++++++++++++++----------- apps/api/note/urls.py | 4 ++-- apps/api/note/views.py | 12 +++++++++++- apps/api/urls.py | 2 +- requirements.txt | 3 ++- 7 files changed, 46 insertions(+), 24 deletions(-) diff --git a/apps/api/activity/serializers.py b/apps/api/activity/serializers.py index 4d1bc449..8ab5d901 100644 --- a/apps/api/activity/serializers.py +++ b/apps/api/activity/serializers.py @@ -5,7 +5,7 @@ from activity.models import ActivityType, Activity, Guest from rest_framework import serializers -class ActivityTypeSerializer(serializers.HyperlinkedModelSerializer): +class ActivityTypeSerializer(serializers.ModelSerializer): """ REST API Serializer for Activity types. The djangorestframework plugin will analyse the model `ActivityType` and parse all fields in the API. @@ -15,7 +15,7 @@ class ActivityTypeSerializer(serializers.HyperlinkedModelSerializer): fields = '__all__' -class ActivitySerializer(serializers.HyperlinkedModelSerializer): +class ActivitySerializer(serializers.ModelSerializer): """ REST API Serializer for Activities. The djangorestframework plugin will analyse the model `Activity` and parse all fields in the API. @@ -25,7 +25,7 @@ class ActivitySerializer(serializers.HyperlinkedModelSerializer): fields = '__all__' -class GuestSerializer(serializers.HyperlinkedModelSerializer): +class GuestSerializer(serializers.ModelSerializer): """ REST API Serializer for Guests. The djangorestframework plugin will analyse the model `Guest` and parse all fields in the API. diff --git a/apps/api/members/serializers.py b/apps/api/members/serializers.py index 98e2ba15..76829615 100644 --- a/apps/api/members/serializers.py +++ b/apps/api/members/serializers.py @@ -3,10 +3,10 @@ # SPDX-License-Identifier: GPL-3.0-or-later from member.models import Profile, Club, Role, Membership -from rest_framework import serializers, viewsets +from rest_framework import serializers -class ProfileSerializer(serializers.HyperlinkedModelSerializer): +class ProfileSerializer(serializers.ModelSerializer): """ REST API Serializer for Profiles. The djangorestframework plugin will analyse the model `Profile` and parse all fields in the API. @@ -16,7 +16,7 @@ class ProfileSerializer(serializers.HyperlinkedModelSerializer): fields = '__all__' -class ClubSerializer(serializers.HyperlinkedModelSerializer): +class ClubSerializer(serializers.ModelSerializer): """ REST API Serializer for Clubs. The djangorestframework plugin will analyse the model `Club` and parse all fields in the API. @@ -26,7 +26,7 @@ class ClubSerializer(serializers.HyperlinkedModelSerializer): fields = '__all__' -class RoleSerializer(serializers.HyperlinkedModelSerializer): +class RoleSerializer(serializers.ModelSerializer): """ REST API Serializer for Roles. The djangorestframework plugin will analyse the model `Role` and parse all fields in the API. @@ -36,7 +36,7 @@ class RoleSerializer(serializers.HyperlinkedModelSerializer): fields = '__all__' -class MembershipSerializer(serializers.HyperlinkedModelSerializer): +class MembershipSerializer(serializers.ModelSerializer): """ REST API Serializer for Memberships. The djangorestframework plugin will analyse the model `Memberships` and parse all fields in the API. diff --git a/apps/api/note/serializers.py b/apps/api/note/serializers.py index fb8dd771..ddb800fa 100644 --- a/apps/api/note/serializers.py +++ b/apps/api/note/serializers.py @@ -5,49 +5,60 @@ from note.models.notes import Note, NoteClub, NoteSpecial, NoteUser from note.models.transactions import TransactionTemplate, Transaction, MembershipTransaction from rest_framework import serializers +from rest_polymorphic.serializers import PolymorphicSerializer -class NoteSerializer(serializers.HyperlinkedModelSerializer): +class NoteSerializer(serializers.ModelSerializer): """ REST API Serializer for Notes. The djangorestframework plugin will analyse the model `Note` and parse all fields in the API. """ class Meta: model = Note - fields = ('balance', 'is_active', 'display_image', 'created_at',) + fields = '__all__' + extra_kwargs = { + 'url': {'view_name': 'project-detail', 'lookup_field': 'pk'}, + } -class NoteClubSerializer(serializers.HyperlinkedModelSerializer): +class NoteClubSerializer(serializers.ModelSerializer): """ REST API Serializer for Club's notes. The djangorestframework plugin will analyse the model `NoteClub` and parse all fields in the API. """ class Meta: model = NoteClub - fields = ('balance', 'is_active', 'display_image', 'created_at', 'club',) + fields = '__all__' -class NoteSpecialSerializer(serializers.HyperlinkedModelSerializer): +class NoteSpecialSerializer(serializers.ModelSerializer): """ REST API Serializer for special notes. The djangorestframework plugin will analyse the model `NoteSpecial` and parse all fields in the API. """ class Meta: model = NoteSpecial - fields = ('balance', 'is_active', 'display_image', 'created_at', 'club', 'special_type',) + fields = '__all__' -class NoteUserSerializer(serializers.HyperlinkedModelSerializer): +class NoteUserSerializer(serializers.ModelSerializer): """ REST API Serializer for User's notes. The djangorestframework plugin will analyse the model `NoteUser` and parse all fields in the API. """ class Meta: model = NoteUser - fields = ('balance', 'is_active', 'display_image', 'created_at', 'user',) + fields = '__all__' +class NotePolymorphicSerializer(PolymorphicSerializer): + model_serializer_mapping = { + Note: NoteSerializer, + NoteUser: NoteUserSerializer, + NoteClub: NoteClubSerializer, + NoteSpecial: NoteSpecialSerializer + } -class TransactionTemplateSerializer(serializers.HyperlinkedModelSerializer): +class TransactionTemplateSerializer(serializers.ModelSerializer): """ REST API Serializer for Transaction templates. The djangorestframework plugin will analyse the model `TransactionTemplate` and parse all fields in the API. @@ -57,7 +68,7 @@ class TransactionTemplateSerializer(serializers.HyperlinkedModelSerializer): fields = '__all__' -class TransactionSerializer(serializers.HyperlinkedModelSerializer): +class TransactionSerializer(serializers.ModelSerializer): """ REST API Serializer for Transactions. The djangorestframework plugin will analyse the model `Transaction` and parse all fields in the API. @@ -67,7 +78,7 @@ class TransactionSerializer(serializers.HyperlinkedModelSerializer): fields = '__all__' -class MembershipTransactionSerializer(serializers.HyperlinkedModelSerializer): +class MembershipTransactionSerializer(serializers.ModelSerializer): """ REST API Serializer for Membership transactions. The djangorestframework plugin will analyse the model `MembershipTransaction` and parse all fields in the API. diff --git a/apps/api/note/urls.py b/apps/api/note/urls.py index 766bc963..da12760d 100644 --- a/apps/api/note/urls.py +++ b/apps/api/note/urls.py @@ -2,7 +2,7 @@ # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later -from .views import NoteViewSet, NoteClubViewSet, NoteUserViewSet, NoteSpecialViewSet, \ +from .views import NoteViewSet, NotePolymorphicViewSet, NoteClubViewSet, NoteUserViewSet, NoteSpecialViewSet, \ TransactionViewSet, TransactionTemplateViewSet, MembershipTransactionViewSet @@ -10,7 +10,7 @@ def register_note_urls(router, path): """ Configure router for Note REST API. """ - router.register(path + r'note', NoteViewSet) + router.register(path + r'note', NotePolymorphicViewSet) router.register(path + r'club', NoteClubViewSet) router.register(path + r'user', NoteUserViewSet) router.register(path + r'special', NoteSpecialViewSet) diff --git a/apps/api/note/views.py b/apps/api/note/views.py index eb4a3019..90a7dd29 100644 --- a/apps/api/note/views.py +++ b/apps/api/note/views.py @@ -4,7 +4,7 @@ from note.models.notes import Note, NoteClub, NoteSpecial, NoteUser from note.models.transactions import TransactionTemplate, Transaction, MembershipTransaction -from .serializers import NoteSerializer, NoteClubSerializer, NoteSpecialSerializer, NoteUserSerializer, \ +from .serializers import NoteSerializer, NotePolymorphicSerializer, NoteClubSerializer, NoteSpecialSerializer, NoteUserSerializer, \ TransactionTemplateSerializer, TransactionSerializer, MembershipTransactionSerializer from rest_framework import viewsets @@ -49,6 +49,16 @@ class NoteUserViewSet(viewsets.ModelViewSet): serializer_class = NoteUserSerializer +class NotePolymorphicViewSet(viewsets.ModelViewSet): + """ + REST API View set. + The djangorestframework plugin will get all `NoteUser` objects, serialize it to JSON with the given serializer, + then render it on /api/note/user/ + """ + queryset = Note.objects.all() + serializer_class = NotePolymorphicSerializer + + class TransactionTemplateViewSet(viewsets.ModelViewSet): """ REST API View set. diff --git a/apps/api/urls.py b/apps/api/urls.py index c9f3002d..d272fc8d 100644 --- a/apps/api/urls.py +++ b/apps/api/urls.py @@ -9,7 +9,7 @@ from .activity.urls import register_activity_urls from .members.urls import register_members_urls from .note.urls import register_note_urls -class UserSerializer(serializers.HyperlinkedModelSerializer): +class UserSerializer(serializers.ModelSerializer): """ REST API Serializer for Users. The djangorestframework plugin will analyse the model `User` and parse all fields in the API. diff --git a/requirements.txt b/requirements.txt index 2f1aaf34..30753fd2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -21,4 +21,5 @@ requests-oauthlib==1.2.0 six==1.12.0 sqlparse==0.3.0 urllib3==1.25.3 -djangorestframework==3.11.0 +djangorestframework==3.9.0 +django-rest-polymorphic==0.1.8 From d22350faf8e17632cd04c2217afd7eb5d6d371cd Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Fri, 7 Feb 2020 20:51:25 +0100 Subject: [PATCH 10/33] /note/user, /note/club, /note/special => /note --- apps/api/activity/urls.py | 6 +++--- apps/api/members/urls.py | 8 ++++---- apps/api/note/urls.py | 11 ++++------- apps/api/urls.py | 6 +++--- 4 files changed, 14 insertions(+), 17 deletions(-) diff --git a/apps/api/activity/urls.py b/apps/api/activity/urls.py index ca0fab43..56665730 100644 --- a/apps/api/activity/urls.py +++ b/apps/api/activity/urls.py @@ -9,6 +9,6 @@ def register_activity_urls(router, path): """ Configure router for Activity REST API. """ - router.register(path + r'activity', ActivityViewSet) - router.register(path + r'type', ActivityTypeViewSet) - router.register(path + r'guest', GuestViewSet) + router.register(path + '/activity', ActivityViewSet) + router.register(path + '/type', ActivityTypeViewSet) + router.register(path + '/guest', GuestViewSet) diff --git a/apps/api/members/urls.py b/apps/api/members/urls.py index ffce1cbd..f60465c0 100644 --- a/apps/api/members/urls.py +++ b/apps/api/members/urls.py @@ -9,7 +9,7 @@ def register_members_urls(router, path): """ Configure router for Member REST API. """ - router.register(path + r'profile', ProfileViewSet) - router.register(path + r'club', ClubViewSet) - router.register(path + r'role', RoleViewSet) - router.register(path + r'membership', MembershipViewSet) + router.register(path + '/profile', ProfileViewSet) + router.register(path + '/club', ClubViewSet) + router.register(path + '/role', RoleViewSet) + router.register(path + '/membership', MembershipViewSet) diff --git a/apps/api/note/urls.py b/apps/api/note/urls.py index da12760d..2bc56aa6 100644 --- a/apps/api/note/urls.py +++ b/apps/api/note/urls.py @@ -10,11 +10,8 @@ def register_note_urls(router, path): """ Configure router for Note REST API. """ - router.register(path + r'note', NotePolymorphicViewSet) - router.register(path + r'club', NoteClubViewSet) - router.register(path + r'user', NoteUserViewSet) - router.register(path + r'special', NoteSpecialViewSet) + router.register(path + '', NotePolymorphicViewSet) - router.register(path + r'transaction/transaction', TransactionViewSet) - router.register(path + r'transaction/template', TransactionTemplateViewSet) - router.register(path + r'transaction/membership', MembershipTransactionViewSet) + router.register(path + '/transaction/transaction', TransactionViewSet) + router.register(path + '/transaction/template', TransactionTemplateViewSet) + router.register(path + '/transaction/membership', MembershipTransactionViewSet) diff --git a/apps/api/urls.py b/apps/api/urls.py index d272fc8d..03db1382 100644 --- a/apps/api/urls.py +++ b/apps/api/urls.py @@ -32,13 +32,13 @@ router = routers.DefaultRouter() router.register(r'users', UserViewSet) # Routers for members app -register_members_urls(router, r'members/') +register_members_urls(router, r'members') # Routers for activity app -register_activity_urls(router, r'activity/') +register_activity_urls(router, r'activity') # Routers for note app -register_note_urls(router, r'note/') +register_note_urls(router, r'note') # Wire up our API using automatic URL routing. # Additionally, we include login URLs for the browsable API. From f54e2ed14553bee6fe9f9d40e72fa860e562f8c3 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 8 Feb 2020 15:08:55 +0100 Subject: [PATCH 11/33] Aliases were missing --- apps/api/note/serializers.py | 13 ++++++++++++- apps/api/note/urls.py | 5 +++-- apps/api/note/views.py | 14 ++++++++++++-- apps/api/urls.py | 6 ++++-- 4 files changed, 31 insertions(+), 7 deletions(-) diff --git a/apps/api/note/serializers.py b/apps/api/note/serializers.py index ddb800fa..34a1f368 100644 --- a/apps/api/note/serializers.py +++ b/apps/api/note/serializers.py @@ -2,7 +2,7 @@ # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later -from note.models.notes import Note, NoteClub, NoteSpecial, NoteUser +from note.models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias from note.models.transactions import TransactionTemplate, Transaction, MembershipTransaction from rest_framework import serializers from rest_polymorphic.serializers import PolymorphicSerializer @@ -50,6 +50,17 @@ class NoteUserSerializer(serializers.ModelSerializer): model = NoteUser fields = '__all__' + +class AliasSerializer(serializers.ModelSerializer): + """ + REST API Serializer for Aliases. + The djangorestframework plugin will analyse the model `Alias` and parse all fields in the API. + """ + class Meta: + model = Alias + fields = '__all__' + + class NotePolymorphicSerializer(PolymorphicSerializer): model_serializer_mapping = { Note: NoteSerializer, diff --git a/apps/api/note/urls.py b/apps/api/note/urls.py index 2bc56aa6..0c25785d 100644 --- a/apps/api/note/urls.py +++ b/apps/api/note/urls.py @@ -2,7 +2,7 @@ # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later -from .views import NoteViewSet, NotePolymorphicViewSet, NoteClubViewSet, NoteUserViewSet, NoteSpecialViewSet, \ +from .views import NoteViewSet, NotePolymorphicViewSet, NoteClubViewSet, NoteUserViewSet, NoteSpecialViewSet, AliasViewSet, \ TransactionViewSet, TransactionTemplateViewSet, MembershipTransactionViewSet @@ -10,7 +10,8 @@ def register_note_urls(router, path): """ Configure router for Note REST API. """ - router.register(path + '', NotePolymorphicViewSet) + router.register(path + '/note', NotePolymorphicViewSet) + router.register(path + '/alias', AliasViewSet) router.register(path + '/transaction/transaction', TransactionViewSet) router.register(path + '/transaction/template', TransactionTemplateViewSet) diff --git a/apps/api/note/views.py b/apps/api/note/views.py index 90a7dd29..c6aa7689 100644 --- a/apps/api/note/views.py +++ b/apps/api/note/views.py @@ -2,9 +2,9 @@ # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later -from note.models.notes import Note, NoteClub, NoteSpecial, NoteUser +from note.models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias from note.models.transactions import TransactionTemplate, Transaction, MembershipTransaction -from .serializers import NoteSerializer, NotePolymorphicSerializer, NoteClubSerializer, NoteSpecialSerializer, NoteUserSerializer, \ +from .serializers import NoteSerializer, NotePolymorphicSerializer, NoteClubSerializer, NoteSpecialSerializer, NoteUserSerializer, AliasSerializer, \ TransactionTemplateSerializer, TransactionSerializer, MembershipTransactionSerializer from rest_framework import viewsets @@ -59,6 +59,16 @@ class NotePolymorphicViewSet(viewsets.ModelViewSet): serializer_class = NotePolymorphicSerializer +class AliasViewSet(viewsets.ModelViewSet): + """ + REST API View set. + The djangorestframework plugin will get all `Alias` objects, serialize it to JSON with the given serializer, + then render it on /api/aliases/ + """ + queryset = Alias.objects.all() + serializer_class = AliasSerializer + + class TransactionTemplateViewSet(viewsets.ModelViewSet): """ REST API View set. diff --git a/apps/api/urls.py b/apps/api/urls.py index 03db1382..3c5f6c78 100644 --- a/apps/api/urls.py +++ b/apps/api/urls.py @@ -5,6 +5,8 @@ from django.conf.urls import url, include from django.contrib.auth.models import User from rest_framework import routers, serializers, viewsets + +from note.models import Alias from .activity.urls import register_activity_urls from .members.urls import register_members_urls from .note.urls import register_note_urls @@ -16,7 +18,7 @@ class UserSerializer(serializers.ModelSerializer): """ class Meta: model = User - fields = ('username', 'first_name', 'last_name', 'email', 'is_staff',) + fields = '__all__' class UserViewSet(viewsets.ModelViewSet): """ @@ -29,7 +31,7 @@ class UserViewSet(viewsets.ModelViewSet): # Routers provide an easy way of automatically determining the URL conf. router = routers.DefaultRouter() -router.register(r'users', UserViewSet) +router.register('user', UserViewSet) # Routers for members app register_members_urls(router, r'members') From dc87a5d77bfd8a8f57c2d277f206c96c96e0a5c5 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 8 Feb 2020 17:17:00 +0100 Subject: [PATCH 12/33] Filter notes and aliases by aliases (regexp) or note type --- apps/api/note/views.py | 62 ++++++++++++++++++++++++++++++++++++++++-- apps/api/urls.py | 13 ++++----- 2 files changed, 66 insertions(+), 9 deletions(-) diff --git a/apps/api/note/views.py b/apps/api/note/views.py index c6aa7689..5c7cdc96 100644 --- a/apps/api/note/views.py +++ b/apps/api/note/views.py @@ -2,6 +2,7 @@ # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later +from django.db.models import Q from note.models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias from note.models.transactions import TransactionTemplate, Transaction, MembershipTransaction from .serializers import NoteSerializer, NotePolymorphicSerializer, NoteClubSerializer, NoteSpecialSerializer, NoteUserSerializer, AliasSerializer, \ @@ -52,12 +53,40 @@ class NoteUserViewSet(viewsets.ModelViewSet): class NotePolymorphicViewSet(viewsets.ModelViewSet): """ REST API View set. - The djangorestframework plugin will get all `NoteUser` objects, serialize it to JSON with the given serializer, - then render it on /api/note/user/ + The djangorestframework plugin will get all `Note` objects (with polymorhism), serialize it to JSON with the given serializer, + then render it on /api/note/note/ """ queryset = Note.objects.all() serializer_class = NotePolymorphicSerializer + def get_queryset(self): + """ + Parse query and apply filters. + :return: The filtered set of requested notes + """ + queryset = Note.objects.all() + + alias = self.request.query_params.get("alias", ".*") + queryset = queryset.filter(Q(alias__name__regex=alias) | Q(alias__normalized_name__regex=alias)) + + note_id = self.request.query_params.get("id", None) + if note_id: + queryset = queryset.filter(id=note_id) + + note_type = self.request.query_params.get("type", None) + if note_type: + l = str(note_type).lower() + if "user" in l: + queryset = queryset.filter(polymorphic_ctype__model="noteuser") + elif "club" in l: + queryset = queryset.filter(polymorphic_ctype__model="noteclub") + elif "special" in l: + queryset = queryset.filter(polymorphic_ctype__model="notespecial") + else: + queryset = queryset.none() + + return queryset + class AliasViewSet(viewsets.ModelViewSet): """ @@ -68,6 +97,35 @@ class AliasViewSet(viewsets.ModelViewSet): queryset = Alias.objects.all() serializer_class = AliasSerializer + def get_queryset(self): + """ + Parse query and apply filters. + :return: The filtered set of requested aliases + """ + + queryset = Alias.objects.all() + + alias = self.request.query_params.get("alias", ".*") + queryset = queryset.filter(Q(name__regex=alias) | Q(normalized_name__regex=alias)) + + note_id = self.request.query_params.get("note", None) + if note_id: + queryset = queryset.filter(id=note_id) + + note_type = self.request.query_params.get("type", None) + if note_type: + l = str(note_type).lower() + if "user" in l: + queryset = queryset.filter(note__polymorphic_ctype__model="noteuser") + elif "club" in l: + queryset = queryset.filter(note__polymorphic_ctype__model="noteclub") + elif "special" in l: + queryset = queryset.filter(note__polymorphic_ctype__model="notespecial") + else: + queryset = queryset.none() + + return queryset + class TransactionTemplateViewSet(viewsets.ModelViewSet): """ diff --git a/apps/api/urls.py b/apps/api/urls.py index 3c5f6c78..60758050 100644 --- a/apps/api/urls.py +++ b/apps/api/urls.py @@ -6,7 +6,6 @@ from django.conf.urls import url, include from django.contrib.auth.models import User from rest_framework import routers, serializers, viewsets -from note.models import Alias from .activity.urls import register_activity_urls from .members.urls import register_members_urls from .note.urls import register_note_urls @@ -18,7 +17,7 @@ class UserSerializer(serializers.ModelSerializer): """ class Meta: model = User - fields = '__all__' + exclude = ('password', 'groups', 'user_permissions',) class UserViewSet(viewsets.ModelViewSet): """ @@ -34,17 +33,17 @@ router = routers.DefaultRouter() router.register('user', UserViewSet) # Routers for members app -register_members_urls(router, r'members') +register_members_urls(router, 'members') # Routers for activity app -register_activity_urls(router, r'activity') +register_activity_urls(router, 'activity') # Routers for note app -register_note_urls(router, r'note') +register_note_urls(router, 'note') # Wire up our API using automatic URL routing. # Additionally, we include login URLs for the browsable API. urlpatterns = [ - url(r'^', include(router.urls)), - url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')) + url('^', include(router.urls)), + url('^api-auth/', include('rest_framework.urls', namespace='rest_framework')) ] \ No newline at end of file From 2880b5b3c72da33fa751f85fa19452230f09f0d1 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 8 Feb 2020 17:37:26 +0100 Subject: [PATCH 13/33] Useless to search a note by its id (already implemented) --- apps/api/note/views.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apps/api/note/views.py b/apps/api/note/views.py index 5c7cdc96..d751b5f8 100644 --- a/apps/api/note/views.py +++ b/apps/api/note/views.py @@ -69,10 +69,6 @@ class NotePolymorphicViewSet(viewsets.ModelViewSet): alias = self.request.query_params.get("alias", ".*") queryset = queryset.filter(Q(alias__name__regex=alias) | Q(alias__normalized_name__regex=alias)) - note_id = self.request.query_params.get("id", None) - if note_id: - queryset = queryset.filter(id=note_id) - note_type = self.request.query_params.get("type", None) if note_type: l = str(note_type).lower() From db218a2783bb46a18283c8139ed1de57e2cb7a22 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 8 Feb 2020 18:27:27 +0100 Subject: [PATCH 14/33] Add __init__ files --- apps/api/activity/__init__.py | 0 apps/api/members/__init__.py | 0 apps/api/note/__init__.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/api/activity/__init__.py create mode 100644 apps/api/members/__init__.py create mode 100644 apps/api/note/__init__.py diff --git a/apps/api/activity/__init__.py b/apps/api/activity/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/api/members/__init__.py b/apps/api/members/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/api/note/__init__.py b/apps/api/note/__init__.py new file mode 100644 index 00000000..e69de29b From c053235996765aa75edf8e483218ac95770b984d Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 8 Feb 2020 20:23:17 +0100 Subject: [PATCH 15/33] Autocomplete --- apps/note/forms.py | 26 +++++++++++++++++++- apps/note/urls.py | 4 ++- apps/note/views.py | 22 ++++++++++++++--- note_kfet/settings/base.py | 3 +++ requirements.txt | 7 +++--- templates/note/transaction_form.html | 4 +++ templates/note/transactiontemplate_form.html | 4 +++ 7 files changed, 62 insertions(+), 8 deletions(-) diff --git a/apps/note/forms.py b/apps/note/forms.py index d74fa5b4..2e814be6 100644 --- a/apps/note/forms.py +++ b/apps/note/forms.py @@ -1,9 +1,33 @@ #!/usr/bin/env python +from dal import autocomplete from django import forms -from .models import TransactionTemplate +from .models import Transaction, TransactionTemplate class TransactionTemplateForm(forms.ModelForm): class Meta: model = TransactionTemplate fields ='__all__' + + widgets = { + 'destination': autocomplete.ModelSelect2(url='note:note_autocomplete', + attrs={ + 'data-placeholder': 'Note ...', + 'data-minimum-input-length': 1, + }), + } + + +class TransactionForm(forms.ModelForm): + class Meta: + model = Transaction + fields = ('destination', 'reason', 'amount',) + + widgets = { + 'destination': autocomplete.ModelSelect2(url='note:note_autocomplete', + attrs={ + 'data-placeholder': 'Note ...', + 'data-minimum-input-length': 1, + }), + } + diff --git a/apps/note/urls.py b/apps/note/urls.py index 5e423d46..d4e390a1 100644 --- a/apps/note/urls.py +++ b/apps/note/urls.py @@ -5,11 +5,13 @@ from django.urls import path from . import views +from .models import Note app_name = 'note' urlpatterns = [ path('transfer/', views.TransactionCreate.as_view(), name='transfer'), path('buttons/create/',views.TransactionTemplateCreateView.as_view(),name='template_create'), path('buttons/update//',views.TransactionTemplateUpdateView.as_view(),name='template_update'), - path('buttons/',views.TransactionTemplateListView.as_view(),name='template_list') + path('buttons/',views.TransactionTemplateListView.as_view(),name='template_list'), + path('note-autocomplete/', views.NoteAutocomplete.as_view(model=Note),name='note_autocomplete'), ] diff --git a/apps/note/views.py b/apps/note/views.py index 08f4f630..716d16ff 100644 --- a/apps/note/views.py +++ b/apps/note/views.py @@ -2,12 +2,14 @@ # Copyright (C) 2018-2019 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later +from dal import autocomplete from django.contrib.auth.mixins import LoginRequiredMixin +from django.db.models import Q from django.utils.translation import gettext_lazy as _ from django.views.generic import CreateView, ListView, DetailView, UpdateView -from .models import Transaction,TransactionTemplate -from .forms import TransactionTemplateForm +from .models import Transaction, TransactionTemplate, Note +from .forms import TransactionForm, TransactionTemplateForm class TransactionCreate(LoginRequiredMixin, CreateView): """ @@ -16,7 +18,7 @@ class TransactionCreate(LoginRequiredMixin, CreateView): TODO: If user have sufficient rights, they can transfer from an other note """ model = Transaction - fields = ('destination', 'amount', 'reason') + form_class = TransactionForm def get_context_data(self, **kwargs): """ @@ -27,6 +29,20 @@ class TransactionCreate(LoginRequiredMixin, CreateView): 'to one or others') return context + +class NoteAutocomplete(autocomplete.Select2QuerySetView): + """ + Auto complete note by aliases + """ + def get_queryset(self): + qs = Note.objects.all() + + if self.q: + qs = qs.filter(Q(alias__name__regex=self.q) | Q(alias__normalized_name__regex=self.q)) + + return qs + + class TransactionTemplateCreateView(LoginRequiredMixin,CreateView): """ Create TransactionTemplate diff --git a/note_kfet/settings/base.py b/note_kfet/settings/base.py index 17283a95..d7061efd 100644 --- a/note_kfet/settings/base.py +++ b/note_kfet/settings/base.py @@ -52,6 +52,9 @@ INSTALLED_APPS = [ 'django.contrib.staticfiles', # API 'rest_framework', + # Autocomplete + 'dal', + 'dal_select2', # Note apps 'activity', diff --git a/requirements.txt b/requirements.txt index 30753fd2..3be0a4d2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,11 +3,14 @@ chardet==3.0.4 defusedxml==0.6.0 Django==2.2.3 django-allauth==0.39.1 +django-autocomplete-light==3.3.0 django-crispy-forms==1.7.2 django-extensions==2.1.9 django-filter==2.2.0 django-guardian==2.1.0 django-polymorphic==2.0.3 +djangorestframework==3.9.0 +django-rest-polymorphic==0.1.8 django-reversion==3.0.3 django-tables2==2.1.0 docutils==0.14 @@ -20,6 +23,4 @@ requests==2.22.0 requests-oauthlib==1.2.0 six==1.12.0 sqlparse==0.3.0 -urllib3==1.25.3 -djangorestframework==3.9.0 -django-rest-polymorphic==0.1.8 +urllib3==1.25.3 \ No newline at end of file diff --git a/templates/note/transaction_form.html b/templates/note/transaction_form.html index ff8504bc..2a480fc8 100644 --- a/templates/note/transaction_form.html +++ b/templates/note/transaction_form.html @@ -35,3 +35,7 @@ SPDX-License-Identifier: GPL-2.0-or-later {% endblock %} + +{% block extracss %} + {{ form.media }} +{% endblock extracss %} diff --git a/templates/note/transactiontemplate_form.html b/templates/note/transactiontemplate_form.html index 3fc2dd8b..5911d5d2 100644 --- a/templates/note/transactiontemplate_form.html +++ b/templates/note/transactiontemplate_form.html @@ -9,3 +9,7 @@ {% endblock %} + +{% block extracss %} + {{ form.media }} +{% endblock extracss %} \ No newline at end of file From 7bafc8971301616f4fd5cd16e3bb49d3ab28b506 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 8 Feb 2020 20:27:22 +0100 Subject: [PATCH 16/33] Add form media to each page --- templates/base.html | 6 ++++++ templates/note/transaction_form.html | 4 ---- templates/note/transactiontemplate_form.html | 4 ---- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/templates/base.html b/templates/base.html index 05328d0c..294da4e0 100644 --- a/templates/base.html +++ b/templates/base.html @@ -31,6 +31,12 @@ SPDX-License-Identifier: GPL-3.0-or-later crossorigin="anonymous"> + + {# Si un formulaire requiert des données supplémentaires (notamment JS), les données sont chargées #} + {% if form.media %} + {{ form.media }} + {% endif %} + {% block extracss %}{% endblock %} diff --git a/templates/note/transaction_form.html b/templates/note/transaction_form.html index 2a480fc8..ff8504bc 100644 --- a/templates/note/transaction_form.html +++ b/templates/note/transaction_form.html @@ -35,7 +35,3 @@ SPDX-License-Identifier: GPL-2.0-or-later {% endblock %} - -{% block extracss %} - {{ form.media }} -{% endblock extracss %} diff --git a/templates/note/transactiontemplate_form.html b/templates/note/transactiontemplate_form.html index 5911d5d2..3fc2dd8b 100644 --- a/templates/note/transactiontemplate_form.html +++ b/templates/note/transactiontemplate_form.html @@ -9,7 +9,3 @@ {% endblock %} - -{% block extracss %} - {{ form.media }} -{% endblock extracss %} \ No newline at end of file From ce012400e1e9b7764b3a197f07154eb18d86ebe2 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 8 Feb 2020 20:39:37 +0100 Subject: [PATCH 17/33] Club members autocomplete --- apps/member/forms.py | 10 +++++++++- apps/member/urls.py | 1 + apps/member/views.py | 14 +++++++++++++- apps/note/forms.py | 5 +++++ 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/apps/member/forms.py b/apps/member/forms.py index 59d3fec2..5517f1ff 100644 --- a/apps/member/forms.py +++ b/apps/member/forms.py @@ -1,7 +1,7 @@ # -*- mode: python; coding: utf-8 -*- # Copyright (C) 2018-2019 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later - +from dal import autocomplete from django.contrib.auth.forms import UserChangeForm, UserCreationForm from django.contrib.auth.models import User from django import forms @@ -44,6 +44,14 @@ class MembershipForm(forms.ModelForm): class Meta: model = Membership fields = ('user','roles','date_start') + widgets = { + 'user': autocomplete.ModelSelect2(url='member:user_autocomplete', + attrs={ + 'data-placeholder': 'Nom ...', + 'data-minimum-input-length': 1, + }), + } + MemberFormSet = forms.modelformset_factory(Membership, form=MembershipForm, diff --git a/apps/member/urls.py b/apps/member/urls.py index 9bcc1095..d57541c2 100644 --- a/apps/member/urls.py +++ b/apps/member/urls.py @@ -18,4 +18,5 @@ urlpatterns = [ path('user/',views.UserListView.as_view(),name="user_list"), path('user/',views.UserDetailView.as_view(),name="user_detail"), path('user//update',views.UserUpdateView.as_view(),name="user_update_profile"), + path('user/user-autocomplete',views.UserAutocomplete.as_view(),name="user_autocomplete"), ] diff --git a/apps/member/views.py b/apps/member/views.py index 90ea5ec3..608e3dee 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -2,7 +2,7 @@ # Copyright (C) 2018-2019 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later - +from dal import autocomplete from django.contrib.auth.mixins import LoginRequiredMixin from django.utils.translation import gettext_lazy as _ from django.views.generic import CreateView, ListView, DetailView, UpdateView @@ -115,6 +115,18 @@ class UserListView(LoginRequiredMixin,SingleTableView): return context +class UserAutocomplete(autocomplete.Select2QuerySetView): + """ + Auto complete note by aliases + """ + def get_queryset(self): + qs = User.objects.all() + + if self.q: + qs = qs.filter(username__regex=self.q) + + return qs + ################################### ############## CLUB ############### ################################### diff --git a/apps/note/forms.py b/apps/note/forms.py index 2e814be6..34d00487 100644 --- a/apps/note/forms.py +++ b/apps/note/forms.py @@ -24,6 +24,11 @@ class TransactionForm(forms.ModelForm): fields = ('destination', 'reason', 'amount',) widgets = { + 'source': autocomplete.ModelSelect2(url='note:note_autocomplete', + attrs={ + 'data-placeholder': 'Note ...', + 'data-minimum-input-length': 1, + }), 'destination': autocomplete.ModelSelect2(url='note:note_autocomplete', attrs={ 'data-placeholder': 'Note ...', From 142b3359e32b3bb1573a94a11bfd2d10deae310b Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 8 Feb 2020 21:40:32 +0100 Subject: [PATCH 18/33] Comment some code --- apps/member/forms.py | 3 +++ apps/member/urls.py | 2 ++ apps/member/views.py | 7 ++++++- apps/note/forms.py | 4 ++++ apps/note/urls.py | 2 ++ apps/note/views.py | 5 +++++ 6 files changed, 22 insertions(+), 1 deletion(-) diff --git a/apps/member/forms.py b/apps/member/forms.py index 5517f1ff..4d03764e 100644 --- a/apps/member/forms.py +++ b/apps/member/forms.py @@ -44,6 +44,9 @@ class MembershipForm(forms.ModelForm): class Meta: model = Membership fields = ('user','roles','date_start') + # Le champ d'utilisateur est remplacé par un champ d'auto-complétion. + # Quand des lettres sont tapées, une requête est envoyée sur l'API d'auto-complétion + # et récupère les noms d'utilisateur valides widgets = { 'user': autocomplete.ModelSelect2(url='member:user_autocomplete', attrs={ diff --git a/apps/member/urls.py b/apps/member/urls.py index d57541c2..e534cf47 100644 --- a/apps/member/urls.py +++ b/apps/member/urls.py @@ -18,5 +18,7 @@ urlpatterns = [ path('user/',views.UserListView.as_view(),name="user_list"), path('user/',views.UserDetailView.as_view(),name="user_detail"), path('user//update',views.UserUpdateView.as_view(),name="user_update_profile"), + + # API for the user autocompleter path('user/user-autocomplete',views.UserAutocomplete.as_view(),name="user_autocomplete"), ] diff --git a/apps/member/views.py b/apps/member/views.py index 608e3dee..29b9fe20 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -117,9 +117,14 @@ class UserListView(LoginRequiredMixin,SingleTableView): class UserAutocomplete(autocomplete.Select2QuerySetView): """ - Auto complete note by aliases + Auto complete users by usernames """ + def get_queryset(self): + """ + Quand une personne cherche un utilisateur par pseudo, une requête est envoyée sur l'API dédiée à l'auto-complétion. + Cette fonction récupère la requête, et renvoie la liste filtrée des utilisateurs par pseudos. + """ qs = User.objects.all() if self.q: diff --git a/apps/note/forms.py b/apps/note/forms.py index 34d00487..1e45ac0c 100644 --- a/apps/note/forms.py +++ b/apps/note/forms.py @@ -9,6 +9,9 @@ class TransactionTemplateForm(forms.ModelForm): model = TransactionTemplate fields ='__all__' + # Le champ de destination est remplacé par un champ d'auto-complétion. + # Quand des lettres sont tapées, une requête est envoyée sur l'API d'auto-complétion + # et récupère les aliases valides widgets = { 'destination': autocomplete.ModelSelect2(url='note:note_autocomplete', attrs={ @@ -23,6 +26,7 @@ class TransactionForm(forms.ModelForm): model = Transaction fields = ('destination', 'reason', 'amount',) + # Voir ci-dessus widgets = { 'source': autocomplete.ModelSelect2(url='note:note_autocomplete', attrs={ diff --git a/apps/note/urls.py b/apps/note/urls.py index d4e390a1..91829901 100644 --- a/apps/note/urls.py +++ b/apps/note/urls.py @@ -13,5 +13,7 @@ urlpatterns = [ path('buttons/create/',views.TransactionTemplateCreateView.as_view(),name='template_create'), path('buttons/update//',views.TransactionTemplateUpdateView.as_view(),name='template_update'), path('buttons/',views.TransactionTemplateListView.as_view(),name='template_list'), + + # API for the note autocompleter path('note-autocomplete/', views.NoteAutocomplete.as_view(model=Note),name='note_autocomplete'), ] diff --git a/apps/note/views.py b/apps/note/views.py index 716d16ff..625d4f98 100644 --- a/apps/note/views.py +++ b/apps/note/views.py @@ -34,7 +34,12 @@ class NoteAutocomplete(autocomplete.Select2QuerySetView): """ Auto complete note by aliases """ + def get_queryset(self): + """ + Quand une personne cherche un alias, une requête est envoyée sur l'API dédiée à l'auto-complétion. + Cette fonction récupère la requête, et renvoie la liste filtrée des notes par aliases. + """ qs = Note.objects.all() if self.q: From 732f8bc3d8f983a677be069500361b8daf17d25f Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 8 Feb 2020 23:24:49 +0100 Subject: [PATCH 19/33] =?UTF-8?q?Pr=C3=A9paration=20du=20code=20pour=20le?= =?UTF-8?q?=20syst=C3=A8me=20de=20droits=20&=20filtrage=20par=20type=20de?= =?UTF-8?q?=20note=20pour=20l'auto-compl=C3=A9tion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/member/views.py | 4 ++++ apps/note/forms.py | 16 +++++++++++----- apps/note/views.py | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 5 deletions(-) diff --git a/apps/member/views.py b/apps/member/views.py index 29b9fe20..2070d7a0 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -125,6 +125,10 @@ class UserAutocomplete(autocomplete.Select2QuerySetView): Quand une personne cherche un utilisateur par pseudo, une requête est envoyée sur l'API dédiée à l'auto-complétion. Cette fonction récupère la requête, et renvoie la liste filtrée des utilisateurs par pseudos. """ + # Un utilisateur non connecté n'a accès à aucune information + if not self.request.user.is_authenticated: + return User.objects.none() + qs = User.objects.all() if self.q: diff --git a/apps/note/forms.py b/apps/note/forms.py index 1e45ac0c..247c6215 100644 --- a/apps/note/forms.py +++ b/apps/note/forms.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -from dal import autocomplete +from dal import autocomplete, forward from django import forms from .models import Transaction, TransactionTemplate @@ -12,6 +12,8 @@ class TransactionTemplateForm(forms.ModelForm): # Le champ de destination est remplacé par un champ d'auto-complétion. # Quand des lettres sont tapées, une requête est envoyée sur l'API d'auto-complétion # et récupère les aliases valides + # Pour force le type d'une note, il faut rajouter le paramètre : + # forward=(forward.Const('TYPE', 'note_type') où TYPE est dans {user, club, special} widgets = { 'destination': autocomplete.ModelSelect2(url='note:note_autocomplete', attrs={ @@ -22,9 +24,14 @@ class TransactionTemplateForm(forms.ModelForm): class TransactionForm(forms.ModelForm): + def save(self, commit=True): + self.instance.transaction_type = 'transfert' + + super().save(commit) + class Meta: model = Transaction - fields = ('destination', 'reason', 'amount',) + fields = ('source', 'destination', 'reason', 'amount',) # Voir ci-dessus widgets = { @@ -32,11 +39,10 @@ class TransactionForm(forms.ModelForm): attrs={ 'data-placeholder': 'Note ...', 'data-minimum-input-length': 1, - }), + },), 'destination': autocomplete.ModelSelect2(url='note:note_autocomplete', attrs={ 'data-placeholder': 'Note ...', 'data-minimum-input-length': 1, - }), + },), } - diff --git a/apps/note/views.py b/apps/note/views.py index 625d4f98..b4687f22 100644 --- a/apps/note/views.py +++ b/apps/note/views.py @@ -30,6 +30,27 @@ class TransactionCreate(LoginRequiredMixin, CreateView): return context + def get_form(self, form_class=None): + """ + If the user has no right to transfer funds, then it won't have the choice of the source of the transfer. + """ + form = super().get_form(form_class) + + if False: # TODO: fix it with "if %user has no right to transfer funds" + del form.fields['source'] + + return form + + def form_valid(self, form): + """ + If the user has no right to transfer funds, then it will be the source of the transfer by default. + """ + if False: # TODO: fix it with "if %user has no right to transfer funds" + form.instance.source = self.request.user.note + + return super().form_valid(form) + + class NoteAutocomplete(autocomplete.Select2QuerySetView): """ Auto complete note by aliases @@ -40,11 +61,29 @@ class NoteAutocomplete(autocomplete.Select2QuerySetView): Quand une personne cherche un alias, une requête est envoyée sur l'API dédiée à l'auto-complétion. Cette fonction récupère la requête, et renvoie la liste filtrée des notes par aliases. """ + # Un utilisateur non connecté n'a accès à aucune information + if not self.request.user.is_authenticated: + return Note.objects.none() + qs = Note.objects.all() + # self.q est le paramètre de la recherche if self.q: qs = qs.filter(Q(alias__name__regex=self.q) | Q(alias__normalized_name__regex=self.q)) + # Filtrage par type de note (user, club, special) + note_type = self.forwarded.get("note_type", None) + if note_type: + l = str(note_type).lower() + if "user" in l: + qs = qs.filter(polymorphic_ctype__model="noteuser") + elif "club" in l: + qs = qs.filter(polymorphic_ctype__model="noteclub") + elif "special" in l: + qs = qs.filter(polymorphic_ctype__model="notespecial") + else: + qs = qs.none() + return qs From 596686497d269759754ffb5d3de1850549937509 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 8 Feb 2020 23:49:39 +0100 Subject: [PATCH 20/33] Aliases are case insensitive --- apps/api/note/views.py | 4 ++-- apps/note/views.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/api/note/views.py b/apps/api/note/views.py index d751b5f8..63ab08e5 100644 --- a/apps/api/note/views.py +++ b/apps/api/note/views.py @@ -67,7 +67,7 @@ class NotePolymorphicViewSet(viewsets.ModelViewSet): queryset = Note.objects.all() alias = self.request.query_params.get("alias", ".*") - queryset = queryset.filter(Q(alias__name__regex=alias) | Q(alias__normalized_name__regex=alias)) + queryset = queryset.filter(Q(alias__name__regex=alias) | Q(alias__normalized_name__regex=alias.lower())) note_type = self.request.query_params.get("type", None) if note_type: @@ -102,7 +102,7 @@ class AliasViewSet(viewsets.ModelViewSet): queryset = Alias.objects.all() alias = self.request.query_params.get("alias", ".*") - queryset = queryset.filter(Q(name__regex=alias) | Q(normalized_name__regex=alias)) + queryset = queryset.filter(Q(name__regex=alias) | Q(normalized_name__regex=alias.lower())) note_id = self.request.query_params.get("note", None) if note_id: diff --git a/apps/note/views.py b/apps/note/views.py index b4687f22..9239c394 100644 --- a/apps/note/views.py +++ b/apps/note/views.py @@ -69,7 +69,7 @@ class NoteAutocomplete(autocomplete.Select2QuerySetView): # self.q est le paramètre de la recherche if self.q: - qs = qs.filter(Q(alias__name__regex=self.q) | Q(alias__normalized_name__regex=self.q)) + qs = qs.filter(Q(alias__name__regex=self.q) | Q(alias__normalized_name__regex=self.q.lower())) # Filtrage par type de note (user, club, special) note_type = self.forwarded.get("note_type", None) From 4d7e552681c73fe8ac8bf3651b4258d6e9c8db9d Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sun, 16 Feb 2020 22:29:27 +0100 Subject: [PATCH 21/33] One import was missing --- apps/note/tables.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/note/tables.py b/apps/note/tables.py index 4d4e9608..31cefe41 100644 --- a/apps/note/tables.py +++ b/apps/note/tables.py @@ -1,5 +1,7 @@ #!/usr/bin/env python import django_tables2 as tables +from django.db.models import F + from .models.transactions import Transaction From 2e4464e98215206e1cf1444cd48fde3aaf1a14e3 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sun, 16 Feb 2020 23:27:59 +0100 Subject: [PATCH 22/33] Autocomplete note aliases for consos --- apps/note/forms.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/apps/note/forms.py b/apps/note/forms.py index 5c27d95b..09818931 100644 --- a/apps/note/forms.py +++ b/apps/note/forms.py @@ -48,6 +48,7 @@ class TransactionForm(forms.ModelForm): } class ConsoForm(forms.ModelForm): + def save(self, commit=True): button: TransactionTemplate = TransactionTemplate.objects.filter(name=self.data['button']).get() self.instance.destination = button.destination @@ -59,3 +60,14 @@ class ConsoForm(forms.ModelForm): class Meta: model = Transaction fields = ('source',) + + # Le champ d'utilisateur est remplacé par un champ d'auto-complétion. + # Quand des lettres sont tapées, une requête est envoyée sur l'API d'auto-complétion + # et récupère les aliases de note valides + widgets = { + 'source': autocomplete.ModelSelect2(url='note:note_autocomplete', + attrs={ + 'data-placeholder': 'Note ...', + 'data-minimum-input-length': 1, + }), + } From 4bc342c66816d1fa2ed8d32496851c948aef1c7b Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sun, 16 Feb 2020 23:39:54 +0100 Subject: [PATCH 23/33] Request for autocompletion is normalized --- apps/note/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/note/views.py b/apps/note/views.py index eebc3a53..0b56a485 100644 --- a/apps/note/views.py +++ b/apps/note/views.py @@ -9,7 +9,7 @@ from django.urls import reverse_lazy, reverse from django.utils.translation import gettext_lazy as _ from django.views.generic import CreateView, ListView, DetailView, UpdateView -from .models import Note, Transaction, TransactionCategory, TransactionTemplate +from .models import Note, Transaction, TransactionCategory, TransactionTemplate, Alias from .forms import TransactionForm, TransactionTemplateForm, ConsoForm class TransactionCreate(LoginRequiredMixin, CreateView): @@ -70,7 +70,7 @@ class NoteAutocomplete(autocomplete.Select2QuerySetView): # self.q est le paramètre de la recherche if self.q: - qs = qs.filter(Q(alias__name__regex=self.q) | Q(alias__normalized_name__regex=self.q.lower())) + qs = qs.filter(Q(alias__name__regex=self.q) | Q(alias__normalized_name__regex=Alias.normalize(self.q))) # Filtrage par type de note (user, club, special) note_type = self.forwarded.get("note_type", None) From 52514f58d6e74574c9bf37d56fb37b9ca5294c5f Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Mon, 17 Feb 2020 01:13:14 +0100 Subject: [PATCH 24/33] Show matched aliases in the autocompletion list --- apps/note/views.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/apps/note/views.py b/apps/note/views.py index 0b56a485..fe888613 100644 --- a/apps/note/views.py +++ b/apps/note/views.py @@ -70,7 +70,7 @@ class NoteAutocomplete(autocomplete.Select2QuerySetView): # self.q est le paramètre de la recherche if self.q: - qs = qs.filter(Q(alias__name__regex=self.q) | Q(alias__normalized_name__regex=Alias.normalize(self.q))) + qs = qs.filter(Q(alias__name__regex=self.q) | Q(alias__normalized_name__regex=Alias.normalize(self.q))).distinct() # Filtrage par type de note (user, club, special) note_type = self.forwarded.get("note_type", None) @@ -87,6 +87,17 @@ class NoteAutocomplete(autocomplete.Select2QuerySetView): return qs + def get_result_label(self, result): + aliases = Alias.objects.filter(Q(name__regex=self.q) | Q(normalized_name__regex=Alias.normalize(self.q))).all() + res = str(result) + if aliases.count() > 1 or (aliases.count() == 1 and aliases.get().name != str(result)): + res += " (alias " + for alias in aliases: + if alias.name != str(result): + res += alias.name + ", " + res = res[:-2] + ")" + return res + class TransactionTemplateCreateView(LoginRequiredMixin,CreateView): """ From 6a4e0de44498707d51f71db97faee3c96926e72a Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Mon, 17 Feb 2020 11:19:16 +0100 Subject: [PATCH 25/33] Show autocompleted aliases instead of note names --- apps/note/views.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/apps/note/views.py b/apps/note/views.py index fe888613..7e45d44d 100644 --- a/apps/note/views.py +++ b/apps/note/views.py @@ -64,40 +64,40 @@ class NoteAutocomplete(autocomplete.Select2QuerySetView): """ # Un utilisateur non connecté n'a accès à aucune information if not self.request.user.is_authenticated: - return Note.objects.none() + return Alias.objects.none() - qs = Note.objects.all() + qs = Alias.objects.all() # self.q est le paramètre de la recherche if self.q: - qs = qs.filter(Q(alias__name__regex=self.q) | Q(alias__normalized_name__regex=Alias.normalize(self.q))).distinct() + qs = qs.filter(Q(name__regex=self.q) | Q(normalized_name__regex=Alias.normalize(self.q)))\ + .order_by('normalized_name').distinct() # Filtrage par type de note (user, club, special) note_type = self.forwarded.get("note_type", None) if note_type: l = str(note_type).lower() if "user" in l: - qs = qs.filter(polymorphic_ctype__model="noteuser") + qs = qs.filter(note__polymorphic_ctype__model="noteuser") elif "club" in l: - qs = qs.filter(polymorphic_ctype__model="noteclub") + qs = qs.filter(note__polymorphic_ctype__model="noteclub") elif "special" in l: - qs = qs.filter(polymorphic_ctype__model="notespecial") + qs = qs.filter(note__polymorphic_ctype__model="notespecial") else: qs = qs.none() return qs def get_result_label(self, result): - aliases = Alias.objects.filter(Q(name__regex=self.q) | Q(normalized_name__regex=Alias.normalize(self.q))).all() - res = str(result) - if aliases.count() > 1 or (aliases.count() == 1 and aliases.get().name != str(result)): - res += " (alias " - for alias in aliases: - if alias.name != str(result): - res += alias.name + ", " - res = res[:-2] + ")" + res = result.name + note_name = str(result.note) + if res != note_name: + res += " (aka. " + note_name + ")" return res + def get_result_value(self, result): + return str(result.note.pk) + class TransactionTemplateCreateView(LoginRequiredMixin,CreateView): """ From 3ce5f3141171d5279ff514d465c5420c7c6819d8 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Mon, 17 Feb 2020 11:21:05 +0100 Subject: [PATCH 26/33] Cannot set username that is similar to an alias we don't own --- apps/member/views.py | 30 ++++++++++++++++++++++++++++-- apps/note/models/notes.py | 7 ++++++- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/apps/member/views.py b/apps/member/views.py index 2070d7a0..ec7a3e0c 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -4,6 +4,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later from dal import autocomplete from django.contrib.auth.mixins import LoginRequiredMixin +from django.core.exceptions import ValidationError from django.utils.translation import gettext_lazy as _ from django.views.generic import CreateView, ListView, DetailView, UpdateView from django.http import HttpResponseRedirect @@ -14,7 +15,7 @@ from django.db.models import Q from django_tables2.views import SingleTableView - +from note.models import Alias, Note, NoteUser from .models import Profile, Club, Membership from .forms import SignUpForm, ProfileForm, ClubForm,MembershipForm, MemberFormSet,FormSetHelper from .tables import ClubTable,UserTable @@ -57,13 +58,38 @@ class UserUpdateView(LoginRequiredMixin,UpdateView): second_form = ProfileForm def get_context_data(self,**kwargs): context = super().get_context_data(**kwargs) - context["profile_form"] = self.second_form(instance=context['user'].profile) + context['user_modified'] = context['user'] + context['user'] = self.request.user + context["profile_form"] = self.second_form(instance=context['user_modified'].profile) return context + def get_form(self, form_class=None): + from django.forms import forms + form: forms.Form = super().get_form(form_class) + if 'username' not in form.data: + return form + + new_username = form.data['username'] + + note = NoteUser.objects.filter(alias__normalized_name=Alias.normalize(new_username)) + if note.exists() and note.get().user != self.request.user: + form.add_error('username', _("An alias with a similar name already exists.")) + + return form + + def form_valid(self, form): profile_form = ProfileForm(data=self.request.POST,instance=self.request.user.profile) if form.is_valid() and profile_form.is_valid(): + new_username = form.data['username'] + alias = Alias.objects.filter(name=new_username) + if not alias.exists(): + similar = Alias.objects.filter(normalized_name=Alias.normalize(new_username)) + if similar.exists(): + similar.delete() + note = NoteUser.objects.filter(alias__normalized_name=Alias.normalize(self.request.user.username)).get() + Alias(name=new_username, note=note).save() user = form.save() profile = profile_form.save(commit=False) profile.user = user diff --git a/apps/note/models/notes.py b/apps/note/models/notes.py index e3ab7931..6a0c5ebe 100644 --- a/apps/note/models/notes.py +++ b/apps/note/models/notes.py @@ -85,7 +85,7 @@ class Note(PolymorphicModel): """ Verify alias (simulate save) """ - aliases = Alias.objects.filter(name=str(self)) + aliases = Alias.objects.filter(normalized_name=Alias.normalize(str(self))) if aliases.exists(): # Alias exists, so check if it is linked to this note if aliases.first().note != self: @@ -233,3 +233,8 @@ class Alias(models.Model): 'already exists.')) except Alias.DoesNotExist: pass + + def delete(self, using=None, keep_parents=False): + if self.name == str(self.note): + raise ValidationError(_("You can't delete your main alias.")) + return super().delete(using, keep_parents) From c884cbb0adba0e46b6c524f5e4daf15ba7b239da Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Mon, 17 Feb 2020 11:36:46 +0100 Subject: [PATCH 27/33] Add some comments --- apps/member/views.py | 14 ++++++-------- apps/note/views.py | 4 +++- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/member/views.py b/apps/member/views.py index ec7a3e0c..174072ab 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -4,11 +4,8 @@ # SPDX-License-Identifier: GPL-3.0-or-later from dal import autocomplete from django.contrib.auth.mixins import LoginRequiredMixin -from django.core.exceptions import ValidationError from django.utils.translation import gettext_lazy as _ from django.views.generic import CreateView, ListView, DetailView, UpdateView -from django.http import HttpResponseRedirect -from django.contrib.auth.forms import UserCreationForm from django.contrib.auth.models import User from django.urls import reverse_lazy from django.db.models import Q @@ -65,13 +62,13 @@ class UserUpdateView(LoginRequiredMixin,UpdateView): return context def get_form(self, form_class=None): - from django.forms import forms - form: forms.Form = super().get_form(form_class) + form = super().get_form(form_class) if 'username' not in form.data: return form new_username = form.data['username'] + # Si l'utilisateur cherche à modifier son pseudo, le nouveau pseudo ne doit pas être proche d'un alias existant note = NoteUser.objects.filter(alias__normalized_name=Alias.normalize(new_username)) if note.exists() and note.get().user != self.request.user: form.add_error('username', _("An alias with a similar name already exists.")) @@ -84,16 +81,17 @@ class UserUpdateView(LoginRequiredMixin,UpdateView): if form.is_valid() and profile_form.is_valid(): new_username = form.data['username'] alias = Alias.objects.filter(name=new_username) + # Si le nouveau pseudo n'est pas un de nos alias, on supprime éventuellement un alias similaire pour le remplacer if not alias.exists(): similar = Alias.objects.filter(normalized_name=Alias.normalize(new_username)) if similar.exists(): similar.delete() - note = NoteUser.objects.filter(alias__normalized_name=Alias.normalize(self.request.user.username)).get() - Alias(name=new_username, note=note).save() - user = form.save() + + user = form.save(commit=False) profile = profile_form.save(commit=False) profile.user = user profile.save() + user.save() return super().form_valid(form) def get_success_url(self, **kwargs): diff --git a/apps/note/views.py b/apps/note/views.py index 7e45d44d..3414a6c0 100644 --- a/apps/note/views.py +++ b/apps/note/views.py @@ -60,7 +60,7 @@ class NoteAutocomplete(autocomplete.Select2QuerySetView): def get_queryset(self): """ Quand une personne cherche un alias, une requête est envoyée sur l'API dédiée à l'auto-complétion. - Cette fonction récupère la requête, et renvoie la liste filtrée des notes par aliases. + Cette fonction récupère la requête, et renvoie la liste filtrée des aliases. """ # Un utilisateur non connecté n'a accès à aucune information if not self.request.user.is_authenticated: @@ -89,6 +89,7 @@ class NoteAutocomplete(autocomplete.Select2QuerySetView): return qs def get_result_label(self, result): + # Gère l'affichage de l'alias dans la recherche res = result.name note_name = str(result.note) if res != note_name: @@ -96,6 +97,7 @@ class NoteAutocomplete(autocomplete.Select2QuerySetView): return res def get_result_value(self, result): + # Le résultat renvoyé doit être l'identifiant de la note, et non de l'alias return str(result.note.pk) From f3f13c51532d867e937b697debd631cf8929a6a5 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Mon, 17 Feb 2020 14:08:40 +0100 Subject: [PATCH 28/33] Reformat code, add some missing lines at end of files --- apps/api/activity/views.py | 6 ++++-- apps/api/members/views.py | 3 ++- apps/api/note/urls.py | 4 ++-- apps/api/note/views.py | 8 +++++--- apps/api/urls.py | 6 +++++- requirements.txt | 2 +- 6 files changed, 19 insertions(+), 10 deletions(-) diff --git a/apps/api/activity/views.py b/apps/api/activity/views.py index af2f0fe1..de6b8979 100644 --- a/apps/api/activity/views.py +++ b/apps/api/activity/views.py @@ -2,9 +2,11 @@ # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later +from rest_framework import viewsets + from activity.models import ActivityType, Activity, Guest from .serializers import ActivityTypeSerializer, ActivitySerializer, GuestSerializer -from rest_framework import viewsets + class ActivityTypeViewSet(viewsets.ModelViewSet): """ @@ -33,4 +35,4 @@ class GuestViewSet(viewsets.ModelViewSet): then render it on /api/activity/guest/ """ queryset = Guest.objects.all() - serializer_class = GuestSerializer \ No newline at end of file + serializer_class = GuestSerializer diff --git a/apps/api/members/views.py b/apps/api/members/views.py index 9755151e..06b95dcc 100644 --- a/apps/api/members/views.py +++ b/apps/api/members/views.py @@ -2,9 +2,10 @@ # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later +from rest_framework import viewsets + from member.models import Profile, Club, Role, Membership from .serializers import ProfileSerializer, ClubSerializer, RoleSerializer, MembershipSerializer -from rest_framework import viewsets class ProfileViewSet(viewsets.ModelViewSet): diff --git a/apps/api/note/urls.py b/apps/api/note/urls.py index 0c25785d..4ef14e28 100644 --- a/apps/api/note/urls.py +++ b/apps/api/note/urls.py @@ -2,8 +2,8 @@ # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later -from .views import NoteViewSet, NotePolymorphicViewSet, NoteClubViewSet, NoteUserViewSet, NoteSpecialViewSet, AliasViewSet, \ - TransactionViewSet, TransactionTemplateViewSet, MembershipTransactionViewSet +from .views import NotePolymorphicViewSet, AliasViewSet, \ + TransactionViewSet, TransactionTemplateViewSet, MembershipTransactionViewSet def register_note_urls(router, path): diff --git a/apps/api/note/views.py b/apps/api/note/views.py index 63ab08e5..07bc9fd2 100644 --- a/apps/api/note/views.py +++ b/apps/api/note/views.py @@ -3,11 +3,13 @@ # SPDX-License-Identifier: GPL-3.0-or-later from django.db.models import Q +from rest_framework import viewsets + from note.models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias from note.models.transactions import TransactionTemplate, Transaction, MembershipTransaction -from .serializers import NoteSerializer, NotePolymorphicSerializer, NoteClubSerializer, NoteSpecialSerializer, NoteUserSerializer, AliasSerializer, \ - TransactionTemplateSerializer, TransactionSerializer, MembershipTransactionSerializer -from rest_framework import viewsets +from .serializers import NoteSerializer, NotePolymorphicSerializer, NoteClubSerializer, NoteSpecialSerializer, \ + NoteUserSerializer, AliasSerializer, \ + TransactionTemplateSerializer, TransactionSerializer, MembershipTransactionSerializer class NoteViewSet(viewsets.ModelViewSet): diff --git a/apps/api/urls.py b/apps/api/urls.py index 60758050..9c64abed 100644 --- a/apps/api/urls.py +++ b/apps/api/urls.py @@ -10,15 +10,18 @@ from .activity.urls import register_activity_urls from .members.urls import register_members_urls from .note.urls import register_note_urls + class UserSerializer(serializers.ModelSerializer): """ REST API Serializer for Users. The djangorestframework plugin will analyse the model `User` and parse all fields in the API. """ + class Meta: model = User exclude = ('password', 'groups', 'user_permissions',) + class UserViewSet(viewsets.ModelViewSet): """ REST API View set. @@ -28,6 +31,7 @@ class UserViewSet(viewsets.ModelViewSet): queryset = User.objects.all() serializer_class = UserSerializer + # Routers provide an easy way of automatically determining the URL conf. router = routers.DefaultRouter() router.register('user', UserViewSet) @@ -46,4 +50,4 @@ register_note_urls(router, 'note') urlpatterns = [ url('^', include(router.urls)), url('^api-auth/', include('rest_framework.urls', namespace='rest_framework')) -] \ No newline at end of file +] diff --git a/requirements.txt b/requirements.txt index 3be0a4d2..d103764e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -23,4 +23,4 @@ requests==2.22.0 requests-oauthlib==1.2.0 six==1.12.0 sqlparse==0.3.0 -urllib3==1.25.3 \ No newline at end of file +urllib3==1.25.3 From 55977bcbe74473a3c7dfdbba0f9b081de6f889a7 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Mon, 17 Feb 2020 19:25:33 +0100 Subject: [PATCH 29/33] Token authentication --- apps/api/urls.py | 3 ++- apps/member/urls.py | 1 + apps/member/views.py | 19 ++++++++++++++++++- note_kfet/settings/base.py | 4 ++++ templates/member/generate_auth_token.html | 6 ++++++ 5 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 templates/member/generate_auth_token.html diff --git a/apps/api/urls.py b/apps/api/urls.py index 9c64abed..cb1dfaf3 100644 --- a/apps/api/urls.py +++ b/apps/api/urls.py @@ -5,6 +5,7 @@ from django.conf.urls import url, include from django.contrib.auth.models import User from rest_framework import routers, serializers, viewsets +from rest_framework.authtoken import views as token_views from .activity.urls import register_activity_urls from .members.urls import register_members_urls @@ -49,5 +50,5 @@ register_note_urls(router, 'note') # Additionally, we include login URLs for the browsable API. urlpatterns = [ url('^', include(router.urls)), - url('^api-auth/', include('rest_framework.urls', namespace='rest_framework')) + url('^api-auth/', include('rest_framework.urls', namespace='rest_framework')), ] diff --git a/apps/member/urls.py b/apps/member/urls.py index e534cf47..faadf97d 100644 --- a/apps/member/urls.py +++ b/apps/member/urls.py @@ -18,6 +18,7 @@ urlpatterns = [ path('user/',views.UserListView.as_view(),name="user_list"), path('user/',views.UserDetailView.as_view(),name="user_detail"), path('user//update',views.UserUpdateView.as_view(),name="user_update_profile"), + path('generate-auth-token/', views.GenerateAuthTokenView.as_view(), name='generate_auth_token'), # API for the user autocompleter path('user/user-autocomplete',views.UserAutocomplete.as_view(),name="user_autocomplete"), diff --git a/apps/member/views.py b/apps/member/views.py index 174072ab..8fa071c1 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -5,12 +5,13 @@ from dal import autocomplete from django.contrib.auth.mixins import LoginRequiredMixin from django.utils.translation import gettext_lazy as _ -from django.views.generic import CreateView, ListView, DetailView, UpdateView +from django.views.generic import CreateView, ListView, DetailView, UpdateView, TemplateView from django.contrib.auth.models import User from django.urls import reverse_lazy from django.db.models import Q from django_tables2.views import SingleTableView +from rest_framework.authtoken.models import Token from note.models import Alias, Note, NoteUser from .models import Profile, Club, Membership @@ -139,6 +140,22 @@ class UserListView(LoginRequiredMixin,SingleTableView): return context +class GenerateAuthTokenView(LoginRequiredMixin, TemplateView): + """ + Génère un jeton d'authentification pour un utilisateur + """ + template_name = "member/generate_auth_token.html" + + def get_context_data(self): + context = super().get_context_data() + + if Token.objects.filter(user=self.request.user).exists(): + Token.objects.get(user=self.request.user).delete() + token = Token.objects.create(user=self.request.user) + + context['token'] = token.key + return context + class UserAutocomplete(autocomplete.Select2QuerySetView): """ Auto complete users by usernames diff --git a/note_kfet/settings/base.py b/note_kfet/settings/base.py index d7061efd..8277e71c 100644 --- a/note_kfet/settings/base.py +++ b/note_kfet/settings/base.py @@ -52,6 +52,7 @@ INSTALLED_APPS = [ 'django.contrib.staticfiles', # API 'rest_framework', + 'rest_framework.authtoken', # Autocomplete 'dal', 'dal_select2', @@ -127,6 +128,9 @@ REST_FRAMEWORK = { # or allow read-only access for unauthenticated users. 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly' + ], + 'DEFAULT_AUTHENTICATION_CLASSES': [ + 'rest_framework.authentication.TokenAuthentication', ] } diff --git a/templates/member/generate_auth_token.html b/templates/member/generate_auth_token.html new file mode 100644 index 00000000..6c64d972 --- /dev/null +++ b/templates/member/generate_auth_token.html @@ -0,0 +1,6 @@ +{% extends "base.html" %} +{% load i18n static pretty_money django_tables2 %} + +{% block content %} + Jeton : {{ token }} +{% endblock %} From 559445c8b46a3ada14f52a807c9a85dfe6cc961a Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Mon, 17 Feb 2020 19:44:56 +0100 Subject: [PATCH 30/33] Add some decoration --- apps/api/urls.py | 2 ++ apps/member/views.py | 2 +- note_kfet/settings/base.py | 2 ++ templates/member/generate_auth_token.html | 16 ++++++++++++++++ templates/member/profile_detail.html | 5 +++-- 5 files changed, 24 insertions(+), 3 deletions(-) diff --git a/apps/api/urls.py b/apps/api/urls.py index cb1dfaf3..6fe3e99f 100644 --- a/apps/api/urls.py +++ b/apps/api/urls.py @@ -46,6 +46,8 @@ register_activity_urls(router, 'activity') # Routers for note app register_note_urls(router, 'note') +app_name = 'api' + # Wire up our API using automatic URL routing. # Additionally, we include login URLs for the browsable API. urlpatterns = [ diff --git a/apps/member/views.py b/apps/member/views.py index 8fa071c1..19b04992 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -142,7 +142,7 @@ class UserListView(LoginRequiredMixin,SingleTableView): class GenerateAuthTokenView(LoginRequiredMixin, TemplateView): """ - Génère un jeton d'authentification pour un utilisateur + Génère un jeton d'authentification pour un utilisateur et détruit l'ancien """ template_name = "member/generate_auth_token.html" diff --git a/note_kfet/settings/base.py b/note_kfet/settings/base.py index 8277e71c..9a526863 100644 --- a/note_kfet/settings/base.py +++ b/note_kfet/settings/base.py @@ -61,6 +61,7 @@ INSTALLED_APPS = [ 'activity', 'member', 'note', + 'api', ] LOGIN_REDIRECT_URL = '/note/transfer/' @@ -127,6 +128,7 @@ REST_FRAMEWORK = { # Use Django's standard `django.contrib.auth` permissions, # or allow read-only access for unauthenticated users. 'DEFAULT_PERMISSION_CLASSES': [ + # TODO Maybe replace it with our custom permissions system 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly' ], 'DEFAULT_AUTHENTICATION_CLASSES': [ diff --git a/templates/member/generate_auth_token.html b/templates/member/generate_auth_token.html index 6c64d972..22ff55fc 100644 --- a/templates/member/generate_auth_token.html +++ b/templates/member/generate_auth_token.html @@ -3,4 +3,20 @@ {% block content %} Jeton : {{ token }} + +
+ Conservez bien précieusement ce jeton d'authentification, car il ne vous sera jamais donné de nouveau. + Revenir sur cette page aura pour conséquence de révoquer tout ancien jeton d'authentification. + Cela peut entre autres mener à des plantages d'autres applications qui pouvaient utiliser ce jeton. +
+ +
+

À quoi sert ce jeton ?

+ + Ce jeton vous permet de vous connecter à l'API de la Note Kfet. + Il suffit pour cela d'ajouter en en-tête de vos requêtes Authorization: Token <TOKEN> + pour pouvoir vous identifier. + + Une documentation de l'API arrivera ultérieurement. +
{% endblock %} diff --git a/templates/member/profile_detail.html b/templates/member/profile_detail.html index 53a0b9a0..7a253f1f 100644 --- a/templates/member/profile_detail.html +++ b/templates/member/profile_detail.html @@ -8,13 +8,13 @@
{% trans 'name'|capfirst %}
-
{{ object.user.name }}
+
{{ object.user.last_name }}
{% trans 'first name'|capfirst %}
{{ object.user.first_name }}
{% trans 'username'|capfirst %}
{{ object.user.username }}
Aliases
-
{{ object.user.note.aliases_set.all }}
+
{{ object.user.note.alias_set.all }}
{% trans 'section'|capfirst %}
{{ object.section }}
{% trans 'address'|capfirst %}
@@ -23,6 +23,7 @@
{{ object.user.note.balance | pretty_money }}
+ {% trans 'Generate auth token' %} {% trans 'Update Profile' %} {% trans 'Change password' %}
From b7383b35f7b07e0ac7b11b21f9f02627b8f3c3e7 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Mon, 17 Feb 2020 21:32:08 +0100 Subject: [PATCH 31/33] Manage auth token --- apps/member/urls.py | 2 +- apps/member/views.py | 19 ++++++++-------- templates/member/generate_auth_token.html | 22 ------------------ templates/member/manage_auth_tokens.html | 27 +++++++++++++++++++++++ templates/member/profile_detail.html | 4 +++- 5 files changed, 41 insertions(+), 33 deletions(-) delete mode 100644 templates/member/generate_auth_token.html create mode 100644 templates/member/manage_auth_tokens.html diff --git a/apps/member/urls.py b/apps/member/urls.py index faadf97d..d4e3e6af 100644 --- a/apps/member/urls.py +++ b/apps/member/urls.py @@ -18,7 +18,7 @@ urlpatterns = [ path('user/',views.UserListView.as_view(),name="user_list"), path('user/',views.UserDetailView.as_view(),name="user_detail"), path('user//update',views.UserUpdateView.as_view(),name="user_update_profile"), - path('generate-auth-token/', views.GenerateAuthTokenView.as_view(), name='generate_auth_token'), + path('manage-auth-token/', views.ManageAuthTokens.as_view(), name='auth_token'), # API for the user autocompleter path('user/user-autocomplete',views.UserAutocomplete.as_view(),name="user_autocomplete"), diff --git a/apps/member/views.py b/apps/member/views.py index 19b04992..f8f8ec2d 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -5,7 +5,7 @@ from dal import autocomplete from django.contrib.auth.mixins import LoginRequiredMixin from django.utils.translation import gettext_lazy as _ -from django.views.generic import CreateView, ListView, DetailView, UpdateView, TemplateView +from django.views.generic import CreateView, ListView, DetailView, UpdateView, RedirectView, TemplateView from django.contrib.auth.models import User from django.urls import reverse_lazy from django.db.models import Q @@ -140,20 +140,21 @@ class UserListView(LoginRequiredMixin,SingleTableView): return context -class GenerateAuthTokenView(LoginRequiredMixin, TemplateView): +class ManageAuthTokens(LoginRequiredMixin, TemplateView): """ - Génère un jeton d'authentification pour un utilisateur et détruit l'ancien + Affiche le jeton d'authentification, et permet de le regénérer """ - template_name = "member/generate_auth_token.html" + model = Token + template_name = "member/manage_auth_tokens.html" - def get_context_data(self): - context = super().get_context_data() + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) - if Token.objects.filter(user=self.request.user).exists(): + if 'regenerate' in self.request.GET and Token.objects.filter(user=self.request.user).exists(): Token.objects.get(user=self.request.user).delete() - token = Token.objects.create(user=self.request.user) - context['token'] = token.key + context['token'] = Token.objects.get_or_create(user=self.request.user)[0] + return context class UserAutocomplete(autocomplete.Select2QuerySetView): diff --git a/templates/member/generate_auth_token.html b/templates/member/generate_auth_token.html deleted file mode 100644 index 22ff55fc..00000000 --- a/templates/member/generate_auth_token.html +++ /dev/null @@ -1,22 +0,0 @@ -{% extends "base.html" %} -{% load i18n static pretty_money django_tables2 %} - -{% block content %} - Jeton : {{ token }} - -
- Conservez bien précieusement ce jeton d'authentification, car il ne vous sera jamais donné de nouveau. - Revenir sur cette page aura pour conséquence de révoquer tout ancien jeton d'authentification. - Cela peut entre autres mener à des plantages d'autres applications qui pouvaient utiliser ce jeton. -
- -
-

À quoi sert ce jeton ?

- - Ce jeton vous permet de vous connecter à l'API de la Note Kfet. - Il suffit pour cela d'ajouter en en-tête de vos requêtes Authorization: Token <TOKEN> - pour pouvoir vous identifier. - - Une documentation de l'API arrivera ultérieurement. -
-{% endblock %} diff --git a/templates/member/manage_auth_tokens.html b/templates/member/manage_auth_tokens.html new file mode 100644 index 00000000..4f992915 --- /dev/null +++ b/templates/member/manage_auth_tokens.html @@ -0,0 +1,27 @@ +{% extends "base.html" %} +{% load i18n static pretty_money django_tables2 %} + +{% block content %} +
+

À quoi sert un jeton d'authentification ?

+ + Un jeton vous permet de vous connecter à l'API de la Note Kfet.
+ Il suffit pour cela d'ajouter en en-tête de vos requêtes Authorization: Token <TOKEN> + pour pouvoir vous identifier.

+ + Une documentation de l'API arrivera ultérieurement. +
+ +
+ {%trans 'Token' %} : {{ token.key }}
+ {%trans 'Created' %} : {{ token.created }} +
+ +
+ Attention : regénérer le jeton va révoquer tout accès autorisé à l'API via ce jeton ! +
+ + + + +{% endblock %} diff --git a/templates/member/profile_detail.html b/templates/member/profile_detail.html index 7a253f1f..dbf7075b 100644 --- a/templates/member/profile_detail.html +++ b/templates/member/profile_detail.html @@ -23,7 +23,9 @@
{{ object.user.note.balance | pretty_money }}
- {% trans 'Generate auth token' %} + {% if object.user.pk == user.pk %} + {% trans 'Manage auth token' %} + {% endif %} {% trans 'Update Profile' %} {% trans 'Change password' %}
From 987b898a33f13cb2576c82392ca847a28ae87dfc Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Mon, 17 Feb 2020 23:30:55 +0100 Subject: [PATCH 32/33] Auth token is hidden --- apps/member/views.py | 13 ++++++++----- templates/member/manage_auth_tokens.html | 10 ++++++++-- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/apps/member/views.py b/apps/member/views.py index f8f8ec2d..be2d8d58 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -4,6 +4,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later from dal import autocomplete from django.contrib.auth.mixins import LoginRequiredMixin +from django.shortcuts import redirect from django.utils.translation import gettext_lazy as _ from django.views.generic import CreateView, ListView, DetailView, UpdateView, RedirectView, TemplateView from django.contrib.auth.models import User @@ -147,14 +148,16 @@ class ManageAuthTokens(LoginRequiredMixin, TemplateView): model = Token template_name = "member/manage_auth_tokens.html" + def get(self, request, *args, **kwargs): + if 'regenerate' in request.GET and Token.objects.filter(user=request.user).exists(): + Token.objects.get(user=self.request.user).delete() + return redirect(reverse_lazy('member:auth_token') + "?show", permanent=True) + + return super().get(request, *args, **kwargs) + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - - if 'regenerate' in self.request.GET and Token.objects.filter(user=self.request.user).exists(): - Token.objects.get(user=self.request.user).delete() - context['token'] = Token.objects.get_or_create(user=self.request.user)[0] - return context class UserAutocomplete(autocomplete.Select2QuerySetView): diff --git a/templates/member/manage_auth_tokens.html b/templates/member/manage_auth_tokens.html index 4f992915..0103fbbb 100644 --- a/templates/member/manage_auth_tokens.html +++ b/templates/member/manage_auth_tokens.html @@ -13,7 +13,13 @@
- {%trans 'Token' %} : {{ token.key }}
+ {%trans 'Token' %} : + {% if 'show' in request.GET %} + {{ token.key }} (cacher) + {% else %} + caché (montrer) + {% endif %} +
{%trans 'Created' %} : {{ token.created }}
@@ -21,7 +27,7 @@ Attention : regénérer le jeton va révoquer tout accès autorisé à l'API via ce jeton ! - + {% endblock %} From 55722b801a196c5bc83eb19efa527f77e94f0e8a Mon Sep 17 00:00:00 2001 From: Alexandre Iooss Date: Tue, 18 Feb 2020 11:58:42 +0100 Subject: [PATCH 33/33] Split API in each app --- apps/{api/activity => activity/api}/__init__.py | 0 apps/{api/activity => activity/api}/serializers.py | 2 +- apps/{api/activity => activity/api}/urls.py | 0 apps/{api/activity => activity/api}/views.py | 2 +- apps/api/urls.py | 13 ++++--------- apps/{api/members => member/api}/__init__.py | 0 apps/{api/members => member/api}/serializers.py | 2 +- apps/{api/members => member/api}/urls.py | 0 apps/{api/members => member/api}/views.py | 2 +- apps/{api/note => note/api}/__init__.py | 0 apps/{api/note => note/api}/serializers.py | 4 ++-- apps/{api/note => note/api}/urls.py | 0 apps/{api/note => note/api}/views.py | 4 ++-- 13 files changed, 12 insertions(+), 17 deletions(-) rename apps/{api/activity => activity/api}/__init__.py (100%) rename apps/{api/activity => activity/api}/serializers.py (94%) rename apps/{api/activity => activity/api}/urls.py (100%) rename apps/{api/activity => activity/api}/views.py (95%) rename apps/{api/members => member/api}/__init__.py (100%) rename apps/{api/members => member/api}/serializers.py (95%) rename apps/{api/members => member/api}/urls.py (100%) rename apps/{api/members => member/api}/views.py (96%) rename apps/{api/note => note/api}/__init__.py (100%) rename apps/{api/note => note/api}/serializers.py (94%) rename apps/{api/note => note/api}/urls.py (100%) rename apps/{api/note => note/api}/views.py (97%) diff --git a/apps/api/activity/__init__.py b/apps/activity/api/__init__.py similarity index 100% rename from apps/api/activity/__init__.py rename to apps/activity/api/__init__.py diff --git a/apps/api/activity/serializers.py b/apps/activity/api/serializers.py similarity index 94% rename from apps/api/activity/serializers.py rename to apps/activity/api/serializers.py index 8ab5d901..f7f949e7 100644 --- a/apps/api/activity/serializers.py +++ b/apps/activity/api/serializers.py @@ -2,7 +2,7 @@ # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later -from activity.models import ActivityType, Activity, Guest +from ..models import ActivityType, Activity, Guest from rest_framework import serializers class ActivityTypeSerializer(serializers.ModelSerializer): diff --git a/apps/api/activity/urls.py b/apps/activity/api/urls.py similarity index 100% rename from apps/api/activity/urls.py rename to apps/activity/api/urls.py diff --git a/apps/api/activity/views.py b/apps/activity/api/views.py similarity index 95% rename from apps/api/activity/views.py rename to apps/activity/api/views.py index de6b8979..4a0973e5 100644 --- a/apps/api/activity/views.py +++ b/apps/activity/api/views.py @@ -4,7 +4,7 @@ from rest_framework import viewsets -from activity.models import ActivityType, Activity, Guest +from ..models import ActivityType, Activity, Guest from .serializers import ActivityTypeSerializer, ActivitySerializer, GuestSerializer diff --git a/apps/api/urls.py b/apps/api/urls.py index 6fe3e99f..7ac56ca1 100644 --- a/apps/api/urls.py +++ b/apps/api/urls.py @@ -7,9 +7,9 @@ from django.contrib.auth.models import User from rest_framework import routers, serializers, viewsets from rest_framework.authtoken import views as token_views -from .activity.urls import register_activity_urls -from .members.urls import register_members_urls -from .note.urls import register_note_urls +from activity.api.urls import register_activity_urls +from member.api.urls import register_members_urls +from note.api.urls import register_note_urls class UserSerializer(serializers.ModelSerializer): @@ -34,16 +34,11 @@ class UserViewSet(viewsets.ModelViewSet): # Routers provide an easy way of automatically determining the URL conf. +# Register each app API router and user viewset router = routers.DefaultRouter() router.register('user', UserViewSet) - -# Routers for members app register_members_urls(router, 'members') - -# Routers for activity app register_activity_urls(router, 'activity') - -# Routers for note app register_note_urls(router, 'note') app_name = 'api' diff --git a/apps/api/members/__init__.py b/apps/member/api/__init__.py similarity index 100% rename from apps/api/members/__init__.py rename to apps/member/api/__init__.py diff --git a/apps/api/members/serializers.py b/apps/member/api/serializers.py similarity index 95% rename from apps/api/members/serializers.py rename to apps/member/api/serializers.py index 76829615..cf4420d5 100644 --- a/apps/api/members/serializers.py +++ b/apps/member/api/serializers.py @@ -2,7 +2,7 @@ # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later -from member.models import Profile, Club, Role, Membership +from ..models import Profile, Club, Role, Membership from rest_framework import serializers diff --git a/apps/api/members/urls.py b/apps/member/api/urls.py similarity index 100% rename from apps/api/members/urls.py rename to apps/member/api/urls.py diff --git a/apps/api/members/views.py b/apps/member/api/views.py similarity index 96% rename from apps/api/members/views.py rename to apps/member/api/views.py index 06b95dcc..36e8a33f 100644 --- a/apps/api/members/views.py +++ b/apps/member/api/views.py @@ -4,7 +4,7 @@ from rest_framework import viewsets -from member.models import Profile, Club, Role, Membership +from ..models import Profile, Club, Role, Membership from .serializers import ProfileSerializer, ClubSerializer, RoleSerializer, MembershipSerializer diff --git a/apps/api/note/__init__.py b/apps/note/api/__init__.py similarity index 100% rename from apps/api/note/__init__.py rename to apps/note/api/__init__.py diff --git a/apps/api/note/serializers.py b/apps/note/api/serializers.py similarity index 94% rename from apps/api/note/serializers.py rename to apps/note/api/serializers.py index 34a1f368..afc3b419 100644 --- a/apps/api/note/serializers.py +++ b/apps/note/api/serializers.py @@ -2,8 +2,8 @@ # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later -from note.models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias -from note.models.transactions import TransactionTemplate, Transaction, MembershipTransaction +from ..models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias +from ..models.transactions import TransactionTemplate, Transaction, MembershipTransaction from rest_framework import serializers from rest_polymorphic.serializers import PolymorphicSerializer diff --git a/apps/api/note/urls.py b/apps/note/api/urls.py similarity index 100% rename from apps/api/note/urls.py rename to apps/note/api/urls.py diff --git a/apps/api/note/views.py b/apps/note/api/views.py similarity index 97% rename from apps/api/note/views.py rename to apps/note/api/views.py index 07bc9fd2..37ca4e20 100644 --- a/apps/api/note/views.py +++ b/apps/note/api/views.py @@ -5,8 +5,8 @@ from django.db.models import Q from rest_framework import viewsets -from note.models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias -from note.models.transactions import TransactionTemplate, Transaction, MembershipTransaction +from ..models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias +from ..models.transactions import TransactionTemplate, Transaction, MembershipTransaction from .serializers import NoteSerializer, NotePolymorphicSerializer, NoteClubSerializer, NoteSpecialSerializer, \ NoteUserSerializer, AliasSerializer, \ TransactionTemplateSerializer, TransactionSerializer, MembershipTransactionSerializer