Format code

This commit is contained in:
Alexandre Iooss 2020-02-18 12:31:15 +01:00
parent 0d7db68372
commit f89d91e524
No known key found for this signature in database
GPG Key ID: 6C79278F3FCDCC02
19 changed files with 262 additions and 167 deletions

View File

@ -12,7 +12,7 @@ class ActivityAdmin(admin.ModelAdmin):
Admin customisation for Activity Admin customisation for Activity
""" """
list_display = ('name', 'activity_type', 'organizer') list_display = ('name', 'activity_type', 'organizer')
list_filter = ('activity_type',) list_filter = ('activity_type', )
search_fields = ['name', 'organizer__name'] search_fields = ['name', 'organizer__name']
# Organize activities by start date # Organize activities by start date

View File

@ -5,6 +5,7 @@
from ..models import ActivityType, Activity, Guest from ..models import ActivityType, Activity, Guest
from rest_framework import serializers from rest_framework import serializers
class ActivityTypeSerializer(serializers.ModelSerializer): class ActivityTypeSerializer(serializers.ModelSerializer):
""" """
REST API Serializer for Activity types. REST API Serializer for Activity types.

View File

@ -17,10 +17,13 @@ class UserSerializer(serializers.ModelSerializer):
REST API Serializer for Users. REST API Serializer for Users.
The djangorestframework plugin will analyse the model `User` and parse all fields in the API. The djangorestframework plugin will analyse the model `User` and parse all fields in the API.
""" """
class Meta: class Meta:
model = User model = User
exclude = ('password', 'groups', 'user_permissions',) exclude = (
'password',
'groups',
'user_permissions',
)
class UserViewSet(viewsets.ModelViewSet): class UserViewSet(viewsets.ModelViewSet):

View File

@ -19,9 +19,9 @@ class ProfileInline(admin.StackedInline):
class CustomUserAdmin(UserAdmin): class CustomUserAdmin(UserAdmin):
inlines = (ProfileInline,) inlines = (ProfileInline, )
list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff') list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff')
list_select_related = ('profile',) list_select_related = ('profile', )
form = ProfileForm form = ProfileForm
def get_inline_instances(self, request, obj=None): def get_inline_instances(self, request, obj=None):

View File

@ -2,30 +2,35 @@
# 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 django_filters import FilterSet, CharFilter,NumberFilter from django_filters import FilterSet, CharFilter, NumberFilter
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db.models import CharField from django.db.models import CharField
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Submit from crispy_forms.layout import Layout, Submit
from .models import Club from .models import Club
class UserFilter(FilterSet): class UserFilter(FilterSet):
class Meta: class Meta:
model = User model = User
fields = ['last_name','first_name','username','profile__section'] fields = ['last_name', 'first_name', 'username', 'profile__section']
filter_overrides={ filter_overrides = {
CharField:{ CharField: {
'filter_class':CharFilter, 'filter_class': CharFilter,
'extra': lambda f:{ 'extra': lambda f: {
'lookup_expr':'icontains' 'lookup_expr': 'icontains'
} }
} }
} }
class UserFilterFormHelper(FormHelper): class UserFilterFormHelper(FormHelper):
form_method = 'GET' form_method = 'GET'
layout = Layout( layout = Layout(
'last_name','first_name','username','profile__section', 'last_name',
Submit('Submit','Apply Filter'), 'first_name',
'username',
'profile__section',
Submit('Submit', 'Apply Filter'),
) )

View File

@ -16,11 +16,11 @@ from crispy_forms.bootstrap import InlineField, FormActions, StrictButton, Div,
from crispy_forms.layout import Layout from crispy_forms.layout import Layout
class SignUpForm(UserCreationForm): class SignUpForm(UserCreationForm):
class Meta: class Meta:
model = User model = User
fields = ['first_name','last_name','username','email'] fields = ['first_name', 'last_name', 'username', 'email']
class ProfileForm(forms.ModelForm): class ProfileForm(forms.ModelForm):
""" """
@ -31,48 +31,56 @@ class ProfileForm(forms.ModelForm):
fields = '__all__' fields = '__all__'
exclude = ['user'] exclude = ['user']
class ClubForm(forms.ModelForm): class ClubForm(forms.ModelForm):
class Meta: class Meta:
model = Club model = Club
fields ='__all__' fields = '__all__'
class AddMembersForm(forms.Form): class AddMembersForm(forms.Form):
class Meta: class Meta:
fields = ('',) fields = ('', )
class MembershipForm(forms.ModelForm): 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. # 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 # 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 # et récupère les noms d'utilisateur valides
widgets = { widgets = {
'user': autocomplete.ModelSelect2(url='member:user_autocomplete', 'user':
attrs={ autocomplete.ModelSelect2(
'data-placeholder': 'Nom ...', url='member:user_autocomplete',
'data-minimum-input-length': 1, attrs={
}), 'data-placeholder': 'Nom ...',
'data-minimum-input-length': 1,
},
),
} }
MemberFormSet = forms.modelformset_factory(Membership, MemberFormSet = forms.modelformset_factory(
form=MembershipForm, Membership,
extra=2, form=MembershipForm,
can_delete=True) extra=2,
can_delete=True,
)
class FormSetHelper(FormHelper): class FormSetHelper(FormHelper):
def __init__(self,*args,**kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args,**kwargs) super().__init__(*args, **kwargs)
self.form_tag = False self.form_tag = False
self.form_method = 'POST' self.form_method = 'POST'
self.form_class='form-inline' self.form_class = 'form-inline'
# self.template = 'bootstrap/table_inline_formset.html' # self.template = 'bootstrap/table_inline_formset.html'
self.layout = Layout( self.layout = Layout(
Div( Div(
Div('user',css_class='col-sm-2'), Div('user', css_class='col-sm-2'),
Div('roles',css_class='col-sm-2'), Div('roles', css_class='col-sm-2'),
Div('date_start',css_class='col-sm-2'), Div('date_start', css_class='col-sm-2'),
css_class="row formset-row", css_class="row formset-row",
) ))
)

View File

@ -9,6 +9,7 @@ from django.dispatch import receiver
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
class Profile(models.Model): class Profile(models.Model):
""" """
An user profile An user profile
@ -50,7 +51,8 @@ class Profile(models.Model):
verbose_name_plural = _('user profile') verbose_name_plural = _('user profile')
def get_absolute_url(self): def get_absolute_url(self):
return reverse('user_detail',args=(self.pk,)) return reverse('user_detail', args=(self.pk, ))
class Club(models.Model): class Club(models.Model):
""" """
@ -98,7 +100,7 @@ class Club(models.Model):
return self.name return self.name
def get_absolute_url(self): 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): class Role(models.Model):
@ -129,15 +131,15 @@ class Membership(models.Model):
""" """
user = models.ForeignKey( user = models.ForeignKey(
settings.AUTH_USER_MODEL, settings.AUTH_USER_MODEL,
on_delete=models.PROTECT on_delete=models.PROTECT,
) )
club = models.ForeignKey( club = models.ForeignKey(
Club, Club,
on_delete=models.PROTECT on_delete=models.PROTECT,
) )
roles = models.ForeignKey( roles = models.ForeignKey(
Role, Role,
on_delete=models.PROTECT on_delete=models.PROTECT,
) )
date_start = models.DateField( date_start = models.DateField(
verbose_name=_('membership starts on'), verbose_name=_('membership starts on'),

View File

@ -1,6 +1,3 @@
#!/usr/bin/env python
# -*- 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

View File

@ -5,15 +5,20 @@ from .models import Club
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
class ClubTable(tables.Table): class ClubTable(tables.Table):
class Meta: 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 model = Club
template_name = 'django_tables2/bootstrap.html' template_name = 'django_tables2/bootstrap.html'
fields = ('id','name','email') fields = ('id', 'name', 'email')
row_attrs = {'class':'table-row', row_attrs = {
'data-href': lambda record: record.pk } 'class': 'table-row',
'data-href': lambda record: record.pk
}
class UserTable(tables.Table): class UserTable(tables.Table):
@ -21,7 +26,10 @@ class UserTable(tables.Table):
solde = tables.Column(accessor='note.balance') solde = tables.Column(accessor='note.balance')
class Meta: 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' template_name = 'django_tables2/bootstrap.html'
fields = ('last_name','first_name','username','email') fields = ('last_name', 'first_name', 'username', 'email')
model = User model = User

View File

@ -16,14 +16,14 @@ from rest_framework.authtoken.models import Token
from note.models import Alias, Note, NoteUser 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
from .filters import UserFilter, UserFilterFormHelper from .filters import UserFilter, UserFilterFormHelper
from note.models.transactions import Transaction from note.models.transactions import Transaction
from note.tables import HistoryTable from note.tables import HistoryTable
class UserCreateView(CreateView): class UserCreateView(CreateView):
""" """
Une vue pour inscrire un utilisateur et lui créer un profile Une vue pour inscrire un utilisateur et lui créer un profile
@ -31,10 +31,10 @@ class UserCreateView(CreateView):
form_class = SignUpForm form_class = SignUpForm
success_url = reverse_lazy('login') success_url = reverse_lazy('login')
template_name ='member/signup.html' template_name = 'member/signup.html'
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() context["profile_form"] = self.second_form()
@ -49,17 +49,20 @@ class UserCreateView(CreateView):
profile.save() profile.save()
return super().form_valid(form) return super().form_valid(form)
class UserUpdateView(LoginRequiredMixin,UpdateView):
class UserUpdateView(LoginRequiredMixin, UpdateView):
model = User model = User
fields = ['first_name','last_name','username','email'] fields = ['first_name', 'last_name', 'username', 'email']
template_name = 'member/profile_update.html' template_name = 'member/profile_update.html'
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['user_modified'] = context['user'] context['user_modified'] = context['user']
context['user'] = self.request.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 return context
@ -71,44 +74,52 @@ class UserUpdateView(LoginRequiredMixin,UpdateView):
new_username = form.data['username'] new_username = form.data['username']
# Si l'utilisateur cherche à modifier son pseudo, le nouveau pseudo ne doit pas être proche d'un alias existant # 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: 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 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():
new_username = form.data['username'] new_username = form.data['username']
alias = Alias.objects.filter(name=new_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 # Si le nouveau pseudo n'est pas un de nos alias, on supprime éventuellement un alias similaire pour le remplacer
if not alias.exists(): 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(): if similar.exists():
similar.delete() similar.delete()
user = form.save(commit=False) 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() user.save()
return super().form_valid(form) return super().form_valid(form)
def get_success_url(self, **kwargs): def get_success_url(self, **kwargs):
if kwargs: if kwargs:
return reverse_lazy('member:user_detail', kwargs = {'pk': kwargs['id']}) return reverse_lazy('member:user_detail',
kwargs={'pk': kwargs['id']})
else: 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 ... Affiche les informations sur un utilisateur, sa note, ses clubs ...
""" """
model = Profile model = Profile
context_object_name = "profile" context_object_name = "profile"
def get_context_data(slef,**kwargs):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
user = context['profile'].user user = context['profile'].user
history_list = \ history_list = \
@ -119,7 +130,8 @@ class UserDetailView(LoginRequiredMixin,DetailView):
context['club_list'] = ClubTable(club_list) context['club_list'] = ClubTable(club_list)
return context return context
class UserListView(LoginRequiredMixin,SingleTableView):
class UserListView(LoginRequiredMixin, SingleTableView):
""" """
Affiche la liste des utilisateurs, avec une fonction de recherche statique Affiche la liste des utilisateurs, avec une fonction de recherche statique
""" """
@ -129,13 +141,13 @@ class UserListView(LoginRequiredMixin,SingleTableView):
filter_class = UserFilter filter_class = UserFilter
formhelper_class = UserFilterFormHelper formhelper_class = UserFilterFormHelper
def get_queryset(self,**kwargs): def get_queryset(self, **kwargs):
qs = super().get_queryset() 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() self.filter.form.helper = self.formhelper_class()
return self.filter.qs return self.filter.qs
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["filter"] = self.filter context["filter"] = self.filter
return context return context
@ -149,22 +161,25 @@ class ManageAuthTokens(LoginRequiredMixin, TemplateView):
template_name = "member/manage_auth_tokens.html" template_name = "member/manage_auth_tokens.html"
def get(self, request, *args, **kwargs): 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() 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) return super().get(request, *args, **kwargs)
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['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 return context
class UserAutocomplete(autocomplete.Select2QuerySetView): class UserAutocomplete(autocomplete.Select2QuerySetView):
""" """
Auto complete users by usernames Auto complete users by usernames
""" """
def get_queryset(self): 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. 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 return qs
################################### ###################################
############## CLUB ############### ############## CLUB ###############
################################### ###################################
class ClubCreateView(LoginRequiredMixin,CreateView):
class ClubCreateView(LoginRequiredMixin, CreateView):
""" """
Create Club Create Club
""" """
model = Club model = Club
form_class = ClubForm form_class = ClubForm
def form_valid(self,form): def form_valid(self, form):
return super().form_valid(form) return super().form_valid(form)
class ClubListView(LoginRequiredMixin,SingleTableView):
class ClubListView(LoginRequiredMixin, SingleTableView):
""" """
List existing Clubs List existing Clubs
""" """
model = Club model = Club
table_class = ClubTable 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) context = super().get_context_data(**kwargs)
club = context["club"] club = context["club"]
club_transactions = \ club_transactions = \
@ -218,23 +237,25 @@ class ClubDetailView(LoginRequiredMixin,DetailView):
context['member_list'] = club_member context['member_list'] = club_member
return context return context
class ClubAddMemberView(LoginRequiredMixin,CreateView):
class ClubAddMemberView(LoginRequiredMixin, CreateView):
model = Membership model = Membership
form_class = MembershipForm form_class = MembershipForm
template_name = 'member/add_members.html' 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 = super().get_context_data(**kwargs)
context['formset'] = MemberFormSet() context['formset'] = MemberFormSet()
context['helper'] = FormSetHelper() context['helper'] = FormSetHelper()
return context return context
def post(self,request,*args,**kwargs): def post(self, request, *args, **kwargs):
formset = MembershipFormset(request.POST) formset = MembershipFormset(request.POST)
if formset.is_valid(): if formset.is_valid():
return self.form_valid(formset) return self.form_valid(formset)
else: else:
return self.form_invalid(formset) return self.form_invalid(formset)
def form_valid(self,formset): def form_valid(self, formset):
formset.save() formset.save()
return super().form_valid(formset) return super().form_valid(formset)

View File

@ -25,7 +25,10 @@ class NoteAdmin(PolymorphicParentModelAdmin):
Parent regrouping all note types as children Parent regrouping all note types as children
""" """
child_models = (NoteClub, NoteSpecial, NoteUser) child_models = (NoteClub, NoteSpecial, NoteUser)
list_filter = (PolymorphicChildModelFilter, 'is_active',) list_filter = (
PolymorphicChildModelFilter,
'is_active',
)
# Use a polymorphic list # Use a polymorphic list
list_display = ('pretty', 'balance', 'is_active') list_display = ('pretty', 'balance', 'is_active')
@ -44,11 +47,12 @@ class NoteClubAdmin(PolymorphicChildModelAdmin):
""" """
Child for a club note, see NoteAdmin Child for a club note, see NoteAdmin
""" """
inlines = (AliasInlines,) inlines = (AliasInlines, )
# We can't change club after creation or the balance # We can't change club after creation or the balance
readonly_fields = ('club', 'balance') readonly_fields = ('club', 'balance')
search_fields = ('club',) search_fields = ('club', )
def has_add_permission(self, request): def has_add_permission(self, request):
""" """
A club note should not be manually added A club note should not be manually added
@ -67,7 +71,7 @@ class NoteSpecialAdmin(PolymorphicChildModelAdmin):
""" """
Child for a special note, see NoteAdmin Child for a special note, see NoteAdmin
""" """
readonly_fields = ('balance',) readonly_fields = ('balance', )
@admin.register(NoteUser) @admin.register(NoteUser)
@ -75,7 +79,7 @@ class NoteUserAdmin(PolymorphicChildModelAdmin):
""" """
Child for an user note, see NoteAdmin Child for an user note, see NoteAdmin
""" """
inlines = (AliasInlines,) inlines = (AliasInlines, )
# We can't change user after creation or the balance # We can't change user after creation or the balance
readonly_fields = ('user', 'balance') readonly_fields = ('user', 'balance')
@ -101,7 +105,10 @@ class TransactionAdmin(admin.ModelAdmin):
list_display = ('created_at', 'poly_source', 'poly_destination', list_display = ('created_at', 'poly_source', 'poly_destination',
'quantity', 'amount', 'transaction_type', 'valid') 'quantity', 'amount', 'transaction_type', 'valid')
list_filter = ('transaction_type', 'valid') list_filter = ('transaction_type', 'valid')
autocomplete_fields = ('source', 'destination',) autocomplete_fields = (
'source',
'destination',
)
def poly_source(self, obj): def poly_source(self, obj):
""" """
@ -136,8 +143,8 @@ class TransactionTemplateAdmin(admin.ModelAdmin):
Admin customisation for TransactionTemplate Admin customisation for TransactionTemplate
""" """
list_display = ('name', 'poly_destination', 'amount', 'template_type') list_display = ('name', 'poly_destination', 'amount', 'template_type')
list_filter = ('template_type',) list_filter = ('template_type', )
autocomplete_fields = ('destination',) autocomplete_fields = ('destination', )
def poly_destination(self, obj): def poly_destination(self, obj):
""" """
@ -153,5 +160,5 @@ class TransactionCategoryAdmin(admin.ModelAdmin):
""" """
Admin customisation for TransactionTemplate Admin customisation for TransactionTemplate
""" """
list_display = ('name',) list_display = ('name', )
list_filter = ('name',) list_filter = ('name', )

View File

@ -17,7 +17,10 @@ class NoteSerializer(serializers.ModelSerializer):
model = Note model = Note
fields = '__all__' fields = '__all__'
extra_kwargs = { 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 NoteSpecial: NoteSpecialSerializer
} }
class TransactionTemplateSerializer(serializers.ModelSerializer): class TransactionTemplateSerializer(serializers.ModelSerializer):
""" """
REST API Serializer for Transaction templates. REST API Serializer for Transaction templates.

View File

@ -69,7 +69,9 @@ class NotePolymorphicViewSet(viewsets.ModelViewSet):
queryset = Note.objects.all() queryset = Note.objects.all()
alias = self.request.query_params.get("alias", ".*") 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) note_type = self.request.query_params.get("type", None)
if note_type: if note_type:
@ -79,7 +81,8 @@ class NotePolymorphicViewSet(viewsets.ModelViewSet):
elif "club" in l: elif "club" in l:
queryset = queryset.filter(polymorphic_ctype__model="noteclub") queryset = queryset.filter(polymorphic_ctype__model="noteclub")
elif "special" in l: elif "special" in l:
queryset = queryset.filter(polymorphic_ctype__model="notespecial") queryset = queryset.filter(
polymorphic_ctype__model="notespecial")
else: else:
queryset = queryset.none() queryset = queryset.none()
@ -104,7 +107,8 @@ class AliasViewSet(viewsets.ModelViewSet):
queryset = Alias.objects.all() queryset = Alias.objects.all()
alias = self.request.query_params.get("alias", ".*") 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) note_id = self.request.query_params.get("note", None)
if note_id: if note_id:
@ -114,11 +118,14 @@ class AliasViewSet(viewsets.ModelViewSet):
if note_type: if note_type:
l = str(note_type).lower() l = str(note_type).lower()
if "user" in l: if "user" in l:
queryset = queryset.filter(note__polymorphic_ctype__model="noteuser") queryset = queryset.filter(
note__polymorphic_ctype__model="noteuser")
elif "club" in l: elif "club" in l:
queryset = queryset.filter(note__polymorphic_ctype__model="noteclub") queryset = queryset.filter(
note__polymorphic_ctype__model="noteclub")
elif "special" in l: elif "special" in l:
queryset = queryset.filter(note__polymorphic_ctype__model="notespecial") queryset = queryset.filter(
note__polymorphic_ctype__model="notespecial")
else: else:
queryset = queryset.none() queryset = queryset.none()

View File

@ -20,9 +20,9 @@ class NoteConfig(AppConfig):
""" """
post_save.connect( post_save.connect(
signals.save_user_note, signals.save_user_note,
sender=settings.AUTH_USER_MODEL sender=settings.AUTH_USER_MODEL,
) )
post_save.connect( post_save.connect(
signals.save_club_note, signals.save_club_note,
sender='member.Club' sender='member.Club',
) )

View File

@ -4,10 +4,11 @@ from dal import autocomplete, forward
from django import forms from django import forms
from .models import Transaction, TransactionTemplate 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. # 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 # 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 : # 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} # forward=(forward.Const('TYPE', 'note_type') où TYPE est dans {user, club, special}
widgets = { widgets = {
'destination': autocomplete.ModelSelect2(url='note:note_autocomplete', 'destination':
attrs={ autocomplete.ModelSelect2(
'data-placeholder': 'Note ...', url='note:note_autocomplete',
'data-minimum-input-length': 1, attrs={
}), 'data-placeholder': 'Note ...',
'data-minimum-input-length': 1,
},
),
} }
@ -31,26 +35,38 @@ class TransactionForm(forms.ModelForm):
class Meta: class Meta:
model = Transaction model = Transaction
fields = ('source', 'destination', 'reason', 'amount',) fields = (
'source',
'destination',
'reason',
'amount',
)
# Voir ci-dessus # Voir ci-dessus
widgets = { widgets = {
'source': autocomplete.ModelSelect2(url='note:note_autocomplete', 'source':
attrs={ autocomplete.ModelSelect2(
'data-placeholder': 'Note ...', url='note:note_autocomplete',
'data-minimum-input-length': 1, attrs={
},), 'data-placeholder': 'Note ...',
'destination': autocomplete.ModelSelect2(url='note:note_autocomplete', 'data-minimum-input-length': 1,
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
self.instance.amount = button.amount self.instance.amount = button.amount
self.instance.transaction_type = 'bouton' self.instance.transaction_type = 'bouton'
@ -59,15 +75,18 @@ 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. # 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 # 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 # et récupère les aliases de note valides
widgets = { widgets = {
'source': autocomplete.ModelSelect2(url='note:note_autocomplete', 'source':
attrs={ autocomplete.ModelSelect2(
'data-placeholder': 'Note ...', url='note:note_autocomplete',
'data-minimum-input-length': 1, attrs={
}), 'data-placeholder': 'Note ...',
'data-minimum-input-length': 1,
},
),
} }

View File

@ -10,7 +10,6 @@ from django.core.validators import RegexValidator
from django.db import models from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from polymorphic.models import PolymorphicModel from polymorphic.models import PolymorphicModel
""" """
Defines each note types Defines each note types
""" """
@ -34,8 +33,7 @@ class Note(PolymorphicModel):
default=True, default=True,
help_text=_( help_text=_(
'Designates whether this note should be treated as active. ' '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( display_image = models.ImageField(
verbose_name=_('display image'), verbose_name=_('display image'),
@ -85,7 +83,8 @@ class Note(PolymorphicModel):
""" """
Verify alias (simulate save) 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(): 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:
@ -181,15 +180,15 @@ class Alias(models.Model):
validators=[ validators=[
RegexValidator( RegexValidator(
regex=settings.ALIAS_VALIDATOR_REGEX, 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( normalized_name = models.CharField(
max_length=255, max_length=255,
unique=True, unique=True,
default='', default='',
editable=False editable=False,
) )
note = models.ForeignKey( note = models.ForeignKey(
Note, Note,
@ -209,11 +208,9 @@ class Alias(models.Model):
Normalizes a string: removes most diacritics and does casefolding Normalizes a string: removes most diacritics and does casefolding
""" """
return ''.join( return ''.join(
char char for char in unicodedata.normalize('NFKD', string.casefold())
for char in unicodedata.normalize('NFKD', string.casefold())
if all(not unicodedata.category(char).startswith(cat) if all(not unicodedata.category(char).startswith(cat)
for cat in {'M', 'P', 'Z', 'C'}) for cat in {'M', 'P', 'Z', 'C'})).casefold()
).casefold()
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
""" """
@ -229,8 +226,9 @@ class Alias(models.Model):
raise ValidationError(_('Alias too long.')) raise ValidationError(_('Alias too long.'))
try: try:
if self != Alias.objects.get(normalized_name=normalized_name): if self != Alias.objects.get(normalized_name=normalized_name):
raise ValidationError(_('An alias with a similar name ' raise ValidationError(
'already exists.')) _('An alias with a similar name '
'already exists.'))
except Alias.DoesNotExist: except Alias.DoesNotExist:
pass pass

View File

@ -7,16 +7,19 @@ from .models.transactions import Transaction
class HistoryTable(tables.Table): class HistoryTable(tables.Table):
class Meta: 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 model = Transaction
template_name = 'django_tables2/bootstrap.html' 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): def order_total(self, QuerySet, is_descending):
# needed for rendering # needed for rendering
QuerySet = QuerySet.annotate( QuerySet = QuerySet.annotate(
total=F('amount') * F('quantity') total=F('amount') *
).order_by(('-' if is_descending else '') + 'total') F('quantity')).order_by(('-' if is_descending else '') + 'total')
return (QuerySet, True) return (QuerySet, True)

View File

@ -2,10 +2,17 @@ from django import template
def pretty_money(value): def pretty_money(value):
if value%100 == 0: if value % 100 == 0:
return "{:s}{:d}".format("- " if value < 0 else "", abs(value) // 100) return "{:s}{:d}".format(
"- " if value < 0 else "",
abs(value) // 100,
)
else: 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() register = template.Library()

View File

@ -12,6 +12,7 @@ from django.views.generic import CreateView, ListView, DetailView, UpdateView
from .models import Note, Transaction, TransactionCategory, TransactionTemplate, Alias from .models import Note, Transaction, TransactionCategory, TransactionTemplate, Alias
from .forms import TransactionForm, TransactionTemplateForm, ConsoForm from .forms import TransactionForm, TransactionTemplateForm, ConsoForm
class TransactionCreate(LoginRequiredMixin, CreateView): class TransactionCreate(LoginRequiredMixin, CreateView):
""" """
Show transfer page Show transfer page
@ -30,14 +31,13 @@ class TransactionCreate(LoginRequiredMixin, CreateView):
'to one or others') 'to one or others')
return context return context
def get_form(self, form_class=None): 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. 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) 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'] del form.fields['source']
return form 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 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 form.instance.source = self.request.user.note
return super().form_valid(form) return super().form_valid(form)
@ -56,7 +56,6 @@ class NoteAutocomplete(autocomplete.Select2QuerySetView):
""" """
Auto complete note by aliases Auto complete note by aliases
""" """
def get_queryset(self): 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. 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) return str(result.note.pk)
class TransactionTemplateCreateView(LoginRequiredMixin,CreateView): class TransactionTemplateCreateView(LoginRequiredMixin, CreateView):
""" """
Create TransactionTemplate Create TransactionTemplate
""" """
model = TransactionTemplate model = TransactionTemplate
form_class = TransactionTemplateForm form_class = TransactionTemplateForm
class TransactionTemplateListView(LoginRequiredMixin,ListView):
class TransactionTemplateListView(LoginRequiredMixin, ListView):
""" """
List TransactionsTemplates List TransactionsTemplates
""" """
model = TransactionTemplate model = TransactionTemplate
form_class = TransactionTemplateForm form_class = TransactionTemplateForm
class TransactionTemplateUpdateView(LoginRequiredMixin,UpdateView):
class TransactionTemplateUpdateView(LoginRequiredMixin, UpdateView):
""" """
""" """
model = TransactionTemplate model = TransactionTemplate
form_class = TransactionTemplateForm form_class = TransactionTemplateForm
class ConsoView(LoginRequiredMixin,CreateView):
class ConsoView(LoginRequiredMixin, CreateView):
""" """
Consume Consume
""" """
@ -139,11 +141,14 @@ class ConsoView(LoginRequiredMixin,CreateView):
if 'template_type' not in self.kwargs.keys(): if 'template_type' not in self.kwargs.keys():
return context return context
template_type = TransactionCategory.objects.filter(name=self.kwargs.get('template_type')).get() template_type = TransactionCategory.objects.filter(
context['buttons'] = TransactionTemplate.objects.filter(template_type=template_type) name=self.kwargs.get('template_type')).get()
context['buttons'] = TransactionTemplate.objects.filter(
template_type=template_type)
context['title'] = template_type context['title'] = template_type
return context return context
def get_success_url(self): 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'), ))