From 154ea64b9fb7a59470fbba0d3c94afb37781afb5 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 11 Apr 2020 03:37:06 +0200 Subject: [PATCH 01/55] Launching WEI app, add models --- apps/activity/models.py | 4 +- apps/activity/views.py | 8 +- apps/treasury/views.py | 6 +- apps/wei/__init__.py | 4 + apps/wei/admin.py | 2 + apps/wei/apps.py | 11 ++ apps/wei/migrations/__init__.py | 0 apps/wei/models.py | 243 ++++++++++++++++++++++++++++++++ apps/wei/urls.py | 9 ++ apps/wei/views.py | 5 + note_kfet/settings/base.py | 1 + note_kfet/urls.py | 1 + 12 files changed, 285 insertions(+), 9 deletions(-) create mode 100644 apps/wei/__init__.py create mode 100644 apps/wei/admin.py create mode 100644 apps/wei/apps.py create mode 100644 apps/wei/migrations/__init__.py create mode 100644 apps/wei/models.py create mode 100644 apps/wei/urls.py create mode 100644 apps/wei/views.py diff --git a/apps/activity/models.py b/apps/activity/models.py index 29f04b39..cab229c4 100644 --- a/apps/activity/models.py +++ b/apps/activity/models.py @@ -139,7 +139,7 @@ class Entry(models.Model): verbose_name = _("entry") verbose_name_plural = _("entries") - def save(self, *args,**kwargs): + def save(self, *args, **kwargs): qs = Entry.objects.filter(~Q(pk=self.pk), activity=self.activity, note=self.note, guest=self.guest) if qs.exists(): @@ -153,7 +153,7 @@ class Entry(models.Model): if self.note.balance < 0: raise ValidationError(_("The balance is negative.")) - ret = super().save(*args,**kwargs) + ret = super().save(*args, **kwargs) if insert and self.guest: GuestTransaction.objects.create( diff --git a/apps/activity/views.py b/apps/activity/views.py index 14746929..12386bd1 100644 --- a/apps/activity/views.py +++ b/apps/activity/views.py @@ -45,8 +45,8 @@ class ActivityListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView context['title'] = _("Activities") upcoming_activities = Activity.objects.filter(date_end__gt=datetime.now()) - context['upcoming'] = ActivityTable(data=upcoming_activities - .filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view"))) + context['upcoming'] = ActivityTable( + data=upcoming_activities.filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view"))) return context @@ -153,9 +153,9 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView): context["title"] = _('Entry for activity "{}"').format(activity.name) context["noteuser_ctype"] = ContentType.objects.get_for_model(NoteUser).pk context["notespecial_ctype"] = ContentType.objects.get_for_model(NoteSpecial).pk - + context["activities_open"] = Activity.objects.filter(open=True).filter( PermissionBackend.filter_queryset(self.request.user, Activity, "view")).filter( PermissionBackend.filter_queryset(self.request.user, Activity, "change")).all() - return context \ No newline at end of file + return context diff --git a/apps/treasury/views.py b/apps/treasury/views.py index 7361d1d2..8d744443 100644 --- a/apps/treasury/views.py +++ b/apps/treasury/views.py @@ -203,9 +203,9 @@ class RemittanceCreateView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context["table"] = RemittanceTable(data=Remittance.objects - .filter(PermissionBackend.filter_queryset(self.request.user, Remittance, "view")) - .all()) + context["table"] = RemittanceTable( + data=Remittance.objects.filter( + PermissionBackend.filter_queryset(self.request.user, Remittance, "view")).all()) context["special_transactions"] = SpecialTransactionTable(data=SpecialTransaction.objects.none()) return context diff --git a/apps/wei/__init__.py b/apps/wei/__init__.py new file mode 100644 index 00000000..ad360dae --- /dev/null +++ b/apps/wei/__init__.py @@ -0,0 +1,4 @@ +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +default_app_config = 'wei.apps.WeiConfig' diff --git a/apps/wei/admin.py b/apps/wei/admin.py new file mode 100644 index 00000000..4e945ad5 --- /dev/null +++ b/apps/wei/admin.py @@ -0,0 +1,2 @@ +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later diff --git a/apps/wei/apps.py b/apps/wei/apps.py new file mode 100644 index 00000000..f6332232 --- /dev/null +++ b/apps/wei/apps.py @@ -0,0 +1,11 @@ +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ + + +class WeiConfig(AppConfig): + name = 'wei' + verbose_name = _('WEI') + diff --git a/apps/wei/migrations/__init__.py b/apps/wei/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/wei/models.py b/apps/wei/models.py new file mode 100644 index 00000000..910f8309 --- /dev/null +++ b/apps/wei/models.py @@ -0,0 +1,243 @@ +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from django.contrib.auth.models import User +from django.db import models +from django.utils.translation import gettext_lazy as _ + +from note.models import NoteSpecial + + +class WEI(models.Model): + """ + Store WEI information + """ + name = models.CharField( + max_length=255, + unique=True, + verbose_name=_("name"), + ) + + year = models.PositiveIntegerField( + unique=True, + verbose_name=_("year"), + ) + + start = models.DateField( + verbose_name=_("start date"), + ) + + end = models.DateField( + verbose_name=_("end date"), + ) + + price_paid = models.PositiveIntegerField( + verbose_name=_("Price for paid students"), + ) + + price_unpaid = models.PositiveIntegerField( + verbose_name=_("Price for unpaid students"), + ) + + email = models.EmailField( + verbose_name=_("contact email"), + ) + + registrations_open = models.BooleanField( + verbose_name=_("registrations open"), + ) + + def __str__(self): + return self.name + + class Meta: + verbose_name = _("WEI") + verbose_name_plural = _("WEI") + + +class Bus(models.Model): + """ + The best bus for the best WEI + """ + wei = models.ForeignKey( + WEI, + on_delete=models.PROTECT, + related_name="buses", + verbose_name=_("WEI"), + ) + + name = models.CharField( + max_length=255, + verbose_name=_("name"), + ) + + def __str__(self): + return self.name + + class Meta: + unique_together = ('wei', 'name',) + + +class BusTeam(models.Model): + """ + A bus has multiple teams + """ + bus = models.ForeignKey( + Bus, + on_delete=models.CASCADE, + related_name="teams", + verbose_name=_("bus"), + ) + + name = models.CharField( + max_length=255, + ) + + color = models.PositiveIntegerField( # Use a color picker to get the hexa code + verbose_name=_("color"), + help_text=_("The color of the T-Shirt, stored with its number equivalent"), + ) + + def __str__(self): + return self.name + " (" + str(self.bus) + ")" + + class Meta: + unique_together = ('bus', 'name',) + verbose_name = _("Bus team") + verbose_name_plural = _("Bus teams") + + +class WEIRole(models.Model): + """ + A Role for the WEI can be bus chief, team chief, free electron, ... + """ + name = models.CharField( + max_length=255, + unique=True, + ) + + +class WEIUser(models.Model): + """ + Store personal data that can be useful for the WEI. + """ + + user = models.ForeignKey( + User, + on_delete=models.PROTECT, + related_name="wei", + verbose_name=_("user"), + ) + + wei = models.ForeignKey( + WEI, + on_delete=models.PROTECT, + related_name="users", + verbose_name=_("WEI"), + ) + + role = models.ForeignKey( + WEIRole, + on_delete=models.PROTECT, + verbose_name=_("role"), + ) + + birth_date = models.DateField( + verbose_name=_("birth date"), + ) + + gender = models.CharField( + max_length=16, + choices=( + ('male', _("Male")), + ('female', _("Female")), + ('nonbinary', _("Non binary")), + ), + verbose_name=_("gender"), + ) + + health_issues = models.TextField( + verbose_name=_("health issues"), + ) + + emergency_contact_name = models.CharField( + max_length=255, + verbose_name=_("emergency contact name"), + ) + + emergency_contact_phone = models.CharField( + max_length=32, + verbose_name=_("emergency contact phone"), + ) + + payment_method = models.ForeignKey( + NoteSpecial, + on_delete=models.PROTECT, + null=True, # null = no credit, paid with note + related_name="+", + verbose_name=_("payment method"), + ) + + soge_credit = models.BooleanField( + verbose_name=_("Credit from Société générale"), + ) + + ml_events_registation = models.BooleanField( + verbose_name=_("Register on the mailing list to stay informed of the events of the campus (1 mail/week)"), + ) + + ml_sport_registation = models.BooleanField( + verbose_name=_("Register on the mailing list to stay informed of the sport events of the campus (1 mail/week)"), + ) + + ml_art_registation = models.BooleanField( + verbose_name=_("Register on the mailing list to stay informed of the art events of the campus (1 mail/week)"), + ) + + team = models.ForeignKey( + BusTeam, + on_delete=models.PROTECT, + related_name="users", + null=True, + blank=True, + verbose_name=_("team"), + ) + + bus_choice1 = models.ForeignKey( + Bus, + on_delete=models.PROTECT, + related_name="+", + verbose_name=_("bus choice 1"), + ) + + bus_choice2 = models.ForeignKey( + Bus, + on_delete=models.PROTECT, + related_name="+", + null=True, + blank=True, + verbose_name=_("bus choice 2"), + ) + + bus_choice3 = models.ForeignKey( + Bus, + on_delete=models.PROTECT, + related_name="+", + null=True, + blank=True, + verbose_name=_("bus choice 3"), + ) + + asked_roles = models.ManyToManyField( + WEIRole, + related_name="+", + verbose_name=_("asked roles"), + ) + + def __str__(self): + return str(self.user) + + class Meta: + unique_together = ('user', 'wei',) + verbose_name = _("WEI User") + verbose_name_plural = _("WEI Users") diff --git a/apps/wei/urls.py b/apps/wei/urls.py new file mode 100644 index 00000000..afd1c566 --- /dev/null +++ b/apps/wei/urls.py @@ -0,0 +1,9 @@ +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from django.urls import path + + +app_name = 'wei' +urlpatterns = [ +] diff --git a/apps/wei/views.py b/apps/wei/views.py new file mode 100644 index 00000000..8b245779 --- /dev/null +++ b/apps/wei/views.py @@ -0,0 +1,5 @@ +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from django.shortcuts import render + diff --git a/note_kfet/settings/base.py b/note_kfet/settings/base.py index 283f8e56..5ed8b1d8 100644 --- a/note_kfet/settings/base.py +++ b/note_kfet/settings/base.py @@ -62,6 +62,7 @@ INSTALLED_APPS = [ 'permission', 'registration', 'treasury', + 'wei', ] LOGIN_REDIRECT_URL = '/note/transfer/' diff --git a/note_kfet/urls.py b/note_kfet/urls.py index 90d44a07..4311c0b5 100644 --- a/note_kfet/urls.py +++ b/note_kfet/urls.py @@ -19,6 +19,7 @@ urlpatterns = [ path('registration/', include('registration.urls')), path('activity/', include('activity.urls')), path('treasury/', include('treasury.urls')), + path('wei/', include('wei.urls')), # Include Django Contrib and Core routers path('i18n/', include('django.conf.urls.i18n')), From a186ccbb269faa6b1b4a17c95e8c34228e73cfd0 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 11 Apr 2020 17:42:08 +0200 Subject: [PATCH 02/55] Update WEI models --- apps/member/models.py | 1 + apps/wei/models.py | 181 +++++++++++++++++++----------------------- 2 files changed, 84 insertions(+), 98 deletions(-) diff --git a/apps/member/models.py b/apps/member/models.py index 3a022434..35406d54 100644 --- a/apps/member/models.py +++ b/apps/member/models.py @@ -171,6 +171,7 @@ class Club(models.Model): self.membership_start.month, self.membership_start.day) self.membership_end = datetime.date(self.membership_end.year + 1, self.membership_end.month, self.membership_end.day) + self._force_save = True self.save(force_update=True) def save(self, force_insert=False, force_update=False, using=None, diff --git a/apps/wei/models.py b/apps/wei/models.py index 910f8309..45834256 100644 --- a/apps/wei/models.py +++ b/apps/wei/models.py @@ -1,58 +1,28 @@ # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later +import json from django.contrib.auth.models import User from django.db import models from django.utils.translation import gettext_lazy as _ +from member.models import Role, Club, Membership from note.models import NoteSpecial -class WEI(models.Model): +class WEIClub(Club): """ - Store WEI information """ - name = models.CharField( - max_length=255, - unique=True, - verbose_name=_("name"), - ) - year = models.PositiveIntegerField( unique=True, verbose_name=_("year"), ) - start = models.DateField( - verbose_name=_("start date"), - ) - - end = models.DateField( - verbose_name=_("end date"), - ) - - price_paid = models.PositiveIntegerField( - verbose_name=_("Price for paid students"), - ) - - price_unpaid = models.PositiveIntegerField( - verbose_name=_("Price for unpaid students"), - ) - - email = models.EmailField( - verbose_name=_("contact email"), - ) - - registrations_open = models.BooleanField( - verbose_name=_("registrations open"), - ) - - def __str__(self): - return self.name - - class Meta: - verbose_name = _("WEI") - verbose_name_plural = _("WEI") + def update_membership_dates(self): + """ + We can't join the WEI next years. + """ + return class Bus(models.Model): @@ -60,7 +30,7 @@ class Bus(models.Model): The best bus for the best WEI """ wei = models.ForeignKey( - WEI, + WEIClub, on_delete=models.PROTECT, related_name="buses", verbose_name=_("WEI"), @@ -107,17 +77,19 @@ class BusTeam(models.Model): verbose_name_plural = _("Bus teams") -class WEIRole(models.Model): +class WEIRole(Role): """ A Role for the WEI can be bus chief, team chief, free electron, ... """ - name = models.CharField( - max_length=255, - unique=True, + bus = models.ForeignKey( + Bus, + on_delete=models.CASCADE, + related_name="roles", + verbose_name=_("bus"), ) -class WEIUser(models.Model): +class WEIRegistration(models.Model): """ Store personal data that can be useful for the WEI. """ @@ -130,16 +102,22 @@ class WEIUser(models.Model): ) wei = models.ForeignKey( - WEI, + WEIClub, on_delete=models.PROTECT, related_name="users", verbose_name=_("WEI"), ) - role = models.ForeignKey( - WEIRole, + payment_method = models.ForeignKey( + NoteSpecial, on_delete=models.PROTECT, - verbose_name=_("role"), + null=True, # null = no credit, paid with note + related_name="+", + verbose_name=_("payment method"), + ) + + soge_credit = models.BooleanField( + verbose_name=_("Credit from Société générale"), ) birth_date = models.DateField( @@ -170,69 +148,45 @@ class WEIUser(models.Model): verbose_name=_("emergency contact phone"), ) - payment_method = models.ForeignKey( - NoteSpecial, - on_delete=models.PROTECT, - null=True, # null = no credit, paid with note - related_name="+", - verbose_name=_("payment method"), - ) - - soge_credit = models.BooleanField( - verbose_name=_("Credit from Société générale"), - ) - - ml_events_registation = models.BooleanField( + ml_events_registration = models.BooleanField( verbose_name=_("Register on the mailing list to stay informed of the events of the campus (1 mail/week)"), ) - ml_sport_registation = models.BooleanField( + ml_sport_registration = models.BooleanField( verbose_name=_("Register on the mailing list to stay informed of the sport events of the campus (1 mail/week)"), ) - ml_art_registation = models.BooleanField( + ml_art_registration = models.BooleanField( verbose_name=_("Register on the mailing list to stay informed of the art events of the campus (1 mail/week)"), ) - team = models.ForeignKey( - BusTeam, - on_delete=models.PROTECT, - related_name="users", - null=True, - blank=True, - verbose_name=_("team"), + information_json = models.TextField( + verbose_name=_("registration information"), + help_text=_("Information about the registration (buses for old members, survey fot the new members), " + "encoded in JSON"), ) - bus_choice1 = models.ForeignKey( - Bus, - on_delete=models.PROTECT, - related_name="+", - verbose_name=_("bus choice 1"), - ) + @property + def information(self): + """ + The information about the registration (the survey for the new members, the bus for the older members, ...) + are stored in a dictionary that can evolve following the years. The dictionary is stored as a JSON string. + """ + return json.loads(self.information_json) - bus_choice2 = models.ForeignKey( - Bus, - on_delete=models.PROTECT, - related_name="+", - null=True, - blank=True, - verbose_name=_("bus choice 2"), - ) + @information.setter + def information(self, information): + """ + Store information as a JSON string + """ + self.information_json = json.dumps(information) - bus_choice3 = models.ForeignKey( - Bus, - on_delete=models.PROTECT, - related_name="+", - null=True, - blank=True, - verbose_name=_("bus choice 3"), - ) - - asked_roles = models.ManyToManyField( - WEIRole, - related_name="+", - verbose_name=_("asked roles"), - ) + @property + def is_1A(self): + """ + We assume that a user is a new member if it not fully registered yet. + """ + return not self.user.profile.registration_valid def __str__(self): return str(self.user) @@ -241,3 +195,34 @@ class WEIUser(models.Model): unique_together = ('user', 'wei',) verbose_name = _("WEI User") verbose_name_plural = _("WEI Users") + + +class WEIMembership(Membership): + bus = models.ForeignKey( + Bus, + on_delete=models.PROTECT, + null=True, + default=None, + verbose_name=_("bus"), + ) + + team = models.ForeignKey( + BusTeam, + on_delete=models.PROTECT, + related_name="memberships", + null=True, + blank=True, + default=None, + verbose_name=_("team"), + ) + + registration = models.OneToOneField( + WEIRegistration, + on_delete=models.PROTECT, + null=True, + blank=True, + default=None, + related_name="membership", + verbose_name=_("WEI registration"), + ) + From 31d2224b8ffe64d07d60fb5ab34781237b068d11 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 11 Apr 2020 23:02:12 +0200 Subject: [PATCH 03/55] List wei --- apps/api/urls.py | 2 + apps/wei/api/__init__.py | 0 apps/wei/api/serializers.py | 17 ++++++++ apps/wei/api/urls.py | 11 +++++ apps/wei/api/views.py | 20 +++++++++ apps/wei/fixtures/initial.json | 76 +++++++++++++++++++++++++++++++++ apps/wei/models.py | 13 +++++- apps/wei/tables.py | 25 +++++++++++ apps/wei/urls.py | 3 ++ apps/wei/views.py | 14 +++++- static/js/base.js | 2 +- templates/base.html | 5 ++- templates/member/club_info.html | 4 +- templates/wei/weiclub_list.html | 70 ++++++++++++++++++++++++++++++ tox.ini | 2 +- 15 files changed, 257 insertions(+), 7 deletions(-) create mode 100644 apps/wei/api/__init__.py create mode 100644 apps/wei/api/serializers.py create mode 100644 apps/wei/api/urls.py create mode 100644 apps/wei/api/views.py create mode 100644 apps/wei/fixtures/initial.json create mode 100644 apps/wei/tables.py create mode 100644 templates/wei/weiclub_list.html diff --git a/apps/api/urls.py b/apps/api/urls.py index 67fdba30..03d6bd68 100644 --- a/apps/api/urls.py +++ b/apps/api/urls.py @@ -15,6 +15,7 @@ from note.api.urls import register_note_urls from treasury.api.urls import register_treasury_urls from logs.api.urls import register_logs_urls from permission.api.urls import register_permission_urls +from wei.api.urls import register_wei_urls class UserSerializer(serializers.ModelSerializer): @@ -78,6 +79,7 @@ register_note_urls(router, 'note') register_treasury_urls(router, 'treasury') register_permission_urls(router, 'permission') register_logs_urls(router, 'logs') +register_wei_urls(router, 'wei') app_name = 'api' diff --git a/apps/wei/api/__init__.py b/apps/wei/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/wei/api/serializers.py b/apps/wei/api/serializers.py new file mode 100644 index 00000000..5b91e2b1 --- /dev/null +++ b/apps/wei/api/serializers.py @@ -0,0 +1,17 @@ +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from rest_framework import serializers + +from ..models import WEIClub + + +class WEIClubSerializer(serializers.ModelSerializer): + """ + REST API Serializer for Clubs. + The djangorestframework plugin will analyse the model `WEIClub` and parse all fields in the API. + """ + + class Meta: + model = WEIClub + fields = '__all__' diff --git a/apps/wei/api/urls.py b/apps/wei/api/urls.py new file mode 100644 index 00000000..f5836b8c --- /dev/null +++ b/apps/wei/api/urls.py @@ -0,0 +1,11 @@ +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from .views import WEIClubViewSet + + +def register_wei_urls(router, path): + """ + Configure router for Member REST API. + """ + router.register(path + '/club', WEIClubViewSet) diff --git a/apps/wei/api/views.py b/apps/wei/api/views.py new file mode 100644 index 00000000..c93512e2 --- /dev/null +++ b/apps/wei/api/views.py @@ -0,0 +1,20 @@ +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from rest_framework.filters import SearchFilter +from api.viewsets import ReadProtectedModelViewSet + +from .serializers import WEIClubSerializer +from ..models import WEIClub + + +class WEIClubViewSet(ReadProtectedModelViewSet): + """ + REST API View set. + The djangorestframework plugin will get all `WEIClub` objects, serialize it to JSON with the given serializer, + then render it on /api/wei/club/ + """ + queryset = WEIClub.objects.all() + serializer_class = WEIClubSerializer + filter_backends = [SearchFilter] + search_fields = ['$name', ] diff --git a/apps/wei/fixtures/initial.json b/apps/wei/fixtures/initial.json new file mode 100644 index 00000000..db371985 --- /dev/null +++ b/apps/wei/fixtures/initial.json @@ -0,0 +1,76 @@ +[ + { + "model": "member.club", + "pk": 3, + "fields": { + "name": "A[WEI] from Home", + "email": "gc.wei@example.com", + "parent_club": 2, + "require_memberships": true, + "membership_fee_paid": 16500, + "membership_fee_unpaid": 9500, + "membership_duration": 30, + "membership_start": "2019-09-01", + "membership_end": "2019-09-16" + } + }, + { + "model": "wei.weiclub", + "pk": 1, + "fields": { + "club_ptr_id": 3, + "year": 2019, + "date_start": "2019-09-14", + "date_end": "2019-09-16" + } + }, + { + "model": "note.note", + "pk": 7, + "fields": { + "polymorphic_ctype": [ + "note", + "noteclub" + ], + "balance": 0, + "last_negative": null, + "is_active": true, + "display_image": "pic/default.png", + "created_at": "2020-02-20T20:16:14.753Z" + } + }, + { + "model": "note.noteclub", + "pk": 7, + "fields": { + "club": 3 + } + }, + { + "model": "note.alias", + "pk": 7, + "fields": { + "name": "A[WEI] from Home", + "normalized_name": "aweifromhome", + "note": 7 + } + }, + { + "model": "note.alias", + "pk": 8, + "fields": { + "name": "WEI 2019", + "normalized_name": "wei2019", + "note": 7 + } + }, + { + "model": "note.alias", + "pk": 9, + "fields": { + "name": "WEI", + "normalized_name": "wei", + "note": 7 + } + } +] diff --git a/apps/wei/models.py b/apps/wei/models.py index 45834256..08ebbc8c 100644 --- a/apps/wei/models.py +++ b/apps/wei/models.py @@ -1,23 +1,32 @@ # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later + import json from django.contrib.auth.models import User from django.db import models from django.utils.translation import gettext_lazy as _ - from member.models import Role, Club, Membership from note.models import NoteSpecial class WEIClub(Club): """ + The WEI is a club. Register to the WEI is equivalent than be member of the club. """ year = models.PositiveIntegerField( unique=True, verbose_name=_("year"), ) + date_start = models.DateField( + verbose_name=_("date start"), + ) + + date_end = models.DateField( + verbose_name=_("date end"), + ) + def update_membership_dates(self): """ We can't join the WEI next years. @@ -84,6 +93,8 @@ class WEIRole(Role): bus = models.ForeignKey( Bus, on_delete=models.CASCADE, + null=True, + default=None, related_name="roles", verbose_name=_("bus"), ) diff --git a/apps/wei/tables.py b/apps/wei/tables.py new file mode 100644 index 00000000..3c1bd3af --- /dev/null +++ b/apps/wei/tables.py @@ -0,0 +1,25 @@ +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +import django_tables2 as tables +from django.urls import reverse_lazy + +from wei.models import WEIClub + + +class WEITable(tables.Table): + """ + List all clubs. + """ + class Meta: + attrs = { + 'class': 'table table-condensed table-striped table-hover' + } + model = WEIClub + template_name = 'django_tables2/bootstrap4.html' + fields = ('name', 'year', 'date_start', 'date_end',) + row_attrs = { + 'class': 'table-row', + 'id': lambda record: "row-" + str(record.pk), + 'data-href': lambda record: reverse_lazy('member:club_detail', args=(record.pk,)) + } diff --git a/apps/wei/urls.py b/apps/wei/urls.py index afd1c566..9c898139 100644 --- a/apps/wei/urls.py +++ b/apps/wei/urls.py @@ -3,7 +3,10 @@ from django.urls import path +from .views import WEIListView + app_name = 'wei' urlpatterns = [ + path('list/', WEIListView.as_view(), name="wei_list"), ] diff --git a/apps/wei/views.py b/apps/wei/views.py index 8b245779..7d7fc01f 100644 --- a/apps/wei/views.py +++ b/apps/wei/views.py @@ -1,5 +1,17 @@ # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later -from django.shortcuts import render +from django.contrib.auth.mixins import LoginRequiredMixin +from django_tables2 import SingleTableView +from permission.views import ProtectQuerysetMixin +from wei.models import WEIClub +from .tables import WEITable + + +class WEIListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView): + """ + List existing WEI + """ + model = WEIClub + table_class = WEITable diff --git a/static/js/base.js b/static/js/base.js index bb73b328..f9f040c1 100644 --- a/static/js/base.js +++ b/static/js/base.js @@ -312,7 +312,7 @@ function in_validate(id, validated) { else invalidity_reason = null; - $("#validate_" + id).html("⟳ ..."); + $("#validate_" + id).html(""); // Perform a PATCH request to the API in order to update the transaction // If the user has insuffisent rights, an error message will appear diff --git a/templates/base.html b/templates/base.html index 3c2c637f..810927f9 100644 --- a/templates/base.html +++ b/templates/base.html @@ -108,9 +108,12 @@ SPDX-License-Identifier: GPL-3.0-or-later {% endif %} {% if "treasury.invoice"|not_empty_model_change_list %} {% endif %} + From 8c0ccdfdd02d9f4d83f3a0efd5bd483ab4b25d40 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Mon, 27 Apr 2020 20:25:02 +0200 Subject: [PATCH 54/55] Don't change the password of another member (+ minor fixes on WEI app) --- apps/member/models.py | 2 +- apps/wei/models.py | 2 ++ apps/wei/views.py | 2 +- templates/member/profile_info.html | 14 ++++++++------ templates/registration/future_profile_detail.html | 7 ------- templates/wei/weimembership_form.html | 4 +++- 6 files changed, 15 insertions(+), 16 deletions(-) diff --git a/apps/member/models.py b/apps/member/models.py index e1c829a4..17b8f044 100644 --- a/apps/member/models.py +++ b/apps/member/models.py @@ -119,7 +119,7 @@ class Profile(models.Model): def soge(self): if "treasury" in settings.INSTALLED_APPS: from treasury.models import SogeCredit - return SogeCredit.objects.filter(user=self.user, credit_transaction=None).exists() + return SogeCredit.objects.filter(user=self.user, credit_transaction__isnull=False).exists() return False class Meta: diff --git a/apps/wei/models.py b/apps/wei/models.py index 3b41fdd0..9cee0d61 100644 --- a/apps/wei/models.py +++ b/apps/wei/models.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later import json +from datetime import date from django.conf import settings from django.contrib.auth.models import User @@ -17,6 +18,7 @@ class WEIClub(Club): """ year = models.PositiveIntegerField( unique=True, + default=date.today().year, verbose_name=_("year"), ) diff --git a/apps/wei/views.py b/apps/wei/views.py index b35e27be..597a44d4 100644 --- a/apps/wei/views.py +++ b/apps/wei/views.py @@ -780,7 +780,7 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Crea ret = super().form_valid(form) membership.refresh_from_db() - membership.roles.add(WEIRole.objects.get("Adhérent WEI")) + membership.roles.add(WEIRole.objects.get(name="Adhérent WEI")) return ret diff --git a/templates/member/profile_info.html b/templates/member/profile_info.html index 74856355..7be10ba1 100644 --- a/templates/member/profile_info.html +++ b/templates/member/profile_info.html @@ -17,12 +17,14 @@
{% trans 'username'|capfirst %}
{{ object.username }}
-
{% trans 'password'|capfirst %}
-
- - {% trans 'Change password' %} - -
+ {% if object.pk == user.pk %} +
{% trans 'password'|capfirst %}
+
+ + {% trans 'Change password' %} + +
+ {% endif %}
{% trans 'section'|capfirst %}
{{ object.profile.section }}
diff --git a/templates/registration/future_profile_detail.html b/templates/registration/future_profile_detail.html index 8c78fb8d..1d2d08c7 100644 --- a/templates/registration/future_profile_detail.html +++ b/templates/registration/future_profile_detail.html @@ -31,13 +31,6 @@ {% endif %} -
{% trans 'password'|capfirst %}
-
- - {% trans 'Change password' %} - -
-
{% trans 'section'|capfirst %}
{{ object.profile.section }}
diff --git a/templates/wei/weimembership_form.html b/templates/wei/weimembership_form.html index 0cde7fe6..995b6c1a 100644 --- a/templates/wei/weimembership_form.html +++ b/templates/wei/weimembership_form.html @@ -119,8 +119,10 @@ From 968a44fee665e504ec076d49766c30aa80d067f1 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Sat, 2 May 2020 02:49:27 +0200 Subject: [PATCH 55/55] For a random reason, django filters templates are missing. Wtf? --- templates/django_filters/rest_framework/crispy_form.html | 5 +++++ templates/django_filters/rest_framework/form.html | 6 ++++++ templates/django_filters/widgets/multiwidget.html | 1 + 3 files changed, 12 insertions(+) create mode 100644 templates/django_filters/rest_framework/crispy_form.html create mode 100644 templates/django_filters/rest_framework/form.html create mode 100644 templates/django_filters/widgets/multiwidget.html diff --git a/templates/django_filters/rest_framework/crispy_form.html b/templates/django_filters/rest_framework/crispy_form.html new file mode 100644 index 00000000..171767c0 --- /dev/null +++ b/templates/django_filters/rest_framework/crispy_form.html @@ -0,0 +1,5 @@ +{% load crispy_forms_tags %} +{% load i18n %} + +

{% trans "Field filters" %}

+{% crispy filter.form %} diff --git a/templates/django_filters/rest_framework/form.html b/templates/django_filters/rest_framework/form.html new file mode 100644 index 00000000..b116e353 --- /dev/null +++ b/templates/django_filters/rest_framework/form.html @@ -0,0 +1,6 @@ +{% load i18n %} +

{% trans "Field filters" %}

+ + {{ filter.form.as_p }} + + diff --git a/templates/django_filters/widgets/multiwidget.html b/templates/django_filters/widgets/multiwidget.html new file mode 100644 index 00000000..089ddb20 --- /dev/null +++ b/templates/django_filters/widgets/multiwidget.html @@ -0,0 +1 @@ +{% for widget in widget.subwidgets %}{% include widget.template_name %}{% if forloop.first %}-{% endif %}{% endfor %}