mirror of
https://gitlab.crans.org/bde/nk20
synced 2024-11-27 02:43:01 +00:00
Merge branch 'api' into 'master'
API & autocomplétion See merge request bde/nk20!8
This commit is contained in:
commit
0d7db68372
0
apps/activity/api/__init__.py
Normal file
0
apps/activity/api/__init__.py
Normal file
35
apps/activity/api/serializers.py
Normal file
35
apps/activity/api/serializers.py
Normal file
@ -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 ..models import ActivityType, Activity, Guest
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = ActivityType
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class ActivitySerializer(serializers.ModelSerializer):
|
||||||
|
"""
|
||||||
|
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.ModelSerializer):
|
||||||
|
"""
|
||||||
|
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__'
|
14
apps/activity/api/urls.py
Normal file
14
apps/activity/api/urls.py
Normal file
@ -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 + '/activity', ActivityViewSet)
|
||||||
|
router.register(path + '/type', ActivityTypeViewSet)
|
||||||
|
router.register(path + '/guest', GuestViewSet)
|
38
apps/activity/api/views.py
Normal file
38
apps/activity/api/views.py
Normal file
@ -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 rest_framework import viewsets
|
||||||
|
|
||||||
|
from ..models import ActivityType, Activity, Guest
|
||||||
|
from .serializers import ActivityTypeSerializer, ActivitySerializer, GuestSerializer
|
||||||
|
|
||||||
|
|
||||||
|
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 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 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
|
51
apps/api/urls.py
Normal file
51
apps/api/urls.py
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
# -*- mode: python; coding: utf-8 -*-
|
||||||
|
# Copyright (C) 2018-2020 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
|
||||||
|
from rest_framework.authtoken import views as token_views
|
||||||
|
|
||||||
|
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):
|
||||||
|
"""
|
||||||
|
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.
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
register_members_urls(router, 'members')
|
||||||
|
register_activity_urls(router, 'activity')
|
||||||
|
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 = [
|
||||||
|
url('^', include(router.urls)),
|
||||||
|
url('^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
|
||||||
|
]
|
0
apps/member/api/__init__.py
Normal file
0
apps/member/api/__init__.py
Normal file
46
apps/member/api/serializers.py
Normal file
46
apps/member/api/serializers.py
Normal file
@ -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 ..models import Profile, Club, Role, Membership
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
|
||||||
|
class ProfileSerializer(serializers.ModelSerializer):
|
||||||
|
"""
|
||||||
|
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.ModelSerializer):
|
||||||
|
"""
|
||||||
|
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.ModelSerializer):
|
||||||
|
"""
|
||||||
|
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.ModelSerializer):
|
||||||
|
"""
|
||||||
|
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__'
|
15
apps/member/api/urls.py
Normal file
15
apps/member/api/urls.py
Normal file
@ -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 + '/profile', ProfileViewSet)
|
||||||
|
router.register(path + '/club', ClubViewSet)
|
||||||
|
router.register(path + '/role', RoleViewSet)
|
||||||
|
router.register(path + '/membership', MembershipViewSet)
|
48
apps/member/api/views.py
Normal file
48
apps/member/api/views.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# -*- mode: python; coding: utf-8 -*-
|
||||||
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from rest_framework import viewsets
|
||||||
|
|
||||||
|
from ..models import Profile, Club, Role, Membership
|
||||||
|
from .serializers import ProfileSerializer, ClubSerializer, RoleSerializer, MembershipSerializer
|
||||||
|
|
||||||
|
|
||||||
|
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 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 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 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
|
@ -1,7 +1,7 @@
|
|||||||
# -*- mode: python; coding: utf-8 -*-
|
# -*- mode: python; coding: utf-8 -*-
|
||||||
# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
from dal import autocomplete
|
||||||
from django.contrib.auth.forms import UserChangeForm, UserCreationForm
|
from django.contrib.auth.forms import UserChangeForm, UserCreationForm
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django import forms
|
from django import forms
|
||||||
@ -44,6 +44,17 @@ class MembershipForm(forms.ModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Membership
|
model = Membership
|
||||||
fields = ('user','roles','date_start')
|
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={
|
||||||
|
'data-placeholder': 'Nom ...',
|
||||||
|
'data-minimum-input-length': 1,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
MemberFormSet = forms.modelformset_factory(Membership,
|
MemberFormSet = forms.modelformset_factory(Membership,
|
||||||
form=MembershipForm,
|
form=MembershipForm,
|
||||||
|
@ -18,4 +18,8 @@ urlpatterns = [
|
|||||||
path('user/',views.UserListView.as_view(),name="user_list"),
|
path('user/',views.UserListView.as_view(),name="user_list"),
|
||||||
path('user/<int:pk>',views.UserDetailView.as_view(),name="user_detail"),
|
path('user/<int:pk>',views.UserDetailView.as_view(),name="user_detail"),
|
||||||
path('user/<int:pk>/update',views.UserUpdateView.as_view(),name="user_update_profile"),
|
path('user/<int:pk>/update',views.UserUpdateView.as_view(),name="user_update_profile"),
|
||||||
|
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"),
|
||||||
]
|
]
|
||||||
|
@ -2,19 +2,19 @@
|
|||||||
|
|
||||||
# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
from dal import autocomplete
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
|
from django.shortcuts import redirect
|
||||||
from django.utils.translation import gettext_lazy as _
|
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, RedirectView, TemplateView
|
||||||
from django.http import HttpResponseRedirect
|
|
||||||
from django.contrib.auth.forms import UserCreationForm
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
from django_tables2.views import SingleTableView
|
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
|
from .models import Profile, Club, Membership
|
||||||
from .forms import SignUpForm, ProfileForm, ClubForm,MembershipForm, MemberFormSet,FormSetHelper
|
from .forms import SignUpForm, ProfileForm, ClubForm,MembershipForm, MemberFormSet,FormSetHelper
|
||||||
from .tables import ClubTable,UserTable
|
from .tables import ClubTable,UserTable
|
||||||
@ -57,17 +57,43 @@ class UserUpdateView(LoginRequiredMixin,UpdateView):
|
|||||||
second_form = ProfileForm
|
second_form = ProfileForm
|
||||||
def get_context_data(self,**kwargs):
|
def get_context_data(self,**kwargs):
|
||||||
context = super().get_context_data(**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
|
return context
|
||||||
|
|
||||||
|
def get_form(self, form_class=None):
|
||||||
|
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."))
|
||||||
|
|
||||||
|
return form
|
||||||
|
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
profile_form = ProfileForm(data=self.request.POST,instance=self.request.user.profile)
|
profile_form = ProfileForm(data=self.request.POST,instance=self.request.user.profile)
|
||||||
if form.is_valid() and profile_form.is_valid():
|
if form.is_valid() and profile_form.is_valid():
|
||||||
user = form.save()
|
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()
|
||||||
|
|
||||||
|
user = form.save(commit=False)
|
||||||
profile = profile_form.save(commit=False)
|
profile = profile_form.save(commit=False)
|
||||||
profile.user = user
|
profile.user = user
|
||||||
profile.save()
|
profile.save()
|
||||||
|
user.save()
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
def get_success_url(self, **kwargs):
|
def get_success_url(self, **kwargs):
|
||||||
@ -115,6 +141,46 @@ class UserListView(LoginRequiredMixin,SingleTableView):
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class ManageAuthTokens(LoginRequiredMixin, TemplateView):
|
||||||
|
"""
|
||||||
|
Affiche le jeton d'authentification, et permet de le regénérer
|
||||||
|
"""
|
||||||
|
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)
|
||||||
|
context['token'] = Token.objects.get_or_create(user=self.request.user)[0]
|
||||||
|
return context
|
||||||
|
|
||||||
|
class UserAutocomplete(autocomplete.Select2QuerySetView):
|
||||||
|
"""
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
# 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:
|
||||||
|
qs = qs.filter(username__regex=self.q)
|
||||||
|
|
||||||
|
return qs
|
||||||
|
|
||||||
###################################
|
###################################
|
||||||
############## CLUB ###############
|
############## CLUB ###############
|
||||||
###################################
|
###################################
|
||||||
|
0
apps/note/api/__init__.py
Normal file
0
apps/note/api/__init__.py
Normal file
99
apps/note/api/serializers.py
Normal file
99
apps/note/api/serializers.py
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
# -*- 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, Alias
|
||||||
|
from ..models.transactions import TransactionTemplate, Transaction, MembershipTransaction
|
||||||
|
from rest_framework import serializers
|
||||||
|
from rest_polymorphic.serializers import PolymorphicSerializer
|
||||||
|
|
||||||
|
|
||||||
|
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 = '__all__'
|
||||||
|
extra_kwargs = {
|
||||||
|
'url': {'view_name': 'project-detail', 'lookup_field': 'pk'},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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 = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
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 = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
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 = '__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,
|
||||||
|
NoteUser: NoteUserSerializer,
|
||||||
|
NoteClub: NoteClubSerializer,
|
||||||
|
NoteSpecial: NoteSpecialSerializer
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = TransactionTemplate
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class TransactionSerializer(serializers.ModelSerializer):
|
||||||
|
"""
|
||||||
|
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 MembershipTransactionSerializer(serializers.ModelSerializer):
|
||||||
|
"""
|
||||||
|
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__'
|
18
apps/note/api/urls.py
Normal file
18
apps/note/api/urls.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# -*- 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 NotePolymorphicViewSet, AliasViewSet, \
|
||||||
|
TransactionViewSet, TransactionTemplateViewSet, MembershipTransactionViewSet
|
||||||
|
|
||||||
|
|
||||||
|
def register_note_urls(router, path):
|
||||||
|
"""
|
||||||
|
Configure router for Note REST API.
|
||||||
|
"""
|
||||||
|
router.register(path + '/note', NotePolymorphicViewSet)
|
||||||
|
router.register(path + '/alias', AliasViewSet)
|
||||||
|
|
||||||
|
router.register(path + '/transaction/transaction', TransactionViewSet)
|
||||||
|
router.register(path + '/transaction/template', TransactionTemplateViewSet)
|
||||||
|
router.register(path + '/transaction/membership', MembershipTransactionViewSet)
|
155
apps/note/api/views.py
Normal file
155
apps/note/api/views.py
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
# -*- mode: python; coding: utf-8 -*-
|
||||||
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from django.db.models import Q
|
||||||
|
from rest_framework import viewsets
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
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 NotePolymorphicViewSet(viewsets.ModelViewSet):
|
||||||
|
"""
|
||||||
|
REST API View set.
|
||||||
|
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.lower()))
|
||||||
|
|
||||||
|
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):
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
|
||||||
|
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.lower()))
|
||||||
|
|
||||||
|
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):
|
||||||
|
"""
|
||||||
|
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
|
@ -1,14 +1,54 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
from dal import autocomplete, forward
|
||||||
from django import forms
|
from django import forms
|
||||||
from .models import TransactionTemplate, Transaction
|
from .models import Transaction, TransactionTemplate
|
||||||
|
|
||||||
class TransactionTemplateForm(forms.ModelForm):
|
class TransactionTemplateForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = TransactionTemplate
|
model = TransactionTemplate
|
||||||
fields ='__all__'
|
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
|
||||||
|
# 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={
|
||||||
|
'data-placeholder': 'Note ...',
|
||||||
|
'data-minimum-input-length': 1,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TransactionForm(forms.ModelForm):
|
||||||
|
def save(self, commit=True):
|
||||||
|
self.instance.transaction_type = 'transfert'
|
||||||
|
|
||||||
|
super().save(commit)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Transaction
|
||||||
|
fields = ('source', 'destination', 'reason', 'amount',)
|
||||||
|
|
||||||
|
# Voir ci-dessus
|
||||||
|
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 ...',
|
||||||
|
'data-minimum-input-length': 1,
|
||||||
|
},),
|
||||||
|
}
|
||||||
|
|
||||||
class ConsoForm(forms.ModelForm):
|
class ConsoForm(forms.ModelForm):
|
||||||
|
|
||||||
def save(self, commit=True):
|
def save(self, commit=True):
|
||||||
button: TransactionTemplate = TransactionTemplate.objects.filter(name=self.data['button']).get()
|
button: TransactionTemplate = TransactionTemplate.objects.filter(name=self.data['button']).get()
|
||||||
self.instance.destination = button.destination
|
self.instance.destination = button.destination
|
||||||
@ -20,3 +60,14 @@ class ConsoForm(forms.ModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Transaction
|
model = Transaction
|
||||||
fields = ('source',)
|
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,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
@ -85,7 +85,7 @@ class Note(PolymorphicModel):
|
|||||||
"""
|
"""
|
||||||
Verify alias (simulate save)
|
Verify alias (simulate save)
|
||||||
"""
|
"""
|
||||||
aliases = Alias.objects.filter(name=str(self))
|
aliases = Alias.objects.filter(normalized_name=Alias.normalize(str(self)))
|
||||||
if aliases.exists():
|
if aliases.exists():
|
||||||
# Alias exists, so check if it is linked to this note
|
# Alias exists, so check if it is linked to this note
|
||||||
if aliases.first().note != self:
|
if aliases.first().note != self:
|
||||||
@ -233,3 +233,8 @@ class Alias(models.Model):
|
|||||||
'already exists.'))
|
'already exists.'))
|
||||||
except Alias.DoesNotExist:
|
except Alias.DoesNotExist:
|
||||||
pass
|
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)
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
import django_tables2 as tables
|
import django_tables2 as tables
|
||||||
|
from django.db.models import F
|
||||||
|
|
||||||
from .models.transactions import Transaction
|
from .models.transactions import Transaction
|
||||||
|
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
|
from .models import Note
|
||||||
|
|
||||||
app_name = 'note'
|
app_name = 'note'
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
@ -14,4 +15,7 @@ urlpatterns = [
|
|||||||
path('buttons/',views.TransactionTemplateListView.as_view(),name='template_list'),
|
path('buttons/',views.TransactionTemplateListView.as_view(),name='template_list'),
|
||||||
path('consos/<str:template_type>/',views.ConsoView.as_view(),name='consos'),
|
path('consos/<str:template_type>/',views.ConsoView.as_view(),name='consos'),
|
||||||
path('consos/',views.ConsoView.as_view(),name='consos'),
|
path('consos/',views.ConsoView.as_view(),name='consos'),
|
||||||
|
|
||||||
|
# API for the note autocompleter
|
||||||
|
path('note-autocomplete/', views.NoteAutocomplete.as_view(model=Note),name='note_autocomplete'),
|
||||||
]
|
]
|
||||||
|
@ -2,13 +2,15 @@
|
|||||||
# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
from dal import autocomplete
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
|
from django.db.models import Q
|
||||||
from django.urls import reverse_lazy, reverse
|
from django.urls import reverse_lazy, reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
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
|
||||||
|
|
||||||
from .models import Transaction,TransactionCategory,TransactionTemplate
|
from .models import Note, Transaction, TransactionCategory, TransactionTemplate, Alias
|
||||||
from .forms import TransactionTemplateForm, ConsoForm
|
from .forms import TransactionForm, TransactionTemplateForm, ConsoForm
|
||||||
|
|
||||||
class TransactionCreate(LoginRequiredMixin, CreateView):
|
class TransactionCreate(LoginRequiredMixin, CreateView):
|
||||||
"""
|
"""
|
||||||
@ -17,7 +19,7 @@ class TransactionCreate(LoginRequiredMixin, CreateView):
|
|||||||
TODO: If user have sufficient rights, they can transfer from an other note
|
TODO: If user have sufficient rights, they can transfer from an other note
|
||||||
"""
|
"""
|
||||||
model = Transaction
|
model = Transaction
|
||||||
fields = ('destination', 'amount', 'reason')
|
form_class = TransactionForm
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
@ -28,6 +30,77 @@ class TransactionCreate(LoginRequiredMixin, CreateView):
|
|||||||
'to one or others')
|
'to one or others')
|
||||||
return context
|
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
|
||||||
|
"""
|
||||||
|
|
||||||
|
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 aliases.
|
||||||
|
"""
|
||||||
|
# Un utilisateur non connecté n'a accès à aucune information
|
||||||
|
if not self.request.user.is_authenticated:
|
||||||
|
return Alias.objects.none()
|
||||||
|
|
||||||
|
qs = Alias.objects.all()
|
||||||
|
|
||||||
|
# self.q est le paramètre de la recherche
|
||||||
|
if self.q:
|
||||||
|
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(note__polymorphic_ctype__model="noteuser")
|
||||||
|
elif "club" in l:
|
||||||
|
qs = qs.filter(note__polymorphic_ctype__model="noteclub")
|
||||||
|
elif "special" in l:
|
||||||
|
qs = qs.filter(note__polymorphic_ctype__model="notespecial")
|
||||||
|
else:
|
||||||
|
qs = qs.none()
|
||||||
|
|
||||||
|
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:
|
||||||
|
res += " (aka. " + note_name + ")"
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
class TransactionTemplateCreateView(LoginRequiredMixin,CreateView):
|
class TransactionTemplateCreateView(LoginRequiredMixin,CreateView):
|
||||||
"""
|
"""
|
||||||
Create TransactionTemplate
|
Create TransactionTemplate
|
||||||
|
@ -50,11 +50,18 @@ INSTALLED_APPS = [
|
|||||||
'django.contrib.sites',
|
'django.contrib.sites',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
|
# API
|
||||||
|
'rest_framework',
|
||||||
|
'rest_framework.authtoken',
|
||||||
|
# Autocomplete
|
||||||
|
'dal',
|
||||||
|
'dal_select2',
|
||||||
|
|
||||||
# Note apps
|
# Note apps
|
||||||
'activity',
|
'activity',
|
||||||
'member',
|
'member',
|
||||||
'note',
|
'note',
|
||||||
|
'api',
|
||||||
]
|
]
|
||||||
LOGIN_REDIRECT_URL = '/note/transfer/'
|
LOGIN_REDIRECT_URL = '/note/transfer/'
|
||||||
|
|
||||||
@ -117,6 +124,18 @@ AUTHENTICATION_BACKENDS = (
|
|||||||
'guardian.backends.ObjectPermissionBackend',
|
'guardian.backends.ObjectPermissionBackend',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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': [
|
||||||
|
'rest_framework.authentication.TokenAuthentication',
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
ANONYMOUS_USER_NAME = None # Disable guardian anonymous user
|
ANONYMOUS_USER_NAME = None # Disable guardian anonymous user
|
||||||
|
|
||||||
GUARDIAN_GET_CONTENT_TYPE = 'polymorphic.contrib.guardian.get_polymorphic_base_content_type'
|
GUARDIAN_GET_CONTENT_TYPE = 'polymorphic.contrib.guardian.get_polymorphic_base_content_type'
|
||||||
|
@ -19,4 +19,7 @@ urlpatterns = [
|
|||||||
path('accounts/', include('django.contrib.auth.urls')),
|
path('accounts/', include('django.contrib.auth.urls')),
|
||||||
path('admin/doc/', include('django.contrib.admindocs.urls')),
|
path('admin/doc/', include('django.contrib.admindocs.urls')),
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
|
|
||||||
|
# Include Django REST API
|
||||||
|
path('api/', include('api.urls')),
|
||||||
]
|
]
|
||||||
|
@ -3,11 +3,14 @@ chardet==3.0.4
|
|||||||
defusedxml==0.6.0
|
defusedxml==0.6.0
|
||||||
Django==2.2.3
|
Django==2.2.3
|
||||||
django-allauth==0.39.1
|
django-allauth==0.39.1
|
||||||
|
django-autocomplete-light==3.3.0
|
||||||
django-crispy-forms==1.7.2
|
django-crispy-forms==1.7.2
|
||||||
django-extensions==2.1.9
|
django-extensions==2.1.9
|
||||||
django-filter==2.2.0
|
django-filter==2.2.0
|
||||||
django-guardian==2.1.0
|
django-guardian==2.1.0
|
||||||
django-polymorphic==2.0.3
|
django-polymorphic==2.0.3
|
||||||
|
djangorestframework==3.9.0
|
||||||
|
django-rest-polymorphic==0.1.8
|
||||||
django-reversion==3.0.3
|
django-reversion==3.0.3
|
||||||
django-tables2==2.1.0
|
django-tables2==2.1.0
|
||||||
docutils==0.14
|
docutils==0.14
|
||||||
|
@ -31,6 +31,12 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
crossorigin="anonymous">
|
crossorigin="anonymous">
|
||||||
<link rel="stylesheet"
|
<link rel="stylesheet"
|
||||||
href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
|
href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
|
||||||
|
|
||||||
|
{# 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 %}
|
{% block extracss %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
33
templates/member/manage_auth_tokens.html
Normal file
33
templates/member/manage_auth_tokens.html
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n static pretty_money django_tables2 %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<h4>À quoi sert un jeton d'authentification ?</h4>
|
||||||
|
|
||||||
|
Un jeton vous permet de vous connecter à <a href="/api/">l'API de la Note Kfet</a>.<br />
|
||||||
|
Il suffit pour cela d'ajouter en en-tête de vos requêtes <code>Authorization: Token <TOKEN></code>
|
||||||
|
pour pouvoir vous identifier.<br /><br />
|
||||||
|
|
||||||
|
Une documentation de l'API arrivera ultérieurement.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<strong>{%trans 'Token' %} :</strong>
|
||||||
|
{% if 'show' in request.GET %}
|
||||||
|
{{ token.key }} (<a href="?">cacher</a>)
|
||||||
|
{% else %}
|
||||||
|
<em>caché</em> (<a href="?show">montrer</a>)
|
||||||
|
{% endif %}
|
||||||
|
<br />
|
||||||
|
<strong>{%trans 'Created' %} :</strong> {{ token.created }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<strong>Attention :</strong> regénérer le jeton va révoquer tout accès autorisé à l'API via ce jeton !
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a href="?regenerate">
|
||||||
|
<button class="btn btn-primary">{% trans 'Regenerate token' %}</button>
|
||||||
|
</a>
|
||||||
|
{% endblock %}
|
@ -8,13 +8,13 @@
|
|||||||
|
|
||||||
<dl class="row">
|
<dl class="row">
|
||||||
<dt class="col-6 col-md-3">{% trans 'name'|capfirst %}</dt>
|
<dt class="col-6 col-md-3">{% trans 'name'|capfirst %}</dt>
|
||||||
<dd class="col-6 col-md-3">{{ object.user.name }}</dd>
|
<dd class="col-6 col-md-3">{{ object.user.last_name }}</dd>
|
||||||
<dt class="col-6 col-md-3">{% trans 'first name'|capfirst %}</dt>
|
<dt class="col-6 col-md-3">{% trans 'first name'|capfirst %}</dt>
|
||||||
<dd class="col-6 col-md-3">{{ object.user.first_name }}</dd>
|
<dd class="col-6 col-md-3">{{ object.user.first_name }}</dd>
|
||||||
<dt class="col-6 col-md-3">{% trans 'username'|capfirst %}</dt>
|
<dt class="col-6 col-md-3">{% trans 'username'|capfirst %}</dt>
|
||||||
<dd class="col-6 col-md-3">{{ object.user.username }}</dd>
|
<dd class="col-6 col-md-3">{{ object.user.username }}</dd>
|
||||||
<dt class="col-6 col-md-3">Aliases</dt>
|
<dt class="col-6 col-md-3">Aliases</dt>
|
||||||
<dd class="col-6 col-md-3">{{ object.user.note.aliases_set.all }}</dd>
|
<dd class="col-6 col-md-3">{{ object.user.note.alias_set.all }}</dd>
|
||||||
<dt class="col-6 col-md-3">{% trans 'section'|capfirst %}</dt>
|
<dt class="col-6 col-md-3">{% trans 'section'|capfirst %}</dt>
|
||||||
<dd class="col-6 col-md-3">{{ object.section }}</dd>
|
<dd class="col-6 col-md-3">{{ object.section }}</dd>
|
||||||
<dt class="col-6 col-md-3">{% trans 'address'|capfirst %}</dt>
|
<dt class="col-6 col-md-3">{% trans 'address'|capfirst %}</dt>
|
||||||
@ -23,6 +23,9 @@
|
|||||||
<dd class="col-6 col-md-3">{{ object.user.note.balance | pretty_money }}</dd>
|
<dd class="col-6 col-md-3">{{ object.user.note.balance | pretty_money }}</dd>
|
||||||
</dl>
|
</dl>
|
||||||
<center>
|
<center>
|
||||||
|
{% if object.user.pk == user.pk %}
|
||||||
|
<a class="btn btn-primary" href="{% url 'member:auth_token' %}">{% trans 'Manage auth token' %}</a>
|
||||||
|
{% endif %}
|
||||||
<a class="btn btn-primary" href="{% url 'member:user_update_profile' object.pk %}">{% trans 'Update Profile' %}</a>
|
<a class="btn btn-primary" href="{% url 'member:user_update_profile' object.pk %}">{% trans 'Update Profile' %}</a>
|
||||||
<a class="btn btn-primary" href="{% url 'password_change' %}">{% trans 'Change password' %}</a>
|
<a class="btn btn-primary" href="{% url 'password_change' %}">{% trans 'Change password' %}</a>
|
||||||
</center>
|
</center>
|
||||||
|
Loading…
Reference in New Issue
Block a user