From 94dafa5c5ba50fc8f97988451b546088947efb8a Mon Sep 17 00:00:00 2001 From: catvayor Date: Tue, 24 Dec 2024 12:03:12 +0100 Subject: [PATCH 01/10] feat(kfet): adding 'membre kfet' To comply with Status voted on 2024-nov-13 --- gestioncof/admin.py | 12 +++++++++- gestioncof/forms.py | 1 + ...profile_is_kfet_alter_cofprofile_is_cof.py | 23 +++++++++++++++++++ gestioncof/models.py | 4 +++- gestioncof/views.py | 1 + kfet/forms.py | 2 +- kfet/models.py | 4 ++++ kfet/static/kfet/js/account.js | 3 ++- kfet/templates/kfet/left_account.html | 4 +++- kfet/views.py | 3 +++ 10 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 gestioncof/migrations/0021_cofprofile_is_kfet_alter_cofprofile_is_cof.py diff --git a/gestioncof/admin.py b/gestioncof/admin.py index bb90cf98..d6165ec7 100644 --- a/gestioncof/admin.py +++ b/gestioncof/admin.py @@ -136,9 +136,18 @@ class UserProfileAdmin(UserAdmin): except CofProfile.DoesNotExist: return False - is_cof.short_description = "Membre du COF" + is_cof.short_description = "Membre COF" is_cof.boolean = True + def is_kfet(self, obj): + try: + return obj.profile.is_kfet + except CofProfile.DoesNotExist: + return False + + is_kfet.short_description = "Membre K-Fêt" + is_kfet.boolean = True + list_display = UserAdmin.list_display + ( "profile_phone", "profile_occupation", @@ -146,6 +155,7 @@ class UserProfileAdmin(UserAdmin): "profile_mailing_bda", "profile_mailing_bda_revente", "is_cof", + "is_kfet", "is_buro", ) list_display_links = ("username", "email", "first_name", "last_name") diff --git a/gestioncof/forms.py b/gestioncof/forms.py index 1d482e7f..583050c9 100644 --- a/gestioncof/forms.py +++ b/gestioncof/forms.py @@ -284,6 +284,7 @@ class RegistrationProfileForm(forms.ModelForm): "occupation", "departement", "is_cof", + "is_kfet", "type_cotiz", "mailing_cof", "mailing_bda", diff --git a/gestioncof/migrations/0021_cofprofile_is_kfet_alter_cofprofile_is_cof.py b/gestioncof/migrations/0021_cofprofile_is_kfet_alter_cofprofile_is_cof.py new file mode 100644 index 00000000..51bba7ff --- /dev/null +++ b/gestioncof/migrations/0021_cofprofile_is_kfet_alter_cofprofile_is_cof.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.16 on 2024-12-24 10:09 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("gestioncof", "0020_merge_20241218_2240"), + ] + + operations = [ + migrations.AddField( + model_name="cofprofile", + name="is_kfet", + field=models.BooleanField(default=False, verbose_name="Membre K-Fêt"), + ), + migrations.AlterField( + model_name="cofprofile", + name="is_cof", + field=models.BooleanField(default=False, verbose_name="Membre COF"), + ), + ] diff --git a/gestioncof/models.py b/gestioncof/models.py index c6d32efc..cf171d9f 100644 --- a/gestioncof/models.py +++ b/gestioncof/models.py @@ -49,7 +49,9 @@ class CofProfile(models.Model): login_clipper = models.CharField( "Login clipper", max_length=32, blank=True, unique=True, null=True ) - is_cof = models.BooleanField("Membre du COF", default=False) + is_cof = models.BooleanField("Membre COF", default=False) + is_kfet = models.BooleanField("Membre K-Fêt", default=False) + # TODO: 2 date_adhesion ? ou juste la première, ou juste la seconde (cas membre kf -> membre COF) date_adhesion = models.DateField("Date d'adhésion", blank=True, null=True) phone = models.CharField("Téléphone", max_length=20, blank=True) occupation = models.CharField( diff --git a/gestioncof/views.py b/gestioncof/views.py index e24de1d9..27ea36fc 100644 --- a/gestioncof/views.py +++ b/gestioncof/views.py @@ -86,6 +86,7 @@ class ResetComptes(BuroRequiredMixin, TemplateView): nb_adherents = CofProfile.objects.filter(is_cof=True).count() CofProfile.objects.update( is_cof=False, + is_kfet=False, date_adhesion=None, mailing_cof=False, mailing_bda=False, diff --git a/kfet/forms.py b/kfet/forms.py index e1db462c..ca39d68d 100644 --- a/kfet/forms.py +++ b/kfet/forms.py @@ -195,7 +195,7 @@ class CofForm(forms.ModelForm): class Meta: model = CofProfile - fields = ["login_clipper", "is_cof", "departement"] + fields = ["login_clipper", "is_cof", "is_kfet", "departement"] class UserForm(forms.ModelForm): diff --git a/kfet/models.py b/kfet/models.py index f30e093e..b9fe6eab 100644 --- a/kfet/models.py +++ b/kfet/models.py @@ -119,6 +119,10 @@ class Account(models.Model): def is_cof(self): return self.cofprofile.is_cof + @property + def is_kfet(self): + return self.cofprofile.is_kfet + # Propriétés supplémentaires @property def balance_ukf(self): diff --git a/kfet/static/kfet/js/account.js b/kfet/static/kfet/js/account.js index 52aa09b8..b7c3fdaf 100644 --- a/kfet/static/kfet/js/account.js +++ b/kfet/static/kfet/js/account.js @@ -5,6 +5,7 @@ var Account = Backbone.Model.extend({ 'name': '', 'email': '', 'is_cof': '', + 'is_kfet': '', 'promo': '', 'balance': '', 'is_frozen': false, @@ -69,7 +70,7 @@ var AccountView = Backbone.View.extend({ }, get_is_cof: function () { - return this.model.get("is_cof") ? 'COF' : 'Non-COF'; + return this.model.get("is_cof") ? 'Membre COF' : (this.model.get("is_kfet") ? 'Membre K-Fêt' : 'Non-COF'); }, get_balance: function () { diff --git a/kfet/templates/kfet/left_account.html b/kfet/templates/kfet/left_account.html index eee35ca2..ec5d3d20 100644 --- a/kfet/templates/kfet/left_account.html +++ b/kfet/templates/kfet/left_account.html @@ -43,7 +43,9 @@
  • {% if account.is_cof %} Adhérent COF + data-placement="right">Membre COF + {% elif account.is_kfet %} + Membre K-Fêt {% else %} Non-COF {% endif %} diff --git a/kfet/views.py b/kfet/views.py index 1f27fa78..c70d427f 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -287,6 +287,7 @@ def account_form_set_readonly_fields(user_form, cof_form): cof_form.fields["login_clipper"].widget.attrs["readonly"] = True cof_form.fields["departement"].widget.attrs["readonly"] = True cof_form.fields["is_cof"].widget.attrs["disabled"] = True + cof_form.fields["is_kfet"].widget.attrs["disabled"] = True def get_account_create_forms( @@ -363,6 +364,7 @@ def get_account_create_forms( # mais on laisse le username en écriture cof_form.fields["login_clipper"].widget.attrs["readonly"] = True cof_form.fields["is_cof"].widget.attrs["disabled"] = True + cof_form.fields["is_kfet"].widget.attrs["disabled"] = True if request: account_form = AccountNoTriForm(request.POST) @@ -988,6 +990,7 @@ def account_read_json(request, trigramme): "name": account.name, "email": account.email, "is_cof": account.is_cof, + "is_kfet": account.is_kfet, "promo": account.promo, "balance": account.balance, "is_frozen": account.is_frozen, From 48bbd0f0dbccf66fd571c36dca13ac138a2309b6 Mon Sep 17 00:00:00 2001 From: catvayor Date: Mon, 30 Dec 2024 15:47:19 +0100 Subject: [PATCH 02/10] feat(kfet): separate subscription date --- ...ename_cofprofile_date_adhesion_and_more.py | 32 +++++++++++++++++++ gestioncof/models.py | 6 ++-- gestioncof/tests/test_views.py | 2 +- gestioncof/views.py | 18 ++++++++--- 4 files changed, 51 insertions(+), 7 deletions(-) create mode 100644 gestioncof/migrations/0022_rename_cofprofile_date_adhesion_and_more.py diff --git a/gestioncof/migrations/0022_rename_cofprofile_date_adhesion_and_more.py b/gestioncof/migrations/0022_rename_cofprofile_date_adhesion_and_more.py new file mode 100644 index 00000000..bf041773 --- /dev/null +++ b/gestioncof/migrations/0022_rename_cofprofile_date_adhesion_and_more.py @@ -0,0 +1,32 @@ +# Generated by Django 4.2.16 on 2024-12-30 14:19 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("gestioncof", "0021_cofprofile_is_kfet_alter_cofprofile_is_cof"), + ] + + operations = [ + migrations.AlterField( + model_name="cofprofile", + name="date_adhesion", + field=models.DateField( + blank=True, null=True, verbose_name="Date d'adhésion COF" + ), + ), + migrations.RenameField( + model_name="cofprofile", + old_name="date_adhesion", + new_name="date_adhesion_cof", + ), + migrations.AddField( + model_name="cofprofile", + name="date_adhesion_kfet", + field=models.DateField( + blank=True, null=True, verbose_name="Date d'adhésion K-Fêt" + ), + ), + ] diff --git a/gestioncof/models.py b/gestioncof/models.py index cf171d9f..223727fc 100644 --- a/gestioncof/models.py +++ b/gestioncof/models.py @@ -51,8 +51,10 @@ class CofProfile(models.Model): ) is_cof = models.BooleanField("Membre COF", default=False) is_kfet = models.BooleanField("Membre K-Fêt", default=False) - # TODO: 2 date_adhesion ? ou juste la première, ou juste la seconde (cas membre kf -> membre COF) - date_adhesion = models.DateField("Date d'adhésion", blank=True, null=True) + date_adhesion_cof = models.DateField("Date d'adhésion COF", blank=True, null=True) + date_adhesion_kfet = models.DateField( + "Date d'adhésion K-Fêt", blank=True, null=True + ) phone = models.CharField("Téléphone", max_length=20, blank=True) occupation = models.CharField( _("Occupation"), diff --git a/gestioncof/tests/test_views.py b/gestioncof/tests/test_views.py index a47e387c..5be08821 100644 --- a/gestioncof/tests/test_views.py +++ b/gestioncof/tests/test_views.py @@ -486,7 +486,7 @@ class ExportMembersViewTests(CSVResponseMixin, ViewTestCaseMixin, TestCase): u1.last_name = "last" u1.email = "user@mail.net" u1.save() - u1.profile.date_adhesion = date(2023, 5, 22) + u1.profile.date_adhesion_cof = date(2023, 5, 22) u1.profile.phone = "0123456789" u1.profile.departement = "Dept" u1.profile.save() diff --git a/gestioncof/views.py b/gestioncof/views.py index 27ea36fc..e2a3e0bd 100644 --- a/gestioncof/views.py +++ b/gestioncof/views.py @@ -15,6 +15,7 @@ from django.contrib.auth.views import ( ) from django.contrib.sites.models import Site from django.core.mail import send_mail +from django.db.models import Q from django.http import Http404, HttpResponse, HttpResponseForbidden from django.shortcuts import get_object_or_404, redirect, render from django.template import loader @@ -87,7 +88,8 @@ class ResetComptes(BuroRequiredMixin, TemplateView): CofProfile.objects.update( is_cof=False, is_kfet=False, - date_adhesion=None, + date_adhesion_cof=None, + date_adhesion_kfet=None, mailing_cof=False, mailing_bda=False, mailing_bda_revente=False, @@ -566,6 +568,7 @@ def registration(request): member = user_form.save() profile, _ = CofProfile.objects.get_or_create(user=member) was_cof = profile.is_cof + was_kfet = profile.is_kfet # Maintenant on remplit le formulaire de profil profile_form = RegistrationProfileForm(request_dict, instance=profile) if ( @@ -577,7 +580,12 @@ def registration(request): profile = profile_form.save() if profile.is_cof and not was_cof: notify_new_member(request, member) - profile.date_adhesion = date.today() + profile.date_adhesion_cof = date.today() + profile.save() + + if profile.is_kfet and not was_kfet: + notify_new_member(request, member) + profile.date_adhesion_kfet = date.today() profile.save() # Enregistrement des inscriptions aux événements @@ -708,7 +716,7 @@ def export_members(request): response["Content-Disposition"] = "attachment; filename=membres_cof.csv" writer = csv.writer(response) - for profile in CofProfile.objects.filter(is_cof=True).all(): + for profile in CofProfile.objects.filter(Q(is_cof=True) | Q(is_kfet=True)).all(): user = profile.user bits = [ user.id, @@ -719,8 +727,10 @@ def export_members(request): profile.phone, profile.occupation, profile.departement, + "COF" if profile.is_cof else "K-Fêt", profile.type_cotiz, - profile.date_adhesion, + profile.date_adhesion_cof, + profile.date_adhesion_kfet, ] writer.writerow([str(bit) for bit in bits]) From 705bc4395ca7565d9e7686b99316578a12e985ad Mon Sep 17 00:00:00 2001 From: catvayor Date: Mon, 6 Jan 2025 18:38:27 +0100 Subject: [PATCH 03/10] feat(kfet): block selling of reserved item --- kfet/forms.py | 2 ++ kfet/migrations/0081_article_no_exte.py | 20 ++++++++++++++++++++ kfet/models.py | 1 + kfet/templates/kfet/article.html | 4 ++++ kfet/templates/kfet/article_read.html | 3 ++- kfet/templates/kfet/kpsul.html | 5 +++-- kfet/views.py | 19 +++++++++++++++++++ 7 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 kfet/migrations/0081_article_no_exte.py diff --git a/kfet/forms.py b/kfet/forms.py index ca39d68d..22b74952 100644 --- a/kfet/forms.py +++ b/kfet/forms.py @@ -350,6 +350,7 @@ class ArticleForm(forms.ModelForm): fields = [ "name", "is_sold", + "no_exte", "hidden", "price", "stock", @@ -364,6 +365,7 @@ class ArticleRestrictForm(ArticleForm): fields = [ "name", "is_sold", + "no_exte", "hidden", "price", "category", diff --git a/kfet/migrations/0081_article_no_exte.py b/kfet/migrations/0081_article_no_exte.py new file mode 100644 index 00000000..7cf515b4 --- /dev/null +++ b/kfet/migrations/0081_article_no_exte.py @@ -0,0 +1,20 @@ +# Generated by Django 4.2.16 on 2025-01-06 16:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("kfet", "0080_accountnegative_last_rappel"), + ] + + operations = [ + migrations.AddField( + model_name="article", + name="no_exte", + field=models.BooleanField( + default=False, verbose_name="Réservé au adhérents" + ), + ), + ] diff --git a/kfet/models.py b/kfet/models.py index b9fe6eab..d44896bd 100644 --- a/kfet/models.py +++ b/kfet/models.py @@ -498,6 +498,7 @@ class ArticleCategory(models.Model): class Article(models.Model): name = models.CharField("nom", max_length=45) is_sold = models.BooleanField("en vente", default=True) + no_exte = models.BooleanField("Réservé au adhérents", default=False) hidden = models.BooleanField( "caché", default=False, diff --git a/kfet/templates/kfet/article.html b/kfet/templates/kfet/article.html index 6b48ddbb..fd51b8ef 100644 --- a/kfet/templates/kfet/article.html +++ b/kfet/templates/kfet/article.html @@ -40,6 +40,7 @@ Prix Stock En vente + Reservé aux adhérents Affiché Dernier inventaire @@ -63,6 +64,7 @@ {{ article.price }}€ {{ article.stock }} {{ article.is_sold | yesno:"En vente,Non vendu"}} + {{ article.no_exte | yesno:"Réservé,Non réservé"}} {{ article.hidden | yesno:"Caché,Affiché" }} {% with last_inventory=article.inventory.0 %} @@ -88,6 +90,7 @@ Prix Stock En vente + Reservé aux adhérents Affiché Dernier inventaire @@ -111,6 +114,7 @@ {{ article.price }}€ {{ article.stock }} {{ article.is_sold | yesno:"En vente,Non vendu"}} + {{ article.no_exte | yesno:"Réservé,Non réservé"}} {{ article.hidden | yesno:"Caché,Affiché" }} {% with last_inventory=article.inventory.0 %} diff --git a/kfet/templates/kfet/article_read.html b/kfet/templates/kfet/article_read.html index 67673c7c..4d2cb436 100644 --- a/kfet/templates/kfet/article_read.html +++ b/kfet/templates/kfet/article_read.html @@ -39,6 +39,7 @@
  • Stock: {{ article.stock }}
  • En vente: {{ article.is_sold|yesno|title }}
  • Affiché: {{ article.hidden|yesno|title }}
  • +
  • Réservé aux adhérents: {{ article.no_exte|yesno|title }}
  • @@ -160,4 +161,4 @@ }); -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/kfet/templates/kfet/kpsul.html b/kfet/templates/kfet/kpsul.html index ad8ee240..1c8bc8c5 100644 --- a/kfet/templates/kfet/kpsul.html +++ b/kfet/templates/kfet/kpsul.html @@ -397,8 +397,8 @@ $(document).ready(function() { // ----- var articles_container = $('#articles_data tbody'); - var article_category_default_html = ''; - var article_default_html = ''; + var article_category_default_html = ''; + var article_default_html = ''; function addArticle(article) { var article_html = $(article_default_html); @@ -411,6 +411,7 @@ $(document).ready(function() { article_html.addClass('low-stock'); } article_html.find('.price').text(amountToUKF(article['price'], false, false)+' UKF'); + article_html.find('.no_exte').text(article['no_exte'] ? "Réservé aux adhérents" : ""); var category_html = articles_container .find('#data-category-'+article['category_id']); if (category_html.length == 0) { diff --git a/kfet/views.py b/kfet/views.py index c70d427f..d94cf6b4 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -1167,6 +1167,24 @@ def kpsul_perform_operations(request): if is_addcost and operation.article.category.has_addcost: operation.addcost_amount /= cof_grant_divisor operation.amount = operation.amount / cof_grant_divisor + if ( + not on_acc.is_cof + and not on_acc.is_kfet + and not on_acc.is_cash + and operation.article.no_exte + ): + data["errors"].append( + { + "code": "reserved", + "message": ( + "L'article " + + operation.article.name + + " est réservé aux adhérents du COF, or " + + on_acc.trigramme + + " ne l'est pas" + ), + } + ) to_articles_stocks[operation.article] -= operation.article_nb else: if on_acc.is_cash: @@ -1718,6 +1736,7 @@ def kpsul_articles_data(request): "id", "name", "price", + "no_exte", "stock", "category_id", "category__name", From 0721cd4e459d0adb123b813af9430726b6a223ac Mon Sep 17 00:00:00 2001 From: catvayor Date: Sat, 18 Jan 2025 11:18:57 +0100 Subject: [PATCH 04/10] feat(kfet): selling reserved on liq require passwd --- .../0082_alter_operation_options.py | 34 +++++++++++++++++++ kfet/models.py | 4 +++ kfet/views.py | 34 +++++++++---------- 3 files changed, 54 insertions(+), 18 deletions(-) create mode 100644 kfet/migrations/0082_alter_operation_options.py diff --git a/kfet/migrations/0082_alter_operation_options.py b/kfet/migrations/0082_alter_operation_options.py new file mode 100644 index 00000000..0af51ccb --- /dev/null +++ b/kfet/migrations/0082_alter_operation_options.py @@ -0,0 +1,34 @@ +# Generated by Django 4.2.16 on 2025-01-18 10:01 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("kfet", "0081_article_no_exte"), + ] + + operations = [ + migrations.AlterModelOptions( + name="operation", + options={ + "permissions": ( + ("perform_deposit", "Effectuer une charge"), + ( + "perform_negative_operations", + "Enregistrer des commandes en négatif", + ), + ( + "perform_liq_reserved", + "Effectuer une opération réservé aux adhérents sur LIQ", + ), + ("cancel_old_operations", "Annuler des commandes non récentes"), + ( + "perform_commented_operations", + "Enregistrer des commandes avec commentaires", + ), + ) + }, + ), + ] diff --git a/kfet/models.py b/kfet/models.py index d44896bd..9485b5e5 100644 --- a/kfet/models.py +++ b/kfet/models.py @@ -759,6 +759,10 @@ class Operation(models.Model): permissions = ( ("perform_deposit", "Effectuer une charge"), ("perform_negative_operations", "Enregistrer des commandes en négatif"), + ( + "perform_liq_reserved", + "Effectuer une opération réservé aux adhérents sur LIQ", + ), ("cancel_old_operations", "Annuler des commandes non récentes"), ( "perform_commented_operations", diff --git a/kfet/views.py b/kfet/views.py index d94cf6b4..81498334 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -1167,24 +1167,22 @@ def kpsul_perform_operations(request): if is_addcost and operation.article.category.has_addcost: operation.addcost_amount /= cof_grant_divisor operation.amount = operation.amount / cof_grant_divisor - if ( - not on_acc.is_cof - and not on_acc.is_kfet - and not on_acc.is_cash - and operation.article.no_exte - ): - data["errors"].append( - { - "code": "reserved", - "message": ( - "L'article " - + operation.article.name - + " est réservé aux adhérents du COF, or " - + on_acc.trigramme - + " ne l'est pas" - ), - } - ) + if not on_acc.is_cof and not on_acc.is_kfet and operation.article.no_exte: + if on_acc.is_cash: + required_perms.add("kfet.perform_liq_reserved") + else: + data["errors"].append( + { + "code": "reserved", + "message": ( + "L'article " + + operation.article.name + + " est réservé aux adhérents du COF, or " + + on_acc.trigramme + + " ne l'est pas" + ), + } + ) to_articles_stocks[operation.article] -= operation.article_nb else: if on_acc.is_cash: From 4747c773eafd39743038851ceea78e0599323dee Mon Sep 17 00:00:00 2001 From: catvayor Date: Tue, 21 Jan 2025 12:20:25 +0100 Subject: [PATCH 05/10] feat(gestioncof): add flag is_chef, which can manage kf members --- gestioncof/admin.py | 10 + gestioncof/decorators.py | 42 +++ gestioncof/forms.py | 13 + .../migrations/0023_cofprofile_is_chef.py | 18 ++ gestioncof/models.py | 1 + gestioncof/templates/gestioncof/home.html | 13 +- .../gestioncof/registration_kf_form.html | 21 ++ .../gestioncof/registration_kf_post.html | 8 + gestioncof/views.py | 279 +++++++++++------- 9 files changed, 294 insertions(+), 111 deletions(-) create mode 100644 gestioncof/migrations/0023_cofprofile_is_chef.py create mode 100644 gestioncof/templates/gestioncof/registration_kf_form.html create mode 100644 gestioncof/templates/gestioncof/registration_kf_post.html diff --git a/gestioncof/admin.py b/gestioncof/admin.py index d6165ec7..3576efda 100644 --- a/gestioncof/admin.py +++ b/gestioncof/admin.py @@ -130,6 +130,15 @@ class UserProfileAdmin(UserAdmin): is_buro.short_description = "Membre du Buro" is_buro.boolean = True + def is_chef(self, obj): + try: + return obj.profile.is_chef + except CofProfile.DoesNotExist: + return False + + is_chef.short_description = "Chef K-Fêt" + is_chef.boolean = True + def is_cof(self, obj): try: return obj.profile.is_cof @@ -157,6 +166,7 @@ class UserProfileAdmin(UserAdmin): "is_cof", "is_kfet", "is_buro", + "is_chef", ) list_display_links = ("username", "email", "first_name", "last_name") list_filter = UserAdmin.list_filter + ( diff --git a/gestioncof/decorators.py b/gestioncof/decorators.py index e227ed45..4eb5ea4c 100644 --- a/gestioncof/decorators.py +++ b/gestioncof/decorators.py @@ -58,6 +58,33 @@ def buro_required(view_func): return login_required(_wrapped_view) +def chef_required(view_func): + """Décorateur qui vérifie que l'utilisateur est connecté et membre du burô ou chef K-fêt. + + - Si l'utilisateur n'est pas connecté, il est redirigé vers la page de + connexion + - Si l'utilisateur est connecté mais pas membre du burô ou chef, il obtient une + page d'erreur 403 Forbidden + """ + + def is_chef(user): + try: + return user.profile.is_chef or user.profile.is_buro + except AttributeError: + return False + + @wraps(view_func) + def _wrapped_view(request, *args, **kwargs): + if is_chef(request.user): + return view_func(request, *args, **kwargs) + + return render( + request, "buro-denied.html", status=403 + ) # TODO: reservé au burô ou au chef + + return login_required(_wrapped_view) + + class CofRequiredMixin(PermissionRequiredMixin): def has_permission(self): if not self.request.user.is_authenticated: @@ -79,3 +106,18 @@ class BuroRequiredMixin(PermissionRequiredMixin): "L'utilisateur %s n'a pas de profil !", self.request.user.username ) return False + + +class ChefRequiredMixin(PermissionRequiredMixin): + def has_permission(self): + if not self.request.user.is_authenticated: + return False + try: + return ( + self.request.user.profile.is_chef or self.request.user.profile.is_buro + ) + except AttributeError: + logger.error( + "L'utilisateur %s n'a pas de profil !", self.request.user.username + ) + return False diff --git a/gestioncof/forms.py b/gestioncof/forms.py index 583050c9..d7453e10 100644 --- a/gestioncof/forms.py +++ b/gestioncof/forms.py @@ -294,6 +294,19 @@ class RegistrationProfileForm(forms.ModelForm): ] +class RegistrationKFProfileForm(forms.ModelForm): + class Meta: + model = CofProfile + fields = [ + "login_clipper", + "phone", + "occupation", + "departement", + "is_kfet", + "comments", + ] + + STATUS_CHOICES = ( ("no", "Non"), ("wait", "Oui mais attente paiement"), diff --git a/gestioncof/migrations/0023_cofprofile_is_chef.py b/gestioncof/migrations/0023_cofprofile_is_chef.py new file mode 100644 index 00000000..9264a74c --- /dev/null +++ b/gestioncof/migrations/0023_cofprofile_is_chef.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.16 on 2025-01-21 10:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("gestioncof", "0022_rename_cofprofile_date_adhesion_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="cofprofile", + name="is_chef", + field=models.BooleanField(default=False, verbose_name="Chef K-Fêt"), + ), + ] diff --git a/gestioncof/models.py b/gestioncof/models.py index 223727fc..d7a7b5a6 100644 --- a/gestioncof/models.py +++ b/gestioncof/models.py @@ -79,6 +79,7 @@ class CofProfile(models.Model): ) comments = models.TextField("Commentaires visibles par l'utilisateur", blank=True) is_buro = models.BooleanField("Membre du Burô", default=False) + is_chef = models.BooleanField("Chef K-Fêt", default=False) petits_cours_accept = models.BooleanField( "Recevoir des petits cours", default=False ) diff --git a/gestioncof/templates/gestioncof/home.html b/gestioncof/templates/gestioncof/home.html index 04d19a3a..1f754023 100644 --- a/gestioncof/templates/gestioncof/home.html +++ b/gestioncof/templates/gestioncof/home.html @@ -8,7 +8,7 @@
    -
    +
    {% if open_surveys %}

    Sondages en cours

    @@ -71,6 +71,17 @@
    + {% if user.profile.is_chef and not user.profile.is_buro %} +
    +

    Administration

    +
    + +
    +
    + {% endif %} {% if user.profile.is_buro %}

    Administration

    diff --git a/gestioncof/templates/gestioncof/registration_kf_form.html b/gestioncof/templates/gestioncof/registration_kf_form.html new file mode 100644 index 00000000..2b0711f6 --- /dev/null +++ b/gestioncof/templates/gestioncof/registration_kf_form.html @@ -0,0 +1,21 @@ +{% load bootstrap %} + + {% if login_clipper %} +

    Inscription associée au compte clipper {{ login_clipper }}

    + {% elif member %} +

    Inscription du compte GestioCOF existant {{ member.username }}

    + {% else %} +

    Inscription d'un nouveau compte (extérieur ?)

    + {% endif %} +
    + {% csrf_token %} + + {{ user_form | bootstrap }} + {{ profile_form | bootstrap }} +
    +
    + {% if login_clipper or member %} + + {% endif %} + +
    diff --git a/gestioncof/templates/gestioncof/registration_kf_post.html b/gestioncof/templates/gestioncof/registration_kf_post.html new file mode 100644 index 00000000..b5690d70 --- /dev/null +++ b/gestioncof/templates/gestioncof/registration_kf_post.html @@ -0,0 +1,8 @@ +{% extends "base_title.html" %} + +{% block realcontent %} +

    Inscription d'un nouveau membre

    +
    + {% include "gestioncof/registration_kf_form.html" %} +
    +{% endblock %} diff --git a/gestioncof/views.py b/gestioncof/views.py index e2a3e0bd..80ed2cfd 100644 --- a/gestioncof/views.py +++ b/gestioncof/views.py @@ -28,7 +28,13 @@ from icalendar import Calendar, Event as Vevent from bda.models import Spectacle, Tirage from gestioncof.autocomplete import cof_autocomplete -from gestioncof.decorators import BuroRequiredMixin, buro_required, cof_required +from gestioncof.decorators import ( + BuroRequiredMixin, + ChefRequiredMixin, + buro_required, + chef_required, + cof_required, +) from gestioncof.forms import ( CalendarForm, ClubsForm, @@ -39,6 +45,7 @@ from gestioncof.forms import ( GestioncofConfigForm, PhoneForm, ProfileForm, + RegistrationKFProfileForm, RegistrationPassUserForm, RegistrationProfileForm, RegistrationUserForm, @@ -429,8 +436,9 @@ def registration_set_ro_fields(user_form, profile_form): profile_form.fields["login_clipper"].widget.attrs["readonly"] = True -@buro_required +@chef_required def registration_form2(request, login_clipper=None, username=None, fullname=None): + is_buro = request.user.profile.is_buro events = Event.objects.filter(old=False).all() member = None if login_clipper: @@ -452,53 +460,79 @@ def registration_form2(request, login_clipper=None, username=None, fullname=None user_form.fields["first_name"].initial = bits[0] if len(bits) > 1: user_form.fields["last_name"].initial = " ".join(bits[1:]) - # profile - profile_form = RegistrationProfileForm( - initial={"login_clipper": login_clipper} - ) + if is_buro: + # profile + profile_form = RegistrationProfileForm( + initial={"login_clipper": login_clipper} + ) + # events & clubs + event_formset = EventFormset(events=events, prefix="events") + clubs_form = ClubsForm() + else: + profile_form = RegistrationKFProfileForm( + initial={"login_clipper": login_clipper} + ) + registration_set_ro_fields(user_form, profile_form) - # events & clubs - event_formset = EventFormset(events=events, prefix="events") - clubs_form = ClubsForm() if username: member = get_object_or_404(User, username=username) (profile, _) = CofProfile.objects.get_or_create(user=member) # already existing, prefill user_form = RegistrationUserForm(instance=member) - profile_form = RegistrationProfileForm(instance=profile) + if is_buro: + profile_form = RegistrationProfileForm(instance=profile) + # events + current_registrations = [] + for event in events: + try: + current_registrations.append( + EventRegistration.objects.get(user=member, event=event) + ) + except EventRegistration.DoesNotExist: + current_registrations.append(None) + event_formset = EventFormset( + events=events, + prefix="events", + current_registrations=current_registrations, + ) + # Clubs + clubs_form = ClubsForm(initial={"clubs": member.clubs.all()}) + else: + profile_form = RegistrationKFProfileForm(instance=profile) registration_set_ro_fields(user_form, profile_form) - # events - current_registrations = [] - for event in events: - try: - current_registrations.append( - EventRegistration.objects.get(user=member, event=event) - ) - except EventRegistration.DoesNotExist: - current_registrations.append(None) - event_formset = EventFormset( - events=events, prefix="events", current_registrations=current_registrations - ) - # Clubs - clubs_form = ClubsForm(initial={"clubs": member.clubs.all()}) elif not login_clipper: # new user user_form = RegistrationPassUserForm() - profile_form = RegistrationProfileForm() - event_formset = EventFormset(events=events, prefix="events") - clubs_form = ClubsForm() - return render( - request, - "gestioncof/registration_form.html", - { - "member": member, - "login_clipper": login_clipper, - "user_form": user_form, - "profile_form": profile_form, - "event_formset": event_formset, - "clubs_form": clubs_form, - }, - ) + if is_buro: + profile_form = RegistrationProfileForm() + event_formset = EventFormset(events=events, prefix="events") + clubs_form = ClubsForm() + else: + profile_form = RegistrationKFProfileForm() + if is_buro: + return render( + request, + "gestioncof/registration_form.html", + { + "member": member, + "login_clipper": login_clipper, + "user_form": user_form, + "profile_form": profile_form, + "event_formset": event_formset, + "clubs_form": clubs_form, + }, + ) + else: + return render( + request, + "gestioncof/registration_kf_form.html", + { + "member": member, + "login_clipper": login_clipper, + "user_form": user_form, + "profile_form": profile_form, + }, + ) def notify_new_member(request, member: User): @@ -529,8 +563,9 @@ def notify_new_member(request, member: User): ) -@buro_required +@chef_required def registration(request): + is_buro = request.user.profile.is_buro if request.POST: request_dict = request.POST.copy() member = None @@ -544,10 +579,15 @@ def registration(request): user_form = RegistrationPassUserForm(request_dict) else: user_form = RegistrationUserForm(request_dict) - profile_form = RegistrationProfileForm(request_dict) - clubs_form = ClubsForm(request_dict) - events = Event.objects.filter(old=False).all() - event_formset = EventFormset(events=events, data=request_dict, prefix="events") + if is_buro: + profile_form = RegistrationProfileForm(request_dict) + clubs_form = ClubsForm(request_dict) + events = Event.objects.filter(old=False).all() + event_formset = EventFormset( + events=events, data=request_dict, prefix="events" + ) + else: + profile_form = RegistrationKFProfileForm(request_dict) if "user_exists" in request_dict and request_dict["user_exists"]: username = request_dict["username"] try: @@ -570,67 +610,74 @@ def registration(request): was_cof = profile.is_cof was_kfet = profile.is_kfet # Maintenant on remplit le formulaire de profil - profile_form = RegistrationProfileForm(request_dict, instance=profile) - if ( - profile_form.is_valid() - and event_formset.is_valid() - and clubs_form.is_valid() + if is_buro: + profile_form = RegistrationProfileForm(request_dict, instance=profile) + else: + profile_form = RegistrationKFProfileForm(request_dict, instance=profile) + if profile_form.is_valid() and ( + not is_buro or (event_formset.is_valid() and clubs_form.is_valid()) ): # Enregistrement du profil profile = profile_form.save() - if profile.is_cof and not was_cof: - notify_new_member(request, member) - profile.date_adhesion_cof = date.today() - profile.save() + if is_buro: + if profile.is_cof and not was_cof: + notify_new_member(request, member) + profile.date_adhesion_cof = date.today() + profile.save() if profile.is_kfet and not was_kfet: notify_new_member(request, member) profile.date_adhesion_kfet = date.today() profile.save() - # Enregistrement des inscriptions aux événements - for form in event_formset: - if "status" not in form.cleaned_data: - form.cleaned_data["status"] = "no" - if form.cleaned_data["status"] == "no": - try: - current_registration = EventRegistration.objects.get( - user=member, event=form.event - ) - current_registration.delete() - except EventRegistration.DoesNotExist: - pass - continue - all_choices = get_event_form_choices(form.event, form) - ( - current_registration, - created_reg, - ) = EventRegistration.objects.get_or_create( - user=member, event=form.event - ) - update_event_form_comments(form.event, form, current_registration) - current_registration.options.set(all_choices) - current_registration.paid = form.cleaned_data["status"] == "paid" - current_registration.save() - # if form.event.title == "Mega 15" and created_reg: - # field = EventCommentField.objects.get( - # event=form.event, name="Commentaires") - # try: - # comments = EventCommentValue.objects.get( - # commentfield=field, - # registration=current_registration).content - # except EventCommentValue.DoesNotExist: - # comments = field.default - # FIXME : il faut faire quelque chose de propre ici, - # par exemple écrire un mail générique pour - # l'inscription aux événements et/ou donner la - # possibilité d'associer un mail aux événements - # send_custom_mail(...) - # Enregistrement des inscriptions aux clubs - member.clubs.clear() - for club in clubs_form.cleaned_data["clubs"]: - club.membres.add(member) - club.save() + if is_buro: + # Enregistrement des inscriptions aux événements + for form in event_formset: + if "status" not in form.cleaned_data: + form.cleaned_data["status"] = "no" + if form.cleaned_data["status"] == "no": + try: + current_registration = EventRegistration.objects.get( + user=member, event=form.event + ) + current_registration.delete() + except EventRegistration.DoesNotExist: + pass + continue + all_choices = get_event_form_choices(form.event, form) + ( + current_registration, + created_reg, + ) = EventRegistration.objects.get_or_create( + user=member, event=form.event + ) + update_event_form_comments( + form.event, form, current_registration + ) + current_registration.options.set(all_choices) + current_registration.paid = ( + form.cleaned_data["status"] == "paid" + ) + current_registration.save() + # if form.event.title == "Mega 15" and created_reg: + # field = EventCommentField.objects.get( + # event=form.event, name="Commentaires") + # try: + # comments = EventCommentValue.objects.get( + # commentfield=field, + # registration=current_registration).content + # except EventCommentValue.DoesNotExist: + # comments = field.default + # FIXME : il faut faire quelque chose de propre ici, + # par exemple écrire un mail générique pour + # l'inscription aux événements et/ou donner la + # possibilité d'associer un mail aux événements + # send_custom_mail(...) + # Enregistrement des inscriptions aux clubs + member.clubs.clear() + for club in clubs_form.cleaned_data["clubs"]: + club.membres.add(member) + club.save() # --- # Success @@ -642,23 +689,35 @@ def registration(request): member.get_full_name(), member.email ) ) - if profile.is_cof: + if is_buro and profile.is_cof: msg += "\nIl est désormais membre du COF n°{:d} !".format( member.profile.id ) messages.success(request, msg, extra_tags="safe") - return render( - request, - "gestioncof/registration_post.html", - { - "user_form": user_form, - "profile_form": profile_form, - "member": member, - "login_clipper": login_clipper, - "event_formset": event_formset, - "clubs_form": clubs_form, - }, - ) + if is_buro: + return render( + request, + "gestioncof/registration_post.html", + { + "user_form": user_form, + "profile_form": profile_form, + "member": member, + "login_clipper": login_clipper, + "event_formset": event_formset, + "clubs_form": clubs_form, + }, + ) + else: + return render( + request, + "gestioncof/registration_kf_post.html", + { + "user_form": user_form, + "profile_form": profile_form, + "member": member, + "login_clipper": login_clipper, + }, + ) else: return render(request, "registration.html") @@ -986,6 +1045,6 @@ class UserAutocompleteView(BuroRequiredMixin, Select2QuerySetView): search_fields = ("username", "first_name", "last_name") -class RegistrationAutocompleteView(BuroRequiredMixin, AutocompleteView): +class RegistrationAutocompleteView(ChefRequiredMixin, AutocompleteView): template_name = "gestioncof/search_results.html" search_composer = cof_autocomplete From 897ee5dc170bdba1c4a94dcdc2f01934273c7144 Mon Sep 17 00:00:00 2001 From: catvayor Date: Thu, 27 Feb 2025 17:20:19 +0100 Subject: [PATCH 06/10] feat(kfet/subscription): allow kf team to register subscription --- gestioncof/models.py | 46 +++++++++++++++++++++++++ gestioncof/views.py | 45 +++--------------------- kfet/forms.py | 6 ++++ kfet/templates/kfet/account_update.html | 3 +- kfet/views.py | 37 +++++++++++++++----- 5 files changed, 87 insertions(+), 50 deletions(-) diff --git a/gestioncof/models.py b/gestioncof/models.py index d7a7b5a6..30ba087a 100644 --- a/gestioncof/models.py +++ b/gestioncof/models.py @@ -1,7 +1,13 @@ +from datetime import date +from smtplib import SMTPRecipientsRefused + +from django.contrib import messages from django.contrib.auth.models import User +from django.core.mail import send_mail from django.db import models from django.db.models.signals import post_delete, post_save from django.dispatch import receiver +from django.template import loader from django.utils.translation import gettext_lazy as _ from bda.models import Spectacle @@ -94,6 +100,46 @@ class CofProfile(models.Model): def __str__(self): return self.user.username + def make_adh_cof(self, request, was_cof): + if self.is_cof and not was_cof: + notify_new_member(request, self.user) + self.date_adhesion_cof = date.today() + self.save() + + def make_adh_kfet(self, request, was_kfet): + if self.is_kfet and not was_kfet: + notify_new_member(request, self.user) + self.date_adhesion_kfet = date.today() + self.save() + + +def notify_new_member(request, member: User): + if not member.email: + messages.warning( + request, + "GestioCOF n'a pas d'adresse mail pour {}, ".format(member) + + "aucun email de bienvenue n'a été envoyé", + ) + return + + # Try to send a welcome email and report SMTP errors + try: + send_mail( + "Bienvenue au COF", + loader.render_to_string( + "gestioncof/mails/welcome.txt", context={"member": member} + ), + "cof@ens.fr", + [member.email], + ) + except SMTPRecipientsRefused: + messages.error( + request, + "Error lors de l'envoi de l'email de bienvenue à {} ({})".format( + member, member.email + ), + ) + @receiver(post_save, sender=User) def create_user_profile(sender, instance, created, **kwargs): diff --git a/gestioncof/views.py b/gestioncof/views.py index 80ed2cfd..112ce905 100644 --- a/gestioncof/views.py +++ b/gestioncof/views.py @@ -1,7 +1,6 @@ import csv import uuid -from datetime import date, timedelta -from smtplib import SMTPRecipientsRefused +from datetime import timedelta from urllib.parse import parse_qs, urlencode, urlparse, urlunparse from django.contrib import messages @@ -14,11 +13,9 @@ from django.contrib.auth.views import ( redirect_to_login, ) from django.contrib.sites.models import Site -from django.core.mail import send_mail from django.db.models import Q from django.http import Http404, HttpResponse, HttpResponseForbidden from django.shortcuts import get_object_or_404, redirect, render -from django.template import loader from django.urls import reverse_lazy from django.utils import timezone from django.utils.translation import gettext_lazy as _ @@ -535,34 +532,6 @@ def registration_form2(request, login_clipper=None, username=None, fullname=None ) -def notify_new_member(request, member: User): - if not member.email: - messages.warning( - request, - "GestioCOF n'a pas d'adresse mail pour {}, ".format(member) - + "aucun email de bienvenue n'a été envoyé", - ) - return - - # Try to send a welcome email and report SMTP errors - try: - send_mail( - "Bienvenue au COF", - loader.render_to_string( - "gestioncof/mails/welcome.txt", context={"member": member} - ), - "cof@ens.fr", - [member.email], - ) - except SMTPRecipientsRefused: - messages.error( - request, - "Error lors de l'envoi de l'email de bienvenue à {} ({})".format( - member, member.email - ), - ) - - @chef_required def registration(request): is_buro = request.user.profile.is_buro @@ -620,15 +589,11 @@ def registration(request): # Enregistrement du profil profile = profile_form.save() if is_buro: - if profile.is_cof and not was_cof: - notify_new_member(request, member) - profile.date_adhesion_cof = date.today() - profile.save() + if profile.is_cof: + profile.make_adh_cof(request, was_cof) - if profile.is_kfet and not was_kfet: - notify_new_member(request, member) - profile.date_adhesion_kfet = date.today() - profile.save() + if profile.is_kfet: + profile.make_adh_kfet(request, was_kfet) if is_buro: # Enregistrement des inscriptions aux événements diff --git a/kfet/forms.py b/kfet/forms.py index 22b74952..cf7f80d4 100644 --- a/kfet/forms.py +++ b/kfet/forms.py @@ -198,6 +198,12 @@ class CofForm(forms.ModelForm): fields = ["login_clipper", "is_cof", "is_kfet", "departement"] +class CofKFForm(forms.ModelForm): + class Meta: + model = CofProfile + fields = ["is_kfet"] + + class UserForm(forms.ModelForm): class Meta: model = User diff --git a/kfet/templates/kfet/account_update.html b/kfet/templates/kfet/account_update.html index 65965d83..7115b9e2 100644 --- a/kfet/templates/kfet/account_update.html +++ b/kfet/templates/kfet/account_update.html @@ -34,10 +34,11 @@ Modification de mes informations {% include 'kfet/form_snippet.html' with form=account_form %} {% include 'kfet/form_snippet.html' with form=frozen_form %} {% include 'kfet/form_snippet.html' with form=group_form %} + {% include 'kfet/form_snippet.html' with form=cof_form %} {% include 'kfet/form_snippet.html' with form=pwd_form %} {% include 'kfet/form_authentication_snippet.html' %} {% include 'kfet/form_submit_snippet.html' with value="Mettre à jour" %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/kfet/views.py b/kfet/views.py index 81498334..018d2421 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -66,6 +66,7 @@ from kfet.forms import ( CheckoutStatementCreateForm, CheckoutStatementUpdateForm, CofForm, + CofKFForm, ContactForm, DemandeSoireeForm, FilterHistoryForm, @@ -187,13 +188,17 @@ def account(request): positive_accounts = Account.objects.filter(balance__gte=0).exclude(trigramme="#13") negative_accounts = Account.objects.filter(balance__lt=0).exclude(trigramme="#13") - return render(request, "kfet/account.html", { - "accounts": accounts, - "positive_count": positive_accounts.count(), - "positives_sum": sum(acc.balance for acc in positive_accounts), - "negative_count": negative_accounts.count(), - "negatives_sum": sum(acc.balance for acc in negative_accounts), - }) + return render( + request, + "kfet/account.html", + { + "accounts": accounts, + "positive_count": positive_accounts.count(), + "positives_sum": sum(acc.balance for acc in positive_accounts), + "negative_count": negative_accounts.count(), + "negatives_sum": sum(acc.balance for acc in negative_accounts), + }, + ) @login_required @@ -252,6 +257,11 @@ def account_create(request): account = trigramme_form.save(data=data) account_form = AccountNoTriForm(request.POST, instance=account) account_form.save() + was_kfet = account.is_kfet + account.cofprofile.is_kfet = cof_form.cleaned_data["is_kfet"] + account.cofprofile.save() + if account.cofprofile.is_kfet: + account.cofprofile.make_adh_kfet(request, was_kfet) messages.success(request, "Compte créé : %s" % account.trigramme) account.send_creation_email() return redirect("kfet.account.create") @@ -287,7 +297,6 @@ def account_form_set_readonly_fields(user_form, cof_form): cof_form.fields["login_clipper"].widget.attrs["readonly"] = True cof_form.fields["departement"].widget.attrs["readonly"] = True cof_form.fields["is_cof"].widget.attrs["disabled"] = True - cof_form.fields["is_kfet"].widget.attrs["disabled"] = True def get_account_create_forms( @@ -364,7 +373,6 @@ def get_account_create_forms( # mais on laisse le username en écriture cof_form.fields["login_clipper"].widget.attrs["readonly"] = True cof_form.fields["is_cof"].widget.attrs["disabled"] = True - cof_form.fields["is_kfet"].widget.attrs["disabled"] = True if request: account_form = AccountNoTriForm(request.POST) @@ -434,6 +442,7 @@ def account_update(request, trigramme): account_form = AccountForm(instance=account) group_form = UserGroupForm(instance=account.user) frozen_form = AccountFrozenForm(instance=account) + cof_form = CofKFForm(instance=account.cofprofile) pwd_form = AccountPwdForm() if request.method == "POST": @@ -441,6 +450,7 @@ def account_update(request, trigramme): account_form = AccountForm(request.POST, instance=account) group_form = UserGroupForm(request.POST, instance=account.user) frozen_form = AccountFrozenForm(request.POST, instance=account) + cof_form = CofKFForm(request.POST, instance=account.cofprofile) pwd_form = AccountPwdForm(request.POST, account=account) forms = [] @@ -457,6 +467,11 @@ def account_update(request, trigramme): elif group_form.has_changed(): warnings.append("statut d'équipe") + if request.user.has_perm("kfet.change_account"): + forms.append(cof_form) + elif cof_form.has_changed(): + warnings.append("adhésion kfet") + # Il ne faut pas valider `pwd_form` si elle est inchangée if pwd_form.has_changed(): if self_update or request.user.has_perm("kfet.change_account_password"): @@ -473,8 +488,11 @@ def account_update(request, trigramme): ) else: if all(form.is_valid() for form in forms): + was_kfet = account.is_kfet for form in forms: form.save() + if account.is_kfet: + account.cofprofile.make_adh_kfet(request, was_kfet) if len(warnings): messages.warning( @@ -506,6 +524,7 @@ def account_update(request, trigramme): "frozen_form": frozen_form, "group_form": group_form, "pwd_form": pwd_form, + "cof_form": cof_form, }, ) From b13ed3c1697d66f4a0885c929d0d74821f202220 Mon Sep 17 00:00:00 2001 From: catvayor Date: Tue, 18 Mar 2025 21:39:58 +0100 Subject: [PATCH 07/10] feat(kfet/stats): statistic for kfet members --- kfet/migrations/0083_operationgroup_is_kfet.py | 18 ++++++++++++++++++ kfet/models.py | 1 + kfet/views.py | 4 +++- 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 kfet/migrations/0083_operationgroup_is_kfet.py diff --git a/kfet/migrations/0083_operationgroup_is_kfet.py b/kfet/migrations/0083_operationgroup_is_kfet.py new file mode 100644 index 00000000..f344d829 --- /dev/null +++ b/kfet/migrations/0083_operationgroup_is_kfet.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.16 on 2025-03-18 10:25 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("kfet", "0082_alter_operation_options"), + ] + + operations = [ + migrations.AddField( + model_name="operationgroup", + name="is_kfet", + field=models.BooleanField(default=False), + ), + ] diff --git a/kfet/models.py b/kfet/models.py index 9485b5e5..ec581550 100644 --- a/kfet/models.py +++ b/kfet/models.py @@ -687,6 +687,7 @@ class OperationGroup(models.Model): at = models.DateTimeField(default=timezone.now) amount = models.DecimalField(max_digits=6, decimal_places=2, default=0) is_cof = models.BooleanField(default=False) + is_kfet = models.BooleanField(default=False) # Optional comment = models.CharField(max_length=255, blank=True, default="") valid_by = models.ForeignKey( diff --git a/kfet/views.py b/kfet/views.py index 018d2421..acef7d72 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -1259,6 +1259,7 @@ def kpsul_perform_operations(request): operationgroup.valid_by = request.user.profile.account_kfet # Filling cof status for statistics operationgroup.is_cof = on_acc.is_cof + operationgroup.is_kfet = on_acc.is_kfet # Starting transaction to ensure data consistency with transaction.atomic(): @@ -1309,6 +1310,7 @@ def kpsul_perform_operations(request): "checkout__name": operationgroup.checkout.name, "at": operationgroup.at, "is_cof": operationgroup.is_cof, + "is_kfet": operationgroup.is_kfet, "comment": operationgroup.comment, "valid_by__trigramme": ( operationgroup.valid_by and operationgroup.valid_by.trigramme or None @@ -1524,7 +1526,7 @@ def cancel_operations(request): # Sort objects by pk to get deterministic responses. opegroups_pk = [opegroup.pk for opegroup in to_groups_amounts] opegroups = ( - OperationGroup.objects.values("id", "amount", "is_cof") + OperationGroup.objects.values("id", "amount", "is_cof", "is_kfet") .filter(pk__in=opegroups_pk) .order_by("pk") ) From 2eaf3542dbcfe0e9941c831b23f83868e9f3782a Mon Sep 17 00:00:00 2001 From: catvayor Date: Thu, 3 Apr 2025 16:05:23 +0200 Subject: [PATCH 08/10] feat(kfet): carte kfet --- gestioncof/decorators.py | 27 ++++++++++++++++++- gestioncof/templates/cof-denied.html | 2 +- gestioncof/templates/gestioncof/carte_kf.html | 19 +++++++++++++ gestioncof/templates/gestioncof/home.html | 3 +++ gestioncof/templates/kfet-denied.html | 5 ++++ gestioncof/urls.py | 1 + gestioncof/views.py | 7 +++++ 7 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 gestioncof/templates/gestioncof/carte_kf.html create mode 100644 gestioncof/templates/kfet-denied.html diff --git a/gestioncof/decorators.py b/gestioncof/decorators.py index 4eb5ea4c..6a9e31ac 100644 --- a/gestioncof/decorators.py +++ b/gestioncof/decorators.py @@ -9,7 +9,7 @@ logger = logging.getLogger(__name__) def cof_required(view_func): - """Décorateur qui vérifie que l'utilisateur est connecté et membre du COF. + """Décorateur qui vérifie que l'utilisateur est connecté et membre COF. - Si l'utilisteur n'est pas connecté, il est redirigé vers la page de connexion @@ -33,6 +33,31 @@ def cof_required(view_func): return login_required(_wrapped_view) +def kfet_required(view_func): + """Décorateur qui vérifie que l'utilisateur est connecté et membre K-Fêt. + + - Si l'utilisteur n'est pas connecté, il est redirigé vers la page de + connexion + - Si l'utilisateur est connecté mais pas membre K-Fêt, il obtient une + page d'erreur lui demandant de s'inscrire à la K-Fêt + """ + + def is_kfet(user): + try: + return user.profile.is_cof or user.profile.is_kfet + except AttributeError: + return False + + @wraps(view_func) + def _wrapped_view(request, *args, **kwargs): + if is_kfet(request.user): + return view_func(request, *args, **kwargs) + + return render(request, "kfet-denied.html", status=403) + + return login_required(_wrapped_view) + + def buro_required(view_func): """Décorateur qui vérifie que l'utilisateur est connecté et membre du burô. diff --git a/gestioncof/templates/cof-denied.html b/gestioncof/templates/cof-denied.html index b2a12717..12cfd4a7 100644 --- a/gestioncof/templates/cof-denied.html +++ b/gestioncof/templates/cof-denied.html @@ -1,5 +1,5 @@ {% extends "base_title.html" %} {% block realcontent %} -

    Section réservée aux membres du COF -- merci de vous inscrire au COF ou de passer au COF/nous envoyer un mail si vous êtes déjà membre :)

    +

    Section réservée aux membres COF -- merci de vous inscrire au COF ou de passer au COF/nous envoyer un mail si vous êtes déjà membre :)

    {% endblock %} diff --git a/gestioncof/templates/gestioncof/carte_kf.html b/gestioncof/templates/gestioncof/carte_kf.html new file mode 100644 index 00000000..624c234e --- /dev/null +++ b/gestioncof/templates/gestioncof/carte_kf.html @@ -0,0 +1,19 @@ +{% extends "base_title.html" %} +{% load bootstrap %} + +{% block page_size %}col-sm-8{%endblock%} + +{% block realcontent %} +

    Pass K-Fêt

    + +
    +

    Profil de {{ user.first_name }} {{ user.last_name }}

    + + {% if user.profile.is_cof %} +

    Membre COF depuis le {{ user.profile.date_adhesion_cof }}

    + {% else %} +

    Membre K-Fêt depuis le {{ user.profile.date_adhesion_kfet }}

    + {% endif %} +
    + +{% endblock %} diff --git a/gestioncof/templates/gestioncof/home.html b/gestioncof/templates/gestioncof/home.html index 1f754023..4fa77d96 100644 --- a/gestioncof/templates/gestioncof/home.html +++ b/gestioncof/templates/gestioncof/home.html @@ -50,6 +50,9 @@
      {# TODO: Since Django 1.9, we can store result with "as", allowing proper value management (if None) #}
    • Page d'accueil
    • + {% if user.profile.is_cof or user.profile.is_kfet %} +
    • Carte K-Fêt
    • + {% endif %}
    • Calendrier
    • {% if perms.kfet.is_team %}
    • K-Psul
    • diff --git a/gestioncof/templates/kfet-denied.html b/gestioncof/templates/kfet-denied.html new file mode 100644 index 00000000..6d18dbb5 --- /dev/null +++ b/gestioncof/templates/kfet-denied.html @@ -0,0 +1,5 @@ +{% extends "base_title.html" %} + +{% block realcontent %} +

      Section réservée aux membres K-Fêt -- merci de vous inscrire au COF ou de passer au COF/nous envoyer un mail si vous êtes déjà membre :)

      +{% endblock %} diff --git a/gestioncof/urls.py b/gestioncof/urls.py index 624f8e22..59930f03 100644 --- a/gestioncof/urls.py +++ b/gestioncof/urls.py @@ -99,6 +99,7 @@ urlpatterns = [ name="cof-user-autocomplete", ), path("config", views.ConfigUpdate.as_view(), name="config.edit"), + path("carte", views.carte_kf, name="profile.carte"), # ----- # Authentification # ----- diff --git a/gestioncof/views.py b/gestioncof/views.py index 112ce905..daac3b07 100644 --- a/gestioncof/views.py +++ b/gestioncof/views.py @@ -31,6 +31,7 @@ from gestioncof.decorators import ( buro_required, chef_required, cof_required, + kfet_required, ) from gestioncof.forms import ( CalendarForm, @@ -428,6 +429,12 @@ def profile(request): return render(request, "gestioncof/profile.html", context) +@kfet_required +def carte_kf(request): + user = request.user + return render(request, "gestioncof/carte_kf.html", {"user": user}) + + def registration_set_ro_fields(user_form, profile_form): user_form.fields["username"].widget.attrs["readonly"] = True profile_form.fields["login_clipper"].widget.attrs["readonly"] = True From 08adfc84048680d12cf6ef63717186533c4a8364 Mon Sep 17 00:00:00 2001 From: catvayor Date: Thu, 3 Apr 2025 16:11:45 +0200 Subject: [PATCH 09/10] feat(cof/header): k-fet status & pretty non-logged --- gestioncof/static/gestioncof/css/cof.css | 3 +++ gestioncof/templates/gestioncof/base_header.html | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/gestioncof/static/gestioncof/css/cof.css b/gestioncof/static/gestioncof/css/cof.css index f98313a6..71056afd 100644 --- a/gestioncof/static/gestioncof/css/cof.css +++ b/gestioncof/static/gestioncof/css/cof.css @@ -701,6 +701,9 @@ header a:active { .user-is-cof { color : #ADE297; } +.user-is-kfet { + color : #FF8C00; +} .user-is-not-cof { color: #EE8585; } diff --git a/gestioncof/templates/gestioncof/base_header.html b/gestioncof/templates/gestioncof/base_header.html index e5f757a7..02356ba6 100644 --- a/gestioncof/templates/gestioncof/base_header.html +++ b/gestioncof/templates/gestioncof/base_header.html @@ -10,10 +10,12 @@ {% endblock %}
      + {% if user.is_authenticated %}   |  Se déconnecter  + {% endif %}
      -

      {% if user.first_name %}{{ user.first_name }}{% else %}{{ user.username }}{% endif %}, {% if user.profile.is_cof %}au COF{% else %}non-COF{% endif %}

      +

      {%if user.is_authenticated %}{% if user.first_name %}{{ user.first_name }}{% else %}{{ user.username }}{% endif %}, {% endif %}{% if user.profile.is_cof %}au COF{% elif user.profile.is_kfet %}membre K-Fêt{% else %}non-COF{% endif %}

    From e308258e40196c9291524c7f8c9a92fb24d6edb3 Mon Sep 17 00:00:00 2001 From: catvayor Date: Fri, 28 Feb 2025 14:40:16 +0100 Subject: [PATCH 10/10] feat(gestionCOF/registration): allow self registration for kfet subscription --- gestioncof/forms.py | 17 ++++++ gestioncof/templates/gestioncof/home.html | 3 + .../gestioncof/self_registration.html | 28 ++++++++++ gestioncof/urls.py | 1 + gestioncof/views.py | 55 ++++++++++++++++++- 5 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 gestioncof/templates/gestioncof/self_registration.html diff --git a/gestioncof/forms.py b/gestioncof/forms.py index d7453e10..e4a2ae3b 100644 --- a/gestioncof/forms.py +++ b/gestioncof/forms.py @@ -458,3 +458,20 @@ class GestioncofConfigForm(ConfigForm): max_length=2048, required=False, ) + + +# ---- +# Formulaire pour les adhésions self-service +# ---- + + +class SubscribForm(forms.Form): + accept_ri = forms.BooleanField( + label="Lu et accepte le réglement intérieur de l'AEENS (COF).", required=True + ) + accept_status = forms.BooleanField( + label="Lu et accepte les status de l'AEENS (COF).", required=True + ) + accept_charte_kf = forms.BooleanField( + label="Lu et accepte la charte de la K-Fêt.", required=True + ) diff --git a/gestioncof/templates/gestioncof/home.html b/gestioncof/templates/gestioncof/home.html index 4fa77d96..b70277b9 100644 --- a/gestioncof/templates/gestioncof/home.html +++ b/gestioncof/templates/gestioncof/home.html @@ -71,6 +71,9 @@ {% if not user.profile.login_clipper %}
  • Changer mon mot de passe
  • {% endif %} + {% if not user.profile.is_cof and not user.profile.is_kfet %} +
  • Adhérer à la K-Fêt
  • + {% endif %}
    diff --git a/gestioncof/templates/gestioncof/self_registration.html b/gestioncof/templates/gestioncof/self_registration.html new file mode 100644 index 00000000..c60dce4f --- /dev/null +++ b/gestioncof/templates/gestioncof/self_registration.html @@ -0,0 +1,28 @@ +{% extends "base_title.html" %} +{% load static %} + +{% block page_size %}col-sm-8{% endblock %} + +{% load bootstrap %} + +{% block realcontent %} + + {% if member %} +

    Inscription K-Fêt du compte GestioCOF existant {{ member.username }}

    + {% else %} +

    Inscription K-Fêt d'un nouveau compte (extérieur ?)

    + {% endif %} +
    + {% csrf_token %} + + {{ user_form | bootstrap }} + {{ profile_form | bootstrap }} + {{ agreement_form | bootstrap }} +
    +
    + {% if login_clipper or member %} + + {% endif %} + +
    +{% endblock %} diff --git a/gestioncof/urls.py b/gestioncof/urls.py index 59930f03..ae418c6f 100644 --- a/gestioncof/urls.py +++ b/gestioncof/urls.py @@ -80,6 +80,7 @@ registration_patterns = [ views.RegistrationAutocompleteView.as_view(), name="cof.registration.autocomplete", ), + path("self_kf", views.self_kf_registration, name="self.kf_registration"), ] urlpatterns = [ diff --git a/gestioncof/views.py b/gestioncof/views.py index daac3b07..e3350a37 100644 --- a/gestioncof/views.py +++ b/gestioncof/views.py @@ -16,7 +16,7 @@ from django.contrib.sites.models import Site from django.db.models import Q from django.http import Http404, HttpResponse, HttpResponseForbidden from django.shortcuts import get_object_or_404, redirect, render -from django.urls import reverse_lazy +from django.urls import reverse, reverse_lazy from django.utils import timezone from django.utils.translation import gettext_lazy as _ from django.views.generic import FormView, TemplateView @@ -47,6 +47,7 @@ from gestioncof.forms import ( RegistrationPassUserForm, RegistrationProfileForm, RegistrationUserForm, + SubscribForm, SurveyForm, SurveyStatusFilterForm, UserForm, @@ -694,6 +695,58 @@ def registration(request): return render(request, "registration.html") +# TODO: without login +@login_required +def self_kf_registration(request): + member = request.user + (profile, _) = CofProfile.objects.get_or_create(user=member) + + if profile.is_kfet or profile.is_cof: + msg = "Vous êtes déjà adhérent du COF !" + messages.success(request, msg) + response = HttpResponse(content="", status=303) + response["Location"] = reverse("profile") + return response + + was_kfet = profile.is_kfet + if request.POST: + user_form = RegistrationUserForm(request.POST, instance=member) + profile_form = PhoneForm(request.POST, instance=profile) + agreement_form = SubscribForm(request.POST) + if ( + user_form.is_valid() + and profile_form.is_valid() + and agreement_form.is_valid() + ): + member = user_form.save() + profile = profile_form.save() + profile.is_kfet = True + profile.save() + profile.make_adh_kfet(request, was_kfet) + + msg = "Votre adhésion a été enregistrée avec succès." + messages.success(request, msg, extra_tags="safe") + response = HttpResponse(content="", status=303) + response["Location"] = reverse("profile") + return response + else: + user_form = RegistrationUserForm(instance=member) + profile_form = PhoneForm(instance=profile) + agreement_form = SubscribForm() + + user_form.fields["username"].widget.attrs["readonly"] = True + return render( + request, + "gestioncof/self_registration.html", + { + "user_form": user_form, + "profile_form": profile_form, + "agreement_form": agreement_form, + "member": member, + }, + ) + + # ----- # Clubs # -----