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

@ -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

@ -10,6 +10,7 @@ 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
@ -23,9 +24,13 @@ class UserFilter(FilterSet):
} }
} }
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',
'first_name',
'username',
'profile__section',
Submit('Submit', 'Apply Filter'), Submit('Submit', 'Apply Filter'),
) )

View File

@ -16,12 +16,12 @@ 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):
""" """
A form for the extras field provided by the :model:`member.Profile` model. A form for the extras field provided by the :model:`member.Profile` model.
@ -31,15 +31,18 @@ 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
@ -48,18 +51,24 @@ class MembershipForm(forms.ModelForm):
# 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':
autocomplete.ModelSelect2(
url='member:user_autocomplete',
attrs={ attrs={
'data-placeholder': 'Nom ...', 'data-placeholder': 'Nom ...',
'data-minimum-input-length': 1, 'data-minimum-input-length': 1,
}), },
),
} }
MemberFormSet = forms.modelformset_factory(Membership, MemberFormSet = forms.modelformset_factory(
Membership,
form=MembershipForm, form=MembershipForm,
extra=2, extra=2,
can_delete=True) can_delete=True,
)
class FormSetHelper(FormHelper): class FormSetHelper(FormHelper):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -74,5 +83,4 @@ class FormSetHelper(FormHelper):
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
@ -52,6 +53,7 @@ class Profile(models.Model):
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):
""" """
A club is a group of people, whose membership is handle by their A club is a group of people, whose membership is handle by their
@ -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

@ -20,10 +20,10 @@ from .forms import SignUpForm, ProfileForm, ClubForm,MembershipForm, MemberForm
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
@ -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,21 +74,26 @@ 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()
@ -98,17 +106,20 @@ class UserUpdateView(LoginRequiredMixin,UpdateView):
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,6 +130,7 @@ 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
@ -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,10 +196,12 @@ class UserAutocomplete(autocomplete.Select2QuerySetView):
return qs return qs
################################### ###################################
############## CLUB ############### ############## CLUB ###############
################################### ###################################
class ClubCreateView(LoginRequiredMixin, CreateView): class ClubCreateView(LoginRequiredMixin, CreateView):
""" """
Create Club Create Club
@ -195,6 +212,7 @@ class ClubCreateView(LoginRequiredMixin,CreateView):
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
@ -202,6 +220,7 @@ class ClubListView(LoginRequiredMixin,SingleTableView):
model = Club model = Club
table_class = ClubTable table_class = ClubTable
class ClubDetailView(LoginRequiredMixin, DetailView): class ClubDetailView(LoginRequiredMixin, DetailView):
model = Club model = Club
context_object_name = "club" context_object_name = "club"
@ -218,10 +237,12 @@ 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()

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')
@ -49,6 +52,7 @@ class NoteClubAdmin(PolymorphicChildModelAdmin):
# 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
@ -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):
""" """

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,6 +4,7 @@ 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
@ -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':
autocomplete.ModelSelect2(
url='note:note_autocomplete',
attrs={ attrs={
'data-placeholder': 'Note ...', 'data-placeholder': 'Note ...',
'data-minimum-input-length': 1, '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':
autocomplete.ModelSelect2(
url='note:note_autocomplete',
attrs={ attrs={
'data-placeholder': 'Note ...', 'data-placeholder': 'Note ...',
'data-minimum-input-length': 1, 'data-minimum-input-length': 1,
},), },
'destination': autocomplete.ModelSelect2(url='note:note_autocomplete', ),
'destination':
autocomplete.ModelSelect2(
url='note:note_autocomplete',
attrs={ attrs={
'data-placeholder': 'Note ...', 'data-placeholder': 'Note ...',
'data-minimum-input-length': 1, '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'
@ -65,9 +81,12 @@ class ConsoForm(forms.ModelForm):
# 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':
autocomplete.ModelSelect2(
url='note:note_autocomplete',
attrs={ attrs={
'data-placeholder': 'Note ...', 'data-placeholder': 'Note ...',
'data-minimum-input-length': 1, '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,7 +226,8 @@ 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(
_('An alias with a similar name '
'already exists.')) 'already exists.'))
except Alias.DoesNotExist: except Alias.DoesNotExist:
pass pass

View File

@ -7,7 +7,10 @@ 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')
@ -17,6 +20,6 @@ class HistoryTable(tables.Table):
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

@ -3,9 +3,16 @@ 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,7 +31,6 @@ 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.
@ -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.
@ -108,6 +107,7 @@ class TransactionTemplateCreateView(LoginRequiredMixin,CreateView):
model = TransactionTemplate model = TransactionTemplate
form_class = TransactionTemplateForm form_class = TransactionTemplateForm
class TransactionTemplateListView(LoginRequiredMixin, ListView): class TransactionTemplateListView(LoginRequiredMixin, ListView):
""" """
List TransactionsTemplates List TransactionsTemplates
@ -115,12 +115,14 @@ class TransactionTemplateListView(LoginRequiredMixin,ListView):
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'), ))