From 5502c6876a1148ac19a22fbd7c6ea6fa99edcb64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Sun, 15 Oct 2017 20:48:15 +0200 Subject: [PATCH] Clean permissions objects - Define default permissions of kfet models. - Unused default permissions are deleted. - `kfet.manage_perms` is now splitted as `kfetauth.(view|add|change)_group` permissions. --- kfet/auth/migrations/0001_initial.py | 4 + kfet/auth/migrations/0002_existing_groups.py | 1 + .../migrations/0003_update_permissions.py | 81 ++++++++ kfet/auth/models.py | 4 + kfet/auth/views.py | 2 +- kfet/migrations/0060_change_models_opts.py | 189 ++++++++++++++++++ kfet/migrations/0061_update_permissions.py | 99 +++++++++ kfet/models.py | 73 ++++++- kfet/templates/kfet/account.html | 4 +- kfet/urls.py | 7 +- 10 files changed, 453 insertions(+), 11 deletions(-) create mode 100644 kfet/auth/migrations/0003_update_permissions.py create mode 100644 kfet/migrations/0060_change_models_opts.py create mode 100644 kfet/migrations/0061_update_permissions.py diff --git a/kfet/auth/migrations/0001_initial.py b/kfet/auth/migrations/0001_initial.py index 036a30c6..eb7ef049 100644 --- a/kfet/auth/migrations/0001_initial.py +++ b/kfet/auth/migrations/0001_initial.py @@ -20,6 +20,9 @@ class Migration(migrations.Migration): ('id', models.AutoField(verbose_name='ID', auto_created=True, serialize=False, primary_key=True)), ('token', models.CharField(unique=True, max_length=50)), ], + options={ + 'default_permissions': (), + }, ), migrations.CreateModel( name='Group', @@ -27,6 +30,7 @@ class Migration(migrations.Migration): ('group_ptr', models.OneToOneField(parent_link=True, serialize=False, primary_key=True, auto_created=True, to='auth.Group')), ], options={ + 'default_permissions': ('view', 'add', 'change'), 'verbose_name': 'Groupe', 'verbose_name_plural': 'Groupes', }, diff --git a/kfet/auth/migrations/0002_existing_groups.py b/kfet/auth/migrations/0002_existing_groups.py index f816429e..7d0144f7 100644 --- a/kfet/auth/migrations/0002_existing_groups.py +++ b/kfet/auth/migrations/0002_existing_groups.py @@ -23,6 +23,7 @@ class Migration(migrations.Migration): dependencies = [ ('kfetauth', '0001_initial'), + ('auth', '0006_require_contenttypes_0002'), ] operations = [ diff --git a/kfet/auth/migrations/0003_update_permissions.py b/kfet/auth/migrations/0003_update_permissions.py new file mode 100644 index 00000000..168e21e7 --- /dev/null +++ b/kfet/auth/migrations/0003_update_permissions.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- +from django.db import migrations +from django.db.models import Q + + +def convert_manage_perms(apps, schema_editor): + """ + Use the permissions of `kfet.auth.models.Group` model, instead of using the + `manage_perms` permission, which is deleted, of `kfet.models.Account`. + + `Group` which have this last permission will get the permissions: + `kfetauth.view_group`, `kfetauth.add_group` and `kfetauth.change_group`. + """ + Group = apps.get_model('auth', 'Group') + Permission = apps.get_model('auth', 'Permission') + ContentType = apps.get_model('contenttypes', 'ContentType') + + try: + old_p = Permission.objects.get( + content_type__app_label='kfet', + content_type__model='account', + codename='manage_perms', + ) + except Permission.DoesNotExist: + return + + groups = old_p.group_set.all() + + ct_group, _ = ContentType.objects.get_or_create( + app_label='kfetauth', + model='group', + ) + + view_p, _ = Permission.objects.get_or_create( + content_type=ct_group, codename='view_group', + defaults={'name': 'Can view Groupe'}) + add_p, _ = Permission.objects.get_or_create( + content_type=ct_group, codename='add_group', + defaults={'name': 'Can add Groupe'}) + change_p, _ = Permission.objects.get_or_create( + content_type=ct_group, codename='change_group', + defaults={'name': 'Can change Group'}) + + GroupPermission = Group.permissions.through + + GroupPermission.objects.bulk_create([ + GroupPermission(permission=p, group=g) + for g in groups + for p in (view_p, add_p, change_p) + ]) + + old_p.delete() + + +def delete_unused_permissions(apps, schema_editor): + Permission = apps.get_model('auth', 'Permission') + + to_delete_q = Q(content_type__model='genericteamtoken') | Q( + content_type__model='group', + codename='delete_group', + ) + + to_delete_q &= Q(content_type__app_label='kfetauth') + + Permission.objects.filter(to_delete_q).delete() + + +class Migration(migrations.Migration): + """ + Data migration about permissions. + """ + dependencies = [ + ('kfetauth', '0002_existing_groups'), + ('auth', '0006_require_contenttypes_0002'), + ('contenttypes', '0002_remove_content_type_name'), + ] + + operations = [ + migrations.RunPython(convert_manage_perms), + migrations.RunPython(delete_unused_permissions), + ] diff --git a/kfet/auth/models.py b/kfet/auth/models.py index 0dfd26fd..c8648eda 100644 --- a/kfet/auth/models.py +++ b/kfet/auth/models.py @@ -20,6 +20,9 @@ class GenericTeamToken(models.Model): objects = GenericTeamTokenManager() + class Meta: + default_permissions = () + class Group(DjangoGroup): @@ -35,6 +38,7 @@ class Group(DjangoGroup): class Meta: verbose_name = _("Groupe") verbose_name_plural = _("Groupes") + default_permissions = ('view', 'add', 'change') KFET_CORE_APP_LABELS = ['kfet', 'kfetauth'] diff --git a/kfet/auth/views.py b/kfet/auth/views.py index 073c558f..7d7b48c5 100644 --- a/kfet/auth/views.py +++ b/kfet/auth/views.py @@ -104,7 +104,7 @@ class GenericLoginView(View): login_generic = GenericLoginView.as_view() -@permission_required('kfet.manage_perms') +@permission_required('kfetauth.view_group') def account_group(request): user_pre = Prefetch( 'user_set', diff --git a/kfet/migrations/0060_change_models_opts.py b/kfet/migrations/0060_change_models_opts.py new file mode 100644 index 00000000..65bba33c --- /dev/null +++ b/kfet/migrations/0060_change_models_opts.py @@ -0,0 +1,189 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('kfet', '0059_create_generic'), + ] + + operations = [ + migrations.AlterModelOptions( + name='account', + options={ + 'permissions': ( + ('is_team', "Membre de l'équipe"), + ('manage_addcosts', 'Gérer les majorations'), + ( + 'edit_balance_account', + "Modifier la balance d'un compte" + ), + ( + 'change_account_password', + "Modifier le mot de passe d'une personne de l'équipe" + ), + ( + 'special_add_account', + 'Créer un compte avec une balance initiale' + ), + ('can_force_close', 'Fermer manuellement la K-Fêt') + ), + 'default_permissions': ('add', 'change'), + 'verbose_name_plural': 'Comptes', + 'verbose_name': 'Compte', + }, + ), + migrations.AlterModelOptions( + name='accountnegative', + options={ + 'default_permissions': ('view', 'change',), + 'verbose_name_plural': 'Comptes en négatif', + 'verbose_name': 'Compte en négatif', + }, + ), + migrations.AlterModelOptions( + name='article', + options={ + 'default_permissions': ('add', 'change'), + 'verbose_name_plural': 'Articles', + 'verbose_name': 'Article', + }, + ), + migrations.AlterModelOptions( + name='articlecategory', + options={ + 'default_permissions': ('change',), + 'verbose_name_plural': "Catégories d'articles", + 'verbose_name': "Catégorie d'articles", + }, + ), + migrations.AlterModelOptions( + name='articlerule', + options={ + 'default_permissions': (), + }, + ), + migrations.AlterModelOptions( + name='checkout', + options={ + 'default_permissions': ('add', 'change'), + 'ordering': ['-valid_to'], + 'verbose_name_plural': 'Caisses', + 'verbose_name': 'Caisse', + }, + ), + migrations.AlterModelOptions( + name='checkoutstatement', + options={ + 'default_permissions': ('add', 'change'), + 'verbose_name_plural': 'Relevés de caisses', + 'verbose_name': 'Relevé de caisse', + }, + ), + migrations.AlterModelOptions( + name='checkouttransfer', + options={ + 'default_permissions': (), + }, + ), + migrations.AlterModelOptions( + name='inventory', + options={ + 'permissions': ( + ( + 'order_to_inventory', + "Générer un inventaire à partir d'une commande" + ), + ), + 'ordering': ['-at'], + 'verbose_name_plural': 'Inventaires', + 'default_permissions': ('add',), + 'verbose_name': 'Inventaire', + }, + ), + migrations.AlterModelOptions( + name='inventoryarticle', + options={ + 'default_permissions': (), + }, + ), + migrations.AlterModelOptions( + name='operation', + options={ + 'permissions': ( + ('perform_deposit', 'Effectuer une charge'), + ( + 'perform_negative_operations', + 'Enregistrer des commandes en négatif' + ), + ( + 'override_frozen_protection', + "Forcer le gel d'un compte" + ), + ( + 'cancel_old_operations', + 'Annuler des commandes non récentes' + ), + ( + 'perform_commented_operations', + 'Enregistrer des commandes avec commentaires' + ), + ), + 'default_permissions': (), + 'verbose_name_plural': 'Opérations', + 'verbose_name': 'Opération', + }, + ), + migrations.AlterModelOptions( + name='operationgroup', + options={ + 'default_permissions': (), + }, + ), + migrations.AlterModelOptions( + name='order', + options={ + 'default_permissions': ('add',), + 'ordering': ['-at'], + 'verbose_name_plural': 'Commandes', + 'verbose_name': 'Commande', + }, + ), + migrations.AlterModelOptions( + name='orderarticle', + options={ + 'default_permissions': (), + }, + ), + migrations.AlterModelOptions( + name='supplier', + options={ + 'default_permissions': ('change',), + 'verbose_name_plural': 'Fournisseurs', + 'verbose_name': 'Fournisseur', + }, + ), + migrations.AlterModelOptions( + name='supplierarticle', + options={ + 'default_permissions': (), + }, + ), + migrations.AlterModelOptions( + name='transfer', + options={ + 'default_permissions': ('add',), + 'verbose_name_plural': 'Transferts', + 'verbose_name': 'Transfert', + }, + ), + migrations.AlterModelOptions( + name='transfergroup', + options={ + 'default_permissions': (), + }, + ), + ] diff --git a/kfet/migrations/0061_update_permissions.py b/kfet/migrations/0061_update_permissions.py new file mode 100644 index 00000000..2e30700e --- /dev/null +++ b/kfet/migrations/0061_update_permissions.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations +from django.db.models import Q + + +def update_permissions(apps, schema_editor): + Permission = apps.get_model('auth', 'Permission') + ContentType = apps.get_model('contenttypes', 'ContentType') + Account = apps.get_model('kfet', 'Account') + + # If `kfet.is_team` permission exists, rename it. + + Permission.objects.filter( + content_type__app_label='kfet', + content_type__model='account', + codename='is_team', + ).update(name="Membre de l'équipe") + + # If `kfet.view_negs` permission exists, move it as + # `kfet.view_accountnegative`. + + try: + view_negs_p = Permission.objects.get( + content_type__app_label='kfet', + content_type__model='accountnegative', + codename='view_negs', + ) + except Permission.DoesNotExist: + pass + else: + # Avoid failure due to unicity constraint if migrations were partially + # applied. + # Because `view_negs` still exists here, it should be safe to consider + # that nothing uses `view_accountnegative` so that it can be deleted. + Permission.objects.filter( + content_type__app_label='kfet', + content_type__model='accountnegative', + codename='view_accountnegative', + ).delete() + + view_negs_p.codename = 'view_accountnegative' + view_negs_p.name = 'Can view Compte en négatif' + view_negs_p.save() + + # Delete unused permissions. + + to_delete = { + 'account': ['delete_account'], + 'accountnegative': [ + 'add_accountnegative', 'delete_accountnegative', 'view_negs'], + 'article': ['delete_article'], + 'articlecategory': ['add_articlecategory', 'delete_articlecategory'], + 'articlerule': '__all__', + 'checkout': ['delete_checkout'], + 'checkoutstatement': ['delete_checkoutstatement'], + 'checkouttransfer': '__all__', + 'inventory': ['change_inventory', 'delete_inventory'], + 'inventoryarticle': '__all__', + 'operation': ['add_operation', 'change_operation', 'delete_operation'], + 'operationgroup': '__all__', + 'order': ['change_order', 'delete_order'], + 'orderarticle': '__all__', + 'supplier': ['add_supplier', 'delete_supplier'], + 'supplierarticle': '__all__', + 'transfer': ['change_transfer', 'delete_transfer'], + 'transfergroup': '__all__', + } + + to_delete_q = Q() + + for model_name, codenames in to_delete.items(): + if codenames == '__all__': + to_delete_q |= Q(content_type__model=model_name) + else: + to_delete_q |= Q( + content_type__model=model_name, + codename__in=codenames, + ) + + to_delete_q &= Q(content_type__app_label='kfet') + + Permission.objects.filter(to_delete_q).delete() + + +class Migration(migrations.Migration): + """ + Data migration which performs permissions cleaning. + """ + dependencies = [ + ('kfet', '0060_change_models_opts'), + ('auth', '0006_require_contenttypes_0002'), + ('contenttypes', '0002_remove_content_type_name'), + ] + + operations = [ + migrations.RunPython(update_permissions), + ] diff --git a/kfet/models.py b/kfet/models.py index 62fd3ed6..82d769a5 100644 --- a/kfet/models.py +++ b/kfet/models.py @@ -8,6 +8,7 @@ from gestioncof.models import CofProfile from django.utils.six.moves import reduce from django.utils import timezone from django.utils.encoding import python_2_unicode_compatible +from django.utils.translation import ugettext_lazy as _ from django.db import transaction from django.db.models import F from datetime import date @@ -83,9 +84,11 @@ class Account(models.Model): blank = True, null = True, default = None) class Meta: + verbose_name = _("Compte") + verbose_name_plural = _("Comptes") + default_permissions = ('add', 'change') permissions = ( - ('is_team', 'Is part of the team'), - ('manage_perms', 'Gérer les permissions K-Fêt'), + ('is_team', "Membre de l'équipe"), ('manage_addcosts', 'Gérer les majorations'), ('edit_balance_account', "Modifier la balance d'un compte"), ('change_account_password', @@ -329,9 +332,9 @@ class AccountNegative(models.Model): comment = models.CharField("commentaire", max_length=255, blank=True) class Meta: - permissions = ( - ('view_negs', 'Voir la liste des négatifs'), - ) + verbose_name = _("Compte en négatif") + verbose_name_plural = _("Comptes en négatif") + default_permissions = ('view', 'change') @property def until_default(self): @@ -355,7 +358,10 @@ class Checkout(models.Model): return reverse('kfet.checkout.read', kwargs={'pk': self.pk}) class Meta: + verbose_name = _("Caisse") + verbose_name_plural = _("Caisses") ordering = ['-valid_to'] + default_permissions = ('add', 'change') def __str__(self): return self.name @@ -370,6 +376,10 @@ class CheckoutTransfer(models.Model): amount = models.DecimalField( max_digits = 6, decimal_places = 2) + class Meta: + default_permissions = () + + @python_2_unicode_compatible class CheckoutStatement(models.Model): by = models.ForeignKey( @@ -408,6 +418,11 @@ class CheckoutStatement(models.Model): "montant des chèques", default=0, max_digits=6, decimal_places=2) + class Meta: + verbose_name = _("Relevé de caisse") + verbose_name_plural = _("Relevés de caisses") + default_permissions = ('add', 'change') + def __str__(self): return '%s %s' % (self.checkout, self.at) @@ -448,6 +463,11 @@ class ArticleCategory(models.Model): "appliquée aux articles de " "cette catégorie.") + class Meta: + verbose_name = _("Catégorie d'articles") + verbose_name_plural = _("Catégories d'articles") + default_permissions = ('change',) + def __str__(self): return self.name @@ -484,6 +504,11 @@ class Article(models.Model): "capacité du contenant", blank = True, null = True, default = None) + class Meta: + verbose_name = _("Article") + verbose_name_plural = _("Articles") + default_permissions = ('add', 'change') + def __str__(self): return '%s - %s' % (self.category.name, self.name) @@ -503,6 +528,10 @@ class ArticleRule(models.Model): related_name = "rule_to") ratio = models.PositiveSmallIntegerField() + class Meta: + default_permissions = () + + class Inventory(models.Model): articles = models.ManyToManyField( Article, @@ -519,7 +548,10 @@ class Inventory(models.Model): blank = True, null = True, default = None) class Meta: + verbose_name = _("Inventaire") + verbose_name_plural = _("Inventaires") ordering = ['-at'] + default_permissions = ('add',) permissions = ( ('order_to_inventory', "Générer un inventaire à partir d'une commande"), ) @@ -534,6 +566,9 @@ class InventoryArticle(models.Model): stock_new = models.IntegerField() stock_error = models.IntegerField(default = 0) + class Meta: + default_permissions = () + def save(self, *args, **kwargs): # S'il s'agit d'un inventaire provenant d'une livraison, il n'y a pas # d'erreur @@ -553,6 +588,11 @@ class Supplier(models.Model): phone = models.CharField("téléphone", max_length = 10) comment = models.TextField("commentaire") + class Meta: + verbose_name = _("Fournisseur") + verbose_name_plural = _("Fournisseurs") + default_permissions = ('change',) + def __str__(self): return self.name @@ -572,6 +612,9 @@ class SupplierArticle(models.Model): max_digits = 7, decimal_places = 4, blank = True, null = True, default = None) + class Meta: + default_permissions = () + class Order(models.Model): supplier = models.ForeignKey( Supplier, on_delete = models.PROTECT, @@ -585,7 +628,10 @@ class Order(models.Model): max_digits = 6, decimal_places = 2, default = 0) class Meta: + verbose_name = _("Commande") + verbose_name_plural = _("Commandes") ordering = ['-at'] + default_permissions = ('add',) class OrderArticle(models.Model): order = models.ForeignKey( @@ -595,6 +641,9 @@ class OrderArticle(models.Model): quantity_ordered = models.IntegerField() quantity_received = models.IntegerField(default = 0) + class Meta: + default_permissions = () + class TransferGroup(models.Model): at = models.DateTimeField(default=timezone.now) # Optional @@ -606,6 +655,9 @@ class TransferGroup(models.Model): related_name = "+", blank = True, null = True, default = None) + class Meta: + default_permissions = () + class Transfer(models.Model): group = models.ForeignKey( @@ -626,6 +678,11 @@ class Transfer(models.Model): canceled_at = models.DateTimeField( null=True, blank=True, default=None) + class Meta: + verbose_name = _("Transfert") + verbose_name_plural = _("Transferts") + default_permissions = ('add',) + def __str__(self): return '{} -> {}: {}€'.format(self.from_acc, self.to_acc, self.amount) @@ -651,6 +708,9 @@ class OperationGroup(models.Model): related_name = "+", blank = True, null = True, default = None) + class Meta: + default_permissions = () + def __str__(self): return ', '.join(map(str, self.opes.all())) @@ -701,6 +761,9 @@ class Operation(models.Model): blank=True, null=True, default=None) class Meta: + verbose_name = _("Opération") + verbose_name_plural = _("Opérations") + default_permissions = () permissions = ( ('perform_deposit', 'Effectuer une charge'), ('perform_negative_operations', diff --git a/kfet/templates/kfet/account.html b/kfet/templates/kfet/account.html index c4147b14..da98f521 100644 --- a/kfet/templates/kfet/account.html +++ b/kfet/templates/kfet/account.html @@ -22,11 +22,11 @@ - {% if perms.kfet.manage_perms %} + {% if perms.kfetauth.view_group %} Permissions {% endif %} - {% if perms.kfet.view_negs %} + {% if perms.kfet.view_accountnegative %} Négatifs {% endif %} diff --git a/kfet/urls.py b/kfet/urls.py index eb4f8311..192fe24c 100644 --- a/kfet/urls.py +++ b/kfet/urls.py @@ -54,16 +54,17 @@ urlpatterns = [ url(r'^accounts/groups$', views.account_group, name='kfet.account.group'), url(r'^accounts/groups/new$', - permission_required('kfet.manage_perms') + permission_required('kfetauth.add_group') (views.AccountGroupCreate.as_view()), name='kfet.account.group.create'), url(r'^accounts/groups/(?P\d+)/edit$', - permission_required('kfet.manage_perms') + permission_required('kfetauth.change_group') (views.AccountGroupUpdate.as_view()), name='kfet.account.group.update'), + url(r'^accounts/negatives$', - permission_required('kfet.view_negs') + permission_required('kfet.view_accountnegative') (views.AccountNegativeList.as_view()), name='kfet.account.negative'),