From c4a60633f805f7921ece5c1305c348cc0f1f8441 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Tue, 4 Feb 2020 01:18:03 +0100 Subject: [PATCH 01/67] Consos --- apps/member/models.py | 3 +++ apps/note/admin.py | 11 ++++++++- apps/note/forms.py | 15 +++++++++++- apps/note/models/__init__.py | 4 ++-- apps/note/models/transactions.py | 24 +++++++++++++++++-- apps/note/urls.py | 4 +++- apps/note/views.py | 34 +++++++++++++++++++++++--- templates/base.html | 2 +- templates/note/conso_form.html | 41 ++++++++++++++++++++++++++++++++ 9 files changed, 127 insertions(+), 11 deletions(-) create mode 100644 templates/note/conso_form.html diff --git a/apps/member/models.py b/apps/member/models.py index 883f9b49..35b7027c 100644 --- a/apps/member/models.py +++ b/apps/member/models.py @@ -118,6 +118,9 @@ class Role(models.Model): verbose_name = _('role') verbose_name_plural = _('roles') + def __str__(self): + return str(self.name) + class Membership(models.Model): """ diff --git a/apps/note/admin.py b/apps/note/admin.py index 298b91c0..0b2461d0 100644 --- a/apps/note/admin.py +++ b/apps/note/admin.py @@ -8,7 +8,7 @@ from polymorphic.admin import PolymorphicChildModelAdmin, \ PolymorphicChildModelFilter, PolymorphicParentModelAdmin from .models.notes import Alias, Note, NoteClub, NoteSpecial, NoteUser -from .models.transactions import Transaction, TransactionTemplate +from .models.transactions import Transaction, TransactionCategory, TransactionTemplate class AliasInlines(admin.TabularInline): @@ -146,3 +146,12 @@ class TransactionTemplateAdmin(admin.ModelAdmin): return str(obj.destination) poly_destination.short_description = _('destination') + + +@admin.register(TransactionCategory) +class TransactionCategoryAdmin(admin.ModelAdmin): + """ + Admin customisation for TransactionTemplate + """ + list_display = ('name',) + list_filter = ('name',) diff --git a/apps/note/forms.py b/apps/note/forms.py index d74fa5b4..d861345b 100644 --- a/apps/note/forms.py +++ b/apps/note/forms.py @@ -1,9 +1,22 @@ #!/usr/bin/env python from django import forms -from .models import TransactionTemplate +from .models import TransactionTemplate, Transaction class TransactionTemplateForm(forms.ModelForm): class Meta: model = TransactionTemplate fields ='__all__' + +class ConsoForm(forms.ModelForm): + def save(self, commit=True): + 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' + self.instance.reason = button.name + super().save(commit) + + class Meta: + model = Transaction + fields = ('source',) diff --git a/apps/note/models/__init__.py b/apps/note/models/__init__.py index b00572ce..ee290bb5 100644 --- a/apps/note/models/__init__.py +++ b/apps/note/models/__init__.py @@ -4,11 +4,11 @@ from .notes import Alias, Note, NoteClub, NoteSpecial, NoteUser from .transactions import MembershipTransaction, Transaction, \ - TransactionTemplate + TransactionCategory, TransactionTemplate __all__ = [ # Notes 'Alias', 'Note', 'NoteClub', 'NoteSpecial', 'NoteUser', # Transactions - 'MembershipTransaction', 'Transaction', 'TransactionTemplate', + 'MembershipTransaction', 'Transaction', 'TransactionCategory', 'TransactionTemplate', ] diff --git a/apps/note/models/transactions.py b/apps/note/models/transactions.py index 4ce23311..5c242255 100644 --- a/apps/note/models/transactions.py +++ b/apps/note/models/transactions.py @@ -13,10 +13,28 @@ from .notes import Note,NoteClub Defines transactions """ +class TransactionCategory(models.Model): + """ + Defined a recurrent transaction category + + Example: food, softs, ... + """ + name = models.CharField( + verbose_name=_("name"), + max_length=31, + unique=True, + ) + + class Meta: + verbose_name = _("transaction category") + verbose_name_plural = _("transaction categories") + + def __str__(self): + return str(self.name) class TransactionTemplate(models.Model): """ - Defined a reccurent transaction + Defined a recurrent transaction associated to selling something (a burger, a beer, ...) """ @@ -35,7 +53,9 @@ class TransactionTemplate(models.Model): verbose_name=_('amount'), help_text=_('in centimes'), ) - template_type = models.CharField( + template_type = models.ForeignKey( + TransactionCategory, + on_delete=models.PROTECT, verbose_name=_('type'), max_length=31 ) diff --git a/apps/note/urls.py b/apps/note/urls.py index 5e423d46..59bb4672 100644 --- a/apps/note/urls.py +++ b/apps/note/urls.py @@ -11,5 +11,7 @@ urlpatterns = [ path('transfer/', views.TransactionCreate.as_view(), name='transfer'), path('buttons/create/',views.TransactionTemplateCreateView.as_view(),name='template_create'), path('buttons/update//',views.TransactionTemplateUpdateView.as_view(),name='template_update'), - path('buttons/',views.TransactionTemplateListView.as_view(),name='template_list') + path('buttons/',views.TransactionTemplateListView.as_view(),name='template_list'), + path('consos//',views.ConsoView.as_view(),name='consos'), + path('consos/',views.ConsoView.as_view(),name='consos'), ] diff --git a/apps/note/views.py b/apps/note/views.py index 08f4f630..ce27832c 100644 --- a/apps/note/views.py +++ b/apps/note/views.py @@ -3,11 +3,12 @@ # SPDX-License-Identifier: GPL-3.0-or-later from django.contrib.auth.mixins import LoginRequiredMixin +from django.urls import reverse_lazy, reverse from django.utils.translation import gettext_lazy as _ from django.views.generic import CreateView, ListView, DetailView, UpdateView -from .models import Transaction,TransactionTemplate -from .forms import TransactionTemplateForm +from .models import Transaction,TransactionCategory,TransactionTemplate +from .forms import TransactionTemplateForm, ConsoForm class TransactionCreate(LoginRequiredMixin, CreateView): """ @@ -45,4 +46,31 @@ class TransactionTemplateUpdateView(LoginRequiredMixin,UpdateView): """ """ model = TransactionTemplate - form_class=TransactionTemplateForm + form_class = TransactionTemplateForm + +class ConsoView(LoginRequiredMixin,CreateView): + """ + Consume + """ + model = Transaction + template_name = "note/conso_form.html" + form_class = ConsoForm + + def get_context_data(self, **kwargs): + """ + Add some context variables in template such as page title + """ + context = super().get_context_data(**kwargs) + context['template_types'] = TransactionCategory.objects.all() + + 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) + context['title'] = template_type + + return context + + def get_success_url(self): + return reverse('note:consos',args=(self.kwargs.get('template_type'),)) diff --git a/templates/base.html b/templates/base.html index 5c857593..96f56881 100644 --- a/templates/base.html +++ b/templates/base.html @@ -46,7 +46,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
- {%trans 'Token' %} : {{ token.key }}
+ {%trans 'Token' %} : + {% if 'show' in request.GET %} + {{ token.key }} (cacher) + {% else %} + caché (montrer) + {% endif %} +
{%trans 'Created' %} : {{ token.created }}
@@ -21,7 +27,7 @@ Attention : regénérer le jeton va révoquer tout accès autorisé à l'API via ce jeton ! - + {% endblock %} From 55722b801a196c5bc83eb19efa527f77e94f0e8a Mon Sep 17 00:00:00 2001 From: Alexandre Iooss Date: Tue, 18 Feb 2020 11:58:42 +0100 Subject: [PATCH 35/67] Split API in each app --- apps/{api/activity => activity/api}/__init__.py | 0 apps/{api/activity => activity/api}/serializers.py | 2 +- apps/{api/activity => activity/api}/urls.py | 0 apps/{api/activity => activity/api}/views.py | 2 +- apps/api/urls.py | 13 ++++--------- apps/{api/members => member/api}/__init__.py | 0 apps/{api/members => member/api}/serializers.py | 2 +- apps/{api/members => member/api}/urls.py | 0 apps/{api/members => member/api}/views.py | 2 +- apps/{api/note => note/api}/__init__.py | 0 apps/{api/note => note/api}/serializers.py | 4 ++-- apps/{api/note => note/api}/urls.py | 0 apps/{api/note => note/api}/views.py | 4 ++-- 13 files changed, 12 insertions(+), 17 deletions(-) rename apps/{api/activity => activity/api}/__init__.py (100%) rename apps/{api/activity => activity/api}/serializers.py (94%) rename apps/{api/activity => activity/api}/urls.py (100%) rename apps/{api/activity => activity/api}/views.py (95%) rename apps/{api/members => member/api}/__init__.py (100%) rename apps/{api/members => member/api}/serializers.py (95%) rename apps/{api/members => member/api}/urls.py (100%) rename apps/{api/members => member/api}/views.py (96%) rename apps/{api/note => note/api}/__init__.py (100%) rename apps/{api/note => note/api}/serializers.py (94%) rename apps/{api/note => note/api}/urls.py (100%) rename apps/{api/note => note/api}/views.py (97%) diff --git a/apps/api/activity/__init__.py b/apps/activity/api/__init__.py similarity index 100% rename from apps/api/activity/__init__.py rename to apps/activity/api/__init__.py diff --git a/apps/api/activity/serializers.py b/apps/activity/api/serializers.py similarity index 94% rename from apps/api/activity/serializers.py rename to apps/activity/api/serializers.py index 8ab5d901..f7f949e7 100644 --- a/apps/api/activity/serializers.py +++ b/apps/activity/api/serializers.py @@ -2,7 +2,7 @@ # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later -from activity.models import ActivityType, Activity, Guest +from ..models import ActivityType, Activity, Guest from rest_framework import serializers class ActivityTypeSerializer(serializers.ModelSerializer): diff --git a/apps/api/activity/urls.py b/apps/activity/api/urls.py similarity index 100% rename from apps/api/activity/urls.py rename to apps/activity/api/urls.py diff --git a/apps/api/activity/views.py b/apps/activity/api/views.py similarity index 95% rename from apps/api/activity/views.py rename to apps/activity/api/views.py index de6b8979..4a0973e5 100644 --- a/apps/api/activity/views.py +++ b/apps/activity/api/views.py @@ -4,7 +4,7 @@ from rest_framework import viewsets -from activity.models import ActivityType, Activity, Guest +from ..models import ActivityType, Activity, Guest from .serializers import ActivityTypeSerializer, ActivitySerializer, GuestSerializer diff --git a/apps/api/urls.py b/apps/api/urls.py index 6fe3e99f..7ac56ca1 100644 --- a/apps/api/urls.py +++ b/apps/api/urls.py @@ -7,9 +7,9 @@ from django.contrib.auth.models import User from rest_framework import routers, serializers, viewsets from rest_framework.authtoken import views as token_views -from .activity.urls import register_activity_urls -from .members.urls import register_members_urls -from .note.urls import register_note_urls +from activity.api.urls import register_activity_urls +from member.api.urls import register_members_urls +from note.api.urls import register_note_urls class UserSerializer(serializers.ModelSerializer): @@ -34,16 +34,11 @@ class UserViewSet(viewsets.ModelViewSet): # Routers provide an easy way of automatically determining the URL conf. +# Register each app API router and user viewset router = routers.DefaultRouter() router.register('user', UserViewSet) - -# Routers for members app register_members_urls(router, 'members') - -# Routers for activity app register_activity_urls(router, 'activity') - -# Routers for note app register_note_urls(router, 'note') app_name = 'api' diff --git a/apps/api/members/__init__.py b/apps/member/api/__init__.py similarity index 100% rename from apps/api/members/__init__.py rename to apps/member/api/__init__.py diff --git a/apps/api/members/serializers.py b/apps/member/api/serializers.py similarity index 95% rename from apps/api/members/serializers.py rename to apps/member/api/serializers.py index 76829615..cf4420d5 100644 --- a/apps/api/members/serializers.py +++ b/apps/member/api/serializers.py @@ -2,7 +2,7 @@ # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later -from member.models import Profile, Club, Role, Membership +from ..models import Profile, Club, Role, Membership from rest_framework import serializers diff --git a/apps/api/members/urls.py b/apps/member/api/urls.py similarity index 100% rename from apps/api/members/urls.py rename to apps/member/api/urls.py diff --git a/apps/api/members/views.py b/apps/member/api/views.py similarity index 96% rename from apps/api/members/views.py rename to apps/member/api/views.py index 06b95dcc..36e8a33f 100644 --- a/apps/api/members/views.py +++ b/apps/member/api/views.py @@ -4,7 +4,7 @@ from rest_framework import viewsets -from member.models import Profile, Club, Role, Membership +from ..models import Profile, Club, Role, Membership from .serializers import ProfileSerializer, ClubSerializer, RoleSerializer, MembershipSerializer diff --git a/apps/api/note/__init__.py b/apps/note/api/__init__.py similarity index 100% rename from apps/api/note/__init__.py rename to apps/note/api/__init__.py diff --git a/apps/api/note/serializers.py b/apps/note/api/serializers.py similarity index 94% rename from apps/api/note/serializers.py rename to apps/note/api/serializers.py index 34a1f368..afc3b419 100644 --- a/apps/api/note/serializers.py +++ b/apps/note/api/serializers.py @@ -2,8 +2,8 @@ # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later -from note.models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias -from note.models.transactions import TransactionTemplate, Transaction, MembershipTransaction +from ..models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias +from ..models.transactions import TransactionTemplate, Transaction, MembershipTransaction from rest_framework import serializers from rest_polymorphic.serializers import PolymorphicSerializer diff --git a/apps/api/note/urls.py b/apps/note/api/urls.py similarity index 100% rename from apps/api/note/urls.py rename to apps/note/api/urls.py diff --git a/apps/api/note/views.py b/apps/note/api/views.py similarity index 97% rename from apps/api/note/views.py rename to apps/note/api/views.py index 07bc9fd2..37ca4e20 100644 --- a/apps/api/note/views.py +++ b/apps/note/api/views.py @@ -5,8 +5,8 @@ from django.db.models import Q from rest_framework import viewsets -from note.models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias -from note.models.transactions import TransactionTemplate, Transaction, MembershipTransaction +from ..models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias +from ..models.transactions import TransactionTemplate, Transaction, MembershipTransaction from .serializers import NoteSerializer, NotePolymorphicSerializer, NoteClubSerializer, NoteSpecialSerializer, \ NoteUserSerializer, AliasSerializer, \ TransactionTemplateSerializer, TransactionSerializer, MembershipTransactionSerializer From f89d91e524dc033a355af5771e81512592e9c015 Mon Sep 17 00:00:00 2001 From: Alexandre Iooss Date: Tue, 18 Feb 2020 12:31:15 +0100 Subject: [PATCH 36/67] Format code --- apps/activity/admin.py | 2 +- apps/activity/api/serializers.py | 1 + apps/api/urls.py | 7 +- apps/member/admin.py | 4 +- apps/member/filters.py | 25 ++++--- apps/member/forms.py | 52 ++++++++------ apps/member/models.py | 12 ++-- apps/member/signals.py | 3 - apps/member/tables.py | 22 ++++-- apps/member/views.py | 99 ++++++++++++++++---------- apps/note/admin.py | 27 ++++--- apps/note/api/serializers.py | 6 +- apps/note/api/views.py | 19 +++-- apps/note/apps.py | 4 +- apps/note/forms.py | 69 +++++++++++------- apps/note/models/notes.py | 24 +++---- apps/note/tables.py | 13 ++-- apps/note/templatetags/pretty_money.py | 13 +++- apps/note/views.py | 27 ++++--- 19 files changed, 262 insertions(+), 167 deletions(-) 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'), )) From 62bfd9a044d84a704e3a499e66994f1d11a60633 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Tue, 18 Feb 2020 13:03:58 +0100 Subject: [PATCH 37/67] Remove redundant ; and import local settings --- note_kfet/settings/__init__.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/note_kfet/settings/__init__.py b/note_kfet/settings/__init__.py index 234e70b9..68a40b88 100644 --- a/note_kfet/settings/__init__.py +++ b/note_kfet/settings/__init__.py @@ -30,12 +30,17 @@ read_env() app_stage = os.environ.get('DJANGO_APP_STAGE', 'dev') if app_stage == 'prod': from .production import * - DATABASES["default"]["PASSWORD"] = os.environ.get('DJANGO_DB_PASSWORD','CHANGE_ME_IN_ENV_SETTINGS'); - SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY','CHANGE_ME_IN_ENV_SETTINGS'); - ALLOWED_HOSTS.append(os.environ.get('ALLOWED_HOSTS','localhost')); + DATABASES["default"]["PASSWORD"] = os.environ.get('DJANGO_DB_PASSWORD','CHANGE_ME_IN_ENV_SETTINGS') + SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY','CHANGE_ME_IN_ENV_SETTINGS') + ALLOWED_HOSTS.append(os.environ.get('ALLOWED_HOSTS','localhost')) else: from .development import * +try: + from .secrets import * +except ImportError: + pass + # env variables set at the of in /env/bin/activate # don't forget to unset in deactivate ! From ec3b445dcf8e612b5f9f4c6f26d85fd9afd15505 Mon Sep 17 00:00:00 2001 From: Alexandre Iooss Date: Tue, 18 Feb 2020 20:58:01 +0100 Subject: [PATCH 38/67] Code quality check with tox --- .gitlab-ci.yml | 15 +- .pylintrc | 379 ------------------------------------------------- tox.ini | 22 ++- 3 files changed, 20 insertions(+), 396 deletions(-) delete mode 100644 .pylintrc diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index bceb5f51..291ed490 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,20 +2,25 @@ image: python:3.6 stages: - test + - quality-assurance before_script: - pip install tox -python36: +py36-django22: image: python:3.6 stage: test - script: tox -e py36 + script: tox -e py36-django22 -python37: +py37-django22: image: python:3.7 stage: test - script: tox -e py37 + script: tox -e py37-django22 linters: - stage: test + image: python:3.6 + stage: quality-assurance script: tox -e linters + + # Be nice to new contributors, but please use `tox` + allow_failure: true diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index 6ddf1f3c..00000000 --- a/.pylintrc +++ /dev/null @@ -1,379 +0,0 @@ -[MASTER] - -# Specify a configuration file. -#rcfile= - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -#init-hook= - -# Add files or directories to the blacklist. They should be base names, not -# paths. -ignore=CVS,.git - -# Pickle collected data for later comparisons. -persistent=yes - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -load-plugins= - -# Use multiple processes to speed up Pylint. -jobs=4 - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code -extension-pkg-whitelist= - -# Allow optimization of some AST trees. This will activate a peephole AST -# optimizer, which will apply various small optimizations. For instance, it can -# be used to obtain the result of joining multiple strings with the addition -# operator. Joining a lot of strings can lead to a maximum recursion error in -# Pylint and this flag can prevent that. It has one side effect, the resulting -# AST will be different than the one from reality. -optimize-ast=no - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED -confidence=INFERENCE_FAILURE - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time. See also the "--disable" option for examples. -#enable= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once).You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use"--disable=all --enable=classes -# --disable=W" -disable=intern-builtin,nonzero-method,parameter-unpacking,backtick,raw_input-builtin,dict-view-method,filter-builtin-not-iterating,long-builtin,unichr-builtin,input-builtin,unicode-builtin,file-builtin,map-builtin-not-iterating,delslice-method,apply-builtin,cmp-method,setslice-method,coerce-method,long-suffix,raising-string,import-star-module-level,buffer-builtin,reload-builtin,unpacking-in-except,print-statement,hex-method,old-octal-literal,metaclass-assignment,dict-iter-method,range-builtin-not-iterating,using-cmp-argument,indexing-exception,no-absolute-import,coerce-builtin,getslice-method,suppressed-message,execfile-builtin,round-builtin,useless-suppression,reduce-builtin,old-raise-syntax,zip-builtin-not-iterating,cmp-builtin,xrange-builtin,standarderror-builtin,old-division,oct-method,next-method-called,old-ne-operator,basestring-builtin - - -[REPORTS] - -# Set the output format. Available formats are text, parseable, colorized, msvs -# (visual studio) and html. You can also give a reporter class, eg -# mypackage.mymodule.MyReporterClass. -output-format=text - -# Put messages in a separate file for each module / package specified on the -# command line instead of printing them on stdout. Reports (if any) will be -# written in a file name "pylint_global.[txt|html]". -files-output=no - -# Tells whether to display a full report or only the messages -reports=no - -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details -#msg-template= - - -[BASIC] - -# List of builtins function names that should not be used, separated by a comma -bad-functions=map,filter - -# Good variable names which should always be accepted, separated by a comma -good-names=i,j,k,ex,Run,_ - -# Bad variable names which should always be refused, separated by a comma -bad-names=foo,bar,baz,toto,tutu,tata - -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= - -# Include a hint for the correct naming format with invalid-name -include-naming-hint=yes - -# Regular expression matching correct argument names -argument-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Naming hint for argument names -argument-name-hint=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression matching correct attribute names -attr-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Naming hint for attribute names -attr-name-hint=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression matching correct constant names -const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - -# Naming hint for constant names -const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - -# Regular expression matching correct class names -class-rgx=[A-Z_][a-zA-Z0-9]+$ - -# Naming hint for class names -class-name-hint=[A-Z_][a-zA-Z0-9]+$ - -# Regular expression matching correct inline iteration names -inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ - -# Naming hint for inline iteration names -inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ - -# Regular expression matching correct class attribute names -class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - -# Naming hint for class attribute names -class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - -# Regular expression matching correct function names -function-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Naming hint for function names -function-name-hint=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression matching correct module names -module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Naming hint for module names -module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Regular expression matching correct method names -method-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Naming hint for method names -method-name-hint=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression matching correct variable names -variable-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Naming hint for variable names -variable-name-hint=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=^_ - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 - - -[ELIF] - -# Maximum number of nested blocks for function / method body -max-nested-blocks=5 - - -[FORMAT] - -# Maximum number of characters on a single line. -max-line-length=100 - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=^\s*(# )??$ - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=no - -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -no-space-check=trailing-comma,dict-separator - -# Maximum number of lines in a module -max-module-lines=1000 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -expected-line-ending-format= - - -[LOGGING] - -# Logging modules to check that the string format arguments are in logging -# function parameter format -logging-modules=logging - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=FIXME,XXX,TODO - - -[SIMILARITIES] - -# Minimum lines number of a similarity. -min-similarity-lines=4 - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=no - - -[SPELLING] - -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. -spelling-store-unknown-words=no - - -[TYPECHECK] - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules= - -# List of classes names for which member attributes should not be checked -# (useful for classes with attributes dynamically set). This supports can work -# with qualified names. -ignored-classes= - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members= - - -[VARIABLES] - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# A regular expression matching the name of dummy variables (i.e. expectedly -# not used). -dummy-variables-rgx=_$|dummy - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid to define new builtins when possible. -additional-builtins= - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_,_cb - - -[CLASSES] - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__,__new__,setUp - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=mcs - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict,_fields,_replace,_source,_make - - -[DESIGN] - -# Maximum number of arguments for function / method -max-args=20 - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore -ignored-argument-names=_.* - -# Maximum number of locals for function / method body -max-locals=20 - -# Maximum number of return / yield for function / method body -max-returns=6 - -# Maximum number of branch for function / method body -max-branches=12 - -# Maximum number of statements in function / method body -max-statements=50 - -# Maximum number of parents for a class (see R0901). -max-parents=7 - -# Maximum number of attributes for a class (see R0902). -max-attributes=10 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=2 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 - -# Maximum number of boolean expressions in an if statement -max-bool-expr=5 - - -[IMPORTS] - -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=optparse - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled) -import-graph= - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled) -ext-import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled) -int-import-graph= - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "Exception" -overgeneral-exceptions=Exception - diff --git a/tox.ini b/tox.ini index c8691372..c4e88c78 100644 --- a/tox.ini +++ b/tox.ini @@ -1,9 +1,13 @@ [tox] -envlist = py36,py37,linters +envlist = + py36-django22 + py37-django22 + linters skipsdist = True [testenv] -basepython = python3 +setenv = + PYTHONWARNINGS = all deps = -r{toxinidir}/requirements.txt coverage @@ -12,11 +16,6 @@ commands = coverage run ./manage.py test {posargs} coverage report -m -[testenv:pre-commit] -deps = pre-commit -commands = - pre-commit run --all-files --show-diff-on-failure - [testenv:linters] deps = -r{toxinidir}/requirements.txt @@ -26,13 +25,12 @@ deps = flake8-typing-imports pep8-naming pyflakes - pylint commands = - flake8 app/activity app/member app/note - pylint . + flake8 apps/activity apps/api apps/member apps/note [flake8] -ignore = D203, W503, E203 +# Ignore too many errors, should be reduced in the future +ignore = D203, W503, E203, I100, I101 exclude = .tox, .git, @@ -45,7 +43,7 @@ exclude = .eggs, *migrations* max-complexity = 10 +max-line-length = 160 import-order-style = google application-import-names = flake8 format = ${cyan}%(path)s${reset}:${yellow_bold}%(row)d${reset}:${green_bold}%(col)d${reset}: ${red_bold}%(code)s${reset} %(text)s - From fb9af1c4c6d80ac98e486c91397048c1c96d4df3 Mon Sep 17 00:00:00 2001 From: Alexandre Iooss Date: Tue, 18 Feb 2020 20:58:45 +0100 Subject: [PATCH 39/67] Just target Django 2.2 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index d103764e..2899ef61 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ certifi==2019.6.16 chardet==3.0.4 defusedxml==0.6.0 -Django==2.2.3 +Django~=2.2 django-allauth==0.39.1 django-autocomplete-light==3.3.0 django-crispy-forms==1.7.2 From cd98f96cd0bd4d7ae79a788f33c3aaa75df8914d Mon Sep 17 00:00:00 2001 From: Alexandre Iooss Date: Tue, 18 Feb 2020 21:14:10 +0100 Subject: [PATCH 40/67] Ignore VSCode project settings --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index f1650504..b57ed74a 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,9 @@ coverage # PyCharm project settings .idea +# VSCode project settings +.vscode + # Local data secrets.py *.log From e679a4b6291a568679b05423493aae7a380b56de Mon Sep 17 00:00:00 2001 From: Alexandre Iooss Date: Tue, 18 Feb 2020 21:14:29 +0100 Subject: [PATCH 41/67] Fix formatting issues --- apps/activity/api/serializers.py | 3 ++- apps/api/urls.py | 2 -- apps/member/api/serializers.py | 3 ++- apps/member/filters.py | 4 +--- apps/member/forms.py | 7 ++----- apps/member/models.py | 2 -- apps/member/tables.py | 4 ++-- apps/member/urls.py | 18 +++++++++--------- apps/member/views.py | 16 +++++++--------- apps/note/api/serializers.py | 5 +++-- apps/note/api/views.py | 16 ++++++++-------- apps/note/forms.py | 3 ++- apps/note/models/transactions.py | 14 +++++++------- apps/note/tables.py | 12 +++++------- apps/note/urls.py | 12 ++++++------ apps/note/views.py | 14 +++++++------- 16 files changed, 63 insertions(+), 72 deletions(-) diff --git a/apps/activity/api/serializers.py b/apps/activity/api/serializers.py index 46bd8384..b3c70338 100644 --- a/apps/activity/api/serializers.py +++ b/apps/activity/api/serializers.py @@ -2,9 +2,10 @@ # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later -from ..models import ActivityType, Activity, Guest from rest_framework import serializers +from ..models import ActivityType, Activity, Guest + class ActivityTypeSerializer(serializers.ModelSerializer): """ diff --git a/apps/api/urls.py b/apps/api/urls.py index 475120fc..4ff23e57 100644 --- a/apps/api/urls.py +++ b/apps/api/urls.py @@ -5,8 +5,6 @@ from django.conf.urls import url, include from django.contrib.auth.models import User from rest_framework import routers, serializers, viewsets -from rest_framework.authtoken import views as token_views - from activity.api.urls import register_activity_urls from member.api.urls import register_members_urls from note.api.urls import register_note_urls diff --git a/apps/member/api/serializers.py b/apps/member/api/serializers.py index cf4420d5..126d69b2 100644 --- a/apps/member/api/serializers.py +++ b/apps/member/api/serializers.py @@ -2,9 +2,10 @@ # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later -from ..models import Profile, Club, Role, Membership from rest_framework import serializers +from ..models import Profile, Club, Role, Membership + class ProfileSerializer(serializers.ModelSerializer): """ diff --git a/apps/member/filters.py b/apps/member/filters.py index 76e0d52b..ab63795a 100644 --- a/apps/member/filters.py +++ b/apps/member/filters.py @@ -2,14 +2,12 @@ # 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 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 - class UserFilter(FilterSet): class Meta: diff --git a/apps/member/forms.py b/apps/member/forms.py index ef32e9b8..3ea23762 100644 --- a/apps/member/forms.py +++ b/apps/member/forms.py @@ -2,17 +2,14 @@ # Copyright (C) 2018-2019 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later from dal import autocomplete -from django.contrib.auth.forms import UserChangeForm, UserCreationForm +from django.contrib.auth.forms import UserCreationForm from django.contrib.auth.models import User from django import forms from .models import Profile, Club, Membership -from django.utils.translation import gettext_lazy as _ - from crispy_forms.helper import FormHelper -from crispy_forms import layout, bootstrap -from crispy_forms.bootstrap import InlineField, FormActions, StrictButton, Div, Field +from crispy_forms.bootstrap import Div from crispy_forms.layout import Layout diff --git a/apps/member/models.py b/apps/member/models.py index ae5d90d5..1009fe11 100644 --- a/apps/member/models.py +++ b/apps/member/models.py @@ -4,8 +4,6 @@ from django.conf import settings from django.db import models -from django.db.models.signals import post_save -from django.dispatch import receiver from django.utils.translation import gettext_lazy as _ from django.urls import reverse, reverse_lazy diff --git a/apps/member/tables.py b/apps/member/tables.py index 8ae10476..e6417353 100644 --- a/apps/member/tables.py +++ b/apps/member/tables.py @@ -1,10 +1,10 @@ #!/usr/bin/env python import django_tables2 as tables -from .models import Club -from django.conf import settings from django.contrib.auth.models import User +from .models import Club + class ClubTable(tables.Table): class Meta: diff --git a/apps/member/urls.py b/apps/member/urls.py index d4e3e6af..d21c2a63 100644 --- a/apps/member/urls.py +++ b/apps/member/urls.py @@ -10,16 +10,16 @@ from . import views app_name = 'member' urlpatterns = [ - path('signup/',views.UserCreateView.as_view(),name="signup"), - path('club/',views.ClubListView.as_view(),name="club_list"), - path('club//',views.ClubDetailView.as_view(),name="club_detail"), - path('club//add_member/',views.ClubAddMemberView.as_view(),name="club_add_member"), - path('club/create/',views.ClubCreateView.as_view(),name="club_create"), - path('user/',views.UserListView.as_view(),name="user_list"), - path('user/',views.UserDetailView.as_view(),name="user_detail"), - path('user//update',views.UserUpdateView.as_view(),name="user_update_profile"), + path('signup/', views.UserCreateView.as_view(), name="signup"), + path('club/', views.ClubListView.as_view(), name="club_list"), + path('club//', views.ClubDetailView.as_view(), name="club_detail"), + path('club//add_member/', views.ClubAddMemberView.as_view(), name="club_add_member"), + path('club/create/', views.ClubCreateView.as_view(), name="club_create"), + path('user/', views.UserListView.as_view(), name="user_list"), + path('user/', views.UserDetailView.as_view(), name="user_detail"), + path('user//update', views.UserUpdateView.as_view(), name="user_update_profile"), path('manage-auth-token/', views.ManageAuthTokens.as_view(), name='auth_token'), # API for the user autocompleter - path('user/user-autocomplete',views.UserAutocomplete.as_view(),name="user_autocomplete"), + path('user/user-autocomplete', views.UserAutocomplete.as_view(), name="user_autocomplete"), ] diff --git a/apps/member/views.py b/apps/member/views.py index 86ce3b19..020b984a 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -6,23 +6,21 @@ from dal import autocomplete from django.contrib.auth.mixins import LoginRequiredMixin from django.shortcuts import redirect from django.utils.translation import gettext_lazy as _ -from django.views.generic import CreateView, ListView, DetailView, UpdateView, RedirectView, TemplateView +from django.views.generic import CreateView, DetailView, UpdateView, TemplateView from django.contrib.auth.models import User from django.urls import reverse_lazy from django.db.models import Q - from django_tables2.views import SingleTableView from rest_framework.authtoken.models import Token +from note.models import Alias, NoteUser +from note.models.transactions import Transaction +from note.tables import HistoryTable -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 .filters import UserFilter, UserFilterFormHelper -from note.models.transactions import Transaction -from note.tables import HistoryTable - class UserCreateView(CreateView): """ @@ -197,9 +195,9 @@ class UserAutocomplete(autocomplete.Select2QuerySetView): return qs -################################### -############## CLUB ############### -################################### +# ******************************* # +# CLUB # +# ******************************* # class ClubCreateView(LoginRequiredMixin, CreateView): diff --git a/apps/note/api/serializers.py b/apps/note/api/serializers.py index 90c802a9..a0b92725 100644 --- a/apps/note/api/serializers.py +++ b/apps/note/api/serializers.py @@ -2,11 +2,12 @@ # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later -from ..models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias -from ..models.transactions import TransactionTemplate, Transaction, MembershipTransaction from rest_framework import serializers from rest_polymorphic.serializers import PolymorphicSerializer +from ..models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias +from ..models.transactions import TransactionTemplate, Transaction, MembershipTransaction + class NoteSerializer(serializers.ModelSerializer): """ diff --git a/apps/note/api/views.py b/apps/note/api/views.py index a63cf102..1d015125 100644 --- a/apps/note/api/views.py +++ b/apps/note/api/views.py @@ -75,12 +75,12 @@ class NotePolymorphicViewSet(viewsets.ModelViewSet): note_type = self.request.query_params.get("type", None) if note_type: - l = str(note_type).lower() - if "user" in l: + types = str(note_type).lower() + if "user" in types: queryset = queryset.filter(polymorphic_ctype__model="noteuser") - elif "club" in l: + elif "club" in types: queryset = queryset.filter(polymorphic_ctype__model="noteclub") - elif "special" in l: + elif "special" in types: queryset = queryset.filter( polymorphic_ctype__model="notespecial") else: @@ -116,14 +116,14 @@ class AliasViewSet(viewsets.ModelViewSet): note_type = self.request.query_params.get("type", None) if note_type: - l = str(note_type).lower() - if "user" in l: + types = str(note_type).lower() + if "user" in types: queryset = queryset.filter( note__polymorphic_ctype__model="noteuser") - elif "club" in l: + elif "club" in types: queryset = queryset.filter( note__polymorphic_ctype__model="noteclub") - elif "special" in l: + elif "special" in types: queryset = queryset.filter( note__polymorphic_ctype__model="notespecial") else: diff --git a/apps/note/forms.py b/apps/note/forms.py index 8f670455..225afb91 100644 --- a/apps/note/forms.py +++ b/apps/note/forms.py @@ -1,7 +1,8 @@ #!/usr/bin/env python -from dal import autocomplete, forward +from dal import autocomplete from django import forms + from .models import Transaction, TransactionTemplate diff --git a/apps/note/models/transactions.py b/apps/note/models/transactions.py index 5c242255..95940b65 100644 --- a/apps/note/models/transactions.py +++ b/apps/note/models/transactions.py @@ -7,12 +7,13 @@ from django.utils import timezone from django.utils.translation import gettext_lazy as _ from django.urls import reverse -from .notes import Note,NoteClub +from .notes import Note, NoteClub """ Defines transactions """ + class TransactionCategory(models.Model): """ Defined a recurrent transaction category @@ -32,6 +33,7 @@ class TransactionCategory(models.Model): def __str__(self): return str(self.name) + class TransactionTemplate(models.Model): """ Defined a recurrent transaction @@ -57,7 +59,7 @@ class TransactionTemplate(models.Model): TransactionCategory, on_delete=models.PROTECT, verbose_name=_('type'), - max_length=31 + max_length=31, ) class Meta: @@ -65,7 +67,7 @@ class TransactionTemplate(models.Model): verbose_name_plural = _("transaction templates") def get_absolute_url(self): - return reverse('note:template_update',args=(self.pk,)) + return reverse('note:template_update', args=(self.pk, )) class Transaction(models.Model): @@ -98,9 +100,7 @@ class Transaction(models.Model): verbose_name=_('quantity'), default=1, ) - amount = models.PositiveIntegerField( - verbose_name=_('amount'), - ) + amount = models.PositiveIntegerField(verbose_name=_('amount'), ) transaction_type = models.CharField( verbose_name=_('type'), max_length=31, @@ -142,7 +142,7 @@ class Transaction(models.Model): @property def total(self): - return self.amount*self.quantity + return self.amount * self.quantity class MembershipTransaction(Transaction): diff --git a/apps/note/tables.py b/apps/note/tables.py index f13b502e..4a52dd13 100644 --- a/apps/note/tables.py +++ b/apps/note/tables.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python import django_tables2 as tables from django.db.models import F @@ -15,11 +14,10 @@ class HistoryTable(tables.Table): template_name = 'django_tables2/bootstrap.html' 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 - QuerySet = QuerySet.annotate( - total=F('amount') * - F('quantity')).order_by(('-' if is_descending else '') + 'total') - return (QuerySet, True) + queryset = queryset.annotate(total=F('amount') * F('quantity')) \ + .order_by(('-' if is_descending else '') + 'total') + return (queryset, True) diff --git a/apps/note/urls.py b/apps/note/urls.py index 7c19c425..6567cb95 100644 --- a/apps/note/urls.py +++ b/apps/note/urls.py @@ -10,12 +10,12 @@ from .models import Note app_name = 'note' urlpatterns = [ path('transfer/', views.TransactionCreate.as_view(), name='transfer'), - path('buttons/create/',views.TransactionTemplateCreateView.as_view(),name='template_create'), - path('buttons/update//',views.TransactionTemplateUpdateView.as_view(),name='template_update'), - path('buttons/',views.TransactionTemplateListView.as_view(),name='template_list'), - path('consos//',views.ConsoView.as_view(),name='consos'), - path('consos/',views.ConsoView.as_view(),name='consos'), + path('buttons/create/', views.TransactionTemplateCreateView.as_view(), name='template_create'), + path('buttons/update//', views.TransactionTemplateUpdateView.as_view(), name='template_update'), + path('buttons/', views.TransactionTemplateListView.as_view(), name='template_list'), + path('consos//', views.ConsoView.as_view(), name='consos'), + path('consos/', views.ConsoView.as_view(), name='consos'), # API for the note autocompleter - path('note-autocomplete/', views.NoteAutocomplete.as_view(model=Note),name='note_autocomplete'), + path('note-autocomplete/', views.NoteAutocomplete.as_view(model=Note), name='note_autocomplete'), ] diff --git a/apps/note/views.py b/apps/note/views.py index d4590e5f..e7776687 100644 --- a/apps/note/views.py +++ b/apps/note/views.py @@ -5,11 +5,11 @@ from dal import autocomplete from django.contrib.auth.mixins import LoginRequiredMixin from django.db.models import Q -from django.urls import reverse_lazy, reverse +from django.urls import reverse from django.utils.translation import gettext_lazy as _ -from django.views.generic import CreateView, ListView, DetailView, UpdateView +from django.views.generic import CreateView, ListView, UpdateView -from .models import Note, Transaction, TransactionCategory, TransactionTemplate, Alias +from .models import Transaction, TransactionCategory, TransactionTemplate, Alias from .forms import TransactionForm, TransactionTemplateForm, ConsoForm @@ -75,12 +75,12 @@ class NoteAutocomplete(autocomplete.Select2QuerySetView): # Filtrage par type de note (user, club, special) note_type = self.forwarded.get("note_type", None) if note_type: - l = str(note_type).lower() - if "user" in l: + types = str(note_type).lower() + if "user" in types: qs = qs.filter(note__polymorphic_ctype__model="noteuser") - elif "club" in l: + elif "club" in types: qs = qs.filter(note__polymorphic_ctype__model="noteclub") - elif "special" in l: + elif "special" in types: qs = qs.filter(note__polymorphic_ctype__model="notespecial") else: qs = qs.none() From 4bbd464f9ceeb7a5cf2d0ba668cee6089e53d840 Mon Sep 17 00:00:00 2001 From: Alexandre Iooss Date: Tue, 18 Feb 2020 21:30:26 +0100 Subject: [PATCH 42/67] Unify file headers --- apps/activity/__init__.py | 3 +-- apps/activity/admin.py | 3 +-- apps/activity/api/serializers.py | 1 - apps/activity/api/urls.py | 1 - apps/activity/api/views.py | 1 - apps/activity/apps.py | 3 +-- apps/activity/models.py | 3 +-- apps/api/urls.py | 1 - apps/member/__init__.py | 3 +-- apps/member/admin.py | 3 +-- apps/member/api/serializers.py | 1 - apps/member/api/urls.py | 1 - apps/member/api/views.py | 1 - apps/member/apps.py | 3 +-- apps/member/filters.py | 3 +-- apps/member/forms.py | 4 ++-- apps/member/models.py | 3 +-- apps/member/signals.py | 3 +-- apps/member/tables.py | 3 ++- apps/member/urls.py | 5 +---- apps/member/views.py | 5 ++--- apps/note/__init__.py | 3 +-- apps/note/admin.py | 3 +-- apps/note/api/serializers.py | 1 - apps/note/api/urls.py | 1 - apps/note/api/views.py | 1 - apps/note/apps.py | 3 +-- apps/note/forms.py | 3 ++- apps/note/models/__init__.py | 3 +-- apps/note/models/notes.py | 3 +-- apps/note/models/transactions.py | 3 +-- apps/note/signals.py | 3 +-- apps/note/tables.py | 3 +++ apps/note/templatetags/pretty_money.py | 3 +++ apps/note/urls.py | 3 +-- apps/note/views.py | 3 +-- entrypoint.sh | 5 +++++ note_kfet/settings/base.py | 3 +-- note_kfet/settings/development.py | 3 +++ note_kfet/settings/production.py | 3 +++ note_kfet/urls.py | 3 +-- note_kfet/wsgi.py | 1 - 42 files changed, 47 insertions(+), 64 deletions(-) diff --git a/apps/activity/__init__.py b/apps/activity/__init__.py index 75df9e1f..195d5302 100644 --- a/apps/activity/__init__.py +++ b/apps/activity/__init__.py @@ -1,5 +1,4 @@ -# -*- mode: python; coding: utf-8 -*- -# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later default_app_config = 'activity.apps.ActivityConfig' diff --git a/apps/activity/admin.py b/apps/activity/admin.py index 494baffc..5ceb4e81 100644 --- a/apps/activity/admin.py +++ b/apps/activity/admin.py @@ -1,5 +1,4 @@ -# -*- mode: python; coding: utf-8 -*- -# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later from django.contrib import admin diff --git a/apps/activity/api/serializers.py b/apps/activity/api/serializers.py index b3c70338..0b9302f1 100644 --- a/apps/activity/api/serializers.py +++ b/apps/activity/api/serializers.py @@ -1,4 +1,3 @@ -# -*- mode: python; coding: utf-8 -*- # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later diff --git a/apps/activity/api/urls.py b/apps/activity/api/urls.py index 56665730..79e0ba30 100644 --- a/apps/activity/api/urls.py +++ b/apps/activity/api/urls.py @@ -1,4 +1,3 @@ -# -*- mode: python; coding: utf-8 -*- # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later diff --git a/apps/activity/api/views.py b/apps/activity/api/views.py index 4a0973e5..5683d458 100644 --- a/apps/activity/api/views.py +++ b/apps/activity/api/views.py @@ -1,4 +1,3 @@ -# -*- mode: python; coding: utf-8 -*- # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later diff --git a/apps/activity/apps.py b/apps/activity/apps.py index 29990f1b..bb72947f 100644 --- a/apps/activity/apps.py +++ b/apps/activity/apps.py @@ -1,5 +1,4 @@ -# -*- mode: python; coding: utf-8 -*- -# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later from django.apps import AppConfig diff --git a/apps/activity/models.py b/apps/activity/models.py index 4dbc5522..8f23060c 100644 --- a/apps/activity/models.py +++ b/apps/activity/models.py @@ -1,5 +1,4 @@ -# -*- mode: python; coding: utf-8 -*- -# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later from django.conf import settings diff --git a/apps/api/urls.py b/apps/api/urls.py index 4ff23e57..7e59a8c0 100644 --- a/apps/api/urls.py +++ b/apps/api/urls.py @@ -1,4 +1,3 @@ -# -*- mode: python; coding: utf-8 -*- # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later diff --git a/apps/member/__init__.py b/apps/member/__init__.py index ec189d6f..298d1dda 100644 --- a/apps/member/__init__.py +++ b/apps/member/__init__.py @@ -1,5 +1,4 @@ -# -*- mode: python; coding: utf-8 -*- -# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later default_app_config = 'member.apps.MemberConfig' diff --git a/apps/member/admin.py b/apps/member/admin.py index 2aa65d09..fb107377 100644 --- a/apps/member/admin.py +++ b/apps/member/admin.py @@ -1,5 +1,4 @@ -# -*- mode: python; coding: utf-8 -*- -# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later from django.contrib import admin diff --git a/apps/member/api/serializers.py b/apps/member/api/serializers.py index 126d69b2..f4df6799 100644 --- a/apps/member/api/serializers.py +++ b/apps/member/api/serializers.py @@ -1,4 +1,3 @@ -# -*- mode: python; coding: utf-8 -*- # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later diff --git a/apps/member/api/urls.py b/apps/member/api/urls.py index f60465c0..15bb83ca 100644 --- a/apps/member/api/urls.py +++ b/apps/member/api/urls.py @@ -1,4 +1,3 @@ -# -*- mode: python; coding: utf-8 -*- # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later diff --git a/apps/member/api/views.py b/apps/member/api/views.py index 36e8a33f..79ba4c12 100644 --- a/apps/member/api/views.py +++ b/apps/member/api/views.py @@ -1,4 +1,3 @@ -# -*- mode: python; coding: utf-8 -*- # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later diff --git a/apps/member/apps.py b/apps/member/apps.py index 928c00e4..2d7f4ab7 100644 --- a/apps/member/apps.py +++ b/apps/member/apps.py @@ -1,5 +1,4 @@ -# -*- mode: python; coding: utf-8 -*- -# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later from django.apps import AppConfig diff --git a/apps/member/filters.py b/apps/member/filters.py index ab63795a..418e52fc 100644 --- a/apps/member/filters.py +++ b/apps/member/filters.py @@ -1,5 +1,4 @@ -# -*- mode: python; coding: utf-8 -*- -# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later from django_filters import FilterSet, CharFilter diff --git a/apps/member/forms.py b/apps/member/forms.py index 3ea23762..66844cf4 100644 --- a/apps/member/forms.py +++ b/apps/member/forms.py @@ -1,6 +1,6 @@ -# -*- mode: python; coding: utf-8 -*- -# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later + from dal import autocomplete from django.contrib.auth.forms import UserCreationForm from django.contrib.auth.models import User diff --git a/apps/member/models.py b/apps/member/models.py index 1009fe11..cd754bd8 100644 --- a/apps/member/models.py +++ b/apps/member/models.py @@ -1,5 +1,4 @@ -# -*- mode: python; coding: utf-8 -*- -# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later from django.conf import settings diff --git a/apps/member/signals.py b/apps/member/signals.py index 6ac23376..4e945ad5 100644 --- a/apps/member/signals.py +++ b/apps/member/signals.py @@ -1,3 +1,2 @@ -# -*- mode: python; coding: utf-8 -*- -# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later diff --git a/apps/member/tables.py b/apps/member/tables.py index e6417353..591149ec 100644 --- a/apps/member/tables.py +++ b/apps/member/tables.py @@ -1,4 +1,5 @@ -#!/usr/bin/env python +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later import django_tables2 as tables from django.contrib.auth.models import User diff --git a/apps/member/urls.py b/apps/member/urls.py index d21c2a63..6a7ed5ce 100644 --- a/apps/member/urls.py +++ b/apps/member/urls.py @@ -1,7 +1,4 @@ -#!/usr/bin/env python - -# -*- mode: python; coding: utf-8 -*- -# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later from django.urls import path diff --git a/apps/member/views.py b/apps/member/views.py index 020b984a..c1231fbc 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -1,7 +1,6 @@ -#!/usr/bin/env python - -# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later + from dal import autocomplete from django.contrib.auth.mixins import LoginRequiredMixin from django.shortcuts import redirect diff --git a/apps/note/__init__.py b/apps/note/__init__.py index 4773b310..f7c331b2 100644 --- a/apps/note/__init__.py +++ b/apps/note/__init__.py @@ -1,5 +1,4 @@ -# -*- mode: python; coding: utf-8 -*- -# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later default_app_config = 'note.apps.NoteConfig' diff --git a/apps/note/admin.py b/apps/note/admin.py index be01da94..3a9721ae 100644 --- a/apps/note/admin.py +++ b/apps/note/admin.py @@ -1,5 +1,4 @@ -# -*- mode: python; coding: utf-8 -*- -# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later from django.contrib import admin diff --git a/apps/note/api/serializers.py b/apps/note/api/serializers.py index a0b92725..db0e3531 100644 --- a/apps/note/api/serializers.py +++ b/apps/note/api/serializers.py @@ -1,4 +1,3 @@ -# -*- mode: python; coding: utf-8 -*- # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later diff --git a/apps/note/api/urls.py b/apps/note/api/urls.py index 4ef14e28..54218796 100644 --- a/apps/note/api/urls.py +++ b/apps/note/api/urls.py @@ -1,4 +1,3 @@ -# -*- mode: python; coding: utf-8 -*- # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later diff --git a/apps/note/api/views.py b/apps/note/api/views.py index 1d015125..94b4a47a 100644 --- a/apps/note/api/views.py +++ b/apps/note/api/views.py @@ -1,4 +1,3 @@ -# -*- mode: python; coding: utf-8 -*- # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later diff --git a/apps/note/apps.py b/apps/note/apps.py index 4ac2c847..4881e3b9 100644 --- a/apps/note/apps.py +++ b/apps/note/apps.py @@ -1,5 +1,4 @@ -# -*- mode: python; coding: utf-8 -*- -# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later from django.apps import AppConfig diff --git a/apps/note/forms.py b/apps/note/forms.py index 225afb91..e4fd344c 100644 --- a/apps/note/forms.py +++ b/apps/note/forms.py @@ -1,4 +1,5 @@ -#!/usr/bin/env python +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later from dal import autocomplete from django import forms diff --git a/apps/note/models/__init__.py b/apps/note/models/__init__.py index ee290bb5..7e6cc310 100644 --- a/apps/note/models/__init__.py +++ b/apps/note/models/__init__.py @@ -1,5 +1,4 @@ -# -*- mode: python; coding: utf-8 -*- -# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later from .notes import Alias, Note, NoteClub, NoteSpecial, NoteUser diff --git a/apps/note/models/notes.py b/apps/note/models/notes.py index a5ab993a..3b616f0e 100644 --- a/apps/note/models/notes.py +++ b/apps/note/models/notes.py @@ -1,5 +1,4 @@ -# -*- mode: python; coding: utf-8 -*- -# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later import unicodedata diff --git a/apps/note/models/transactions.py b/apps/note/models/transactions.py index 95940b65..042faa16 100644 --- a/apps/note/models/transactions.py +++ b/apps/note/models/transactions.py @@ -1,5 +1,4 @@ -# -*- mode: python; coding: utf-8 -*- -# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later from django.db import models diff --git a/apps/note/signals.py b/apps/note/signals.py index 6e5d5c9e..ad376ee0 100644 --- a/apps/note/signals.py +++ b/apps/note/signals.py @@ -1,5 +1,4 @@ -# -*- mode: python; coding: utf-8 -*- -# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later diff --git a/apps/note/tables.py b/apps/note/tables.py index 4a52dd13..e2f5c763 100644 --- a/apps/note/tables.py +++ b/apps/note/tables.py @@ -1,3 +1,6 @@ +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + import django_tables2 as tables from django.db.models import F diff --git a/apps/note/templatetags/pretty_money.py b/apps/note/templatetags/pretty_money.py index c1525056..12530c6e 100644 --- a/apps/note/templatetags/pretty_money.py +++ b/apps/note/templatetags/pretty_money.py @@ -1,3 +1,6 @@ +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + from django import template diff --git a/apps/note/urls.py b/apps/note/urls.py index 6567cb95..e1cc5216 100644 --- a/apps/note/urls.py +++ b/apps/note/urls.py @@ -1,5 +1,4 @@ -# -*- mode: python; coding: utf-8 -*- -# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later from django.urls import path diff --git a/apps/note/views.py b/apps/note/views.py index e7776687..b012ad8b 100644 --- a/apps/note/views.py +++ b/apps/note/views.py @@ -1,5 +1,4 @@ -# -*- mode: python; coding: utf-8 -*- -# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later from dal import autocomplete diff --git a/entrypoint.sh b/entrypoint.sh index da32571a..f05e962a 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,6 +1,11 @@ #!/bin/bash +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + python manage.py compilemessages python manage.py makemigrations + +# Wait for database sleep 5 python manage.py migrate diff --git a/note_kfet/settings/base.py b/note_kfet/settings/base.py index 9a526863..410f496f 100644 --- a/note_kfet/settings/base.py +++ b/note_kfet/settings/base.py @@ -1,5 +1,4 @@ -# -*- mode: python; coding: utf-8 -*- -# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later import os diff --git a/note_kfet/settings/development.py b/note_kfet/settings/development.py index f6d48776..60055ee2 100644 --- a/note_kfet/settings/development.py +++ b/note_kfet/settings/development.py @@ -1,3 +1,6 @@ +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + ######################## # Development Settings # ######################## diff --git a/note_kfet/settings/production.py b/note_kfet/settings/production.py index 02c46ed2..296c17a4 100644 --- a/note_kfet/settings/production.py +++ b/note_kfet/settings/production.py @@ -1,3 +1,6 @@ +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + ######################## # Production Settings # ######################## diff --git a/note_kfet/urls.py b/note_kfet/urls.py index 9fd63eef..303e229a 100644 --- a/note_kfet/urls.py +++ b/note_kfet/urls.py @@ -1,5 +1,4 @@ -# -*- mode: python; coding: utf-8 -*- -# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later from django.contrib import admin diff --git a/note_kfet/wsgi.py b/note_kfet/wsgi.py index 94a8e054..b89430ec 100644 --- a/note_kfet/wsgi.py +++ b/note_kfet/wsgi.py @@ -1,4 +1,3 @@ -# -*- mode: python; coding: utf-8 -*- # Copyright (C) 2016-2019 by BDE # SPDX-License-Identifier: GPL-3.0-or-later From 204fe53047e1b2165b7ec888f87fe6e09cb5629d Mon Sep 17 00:00:00 2001 From: Alexandre Iooss Date: Tue, 18 Feb 2020 21:47:03 +0100 Subject: [PATCH 43/67] Remove broken code --- apps/member/views.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/member/views.py b/apps/member/views.py index c1231fbc..366523b0 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -247,11 +247,13 @@ class ClubAddMemberView(LoginRequiredMixin, CreateView): return context 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) + return + # TODO: Implement POST + # formset = MembershipFormset(request.POST) + # if formset.is_valid(): + # return self.form_valid(formset) + # else: + # return self.form_invalid(formset) def form_valid(self, formset): formset.save() From 91ccff1d1b41002ba446fc0329d8630188a741a5 Mon Sep 17 00:00:00 2001 From: Alexandre Iooss Date: Tue, 18 Feb 2020 22:53:45 +0100 Subject: [PATCH 44/67] Serperlistpopette! I forgot the favicon --- static/favicon/android-chrome-192x192.png | Bin 9915 -> 4361 bytes static/favicon/android-chrome-512x512.png | Bin 27174 -> 11857 bytes static/favicon/apple-touch-icon.png | Bin 6523 -> 2754 bytes static/favicon/browserconfig.xml | 2 +- static/favicon/favicon-16x16.png | Bin 690 -> 573 bytes static/favicon/favicon-32x32.png | Bin 1482 -> 905 bytes static/favicon/favicon.ico | Bin 15086 -> 15086 bytes static/favicon/mstile-150x150.png | Bin 5671 -> 2252 bytes static/favicon/safari-pinned-tab.svg | 2 +- 9 files changed, 2 insertions(+), 2 deletions(-) diff --git a/static/favicon/android-chrome-192x192.png b/static/favicon/android-chrome-192x192.png index 5b31f298276637f9997dccc8dad10be846c5d136..38b7efeee89060046077d5b10dd88b9a5df3565f 100644 GIT binary patch delta 4234 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 delta 9827 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 Lw;-luVL1H{rzg9X diff --git a/static/favicon/android-chrome-512x512.png b/static/favicon/android-chrome-512x512.png index bb9e4daaed45893728a5cef590d245a38a417138..321f2b5a05cfe5c06db23c0d33201b59c365a0df 100644 GIT binary patch literal 11857 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 ZRq#iYKV->JGh=58U}kJ#RBA|${y(ABKm7mz literal 27174 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 U&@Wm40hs4V3*`mMqLU06Z!(ey^8f$< delta 6410 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 - #da532c + #00a300 diff --git a/static/favicon/favicon-16x16.png b/static/favicon/favicon-16x16.png index 5db7ed945375943b1950cd0878c568b7f8d60d09..14ff80a4050e9edc31a7120246d5ccae26b8b827 100644 GIT binary patch delta 411 zcmV;M0c8HN1-%52L4T7_OjJcoX>r}%-6<(40001tjErPtWHdB1Q&Up_002BZJkH3- z=;-KLT3SjSFLZQtXJ={eSs~m@(ZLyM@bSapu*wxz;-sP&;zqF#7^g83okt5}F&}3N5MUz>ZC-1SJ`JFsn!Z#H z!otGZ+S;)x72n_Audl9EMnkf)v1um{pP-*}Su}zpC&16o($dr2-rm;O*OGvFatsnu zAPs6K4!158p*a^!91Fa>yLLh_QXmbxS`DS8q_rm%hizfZ%zwOxHs2U}*rp+3Z`hc_0jNhAV$W;>Cr%-=3U-Rm(FGaGHKNs$4@+ zr4SfioG{DXxGTdGifxWIzTX7DCk(`S-L6$R_IQswE-sUklX!S| zTwGkJsHmKroB#j-IyyQzIXS1Nr2wXgl}?fA{e-O=P40000mbW%=J0IJmW3>@YI z6WWvK&Ds66S$}*cZaL-r{{F)A;z;_s`Rx4sU-sba`~E{m#o_am**xSN*8l(ktw}^d zR5;76Q)zdCFc1_1;gCyEP!7*3+H(z5ylwygpXx?zYb~{}?{&YhVJ4H^$);BO*U)wO zDy~Kaxw}<M?8Dv~O(zSeEJ^ureZZu;}fnQ%CP;Mn}-yntbBS!e^As z5?lRRQL|2(WhU}7UpH3kZX|ucZg-e%?K1b!9#}0_%lS=DEUO)v36=}=RD^+xV3!QJ-w$U3N(X;r!ETxp ze*byShd?|tLr4MAWWOI~NskaSLN!fuKk<0N&wL(O{8I?(x98KKxqp(rcRt3-ni z6QU6pii(J#TYn0>5E{8D6^x9FwY9YK^Yh9r7rni_v@9*!bS`6*& z?A6uP?d|QxI26Rh#7Z0sz>Nm$>+5w>2#-Mxz#JILAsEWa$+|idgBKF5U_CrMIdUuw zhFC|wzrK(_4xN&dYbgg!7me{J`q?U3~nb4r$s1 zvVKtk*D5d9K1Eq@z8f-6-h`TIa|Ov@i5yg2^!G+be} zsac|z;X~M9=C+#t!$h;H{P$hTM%Mm;LcK54{rLWN`)k?BzVX$iaPZ|ec9f}@3E0V&vsAZ#Pw|YSCikV+|uZ)2nEb6nDQ(#@o)wHN>~gNGEIZBIE0k> zu^F&A6XI6k;E*6b40(1T6Vp6ZJ9ZY~n1~(?RZIiSEa&8ESY;@wCHJh=H)6)LKzUQo z`VCeN^7Bo4rWn{^8X%lKg>k{gb<^ifoqxk9DZ!4ZAWM#M!Q8n{^QQLbW07a&=Fpzc z*gIjugsF^$l>>;@-W@ntzyq%&sTE!NB2t`1tA-M^`Tw$5g;71~iq`W96!Y z$Ce*DQpt`zy>hoM>YF`#<*Gh+7VHWH8x}6<>RP&Nj~g>~1)T1Evt~`&H+x4BE(PWj zd%I>VTRhQ{54!>ugNZX|&fK|eqB$pa1?kHY diff --git a/static/favicon/favicon.ico b/static/favicon/favicon.ico index 53393cda7e0850a26d561567294181c849f5021e..0bf544e051b9061348db04aa2b9c1b9655780860 100644 GIT binary patch literal 15086 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 VA#mUGelB!(Wi2Jp1<0Je{{?&t+3)}W literal 15086 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 Q|AlCi$ZbMBo7HIk578%S-v9sr diff --git a/static/favicon/mstile-150x150.png b/static/favicon/mstile-150x150.png index 378274c99391b5f2d3524fe3f6e33c0eed5286a2..0c44361b277e35f3759f20054fd4e000861bf0ee 100644 GIT binary patch delta 2104 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 D21|ea diff --git a/static/favicon/safari-pinned-tab.svg b/static/favicon/safari-pinned-tab.svg index a8425e8a..cb5555d6 100644 --- a/static/favicon/safari-pinned-tab.svg +++ b/static/favicon/safari-pinned-tab.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From d2ec7f6e0db9ed2f203e92add255da32b55ceab3 Mon Sep 17 00:00:00 2001 From: Alexandre Iooss Date: Fri, 21 Feb 2020 10:52:07 +0100 Subject: [PATCH 45/67] Do not justify everything --- templates/base.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/base.html b/templates/base.html index 12b4cf79..81ebed37 100644 --- a/templates/base.html +++ b/templates/base.html @@ -101,7 +101,7 @@ SPDX-License-Identifier: GPL-3.0-or-later {% block sidebar %} {% endblock %} -
+
{% block contenttitle %}

{{ title }}

{% endblock %} {% block content %}

Default content...

From 4af37b7990702cf56ed1043073ac1738851b1502 Mon Sep 17 00:00:00 2001 From: Alexandre Iooss Date: Fri, 21 Feb 2020 10:52:33 +0100 Subject: [PATCH 46/67] Update fr locale --- locale/de/LC_MESSAGES/django.po | 208 ++++++++++++++------------ locale/fr/LC_MESSAGES/django.po | 253 +++++++++++++++++--------------- 2 files changed, 252 insertions(+), 209 deletions(-) diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po index 3b0bb88d..cc63b25a 100644 --- a/locale/de/LC_MESSAGES/django.po +++ b/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-02-06 21:37+0100\n" +"POT-Creation-Date: 2020-02-21 10:36+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,334 +18,346 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: apps/activity/apps.py:11 apps/activity/models.py:77 +#: apps/activity/apps.py:10 apps/activity/models.py:76 msgid "activity" msgstr "" -#: apps/activity/models.py:20 apps/activity/models.py:45 -#: apps/member/models.py:61 apps/member/models.py:112 -#: apps/note/models/notes.py:178 apps/note/models/transactions.py:24 -#: templates/member/profile_detail.html:10 +#: apps/activity/models.py:19 apps/activity/models.py:44 +#: apps/member/models.py:60 apps/member/models.py:111 +#: apps/note/models/notes.py:176 apps/note/models/transactions.py:23 +#: apps/note/models/transactions.py:43 templates/member/profile_detail.html:10 msgid "name" msgstr "" -#: apps/activity/models.py:24 +#: apps/activity/models.py:23 msgid "can invite" msgstr "" -#: apps/activity/models.py:27 +#: apps/activity/models.py:26 msgid "guest entry fee" msgstr "" -#: apps/activity/models.py:31 +#: apps/activity/models.py:30 msgid "activity type" msgstr "" -#: apps/activity/models.py:32 +#: apps/activity/models.py:31 msgid "activity types" msgstr "" -#: apps/activity/models.py:49 +#: apps/activity/models.py:48 msgid "description" msgstr "" -#: apps/activity/models.py:55 apps/note/models/notes.py:154 -#: apps/note/models/transactions.py:39 apps/note/models/transactions.py:85 +#: apps/activity/models.py:54 apps/note/models/notes.py:152 +#: apps/note/models/transactions.py:60 apps/note/models/transactions.py:104 msgid "type" msgstr "" -#: apps/activity/models.py:61 +#: apps/activity/models.py:60 msgid "organizer" msgstr "" -#: apps/activity/models.py:67 +#: apps/activity/models.py:66 msgid "attendees club" msgstr "" -#: apps/activity/models.py:70 +#: apps/activity/models.py:69 msgid "start date" msgstr "" -#: apps/activity/models.py:73 +#: apps/activity/models.py:72 msgid "end date" msgstr "" -#: apps/activity/models.py:78 +#: apps/activity/models.py:77 msgid "activities" msgstr "" -#: apps/activity/models.py:109 +#: apps/activity/models.py:108 msgid "guest" msgstr "" -#: apps/activity/models.py:110 +#: apps/activity/models.py:109 msgid "guests" msgstr "" -#: apps/member/apps.py:11 +#: apps/member/apps.py:10 msgid "member" msgstr "" -#: apps/member/models.py:25 +#: apps/member/models.py:23 msgid "phone number" msgstr "" -#: apps/member/models.py:31 templates/member/profile_detail.html:18 +#: apps/member/models.py:29 templates/member/profile_detail.html:18 msgid "section" msgstr "" -#: apps/member/models.py:32 +#: apps/member/models.py:30 msgid "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\"" msgstr "" -#: apps/member/models.py:38 templates/member/profile_detail.html:20 +#: apps/member/models.py:36 templates/member/profile_detail.html:20 msgid "address" msgstr "" -#: apps/member/models.py:44 +#: apps/member/models.py:42 msgid "paid" msgstr "" -#: apps/member/models.py:49 apps/member/models.py:50 +#: apps/member/models.py:47 apps/member/models.py:48 msgid "user profile" msgstr "" -#: apps/member/models.py:66 +#: apps/member/models.py:65 msgid "email" msgstr "" -#: apps/member/models.py:71 +#: apps/member/models.py:70 msgid "membership fee" msgstr "" -#: apps/member/models.py:75 +#: apps/member/models.py:74 msgid "membership duration" msgstr "" -#: apps/member/models.py:76 +#: apps/member/models.py:75 msgid "The longest time a membership can last (NULL = infinite)." msgstr "" -#: apps/member/models.py:81 +#: apps/member/models.py:80 msgid "membership start" msgstr "" -#: apps/member/models.py:82 +#: apps/member/models.py:81 msgid "How long after January 1st the members can renew their membership." msgstr "" -#: apps/member/models.py:87 +#: apps/member/models.py:86 msgid "membership end" msgstr "" -#: apps/member/models.py:88 +#: apps/member/models.py:87 msgid "" "How long the membership can last after January 1st of the next year after " "members can renew their membership." msgstr "" -#: apps/member/models.py:94 apps/note/models/notes.py:129 +#: apps/member/models.py:93 apps/note/models/notes.py:127 msgid "club" msgstr "" -#: apps/member/models.py:95 +#: apps/member/models.py:94 msgid "clubs" msgstr "" -#: apps/member/models.py:118 +#: apps/member/models.py:117 msgid "role" msgstr "" -#: apps/member/models.py:119 +#: apps/member/models.py:118 msgid "roles" msgstr "" -#: apps/member/models.py:140 +#: apps/member/models.py:142 msgid "membership starts on" msgstr "" -#: apps/member/models.py:143 +#: apps/member/models.py:145 msgid "membership ends on" msgstr "" -#: apps/member/models.py:147 +#: apps/member/models.py:149 msgid "fee" msgstr "" -#: apps/member/models.py:151 +#: apps/member/models.py:153 msgid "membership" msgstr "" -#: apps/member/models.py:152 +#: apps/member/models.py:154 msgid "memberships" msgstr "" -#: apps/note/admin.py:112 apps/note/models/transactions.py:65 +#: apps/member/views.py:78 apps/note/models/notes.py:229 +msgid "An alias with a similar name already exists." +msgstr "" + +#: apps/note/admin.py:118 apps/note/models/transactions.py:86 msgid "source" msgstr "" -#: apps/note/admin.py:120 apps/note/admin.py:148 -#: apps/note/models/transactions.py:32 apps/note/models/transactions.py:71 +#: apps/note/admin.py:126 apps/note/admin.py:154 +#: apps/note/models/transactions.py:51 apps/note/models/transactions.py:92 msgid "destination" msgstr "" -#: apps/note/apps.py:15 apps/note/models/notes.py:51 +#: apps/note/apps.py:14 apps/note/models/notes.py:48 msgid "note" msgstr "" -#: apps/note/models/notes.py:28 +#: apps/note/models/notes.py:26 msgid "account balance" msgstr "" -#: apps/note/models/notes.py:29 +#: apps/note/models/notes.py:27 msgid "in centimes, money credited for this instance" msgstr "" -#: apps/note/models/notes.py:33 +#: apps/note/models/notes.py:31 msgid "active" msgstr "" -#: apps/note/models/notes.py:36 +#: apps/note/models/notes.py:34 msgid "" "Designates whether this note should be treated as active. Unselect this " "instead of deleting notes." msgstr "" -#: apps/note/models/notes.py:41 +#: apps/note/models/notes.py:38 msgid "display image" msgstr "" -#: apps/note/models/notes.py:46 apps/note/models/transactions.py:74 +#: apps/note/models/notes.py:43 apps/note/models/transactions.py:95 msgid "created at" msgstr "" -#: apps/note/models/notes.py:52 +#: apps/note/models/notes.py:49 msgid "notes" msgstr "" -#: apps/note/models/notes.py:60 +#: apps/note/models/notes.py:57 msgid "Note" msgstr "" -#: apps/note/models/notes.py:70 apps/note/models/notes.py:92 +#: apps/note/models/notes.py:67 apps/note/models/notes.py:90 msgid "This alias is already taken." msgstr "" -#: apps/note/models/notes.py:107 +#: apps/note/models/notes.py:105 msgid "user" msgstr "" -#: apps/note/models/notes.py:111 +#: apps/note/models/notes.py:109 msgid "one's note" msgstr "" -#: apps/note/models/notes.py:112 +#: apps/note/models/notes.py:110 msgid "users note" msgstr "" -#: apps/note/models/notes.py:118 +#: apps/note/models/notes.py:116 #, python-format msgid "%(user)s's note" msgstr "" -#: apps/note/models/notes.py:133 +#: apps/note/models/notes.py:131 msgid "club note" msgstr "" -#: apps/note/models/notes.py:134 +#: apps/note/models/notes.py:132 msgid "clubs notes" msgstr "" -#: apps/note/models/notes.py:140 +#: apps/note/models/notes.py:138 #, python-format msgid "Note of %(club)s club" msgstr "" -#: apps/note/models/notes.py:160 +#: apps/note/models/notes.py:158 msgid "special note" msgstr "" -#: apps/note/models/notes.py:161 +#: apps/note/models/notes.py:159 msgid "special notes" msgstr "" -#: apps/note/models/notes.py:184 +#: apps/note/models/notes.py:182 msgid "Invalid alias" msgstr "" -#: apps/note/models/notes.py:200 +#: apps/note/models/notes.py:198 msgid "alias" msgstr "" -#: apps/note/models/notes.py:201 +#: apps/note/models/notes.py:199 msgid "aliases" msgstr "" -#: apps/note/models/notes.py:229 +#: apps/note/models/notes.py:225 msgid "Alias too long." msgstr "" -#: apps/note/models/notes.py:232 -msgid "An alias with a similar name already exists." +#: apps/note/models/notes.py:236 +msgid "You can't delete your main alias." msgstr "" -#: apps/note/models/transactions.py:35 apps/note/models/transactions.py:82 +#: apps/note/models/transactions.py:29 +msgid "transaction category" +msgstr "" + +#: apps/note/models/transactions.py:30 +msgid "transaction categories" +msgstr "" + +#: apps/note/models/transactions.py:54 apps/note/models/transactions.py:102 msgid "amount" msgstr "" -#: apps/note/models/transactions.py:36 +#: apps/note/models/transactions.py:55 msgid "in centimes" msgstr "" -#: apps/note/models/transactions.py:44 +#: apps/note/models/transactions.py:65 msgid "transaction template" msgstr "" -#: apps/note/models/transactions.py:45 +#: apps/note/models/transactions.py:66 msgid "transaction templates" msgstr "" -#: apps/note/models/transactions.py:78 +#: apps/note/models/transactions.py:99 msgid "quantity" msgstr "" -#: apps/note/models/transactions.py:89 +#: apps/note/models/transactions.py:108 msgid "reason" msgstr "" -#: apps/note/models/transactions.py:93 +#: apps/note/models/transactions.py:112 msgid "valid" msgstr "" -#: apps/note/models/transactions.py:98 +#: apps/note/models/transactions.py:117 msgid "transaction" msgstr "" -#: apps/note/models/transactions.py:99 +#: apps/note/models/transactions.py:118 msgid "transactions" msgstr "" -#: apps/note/models/transactions.py:141 +#: apps/note/models/transactions.py:160 msgid "membership transaction" msgstr "" -#: apps/note/models/transactions.py:142 +#: apps/note/models/transactions.py:161 msgid "membership transactions" msgstr "" -#: apps/note/views.py:26 +#: apps/note/views.py:29 msgid "Transfer money from your account to one or others" msgstr "" -#: note_kfet/settings/base.py:130 +#: note_kfet/settings/base.py:148 msgid "German" msgstr "" -#: note_kfet/settings/base.py:131 +#: note_kfet/settings/base.py:149 msgid "English" msgstr "" -#: note_kfet/settings/base.py:132 +#: note_kfet/settings/base.py:150 msgid "French" msgstr "" @@ -369,6 +381,18 @@ msgstr "" msgid "balance" msgstr "" +#: templates/member/manage_auth_tokens.html:16 +msgid "Token" +msgstr "" + +#: templates/member/manage_auth_tokens.html:23 +msgid "Created" +msgstr "" + +#: templates/member/manage_auth_tokens.html:31 +msgid "Regenerate token" +msgstr "" + #: templates/member/profile_detail.html:12 msgid "first name" msgstr "" @@ -377,15 +401,19 @@ msgstr "" msgid "username" msgstr "" -#: templates/member/profile_detail.html:26 +#: templates/member/profile_detail.html:27 +msgid "Manage auth token" +msgstr "" + +#: templates/member/profile_detail.html:29 msgid "Update Profile" msgstr "" -#: templates/member/profile_detail.html:27 +#: templates/member/profile_detail.html:30 msgid "Change password" msgstr "" -#: templates/member/profile_detail.html:35 +#: templates/member/profile_detail.html:38 msgid "View my memberships" msgstr "" diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 0c742fd5..04efe8d9 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -3,7 +3,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-02-06 21:37+0100\n" +"POT-Creation-Date: 2020-02-21 10:36+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -13,396 +13,411 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -#: apps/activity/apps.py:11 apps/activity/models.py:77 +#: apps/activity/apps.py:10 apps/activity/models.py:76 msgid "activity" msgstr "activité" -#: apps/activity/models.py:20 apps/activity/models.py:45 -#: apps/member/models.py:61 apps/member/models.py:112 -#: apps/note/models/notes.py:178 apps/note/models/transactions.py:24 -#: templates/member/profile_detail.html:10 +#: apps/activity/models.py:19 apps/activity/models.py:44 +#: apps/member/models.py:60 apps/member/models.py:111 +#: apps/note/models/notes.py:176 apps/note/models/transactions.py:23 +#: apps/note/models/transactions.py:43 templates/member/profile_detail.html:10 msgid "name" msgstr "nom" -#: apps/activity/models.py:24 +#: apps/activity/models.py:23 msgid "can invite" msgstr "peut inviter" -#: apps/activity/models.py:27 +#: apps/activity/models.py:26 msgid "guest entry fee" msgstr "cotisation de l'entrée invité" -#: apps/activity/models.py:31 +#: apps/activity/models.py:30 msgid "activity type" msgstr "type d'activité" -#: apps/activity/models.py:32 +#: apps/activity/models.py:31 msgid "activity types" msgstr "types d'activité" -#: apps/activity/models.py:49 +#: apps/activity/models.py:48 msgid "description" msgstr "description" -#: apps/activity/models.py:55 apps/note/models/notes.py:154 -#: apps/note/models/transactions.py:39 apps/note/models/transactions.py:85 +#: apps/activity/models.py:54 apps/note/models/notes.py:152 +#: apps/note/models/transactions.py:60 apps/note/models/transactions.py:104 msgid "type" msgstr "type" -#: apps/activity/models.py:61 +#: apps/activity/models.py:60 msgid "organizer" msgstr "organisateur" -#: apps/activity/models.py:67 +#: apps/activity/models.py:66 msgid "attendees club" msgstr "" -#: apps/activity/models.py:70 +#: apps/activity/models.py:69 msgid "start date" msgstr "date de début" -#: apps/activity/models.py:73 +#: apps/activity/models.py:72 msgid "end date" msgstr "date de fin" -#: apps/activity/models.py:78 +#: apps/activity/models.py:77 msgid "activities" msgstr "activités" -#: apps/activity/models.py:109 +#: apps/activity/models.py:108 msgid "guest" msgstr "invité" -#: apps/activity/models.py:110 +#: apps/activity/models.py:109 msgid "guests" msgstr "invités" -#: apps/member/apps.py:11 +#: apps/member/apps.py:10 msgid "member" msgstr "adhérent" -#: apps/member/models.py:25 +#: apps/member/models.py:23 msgid "phone number" msgstr "numéro de téléphone" -#: apps/member/models.py:31 templates/member/profile_detail.html:18 +#: apps/member/models.py:29 templates/member/profile_detail.html:18 msgid "section" msgstr "section" -#: apps/member/models.py:32 +#: apps/member/models.py:30 msgid "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\"" msgstr "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\"" -#: apps/member/models.py:38 templates/member/profile_detail.html:20 +#: apps/member/models.py:36 templates/member/profile_detail.html:20 msgid "address" msgstr "adresse" -#: apps/member/models.py:44 +#: apps/member/models.py:42 msgid "paid" msgstr "payé" -#: apps/member/models.py:49 apps/member/models.py:50 +#: apps/member/models.py:47 apps/member/models.py:48 msgid "user profile" msgstr "profil utilisateur" -#: apps/member/models.py:66 +#: apps/member/models.py:65 msgid "email" msgstr "courriel" -#: apps/member/models.py:71 +#: apps/member/models.py:70 msgid "membership fee" msgstr "cotisation pour adhérer" -#: apps/member/models.py:75 +#: apps/member/models.py:74 msgid "membership duration" msgstr "durée de l'adhésion" -#: apps/member/models.py:76 +#: apps/member/models.py:75 msgid "The longest time a membership can last (NULL = infinite)." msgstr "La durée maximale d'une adhésion (NULL = infinie)." -#: apps/member/models.py:81 +#: apps/member/models.py:80 msgid "membership start" msgstr "début de l'adhésion" -#: apps/member/models.py:82 +#: apps/member/models.py:81 msgid "How long after January 1st the members can renew their membership." -msgstr "" +msgstr "Combien de temps après le 1er Janvier les adhérents peuvent renouveler leur adhésion." -#: apps/member/models.py:87 +#: apps/member/models.py:86 msgid "membership end" msgstr "fin de l'adhésion" -#: apps/member/models.py:88 +#: apps/member/models.py:87 msgid "" "How long the membership can last after January 1st of the next year after " "members can renew their membership." msgstr "" +"Combien de temps l'adhésion peut durer après le 1er Janvier de l'année suivante " +"avant que les adhérents peuvent renouveler leur adhésion." -#: apps/member/models.py:94 apps/note/models/notes.py:129 +#: apps/member/models.py:93 apps/note/models/notes.py:127 msgid "club" msgstr "club" -#: apps/member/models.py:95 +#: apps/member/models.py:94 msgid "clubs" msgstr "clubs" -#: apps/member/models.py:118 +#: apps/member/models.py:117 msgid "role" msgstr "rôle" -#: apps/member/models.py:119 +#: apps/member/models.py:118 msgid "roles" msgstr "rôles" -#: apps/member/models.py:140 +#: apps/member/models.py:142 msgid "membership starts on" msgstr "l'adhésion commence le" -#: apps/member/models.py:143 +#: apps/member/models.py:145 msgid "membership ends on" msgstr "l'adhésion finie le" -#: apps/member/models.py:147 +#: apps/member/models.py:149 msgid "fee" msgstr "cotisation" -#: apps/member/models.py:151 +#: apps/member/models.py:153 msgid "membership" msgstr "adhésion" -#: apps/member/models.py:152 +#: apps/member/models.py:154 msgid "memberships" msgstr "adhésions" -#: apps/note/admin.py:112 apps/note/models/transactions.py:65 +#: apps/member/views.py:78 apps/note/models/notes.py:229 +msgid "An alias with a similar name already exists." +msgstr "Un alias avec un nom similaire existe déjà." + +#: apps/note/admin.py:118 apps/note/models/transactions.py:86 msgid "source" msgstr "source" -#: apps/note/admin.py:120 apps/note/admin.py:148 -#: apps/note/models/transactions.py:32 apps/note/models/transactions.py:71 +#: apps/note/admin.py:126 apps/note/admin.py:154 +#: apps/note/models/transactions.py:51 apps/note/models/transactions.py:92 msgid "destination" msgstr "destination" -#: apps/note/apps.py:15 apps/note/models/notes.py:51 +#: apps/note/apps.py:14 apps/note/models/notes.py:48 msgid "note" msgstr "note" -#: apps/note/models/notes.py:28 +#: apps/note/models/notes.py:26 msgid "account balance" msgstr "solde du compte" -#: apps/note/models/notes.py:29 +#: apps/note/models/notes.py:27 msgid "in centimes, money credited for this instance" msgstr "en centimes, argent crédité pour cette instance" -#: apps/note/models/notes.py:33 +#: apps/note/models/notes.py:31 msgid "active" msgstr "actif" -#: apps/note/models/notes.py:36 +#: apps/note/models/notes.py:34 msgid "" "Designates whether this note should be treated as active. Unselect this " "instead of deleting notes." msgstr "" "Indique si la note est active. Désactiver cela plutôt que supprimer la note." -#: apps/note/models/notes.py:41 +#: apps/note/models/notes.py:38 msgid "display image" msgstr "image affichée" -#: apps/note/models/notes.py:46 apps/note/models/transactions.py:74 +#: apps/note/models/notes.py:43 apps/note/models/transactions.py:95 msgid "created at" msgstr "créée le" -#: apps/note/models/notes.py:52 +#: apps/note/models/notes.py:49 msgid "notes" msgstr "notes" -#: apps/note/models/notes.py:60 +#: apps/note/models/notes.py:57 msgid "Note" msgstr "Note" -#: apps/note/models/notes.py:70 apps/note/models/notes.py:92 +#: apps/note/models/notes.py:67 apps/note/models/notes.py:90 msgid "This alias is already taken." msgstr "Cet alias est déjà pris." -#: apps/note/models/notes.py:107 +#: apps/note/models/notes.py:105 msgid "user" msgstr "utilisateur" -#: apps/note/models/notes.py:111 +#: apps/note/models/notes.py:109 msgid "one's note" msgstr "note d'un utilisateur" -#: apps/note/models/notes.py:112 +#: apps/note/models/notes.py:110 msgid "users note" msgstr "notes des utilisateurs" -#: apps/note/models/notes.py:118 +#: apps/note/models/notes.py:116 #, python-format msgid "%(user)s's note" msgstr "Note de %(user)s" -#: apps/note/models/notes.py:133 +#: apps/note/models/notes.py:131 msgid "club note" msgstr "note d'un club" -#: apps/note/models/notes.py:134 +#: apps/note/models/notes.py:132 msgid "clubs notes" msgstr "notes des clubs" -#: apps/note/models/notes.py:140 -#, fuzzy, python-format -#| msgid "Note for %(club)s club" +#: apps/note/models/notes.py:138 +#, python-format msgid "Note of %(club)s club" msgstr "Note du club %(club)s" -#: apps/note/models/notes.py:160 +#: apps/note/models/notes.py:158 msgid "special note" msgstr "note spéciale" -#: apps/note/models/notes.py:161 +#: apps/note/models/notes.py:159 msgid "special notes" msgstr "notes spéciales" -#: apps/note/models/notes.py:184 +#: apps/note/models/notes.py:182 msgid "Invalid alias" msgstr "Alias invalide" -#: apps/note/models/notes.py:200 +#: apps/note/models/notes.py:198 msgid "alias" msgstr "alias" -#: apps/note/models/notes.py:201 +#: apps/note/models/notes.py:199 msgid "aliases" msgstr "alias" -#: apps/note/models/notes.py:229 +#: apps/note/models/notes.py:225 msgid "Alias too long." msgstr "L'alias est trop long." -#: apps/note/models/notes.py:232 -msgid "An alias with a similar name already exists." -msgstr "Un alias avec un nom similaire existe déjà." +#: apps/note/models/notes.py:236 +msgid "You can't delete your main alias." +msgstr "Vous ne pouvez pas supprimer votre alias principal." -#: apps/note/models/transactions.py:35 apps/note/models/transactions.py:82 +#: apps/note/models/transactions.py:29 +msgid "transaction category" +msgstr "catégorie de transaction" + +#: apps/note/models/transactions.py:30 +msgid "transaction categories" +msgstr "catégories de transaction" + +#: apps/note/models/transactions.py:54 apps/note/models/transactions.py:102 msgid "amount" msgstr "montant" -#: apps/note/models/transactions.py:36 +#: apps/note/models/transactions.py:55 msgid "in centimes" msgstr "en centimes" -#: apps/note/models/transactions.py:44 +#: apps/note/models/transactions.py:65 msgid "transaction template" msgstr "modèle de transaction" -#: apps/note/models/transactions.py:45 +#: apps/note/models/transactions.py:66 msgid "transaction templates" msgstr "modèles de transaction" -#: apps/note/models/transactions.py:78 +#: apps/note/models/transactions.py:99 msgid "quantity" msgstr "quantité" -#: apps/note/models/transactions.py:89 +#: apps/note/models/transactions.py:108 msgid "reason" msgstr "raison" -#: apps/note/models/transactions.py:93 +#: apps/note/models/transactions.py:112 msgid "valid" msgstr "valide" -#: apps/note/models/transactions.py:98 +#: apps/note/models/transactions.py:117 msgid "transaction" msgstr "transaction" -#: apps/note/models/transactions.py:99 +#: apps/note/models/transactions.py:118 msgid "transactions" msgstr "transactions" -#: apps/note/models/transactions.py:141 +#: apps/note/models/transactions.py:160 msgid "membership transaction" msgstr "transaction d'adhésion" -#: apps/note/models/transactions.py:142 +#: apps/note/models/transactions.py:161 msgid "membership transactions" msgstr "transactions d'adhésion" -#: apps/note/views.py:26 +#: apps/note/views.py:29 msgid "Transfer money from your account to one or others" msgstr "Transfert d'argent de ton compte vers un ou plusieurs autres" -#: note_kfet/settings/base.py:130 +#: note_kfet/settings/base.py:148 msgid "German" msgstr "" -#: note_kfet/settings/base.py:131 +#: note_kfet/settings/base.py:149 msgid "English" msgstr "" -#: note_kfet/settings/base.py:132 +#: note_kfet/settings/base.py:150 msgid "French" msgstr "" #: templates/base.html:14 msgid "The ENS Paris-Saclay BDE note." -msgstr "" +msgstr "La note du BDE de l'ENS Paris-Saclay." #: templates/member/club_detail.html:10 -#, fuzzy -#| msgid "membership starts on" msgid "Membership starts on" -msgstr "l'adhésion commence le" +msgstr "L'adhésion commence le" #: templates/member/club_detail.html:12 -#, fuzzy -#| msgid "membership ends on" msgid "Membership ends on" -msgstr "l'adhésion finie le" +msgstr "L'adhésion finie le" #: templates/member/club_detail.html:14 -#, fuzzy -#| msgid "membership duration" msgid "Membership duration" -msgstr "durée de l'adhésion" +msgstr "Durée de l'adhésion" #: templates/member/club_detail.html:18 templates/member/profile_detail.html:22 -#, fuzzy -#| msgid "account balance" msgid "balance" msgstr "solde du compte" +#: templates/member/manage_auth_tokens.html:16 +msgid "Token" +msgstr "Jeton" + +#: templates/member/manage_auth_tokens.html:23 +msgid "Created" +msgstr "Créé le" + +#: templates/member/manage_auth_tokens.html:31 +msgid "Regenerate token" +msgstr "Regénérer le jeton" + #: templates/member/profile_detail.html:12 msgid "first name" msgstr "" #: templates/member/profile_detail.html:14 -#, fuzzy -#| msgid "name" msgid "username" -msgstr "nom" - -#: templates/member/profile_detail.html:26 -#, fuzzy -#| msgid "user profile" -msgid "Update Profile" -msgstr "profil utilisateur" +msgstr "nom d'utilisateur" #: templates/member/profile_detail.html:27 -msgid "Change password" -msgstr "" +msgid "Manage auth token" +msgstr "Gérer les jetons d'authentification" -#: templates/member/profile_detail.html:35 -#, fuzzy -#| msgid "memberships" +#: templates/member/profile_detail.html:29 +msgid "Update Profile" +msgstr "Modifier le profil" + +#: templates/member/profile_detail.html:30 +msgid "Change password" +msgstr "Changer le mot de passe" + +#: templates/member/profile_detail.html:38 msgid "View my memberships" -msgstr "adhésions" +msgstr "Voir mes adhésions" #: templates/member/profile_update.html:13 msgid "Save Changes" -msgstr "" +msgstr "Sauvegarder les changements" #: templates/member/signup.html:14 msgid "Sign Up" From 2e7bf4964b150796e69dea365dc20360f3f844f5 Mon Sep 17 00:00:00 2001 From: Alexandre Iooss Date: Fri, 21 Feb 2020 11:02:29 +0100 Subject: [PATCH 47/67] Upgrade bootstrap and flex footer --- templates/base.html | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/templates/base.html b/templates/base.html index 81ebed37..80f9df9a 100644 --- a/templates/base.html +++ b/templates/base.html @@ -6,8 +6,7 @@ SPDX-License-Identifier: GPL-3.0-or-later - - + {% block title %}{{ title }}{% endblock title %} - {{ request.site.name }} @@ -26,8 +25,8 @@ SPDX-License-Identifier: GPL-3.0-or-later {# Bootstrap CSS #} @@ -39,8 +38,8 @@ SPDX-License-Identifier: GPL-3.0-or-later {% block extracss %}{% endblock %} - -
+ +
-
+
@@ -149,14 +148,14 @@ SPDX-License-Identifier: GPL-3.0-or-later
{# Bootstrap JavaScript #} - - - {% block extrajavascript %} {% endblock extrajavascript %} From 43fd765a3407c84bc3ea53afd70d664cedb22f0a Mon Sep 17 00:00:00 2001 From: Alexandre Iooss Date: Fri, 21 Feb 2020 11:17:14 +0100 Subject: [PATCH 48/67] Title on profile update page --- apps/member/views.py | 1 + locale/de/LC_MESSAGES/django.po | 14 +++++++------- locale/fr/LC_MESSAGES/django.po | 22 ++++++++++++---------- templates/member/profile_update.html | 23 +++++++++++------------ 4 files changed, 31 insertions(+), 29 deletions(-) diff --git a/apps/member/views.py b/apps/member/views.py index 366523b0..fab68b01 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -60,6 +60,7 @@ class UserUpdateView(LoginRequiredMixin, UpdateView): context['user'] = self.request.user context["profile_form"] = self.second_form( instance=context['user_modified'].profile) + context['title'] = _("Update Profile") return context diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po index cc63b25a..296375b2 100644 --- a/locale/de/LC_MESSAGES/django.po +++ b/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-02-21 10:36+0100\n" +"POT-Creation-Date: 2020-02-21 11:16+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -180,7 +180,11 @@ msgstr "" msgid "memberships" msgstr "" -#: apps/member/views.py:78 apps/note/models/notes.py:229 +#: apps/member/views.py:63 templates/member/profile_detail.html:29 +msgid "Update Profile" +msgstr "" + +#: apps/member/views.py:79 apps/note/models/notes.py:229 msgid "An alias with a similar name already exists." msgstr "" @@ -361,7 +365,7 @@ msgstr "" msgid "French" msgstr "" -#: templates/base.html:14 +#: templates/base.html:13 msgid "The ENS Paris-Saclay BDE note." msgstr "" @@ -405,10 +409,6 @@ msgstr "" msgid "Manage auth token" msgstr "" -#: templates/member/profile_detail.html:29 -msgid "Update Profile" -msgstr "" - #: templates/member/profile_detail.html:30 msgid "Change password" msgstr "" diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 04efe8d9..b78eb5fd 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -3,7 +3,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-02-21 10:36+0100\n" +"POT-Creation-Date: 2020-02-21 11:16+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -127,7 +127,9 @@ msgstr "début de l'adhésion" #: apps/member/models.py:81 msgid "How long after January 1st the members can renew their membership." -msgstr "Combien de temps après le 1er Janvier les adhérents peuvent renouveler leur adhésion." +msgstr "" +"Combien de temps après le 1er Janvier les adhérents peuvent renouveler leur " +"adhésion." #: apps/member/models.py:86 msgid "membership end" @@ -138,8 +140,8 @@ msgid "" "How long the membership can last after January 1st of the next year after " "members can renew their membership." msgstr "" -"Combien de temps l'adhésion peut durer après le 1er Janvier de l'année suivante " -"avant que les adhérents peuvent renouveler leur adhésion." +"Combien de temps l'adhésion peut durer après le 1er Janvier de l'année " +"suivante avant que les adhérents peuvent renouveler leur adhésion." #: apps/member/models.py:93 apps/note/models/notes.py:127 msgid "club" @@ -177,7 +179,11 @@ msgstr "adhésion" msgid "memberships" msgstr "adhésions" -#: apps/member/views.py:78 apps/note/models/notes.py:229 +#: apps/member/views.py:63 templates/member/profile_detail.html:29 +msgid "Update Profile" +msgstr "Modifier le profil" + +#: apps/member/views.py:79 apps/note/models/notes.py:229 msgid "An alias with a similar name already exists." msgstr "Un alias avec un nom similaire existe déjà." @@ -359,7 +365,7 @@ msgstr "" msgid "French" msgstr "" -#: templates/base.html:14 +#: templates/base.html:13 msgid "The ENS Paris-Saclay BDE note." msgstr "La note du BDE de l'ENS Paris-Saclay." @@ -403,10 +409,6 @@ msgstr "nom d'utilisateur" msgid "Manage auth token" msgstr "Gérer les jetons d'authentification" -#: templates/member/profile_detail.html:29 -msgid "Update Profile" -msgstr "Modifier le profil" - #: templates/member/profile_detail.html:30 msgid "Change password" msgstr "Changer le mot de passe" diff --git a/templates/member/profile_update.html b/templates/member/profile_update.html index 10936cf7..a47a147b 100644 --- a/templates/member/profile_update.html +++ b/templates/member/profile_update.html @@ -1,17 +1,16 @@ - {% extends "base.html" %} -{% load crispy_forms_tags %} -{% load i18n static pretty_money django_tables2 %} +{% load i18n crispy_forms_tags %} +{% comment %} +SPDX-License-Identifier: GPL-3.0-or-later +{% endcomment %} {% block content %} -
- {% csrf_token %} - {{ form|crispy }} - {{ profile_form|crispy }} - -
- + {% csrf_token %} + {{ form|crispy }} + {{ profile_form|crispy }} + + {% endblock %} From 45ce2eab9e5253e1c490ebd76d6f5cda3d973869 Mon Sep 17 00:00:00 2001 From: Alexandre Iooss Date: Fri, 21 Feb 2020 11:53:37 +0100 Subject: [PATCH 49/67] Two colomn profile page --- apps/member/views.py | 6 +- locale/de/LC_MESSAGES/django.po | 33 ++++--- locale/fr/LC_MESSAGES/django.po | 37 +++++--- templates/member/profile_detail.html | 124 +++++++++++++++------------ 4 files changed, 117 insertions(+), 83 deletions(-) diff --git a/apps/member/views.py b/apps/member/views.py index fab68b01..3570f7b2 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -112,7 +112,7 @@ class UserUpdateView(LoginRequiredMixin, UpdateView): 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 context_object_name = "profile" @@ -126,6 +126,10 @@ class UserDetailView(LoginRequiredMixin, DetailView): club_list = \ Membership.objects.all().filter(user=user).only("club") context['club_list'] = ClubTable(club_list) + context['title'] = _("Account #%(id)s: %(username)s") % { + 'id': user.pk, + 'username': user.username, + } return context diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po index 296375b2..2994b8b1 100644 --- a/locale/de/LC_MESSAGES/django.po +++ b/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-02-21 11:16+0100\n" +"POT-Creation-Date: 2020-02-21 11:52+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -90,7 +90,7 @@ msgstr "" msgid "phone number" msgstr "" -#: apps/member/models.py:29 templates/member/profile_detail.html:18 +#: apps/member/models.py:29 templates/member/profile_detail.html:23 msgid "section" msgstr "" @@ -98,7 +98,7 @@ msgstr "" msgid "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\"" msgstr "" -#: apps/member/models.py:36 templates/member/profile_detail.html:20 +#: apps/member/models.py:36 templates/member/profile_detail.html:26 msgid "address" msgstr "" @@ -180,7 +180,7 @@ msgstr "" msgid "memberships" msgstr "" -#: apps/member/views.py:63 templates/member/profile_detail.html:29 +#: apps/member/views.py:63 templates/member/profile_detail.html:39 msgid "Update Profile" msgstr "" @@ -188,6 +188,11 @@ msgstr "" msgid "An alias with a similar name already exists." msgstr "" +#: apps/member/views.py:129 +#, python-format +msgid "Account #%(id)s: %(username)s" +msgstr "" + #: apps/note/admin.py:118 apps/note/models/transactions.py:86 msgid "source" msgstr "" @@ -285,7 +290,7 @@ msgstr "" msgid "alias" msgstr "" -#: apps/note/models/notes.py:199 +#: apps/note/models/notes.py:199 templates/member/profile_detail.html:32 msgid "aliases" msgstr "" @@ -381,7 +386,7 @@ msgstr "" msgid "Membership duration" msgstr "" -#: templates/member/club_detail.html:18 templates/member/profile_detail.html:22 +#: templates/member/club_detail.html:18 templates/member/profile_detail.html:29 msgid "balance" msgstr "" @@ -397,23 +402,27 @@ msgstr "" msgid "Regenerate token" msgstr "" -#: templates/member/profile_detail.html:12 +#: templates/member/profile_detail.html:10 msgid "first name" msgstr "" -#: templates/member/profile_detail.html:14 +#: templates/member/profile_detail.html:13 msgid "username" msgstr "" -#: templates/member/profile_detail.html:27 -msgid "Manage auth token" +#: templates/member/profile_detail.html:16 +msgid "password" msgstr "" -#: templates/member/profile_detail.html:30 +#: templates/member/profile_detail.html:19 msgid "Change password" msgstr "" -#: templates/member/profile_detail.html:38 +#: templates/member/profile_detail.html:37 +msgid "Manage auth token" +msgstr "" + +#: templates/member/profile_detail.html:49 msgid "View my memberships" msgstr "" diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index b78eb5fd..927349ce 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -3,7 +3,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-02-21 11:16+0100\n" +"POT-Creation-Date: 2020-02-21 11:52+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -85,7 +85,7 @@ msgstr "adhérent" msgid "phone number" msgstr "numéro de téléphone" -#: apps/member/models.py:29 templates/member/profile_detail.html:18 +#: apps/member/models.py:29 templates/member/profile_detail.html:23 msgid "section" msgstr "section" @@ -93,7 +93,7 @@ msgstr "section" msgid "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\"" msgstr "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\"" -#: apps/member/models.py:36 templates/member/profile_detail.html:20 +#: apps/member/models.py:36 templates/member/profile_detail.html:26 msgid "address" msgstr "adresse" @@ -179,7 +179,7 @@ msgstr "adhésion" msgid "memberships" msgstr "adhésions" -#: apps/member/views.py:63 templates/member/profile_detail.html:29 +#: apps/member/views.py:63 templates/member/profile_detail.html:39 msgid "Update Profile" msgstr "Modifier le profil" @@ -187,6 +187,11 @@ msgstr "Modifier le profil" msgid "An alias with a similar name already exists." msgstr "Un alias avec un nom similaire existe déjà." +#: apps/member/views.py:129 +#, python-format +msgid "Account #%(id)s: %(username)s" +msgstr "Compte n°%(id)s : %(username)s" + #: apps/note/admin.py:118 apps/note/models/transactions.py:86 msgid "source" msgstr "source" @@ -285,7 +290,7 @@ msgstr "Alias invalide" msgid "alias" msgstr "alias" -#: apps/note/models/notes.py:199 +#: apps/note/models/notes.py:199 templates/member/profile_detail.html:32 msgid "aliases" msgstr "alias" @@ -381,7 +386,7 @@ msgstr "L'adhésion finie le" msgid "Membership duration" msgstr "Durée de l'adhésion" -#: templates/member/club_detail.html:18 templates/member/profile_detail.html:22 +#: templates/member/club_detail.html:18 templates/member/profile_detail.html:29 msgid "balance" msgstr "solde du compte" @@ -397,23 +402,29 @@ msgstr "Créé le" msgid "Regenerate token" msgstr "Regénérer le jeton" -#: templates/member/profile_detail.html:12 +#: templates/member/profile_detail.html:10 msgid "first name" msgstr "" -#: templates/member/profile_detail.html:14 +#: templates/member/profile_detail.html:13 msgid "username" msgstr "nom d'utilisateur" -#: templates/member/profile_detail.html:27 -msgid "Manage auth token" -msgstr "Gérer les jetons d'authentification" +#: templates/member/profile_detail.html:16 +#, fuzzy +#| msgid "Change password" +msgid "password" +msgstr "Changer le mot de passe" -#: templates/member/profile_detail.html:30 +#: templates/member/profile_detail.html:19 msgid "Change password" msgstr "Changer le mot de passe" -#: templates/member/profile_detail.html:38 +#: templates/member/profile_detail.html:37 +msgid "Manage auth token" +msgstr "Gérer les jetons d'authentification" + +#: templates/member/profile_detail.html:49 msgid "View my memberships" msgstr "Voir mes adhésions" diff --git a/templates/member/profile_detail.html b/templates/member/profile_detail.html index dbf7075b..1b233af1 100644 --- a/templates/member/profile_detail.html +++ b/templates/member/profile_detail.html @@ -2,67 +2,77 @@ {% load i18n static pretty_money django_tables2 %} {% block content %} -

Compte n° {{ object.pk }}

+
+
+ - +
+
{% trans 'name'|capfirst %}, {% trans 'first name' %}
+
{{ object.user.last_name }} {{ object.user.first_name }}
-
-
{% trans 'name'|capfirst %}
-
{{ object.user.last_name }}
-
{% trans 'first name'|capfirst %}
-
{{ object.user.first_name }}
-
{% trans 'username'|capfirst %}
-
{{ object.user.username }}
-
Aliases
-
{{ object.user.note.alias_set.all }}
-
{% trans 'section'|capfirst %}
-
{{ object.section }}
-
{% trans 'address'|capfirst %}
-
{{ object.address }}
-
{% trans 'balance'|capfirst %}
-
{{ object.user.note.balance | pretty_money }}
-
-
- {% if object.user.pk == user.pk %} - {% trans 'Manage auth token' %} - {% endif %} - {% trans 'Update Profile' %} - {% trans 'Change password' %} -
+
{% trans 'username'|capfirst %}
+
{{ object.user.username }}
-
-
-
-
- -
+
{% trans 'password'|capfirst %}
+
+ + {% trans 'Change password' %} + +
+ +
{% trans 'section'|capfirst %}
+
{{ object.section }}
+ +
{% trans 'address'|capfirst %}
+
{{ object.address }}
+ +
{% trans 'balance'|capfirst %}
+
{{ object.user.note.balance | pretty_money }}
+ +
{% trans 'aliases'|capfirst %}
+
{{ object.user.note.alias_set.all|join:", " }}
+
+

+ {% if object.user.pk == user.pk %} + {% trans 'Manage auth token' %} + {% endif %} + {% trans 'Update Profile' %} +

-
-
- {% render_table club_list %} -
+
+
+
+
+
+ +
+
+ +
+
+ {% render_table club_list %} +
+
+
+ +
+
+
+ +
+
+
+
+ {% render_table history_list %} +
+
+
+
-
-
-
-
- -
-
-
-
- {% render_table history_list %} -
-
-
- - - - - {% endblock %} +{% endblock %} From 6caad0b369b200bfce6f1c0d3b08a322c3764b67 Mon Sep 17 00:00:00 2001 From: Alexandre Iooss Date: Fri, 21 Feb 2020 11:55:44 +0100 Subject: [PATCH 50/67] Do not forget responsible grid on second profile page column --- templates/member/profile_detail.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/member/profile_detail.html b/templates/member/profile_detail.html index 1b233af1..353d4bf1 100644 --- a/templates/member/profile_detail.html +++ b/templates/member/profile_detail.html @@ -40,7 +40,7 @@

-
+
From 8d87b2b8f5ebfc319318fc94b541ef9c887bee90 Mon Sep 17 00:00:00 2001 From: Alexandre Iooss Date: Fri, 21 Feb 2020 12:08:36 +0100 Subject: [PATCH 51/67] More style in profile page --- locale/de/LC_MESSAGES/django.po | 26 ++++++------ locale/fr/LC_MESSAGES/django.po | 26 ++++++------ templates/member/profile_detail.html | 60 +++++++++++++++------------- 3 files changed, 58 insertions(+), 54 deletions(-) diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po index 2994b8b1..7fb96c19 100644 --- a/locale/de/LC_MESSAGES/django.po +++ b/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-02-21 11:52+0100\n" +"POT-Creation-Date: 2020-02-21 12:08+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -25,7 +25,7 @@ msgstr "" #: apps/activity/models.py:19 apps/activity/models.py:44 #: apps/member/models.py:60 apps/member/models.py:111 #: apps/note/models/notes.py:176 apps/note/models/transactions.py:23 -#: apps/note/models/transactions.py:43 templates/member/profile_detail.html:10 +#: apps/note/models/transactions.py:43 templates/member/profile_detail.html:11 msgid "name" msgstr "" @@ -90,7 +90,7 @@ msgstr "" msgid "phone number" msgstr "" -#: apps/member/models.py:29 templates/member/profile_detail.html:23 +#: apps/member/models.py:29 templates/member/profile_detail.html:24 msgid "section" msgstr "" @@ -98,7 +98,7 @@ msgstr "" msgid "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\"" msgstr "" -#: apps/member/models.py:36 templates/member/profile_detail.html:26 +#: apps/member/models.py:36 templates/member/profile_detail.html:27 msgid "address" msgstr "" @@ -180,7 +180,7 @@ msgstr "" msgid "memberships" msgstr "" -#: apps/member/views.py:63 templates/member/profile_detail.html:39 +#: apps/member/views.py:63 templates/member/profile_detail.html:42 msgid "Update Profile" msgstr "" @@ -290,7 +290,7 @@ msgstr "" msgid "alias" msgstr "" -#: apps/note/models/notes.py:199 templates/member/profile_detail.html:32 +#: apps/note/models/notes.py:199 templates/member/profile_detail.html:33 msgid "aliases" msgstr "" @@ -386,7 +386,7 @@ msgstr "" msgid "Membership duration" msgstr "" -#: templates/member/club_detail.html:18 templates/member/profile_detail.html:29 +#: templates/member/club_detail.html:18 templates/member/profile_detail.html:30 msgid "balance" msgstr "" @@ -402,27 +402,27 @@ msgstr "" msgid "Regenerate token" msgstr "" -#: templates/member/profile_detail.html:10 +#: templates/member/profile_detail.html:11 msgid "first name" msgstr "" -#: templates/member/profile_detail.html:13 +#: templates/member/profile_detail.html:14 msgid "username" msgstr "" -#: templates/member/profile_detail.html:16 +#: templates/member/profile_detail.html:17 msgid "password" msgstr "" -#: templates/member/profile_detail.html:19 +#: templates/member/profile_detail.html:20 msgid "Change password" msgstr "" -#: templates/member/profile_detail.html:37 +#: templates/member/profile_detail.html:38 msgid "Manage auth token" msgstr "" -#: templates/member/profile_detail.html:49 +#: templates/member/profile_detail.html:53 msgid "View my memberships" msgstr "" diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 927349ce..4d7c650b 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -3,7 +3,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-02-21 11:52+0100\n" +"POT-Creation-Date: 2020-02-21 12:08+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -20,7 +20,7 @@ msgstr "activité" #: apps/activity/models.py:19 apps/activity/models.py:44 #: apps/member/models.py:60 apps/member/models.py:111 #: apps/note/models/notes.py:176 apps/note/models/transactions.py:23 -#: apps/note/models/transactions.py:43 templates/member/profile_detail.html:10 +#: apps/note/models/transactions.py:43 templates/member/profile_detail.html:11 msgid "name" msgstr "nom" @@ -85,7 +85,7 @@ msgstr "adhérent" msgid "phone number" msgstr "numéro de téléphone" -#: apps/member/models.py:29 templates/member/profile_detail.html:23 +#: apps/member/models.py:29 templates/member/profile_detail.html:24 msgid "section" msgstr "section" @@ -93,7 +93,7 @@ msgstr "section" msgid "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\"" msgstr "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\"" -#: apps/member/models.py:36 templates/member/profile_detail.html:26 +#: apps/member/models.py:36 templates/member/profile_detail.html:27 msgid "address" msgstr "adresse" @@ -179,7 +179,7 @@ msgstr "adhésion" msgid "memberships" msgstr "adhésions" -#: apps/member/views.py:63 templates/member/profile_detail.html:39 +#: apps/member/views.py:63 templates/member/profile_detail.html:42 msgid "Update Profile" msgstr "Modifier le profil" @@ -290,7 +290,7 @@ msgstr "Alias invalide" msgid "alias" msgstr "alias" -#: apps/note/models/notes.py:199 templates/member/profile_detail.html:32 +#: apps/note/models/notes.py:199 templates/member/profile_detail.html:33 msgid "aliases" msgstr "alias" @@ -386,7 +386,7 @@ msgstr "L'adhésion finie le" msgid "Membership duration" msgstr "Durée de l'adhésion" -#: templates/member/club_detail.html:18 templates/member/profile_detail.html:29 +#: templates/member/club_detail.html:18 templates/member/profile_detail.html:30 msgid "balance" msgstr "solde du compte" @@ -402,29 +402,29 @@ msgstr "Créé le" msgid "Regenerate token" msgstr "Regénérer le jeton" -#: templates/member/profile_detail.html:10 +#: templates/member/profile_detail.html:11 msgid "first name" msgstr "" -#: templates/member/profile_detail.html:13 +#: templates/member/profile_detail.html:14 msgid "username" msgstr "nom d'utilisateur" -#: templates/member/profile_detail.html:16 +#: templates/member/profile_detail.html:17 #, fuzzy #| msgid "Change password" msgid "password" msgstr "Changer le mot de passe" -#: templates/member/profile_detail.html:19 +#: templates/member/profile_detail.html:20 msgid "Change password" msgstr "Changer le mot de passe" -#: templates/member/profile_detail.html:37 +#: templates/member/profile_detail.html:38 msgid "Manage auth token" msgstr "Gérer les jetons d'authentification" -#: templates/member/profile_detail.html:49 +#: templates/member/profile_detail.html:53 msgid "View my memberships" msgstr "Voir mes adhésions" diff --git a/templates/member/profile_detail.html b/templates/member/profile_detail.html index 353d4bf1..87cdc531 100644 --- a/templates/member/profile_detail.html +++ b/templates/member/profile_detail.html @@ -3,41 +3,45 @@ {% block content %}
-
- +
+
+ +
+
+
{% trans 'name'|capfirst %}, {% trans 'first name' %}
+
{{ object.user.last_name }} {{ object.user.first_name }}
-
-
{% trans 'name'|capfirst %}, {% trans 'first name' %}
-
{{ object.user.last_name }} {{ object.user.first_name }}
+
{% trans 'username'|capfirst %}
+
{{ object.user.username }}
-
{% trans 'username'|capfirst %}
-
{{ object.user.username }}
+
{% trans 'password'|capfirst %}
+
+ + {% trans 'Change password' %} + +
-
{% trans 'password'|capfirst %}
-
- - {% trans 'Change password' %} - -
+
{% trans 'section'|capfirst %}
+
{{ object.section }}
-
{% trans 'section'|capfirst %}
-
{{ object.section }}
+
{% trans 'address'|capfirst %}
+
{{ object.address }}
-
{% trans 'address'|capfirst %}
-
{{ object.address }}
+
{% trans 'balance'|capfirst %}
+
{{ object.user.note.balance | pretty_money }}
-
{% trans 'balance'|capfirst %}
-
{{ object.user.note.balance | pretty_money }}
+
{% trans 'aliases'|capfirst %}
+
{{ object.user.note.alias_set.all|join:", " }}
+
-
{% trans 'aliases'|capfirst %}
-
{{ object.user.note.alias_set.all|join:", " }}
-
-

- {% if object.user.pk == user.pk %} - {% trans 'Manage auth token' %} - {% endif %} - {% trans 'Update Profile' %} -

+ {% if object.user.pk == user.pk %} + {% trans 'Manage auth token' %} + {% endif %} +
+ +
From b395d3a63338b233fee3a879a0a143ef1b881c2e Mon Sep 17 00:00:00 2001 From: Alexandre Iooss Date: Fri, 21 Feb 2020 12:29:11 +0100 Subject: [PATCH 52/67] Overflow on collapsing tables and Bootstrap4 style --- apps/member/tables.py | 10 +++----- apps/note/tables.py | 4 +-- templates/member/profile_detail.html | 37 ++++++++++++---------------- 3 files changed, 22 insertions(+), 29 deletions(-) diff --git a/apps/member/tables.py b/apps/member/tables.py index 591149ec..a6de17d2 100644 --- a/apps/member/tables.py +++ b/apps/member/tables.py @@ -10,11 +10,10 @@ from .models import Club class ClubTable(tables.Table): class Meta: attrs = { - 'class': - 'table table-bordered table-condensed table-striped table-hover' + 'class': 'table table-condensed table-striped table-hover' } model = Club - template_name = 'django_tables2/bootstrap.html' + template_name = 'django_tables2/bootstrap4.html' fields = ('id', 'name', 'email') row_attrs = { 'class': 'table-row', @@ -28,9 +27,8 @@ class UserTable(tables.Table): class Meta: attrs = { - 'class': - 'table table-bordered table-condensed table-striped table-hover' + 'class': 'table table-condensed table-striped table-hover' } - template_name = 'django_tables2/bootstrap.html' + template_name = 'django_tables2/bootstrap4.html' fields = ('last_name', 'first_name', 'username', 'email') model = User diff --git a/apps/note/tables.py b/apps/note/tables.py index e2f5c763..43a1ef74 100644 --- a/apps/note/tables.py +++ b/apps/note/tables.py @@ -11,10 +11,10 @@ class HistoryTable(tables.Table): class Meta: attrs = { 'class': - 'table table-bordered table-condensed table-striped table-hover' + 'table table-condensed table-striped table-hover' } model = Transaction - template_name = 'django_tables2/bootstrap.html' + template_name = 'django_tables2/bootstrap4.html' sequence = ('...', 'total', 'valid') total = tables.Column() # will use Transaction.total() !! diff --git a/templates/member/profile_detail.html b/templates/member/profile_detail.html index 87cdc531..9c13d7c7 100644 --- a/templates/member/profile_detail.html +++ b/templates/member/profile_detail.html @@ -47,33 +47,28 @@
-
-
- -
-
- -
-
- {% render_table club_list %} + +
+ {% render_table club_list %}
-
-
- -
+ -
-
- {% render_table history_list %} -
+
+ {% render_table history_list %}
From 6c017b1993028ecbcb1d04113c15e605ac5621ca Mon Sep 17 00:00:00 2001 From: Alexandre Iooss Date: Fri, 21 Feb 2020 13:50:35 +0100 Subject: [PATCH 53/67] Remove sidebar and drop shadows --- locale/de/LC_MESSAGES/django.po | 4 ++-- locale/fr/LC_MESSAGES/django.po | 4 ++-- templates/base.html | 24 ++++++++---------------- templates/member/profile_detail.html | 4 ++-- 4 files changed, 14 insertions(+), 22 deletions(-) diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po index 7fb96c19..3aadf83e 100644 --- a/locale/de/LC_MESSAGES/django.po +++ b/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-02-21 12:08+0100\n" +"POT-Creation-Date: 2020-02-21 13:50+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -422,7 +422,7 @@ msgstr "" msgid "Manage auth token" msgstr "" -#: templates/member/profile_detail.html:53 +#: templates/member/profile_detail.html:54 msgid "View my memberships" msgstr "" diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 4d7c650b..bdf4fc8f 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -3,7 +3,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-02-21 12:08+0100\n" +"POT-Creation-Date: 2020-02-21 13:50+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -424,7 +424,7 @@ msgstr "Changer le mot de passe" msgid "Manage auth token" msgstr "Gérer les jetons d'authentification" -#: templates/member/profile_detail.html:53 +#: templates/member/profile_detail.html:54 msgid "View my memberships" msgstr "Voir mes adhésions" diff --git a/templates/base.html b/templates/base.html index 80f9df9a..ba7b4c9e 100644 --- a/templates/base.html +++ b/templates/base.html @@ -40,7 +40,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
-
-
-
-
- {% block sidebar %} - {% endblock %} -
-
- {% block contenttitle %}

{{ title }}

{% endblock %} - {% block content %} -

Default content...

- {% endblock content %} -
-
+
+ {% block contenttitle %}

{{ title }}

{% endblock %} + {% block content %} +

Default content...

+ {% endblock content %}