diff --git a/apps/activity/admin.py b/apps/activity/admin.py index 1efe272c..494baffc 100644 --- a/apps/activity/admin.py +++ b/apps/activity/admin.py @@ -12,7 +12,7 @@ class ActivityAdmin(admin.ModelAdmin): Admin customisation for Activity """ list_display = ('name', 'activity_type', 'organizer') - list_filter = ('activity_type',) + list_filter = ('activity_type', ) search_fields = ['name', 'organizer__name'] # Organize activities by start date diff --git a/apps/activity/api/serializers.py b/apps/activity/api/serializers.py index f7f949e7..46bd8384 100644 --- a/apps/activity/api/serializers.py +++ b/apps/activity/api/serializers.py @@ -5,6 +5,7 @@ from ..models import ActivityType, Activity, Guest from rest_framework import serializers + class ActivityTypeSerializer(serializers.ModelSerializer): """ REST API Serializer for Activity types. diff --git a/apps/api/urls.py b/apps/api/urls.py index 7ac56ca1..475120fc 100644 --- a/apps/api/urls.py +++ b/apps/api/urls.py @@ -17,10 +17,13 @@ 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',) + exclude = ( + 'password', + 'groups', + 'user_permissions', + ) class UserViewSet(viewsets.ModelViewSet): diff --git a/apps/member/admin.py b/apps/member/admin.py index f45d5f55..2aa65d09 100644 --- a/apps/member/admin.py +++ b/apps/member/admin.py @@ -19,9 +19,9 @@ class ProfileInline(admin.StackedInline): class CustomUserAdmin(UserAdmin): - inlines = (ProfileInline,) + inlines = (ProfileInline, ) list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff') - list_select_related = ('profile',) + list_select_related = ('profile', ) form = ProfileForm def get_inline_instances(self, request, obj=None): diff --git a/apps/member/filters.py b/apps/member/filters.py index fb1a2128..76e0d52b 100644 --- a/apps/member/filters.py +++ b/apps/member/filters.py @@ -2,30 +2,35 @@ # Copyright (C) 2018-2019 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later -from django_filters import FilterSet, CharFilter,NumberFilter +from django_filters import FilterSet, CharFilter, NumberFilter from django.contrib.auth.models import User from django.db.models import CharField from crispy_forms.helper import FormHelper from crispy_forms.layout import Layout, Submit -from .models import Club +from .models import Club + class UserFilter(FilterSet): class Meta: model = User - fields = ['last_name','first_name','username','profile__section'] - filter_overrides={ - CharField:{ - 'filter_class':CharFilter, - 'extra': lambda f:{ - 'lookup_expr':'icontains' + fields = ['last_name', 'first_name', 'username', 'profile__section'] + filter_overrides = { + CharField: { + 'filter_class': CharFilter, + 'extra': lambda f: { + 'lookup_expr': 'icontains' } } } + class UserFilterFormHelper(FormHelper): form_method = 'GET' layout = Layout( - 'last_name','first_name','username','profile__section', - Submit('Submit','Apply Filter'), + 'last_name', + 'first_name', + 'username', + 'profile__section', + Submit('Submit', 'Apply Filter'), ) diff --git a/apps/member/forms.py b/apps/member/forms.py index 4d03764e..ef32e9b8 100644 --- a/apps/member/forms.py +++ b/apps/member/forms.py @@ -16,11 +16,11 @@ from crispy_forms.bootstrap import InlineField, FormActions, StrictButton, Div, from crispy_forms.layout import Layout - class SignUpForm(UserCreationForm): class Meta: model = User - fields = ['first_name','last_name','username','email'] + fields = ['first_name', 'last_name', 'username', 'email'] + class ProfileForm(forms.ModelForm): """ @@ -31,48 +31,56 @@ class ProfileForm(forms.ModelForm): fields = '__all__' exclude = ['user'] + class ClubForm(forms.ModelForm): class Meta: model = Club - fields ='__all__' + fields = '__all__' + class AddMembersForm(forms.Form): class Meta: - fields = ('',) + fields = ('', ) + class MembershipForm(forms.ModelForm): class Meta: 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, - }), + 'user': + autocomplete.ModelSelect2( + url='member:user_autocomplete', + attrs={ + 'data-placeholder': 'Nom ...', + 'data-minimum-input-length': 1, + }, + ), } -MemberFormSet = forms.modelformset_factory(Membership, - form=MembershipForm, - extra=2, - can_delete=True) +MemberFormSet = forms.modelformset_factory( + Membership, + form=MembershipForm, + extra=2, + can_delete=True, +) + class FormSetHelper(FormHelper): - def __init__(self,*args,**kwargs): - super().__init__(*args,**kwargs) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.form_tag = False self.form_method = 'POST' - self.form_class='form-inline' + self.form_class = 'form-inline' # self.template = 'bootstrap/table_inline_formset.html' self.layout = Layout( Div( - Div('user',css_class='col-sm-2'), - Div('roles',css_class='col-sm-2'), - Div('date_start',css_class='col-sm-2'), + Div('user', css_class='col-sm-2'), + Div('roles', css_class='col-sm-2'), + Div('date_start', css_class='col-sm-2'), css_class="row formset-row", - ) - ) + )) diff --git a/apps/member/models.py b/apps/member/models.py index 35b7027c..ae5d90d5 100644 --- a/apps/member/models.py +++ b/apps/member/models.py @@ -9,6 +9,7 @@ from django.dispatch import receiver from django.utils.translation import gettext_lazy as _ from django.urls import reverse, reverse_lazy + class Profile(models.Model): """ An user profile @@ -50,7 +51,8 @@ class Profile(models.Model): verbose_name_plural = _('user profile') def get_absolute_url(self): - return reverse('user_detail',args=(self.pk,)) + return reverse('user_detail', args=(self.pk, )) + class Club(models.Model): """ @@ -98,7 +100,7 @@ class Club(models.Model): return self.name def get_absolute_url(self): - return reverse_lazy('member:club_detail', args=(self.pk,)) + return reverse_lazy('member:club_detail', args=(self.pk, )) class Role(models.Model): @@ -129,15 +131,15 @@ class Membership(models.Model): """ user = models.ForeignKey( settings.AUTH_USER_MODEL, - on_delete=models.PROTECT + on_delete=models.PROTECT, ) club = models.ForeignKey( Club, - on_delete=models.PROTECT + on_delete=models.PROTECT, ) roles = models.ForeignKey( Role, - on_delete=models.PROTECT + on_delete=models.PROTECT, ) date_start = models.DateField( verbose_name=_('membership starts on'), diff --git a/apps/member/signals.py b/apps/member/signals.py index 6688516b..6ac23376 100644 --- a/apps/member/signals.py +++ b/apps/member/signals.py @@ -1,6 +1,3 @@ -#!/usr/bin/env python - # -*- mode: python; coding: utf-8 -*- # Copyright (C) 2018-2019 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later - diff --git a/apps/member/tables.py b/apps/member/tables.py index 4218948c..8ae10476 100644 --- a/apps/member/tables.py +++ b/apps/member/tables.py @@ -5,15 +5,20 @@ from .models import Club from django.conf import settings from django.contrib.auth.models import User + class ClubTable(tables.Table): class Meta: - attrs = {'class':'table table-bordered table-condensed table-striped table-hover'} + attrs = { + 'class': + 'table table-bordered table-condensed table-striped table-hover' + } model = Club template_name = 'django_tables2/bootstrap.html' - fields = ('id','name','email') - row_attrs = {'class':'table-row', - 'data-href': lambda record: record.pk } - + fields = ('id', 'name', 'email') + row_attrs = { + 'class': 'table-row', + 'data-href': lambda record: record.pk + } class UserTable(tables.Table): @@ -21,7 +26,10 @@ class UserTable(tables.Table): solde = tables.Column(accessor='note.balance') class Meta: - attrs = {'class':'table table-bordered table-condensed table-striped table-hover'} + attrs = { + 'class': + 'table table-bordered table-condensed table-striped table-hover' + } template_name = 'django_tables2/bootstrap.html' - fields = ('last_name','first_name','username','email') + fields = ('last_name', 'first_name', 'username', 'email') model = User diff --git a/apps/member/views.py b/apps/member/views.py index be2d8d58..86ce3b19 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -16,14 +16,14 @@ from rest_framework.authtoken.models import Token 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 +from .forms import SignUpForm, ProfileForm, ClubForm, MembershipForm, MemberFormSet, FormSetHelper +from .tables import ClubTable, UserTable from .filters import UserFilter, UserFilterFormHelper - from note.models.transactions import Transaction from note.tables import HistoryTable + class UserCreateView(CreateView): """ Une vue pour inscrire un utilisateur et lui créer un profile @@ -31,10 +31,10 @@ class UserCreateView(CreateView): form_class = SignUpForm success_url = reverse_lazy('login') - template_name ='member/signup.html' + template_name = 'member/signup.html' second_form = ProfileForm - def get_context_data(self,**kwargs): + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["profile_form"] = self.second_form() @@ -49,17 +49,20 @@ class UserCreateView(CreateView): profile.save() return super().form_valid(form) -class UserUpdateView(LoginRequiredMixin,UpdateView): + +class UserUpdateView(LoginRequiredMixin, UpdateView): model = User - fields = ['first_name','last_name','username','email'] + fields = ['first_name', 'last_name', 'username', 'email'] template_name = 'member/profile_update.html' second_form = ProfileForm - def get_context_data(self,**kwargs): + + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['user_modified'] = context['user'] context['user'] = self.request.user - context["profile_form"] = self.second_form(instance=context['user_modified'].profile) + context["profile_form"] = self.second_form( + instance=context['user_modified'].profile) return context @@ -71,44 +74,52 @@ class UserUpdateView(LoginRequiredMixin,UpdateView): 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)) + 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.")) + 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) + 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) # 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)) + 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.save() user.save() return super().form_valid(form) def get_success_url(self, **kwargs): - if kwargs: - return reverse_lazy('member:user_detail', kwargs = {'pk': kwargs['id']}) + if kwargs: + return reverse_lazy('member:user_detail', + kwargs={'pk': kwargs['id']}) else: - return reverse_lazy('member:user_detail', args = (self.object.id,)) + return reverse_lazy('member:user_detail', args=(self.object.id, )) -class UserDetailView(LoginRequiredMixin,DetailView): + +class UserDetailView(LoginRequiredMixin, DetailView): """ Affiche les informations sur un utilisateur, sa note, ses clubs ... """ model = Profile context_object_name = "profile" - def get_context_data(slef,**kwargs): + + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) user = context['profile'].user history_list = \ @@ -119,7 +130,8 @@ class UserDetailView(LoginRequiredMixin,DetailView): context['club_list'] = ClubTable(club_list) return context -class UserListView(LoginRequiredMixin,SingleTableView): + +class UserListView(LoginRequiredMixin, SingleTableView): """ Affiche la liste des utilisateurs, avec une fonction de recherche statique """ @@ -129,13 +141,13 @@ class UserListView(LoginRequiredMixin,SingleTableView): filter_class = UserFilter formhelper_class = UserFilterFormHelper - def get_queryset(self,**kwargs): + def get_queryset(self, **kwargs): qs = super().get_queryset() - self.filter = self.filter_class(self.request.GET,queryset=qs) + self.filter = self.filter_class(self.request.GET, queryset=qs) self.filter.form.helper = self.formhelper_class() return self.filter.qs - def get_context_data(self,**kwargs): + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["filter"] = self.filter return context @@ -149,22 +161,25 @@ class ManageAuthTokens(LoginRequiredMixin, TemplateView): 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(): + 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 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] + 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. @@ -181,32 +196,36 @@ class UserAutocomplete(autocomplete.Select2QuerySetView): return qs + ################################### ############## CLUB ############### ################################### -class ClubCreateView(LoginRequiredMixin,CreateView): + +class ClubCreateView(LoginRequiredMixin, CreateView): """ Create Club """ model = Club form_class = ClubForm - def form_valid(self,form): + def form_valid(self, form): return super().form_valid(form) -class ClubListView(LoginRequiredMixin,SingleTableView): + +class ClubListView(LoginRequiredMixin, SingleTableView): """ List existing Clubs """ model = Club table_class = ClubTable -class ClubDetailView(LoginRequiredMixin,DetailView): - model = Club - context_object_name="club" - def get_context_data(self,**kwargs): +class ClubDetailView(LoginRequiredMixin, DetailView): + model = Club + context_object_name = "club" + + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) club = context["club"] club_transactions = \ @@ -218,23 +237,25 @@ class ClubDetailView(LoginRequiredMixin,DetailView): context['member_list'] = club_member return context -class ClubAddMemberView(LoginRequiredMixin,CreateView): + +class ClubAddMemberView(LoginRequiredMixin, CreateView): model = Membership form_class = MembershipForm template_name = 'member/add_members.html' - def get_context_data(self,**kwargs): + + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['formset'] = MemberFormSet() context['helper'] = FormSetHelper() return context - def post(self,request,*args,**kwargs): + def post(self, request, *args, **kwargs): formset = MembershipFormset(request.POST) if formset.is_valid(): return self.form_valid(formset) else: return self.form_invalid(formset) - def form_valid(self,formset): + def form_valid(self, formset): formset.save() return super().form_valid(formset) diff --git a/apps/note/admin.py b/apps/note/admin.py index 0b2461d0..be01da94 100644 --- a/apps/note/admin.py +++ b/apps/note/admin.py @@ -25,7 +25,10 @@ class NoteAdmin(PolymorphicParentModelAdmin): Parent regrouping all note types as children """ child_models = (NoteClub, NoteSpecial, NoteUser) - list_filter = (PolymorphicChildModelFilter, 'is_active',) + list_filter = ( + PolymorphicChildModelFilter, + 'is_active', + ) # Use a polymorphic list list_display = ('pretty', 'balance', 'is_active') @@ -44,11 +47,12 @@ class NoteClubAdmin(PolymorphicChildModelAdmin): """ Child for a club note, see NoteAdmin """ - inlines = (AliasInlines,) + inlines = (AliasInlines, ) # We can't change club after creation or the balance readonly_fields = ('club', 'balance') - search_fields = ('club',) + search_fields = ('club', ) + def has_add_permission(self, request): """ A club note should not be manually added @@ -67,7 +71,7 @@ class NoteSpecialAdmin(PolymorphicChildModelAdmin): """ Child for a special note, see NoteAdmin """ - readonly_fields = ('balance',) + readonly_fields = ('balance', ) @admin.register(NoteUser) @@ -75,7 +79,7 @@ class NoteUserAdmin(PolymorphicChildModelAdmin): """ Child for an user note, see NoteAdmin """ - inlines = (AliasInlines,) + inlines = (AliasInlines, ) # We can't change user after creation or the balance readonly_fields = ('user', 'balance') @@ -101,7 +105,10 @@ class TransactionAdmin(admin.ModelAdmin): list_display = ('created_at', 'poly_source', 'poly_destination', 'quantity', 'amount', 'transaction_type', 'valid') list_filter = ('transaction_type', 'valid') - autocomplete_fields = ('source', 'destination',) + autocomplete_fields = ( + 'source', + 'destination', + ) def poly_source(self, obj): """ @@ -136,8 +143,8 @@ class TransactionTemplateAdmin(admin.ModelAdmin): Admin customisation for TransactionTemplate """ list_display = ('name', 'poly_destination', 'amount', 'template_type') - list_filter = ('template_type',) - autocomplete_fields = ('destination',) + list_filter = ('template_type', ) + autocomplete_fields = ('destination', ) def poly_destination(self, obj): """ @@ -153,5 +160,5 @@ class TransactionCategoryAdmin(admin.ModelAdmin): """ Admin customisation for TransactionTemplate """ - list_display = ('name',) - list_filter = ('name',) + list_display = ('name', ) + list_filter = ('name', ) diff --git a/apps/note/api/serializers.py b/apps/note/api/serializers.py index afc3b419..90c802a9 100644 --- a/apps/note/api/serializers.py +++ b/apps/note/api/serializers.py @@ -17,7 +17,10 @@ class NoteSerializer(serializers.ModelSerializer): model = Note fields = '__all__' extra_kwargs = { - 'url': {'view_name': 'project-detail', 'lookup_field': 'pk'}, + 'url': { + 'view_name': 'project-detail', + 'lookup_field': 'pk' + }, } @@ -69,6 +72,7 @@ class NotePolymorphicSerializer(PolymorphicSerializer): NoteSpecial: NoteSpecialSerializer } + class TransactionTemplateSerializer(serializers.ModelSerializer): """ REST API Serializer for Transaction templates. diff --git a/apps/note/api/views.py b/apps/note/api/views.py index 37ca4e20..a63cf102 100644 --- a/apps/note/api/views.py +++ b/apps/note/api/views.py @@ -69,7 +69,9 @@ 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.lower())) + 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: @@ -79,7 +81,8 @@ class NotePolymorphicViewSet(viewsets.ModelViewSet): elif "club" in l: queryset = queryset.filter(polymorphic_ctype__model="noteclub") elif "special" in l: - queryset = queryset.filter(polymorphic_ctype__model="notespecial") + queryset = queryset.filter( + polymorphic_ctype__model="notespecial") else: queryset = queryset.none() @@ -104,7 +107,8 @@ 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.lower())) + 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: @@ -114,11 +118,14 @@ class AliasViewSet(viewsets.ModelViewSet): if note_type: l = str(note_type).lower() if "user" in l: - queryset = queryset.filter(note__polymorphic_ctype__model="noteuser") + queryset = queryset.filter( + note__polymorphic_ctype__model="noteuser") elif "club" in l: - queryset = queryset.filter(note__polymorphic_ctype__model="noteclub") + queryset = queryset.filter( + note__polymorphic_ctype__model="noteclub") elif "special" in l: - queryset = queryset.filter(note__polymorphic_ctype__model="notespecial") + queryset = queryset.filter( + note__polymorphic_ctype__model="notespecial") else: queryset = queryset.none() diff --git a/apps/note/apps.py b/apps/note/apps.py index c53f915a..4ac2c847 100644 --- a/apps/note/apps.py +++ b/apps/note/apps.py @@ -20,9 +20,9 @@ class NoteConfig(AppConfig): """ post_save.connect( signals.save_user_note, - sender=settings.AUTH_USER_MODEL + sender=settings.AUTH_USER_MODEL, ) post_save.connect( signals.save_club_note, - sender='member.Club' + sender='member.Club', ) diff --git a/apps/note/forms.py b/apps/note/forms.py index 09818931..8f670455 100644 --- a/apps/note/forms.py +++ b/apps/note/forms.py @@ -4,10 +4,11 @@ from dal import autocomplete, forward from django import forms from .models import Transaction, TransactionTemplate + class TransactionTemplateForm(forms.ModelForm): class Meta: 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 @@ -15,11 +16,14 @@ class TransactionTemplateForm(forms.ModelForm): # 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, - }), + 'destination': + autocomplete.ModelSelect2( + url='note:note_autocomplete', + attrs={ + 'data-placeholder': 'Note ...', + 'data-minimum-input-length': 1, + }, + ), } @@ -31,26 +35,38 @@ class TransactionForm(forms.ModelForm): class Meta: model = Transaction - fields = ('source', 'destination', 'reason', 'amount',) + 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, - },), + '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): - 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.amount = button.amount self.instance.transaction_type = 'bouton' @@ -59,15 +75,18 @@ class ConsoForm(forms.ModelForm): class Meta: 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, - }), + 'source': + autocomplete.ModelSelect2( + url='note:note_autocomplete', + attrs={ + 'data-placeholder': 'Note ...', + 'data-minimum-input-length': 1, + }, + ), } diff --git a/apps/note/models/notes.py b/apps/note/models/notes.py index 6a0c5ebe..a5ab993a 100644 --- a/apps/note/models/notes.py +++ b/apps/note/models/notes.py @@ -10,7 +10,6 @@ from django.core.validators import RegexValidator from django.db import models from django.utils.translation import gettext_lazy as _ from polymorphic.models import PolymorphicModel - """ Defines each note types """ @@ -34,8 +33,7 @@ class Note(PolymorphicModel): default=True, help_text=_( 'Designates whether this note should be treated as active. ' - 'Unselect this instead of deleting notes.' - ), + 'Unselect this instead of deleting notes.'), ) display_image = models.ImageField( verbose_name=_('display image'), @@ -85,7 +83,8 @@ class Note(PolymorphicModel): """ Verify alias (simulate save) """ - aliases = Alias.objects.filter(normalized_name=Alias.normalize(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: @@ -181,15 +180,15 @@ class Alias(models.Model): validators=[ RegexValidator( regex=settings.ALIAS_VALIDATOR_REGEX, - message=_('Invalid alias') + message=_('Invalid alias'), ) - ] if settings.ALIAS_VALIDATOR_REGEX else [] + ] if settings.ALIAS_VALIDATOR_REGEX else [], ) normalized_name = models.CharField( max_length=255, unique=True, default='', - editable=False + editable=False, ) note = models.ForeignKey( Note, @@ -209,11 +208,9 @@ class Alias(models.Model): Normalizes a string: removes most diacritics and does casefolding """ return ''.join( - char - for char in unicodedata.normalize('NFKD', string.casefold()) + char for char in unicodedata.normalize('NFKD', string.casefold()) if all(not unicodedata.category(char).startswith(cat) - for cat in {'M', 'P', 'Z', 'C'}) - ).casefold() + for cat in {'M', 'P', 'Z', 'C'})).casefold() def save(self, *args, **kwargs): """ @@ -229,8 +226,9 @@ class Alias(models.Model): raise ValidationError(_('Alias too long.')) try: if self != Alias.objects.get(normalized_name=normalized_name): - raise ValidationError(_('An alias with a similar name ' - 'already exists.')) + raise ValidationError( + _('An alias with a similar name ' + 'already exists.')) except Alias.DoesNotExist: pass diff --git a/apps/note/tables.py b/apps/note/tables.py index 31cefe41..f13b502e 100644 --- a/apps/note/tables.py +++ b/apps/note/tables.py @@ -7,16 +7,19 @@ from .models.transactions import Transaction class HistoryTable(tables.Table): class Meta: - attrs = {'class':'table table-bordered table-condensed table-striped table-hover'} + attrs = { + 'class': + 'table table-bordered table-condensed table-striped table-hover' + } model = Transaction template_name = 'django_tables2/bootstrap.html' - sequence = ('...','total','valid') + sequence = ('...', 'total', 'valid') - total = tables.Column() #will use Transaction.total() !! + total = tables.Column() #will use Transaction.total() !! def order_total(self, QuerySet, is_descending): # needed for rendering QuerySet = QuerySet.annotate( - total=F('amount') * F('quantity') - ).order_by(('-' if is_descending else '') + 'total') + total=F('amount') * + F('quantity')).order_by(('-' if is_descending else '') + 'total') return (QuerySet, True) diff --git a/apps/note/templatetags/pretty_money.py b/apps/note/templatetags/pretty_money.py index ec0a0d5b..c1525056 100644 --- a/apps/note/templatetags/pretty_money.py +++ b/apps/note/templatetags/pretty_money.py @@ -2,10 +2,17 @@ from django import template def pretty_money(value): - if value%100 == 0: - return "{:s}{:d} €".format("- " if value < 0 else "", abs(value) // 100) + if value % 100 == 0: + return "{:s}{:d} €".format( + "- " if value < 0 else "", + abs(value) // 100, + ) else: - return "{:s}{:d} € {:02d}".format("- " if value < 0 else "", abs(value) // 100, abs(value) % 100) + return "{:s}{:d} € {:02d}".format( + "- " if value < 0 else "", + abs(value) // 100, + abs(value) % 100, + ) register = template.Library() diff --git a/apps/note/views.py b/apps/note/views.py index 3414a6c0..d4590e5f 100644 --- a/apps/note/views.py +++ b/apps/note/views.py @@ -12,6 +12,7 @@ from django.views.generic import CreateView, ListView, DetailView, UpdateView from .models import Note, Transaction, TransactionCategory, TransactionTemplate, Alias from .forms import TransactionForm, TransactionTemplateForm, ConsoForm + class TransactionCreate(LoginRequiredMixin, CreateView): """ Show transfer page @@ -30,14 +31,13 @@ class TransactionCreate(LoginRequiredMixin, CreateView): 'to one or others') 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" + if False: # TODO: fix it with "if %user has no right to transfer funds" del form.fields['source'] return form @@ -46,7 +46,7 @@ class TransactionCreate(LoginRequiredMixin, CreateView): """ 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" + 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) @@ -56,7 +56,6 @@ 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. @@ -101,27 +100,30 @@ class NoteAutocomplete(autocomplete.Select2QuerySetView): return str(result.note.pk) -class TransactionTemplateCreateView(LoginRequiredMixin,CreateView): +class TransactionTemplateCreateView(LoginRequiredMixin, CreateView): """ Create TransactionTemplate """ model = TransactionTemplate form_class = TransactionTemplateForm -class TransactionTemplateListView(LoginRequiredMixin,ListView): + +class TransactionTemplateListView(LoginRequiredMixin, ListView): """ List TransactionsTemplates """ model = TransactionTemplate form_class = TransactionTemplateForm -class TransactionTemplateUpdateView(LoginRequiredMixin,UpdateView): + +class TransactionTemplateUpdateView(LoginRequiredMixin, UpdateView): """ """ model = TransactionTemplate form_class = TransactionTemplateForm -class ConsoView(LoginRequiredMixin,CreateView): + +class ConsoView(LoginRequiredMixin, CreateView): """ Consume """ @@ -139,11 +141,14 @@ class ConsoView(LoginRequiredMixin,CreateView): if 'template_type' not in self.kwargs.keys(): return context - template_type = TransactionCategory.objects.filter(name=self.kwargs.get('template_type')).get() - context['buttons'] = TransactionTemplate.objects.filter(template_type=template_type) + template_type = TransactionCategory.objects.filter( + name=self.kwargs.get('template_type')).get() + context['buttons'] = TransactionTemplate.objects.filter( + template_type=template_type) context['title'] = template_type return context def get_success_url(self): - return reverse('note:consos',args=(self.kwargs.get('template_type'),)) + return reverse('note:consos', + args=(self.kwargs.get('template_type'), ))