From e92d50593cfcb5ac4040561486255558fb215f00 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Mon, 3 Aug 2020 19:04:36 +0200 Subject: [PATCH 1/9] New models --- .../0002_kfetgroup_kfetpermission.py | 55 +++++++++++++++++++ kfet/auth/migrations/0003_existing_groups.py | 21 +++++++ kfet/auth/models.py | 29 ++++++++++ 3 files changed, 105 insertions(+) create mode 100644 kfet/auth/migrations/0002_kfetgroup_kfetpermission.py create mode 100644 kfet/auth/migrations/0003_existing_groups.py diff --git a/kfet/auth/migrations/0002_kfetgroup_kfetpermission.py b/kfet/auth/migrations/0002_kfetgroup_kfetpermission.py new file mode 100644 index 00000000..48c680d0 --- /dev/null +++ b/kfet/auth/migrations/0002_kfetgroup_kfetpermission.py @@ -0,0 +1,55 @@ +# Generated by Django 2.2.8 on 2020-01-08 21:03 + +import django.contrib.auth.models +import django.db.models.deletion +import django.db.models.manager +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("auth", "0011_update_proxy_permissions"), + ("kfetauth", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="KFetGroup", + fields=[ + ( + "group_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="auth.Group", + ), + ), + ], + options={ + "verbose_name": "Groupe K-Fêt", + "verbose_name_plural": "Groupes K-Fêt", + }, + bases=("auth.group",), + managers=[("objects", django.contrib.auth.models.GroupManager())], + ), + migrations.CreateModel( + name="KFetPermission", + fields=[], + options={ + "verbose_name": "Permission K-Fêt", + "verbose_name_plural": "Permissions K-Fêt", + "proxy": True, + "indexes": [], + "constraints": [], + }, + bases=("auth.permission",), + managers=[ + ("kfet", django.db.models.manager.Manager()), + ("objects", django.contrib.auth.models.PermissionManager()), + ], + ), + ] diff --git a/kfet/auth/migrations/0003_existing_groups.py b/kfet/auth/migrations/0003_existing_groups.py new file mode 100644 index 00000000..855a9c19 --- /dev/null +++ b/kfet/auth/migrations/0003_existing_groups.py @@ -0,0 +1,21 @@ +# Generated by Django 2.2.8 on 2020-01-08 21:04 + +from django.db import migrations + + +def existing_groups(apps, schema_editor): + Group = apps.get_model("auth", "Group") + KFetGroup = apps.get_model("kfetauth", "KFetGroup") + + for group in Group.objects.filter(name__icontains="K-Fêt"): + kf_group = KFetGroup(group_ptr=group, pk=group.pk, name=group.name) + kf_group.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ("kfetauth", "0002_kfetgroup_kfetpermission"), + ] + + operations = [migrations.RunPython(existing_groups)] diff --git a/kfet/auth/models.py b/kfet/auth/models.py index 73a70c22..4dc70d1e 100644 --- a/kfet/auth/models.py +++ b/kfet/auth/models.py @@ -1,5 +1,7 @@ +from django.contrib.auth.models import Group, Permission from django.db import models from django.utils.crypto import get_random_string +from django.utils.translation import ugettext_lazy as _ class GenericTeamTokenManager(models.Manager): @@ -14,3 +16,30 @@ class GenericTeamToken(models.Model): token = models.CharField(max_length=50, unique=True) objects = GenericTeamTokenManager() + + +class KFetGroup(Group): + # Même si on n'ajoute que des méthodes, on fait un héritage complet pour + # mieux distinguer les groupes K-Fêt via l'ORM (i.e. faire `KFetGroup.objects.all`) + + class Meta: + verbose_name = _("Groupe K-Fêt") + verbose_name_plural = _("Groupes K-Fêt") + + +class KFetPermissionManager(models.Manager): + def get_queryset(self): + return ( + super() + .get_queryset() + .filter(content_type__app_label__in=["kfet", "kfetauth"]) + ) + + +class KFetPermission(Permission): + kfet = KFetPermissionManager() + + class Meta: + proxy = True + verbose_name = _("Permission K-Fêt") + verbose_name_plural = _("Permissions K-Fêt") From 6f5fa19fc34027f0b445000b53c2ceb2c65cf129 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Mon, 3 Aug 2020 19:06:02 +0200 Subject: [PATCH 2/9] M2M form mixin --- shared/forms.py | 50 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 shared/forms.py diff --git a/shared/forms.py b/shared/forms.py new file mode 100644 index 00000000..97094e29 --- /dev/null +++ b/shared/forms.py @@ -0,0 +1,50 @@ +from django.forms.models import ModelForm + + +class ProtectedModelForm(ModelForm): + """ + Extension de `ModelForm` + + Quand on save un champ `ManyToMany` dans un `ModelForm`, la méthode appelée + est .set(), qui écrase l'intégralité du contenu. + Le problème survient quand le `field` a un queryset restreint, et qu'on ne + veut pas toucher aux choix qui ne sont pas dans ce queryset... + C'est le but de ce mixin. + + Attributs : + - `protected_fields` : champs qu'on souhaite protéger. + """ + + protected_fields = [] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + for field_name in self.protected_fields: + if field_name not in self.fields: + raise ValueError("Le champ %s n'existe pas !" % field_name) + + def _get_protected_elts(self, field_name): + """ + Renvoie tous les éléments de `instance.` qui ne sont pas + dans `self..queryset` (et sont donc à conserver). + + NB : on "désordonne" tous les querysets via `.order_by()` car Django + ne peut pas effectuer une union de QS ordonnés. + """ + if self.instance.pk: + previous = getattr(self.instance, field_name).order_by() + selectable = self.fields[field_name].queryset.order_by() + return previous.difference(selectable) + else: + # Nouvelle instance, rien à protéger. + return self.fields[field_name].queryset.none() + + def clean(self): + cleaned_data = super().clean() + for field_name in self.protected_fields: + selected_elts = cleaned_data[field_name].order_by() + protected_elts = self._get_protected_elts(field_name) + cleaned_data[field_name] = selected_elts.union(protected_elts) + + return cleaned_data From 91852bd4a0169faa0a797886727c81e777df6228 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Mon, 3 Aug 2020 19:06:22 +0200 Subject: [PATCH 3/9] Template fixes --- kfet/templates/kfet/account_group_form.html | 31 +-------------------- kfet/templates/kfet/form_field_snippet.html | 2 +- 2 files changed, 2 insertions(+), 31 deletions(-) diff --git a/kfet/templates/kfet/account_group_form.html b/kfet/templates/kfet/account_group_form.html index b309d838..7d1410dd 100644 --- a/kfet/templates/kfet/account_group_form.html +++ b/kfet/templates/kfet/account_group_form.html @@ -7,35 +7,6 @@ {% block main %} -
- {% csrf_token %} -
- -
-
- K-Fêt - {{ form.name|add_class:"form-control" }} -
- {% if form.name.errors %} - {{ form.name.errors }} - {% endif %} - {% if form.name.help_text %} - {{ form.name.help_text }} - {% endif %} -
-
- {% include "kfet/form_field_snippet.html" with field=form.permissions %} - {% include "kfet/form_submit_snippet.html" with value="Enregistrer" %} -
- - +{% include "kfet/form_full_snippet.html" with authz=perms.kfet.manage_perms submit_text="Enregistrer" %} {% endblock %} diff --git a/kfet/templates/kfet/form_field_snippet.html b/kfet/templates/kfet/form_field_snippet.html index a2aa087f..b7eddb5d 100644 --- a/kfet/templates/kfet/form_field_snippet.html +++ b/kfet/templates/kfet/form_field_snippet.html @@ -5,7 +5,7 @@
{% if field|widget_type == "checkboxselectmultiple" %}
    - {% for choice in form.permissions %} + {% for choice in field %}