Projet

Général

Profil

0002-a2_rbac-migrate-existing-operations-to-new-model-699.patch

Valentin Deniaud, 12 octobre 2022 16:14

Télécharger (15,8 ko)

Voir les différences:

Subject: [PATCH 2/2] a2_rbac: migrate existing operations to new model
 (#69902)

 .../migrations/0031_new_operation_model.py    | 45 ++++++++++++++++
 .../migrations/0032_copy_operations_data.py   | 43 +++++++++++++++
 .../0033_remove_old_operation_fk.py           | 30 +++++++++++
 src/authentic2/a2_rbac/models.py              | 34 +++++++++++-
 src/authentic2/data_transfer.py               | 10 +++-
 src/authentic2/manager/forms.py               |  3 +-
 .../migrations/0010_delete_operation.py       | 17 ++++++
 src/django_rbac/models.py                     | 33 ------------
 src/django_rbac/utils.py                      |  2 +-
 tests/test_a2_rbac.py                         | 53 ++++++++++++++++++-
 tests/test_commands.py                        |  2 +-
 tests/test_rbac.py                            |  3 +-
 12 files changed, 231 insertions(+), 44 deletions(-)
 create mode 100644 src/authentic2/a2_rbac/migrations/0031_new_operation_model.py
 create mode 100644 src/authentic2/a2_rbac/migrations/0032_copy_operations_data.py
 create mode 100644 src/authentic2/a2_rbac/migrations/0033_remove_old_operation_fk.py
 create mode 100644 src/django_rbac/migrations/0010_delete_operation.py
 delete mode 100644 src/django_rbac/models.py
src/authentic2/a2_rbac/migrations/0031_new_operation_model.py
1
# Generated by Django 2.2.26 on 2022-10-11 14:14
2

  
3
import django.db.models.deletion
4
from django.db import migrations, models
5

  
6

  
7
class Migration(migrations.Migration):
8

  
9
    dependencies = [
10
        ('a2_rbac', '0030_organizationalunit_min_password_strength'),
11
    ]
12

  
13
    operations = [
14
        migrations.CreateModel(
15
            name='Operation',
16
            fields=[
17
                (
18
                    'id',
19
                    models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
20
                ),
21
                ('slug', models.CharField(max_length=32, unique=True, verbose_name='slug')),
22
            ],
23
        ),
24
        migrations.AddField(
25
            model_name='permission',
26
            name='operation_new',
27
            field=models.ForeignKey(
28
                null=True,
29
                on_delete=django.db.models.deletion.CASCADE,
30
                to='a2_rbac.Operation',
31
                verbose_name='operation',
32
            ),
33
            preserve_default=False,
34
        ),
35
        migrations.AlterField(
36
            model_name='permission',
37
            name='operation',
38
            field=models.ForeignKey(
39
                on_delete=django.db.models.deletion.CASCADE,
40
                to='django_rbac.Operation',
41
                verbose_name='operation',
42
                null=True,
43
            ),
44
        ),
45
    ]
src/authentic2/a2_rbac/migrations/0032_copy_operations_data.py
1
# Generated by Django 2.2.26 on 2022-10-11 13:36
2

  
3
from django.db import migrations
4

  
5

  
6
def copy_operations_data(apps, schema_editor):
7
    OldOperation = apps.get_model('django_rbac', 'Operation')
8
    NewOperation = apps.get_model('a2_rbac', 'Operation')
9
    Permission = apps.get_model('a2_rbac', 'Permission')
10

  
11
    operation_map = {}
12
    for operation in OldOperation.objects.all():
13
        operation_map[operation.pk] = NewOperation.objects.create(slug=operation.slug)
14

  
15
    for permission in Permission.objects.all():
16
        permission.operation_new = operation_map[permission.operation_id]
17
        permission.save()
18

  
19

  
20
def reverse_copy_operations_data(apps, schema_editor):
21
    OldOperation = apps.get_model('django_rbac', 'Operation')
22
    NewOperation = apps.get_model('a2_rbac', 'Operation')
23
    Permission = apps.get_model('a2_rbac', 'Permission')
24

  
25
    operation_map = {}
26
    for operation in NewOperation.objects.all():
27
        operation_map[operation.pk] = OldOperation.objects.create(slug=operation.slug)
28

  
29
    for permission in Permission.objects.all():
30
        permission.operation = operation_map[permission.operation_new_id]
31
        permission.save()
32

  
33

  
34
class Migration(migrations.Migration):
35

  
36
    dependencies = [
37
        ('a2_rbac', '0031_new_operation_model'),
38
        ('django_rbac', '0009_auto_20221004_1343'),
39
    ]
40

  
41
    operations = [
42
        migrations.RunPython(copy_operations_data, reverse_code=reverse_copy_operations_data),
43
    ]
src/authentic2/a2_rbac/migrations/0033_remove_old_operation_fk.py
1
# Generated by Django 2.2.26 on 2022-10-11 14:35
2

  
3
import django
4
from django.db import migrations, models
5

  
6

  
7
class Migration(migrations.Migration):
8

  
9
    dependencies = [
10
        ('a2_rbac', '0032_copy_operations_data'),
11
    ]
12

  
13
    operations = [
14
        migrations.RemoveField(
15
            model_name='permission',
16
            name='operation',
17
        ),
18
        migrations.RenameField(
19
            model_name='permission',
20
            old_name='operation_new',
21
            new_name='operation',
22
        ),
23
        migrations.AlterField(
24
            model_name='permission',
25
            name='operation',
26
            field=models.ForeignKey(
27
                on_delete=django.db.models.deletion.CASCADE, to='a2_rbac.Operation', verbose_name='operation'
28
            ),
29
        ),
30
    ]
src/authentic2/a2_rbac/models.py
38 38
from authentic2.validators import HexaColourValidator
39 39
from django_rbac import managers as rbac_managers
40 40
from django_rbac import utils as rbac_utils
41
from django_rbac.models import Operation
42 41

  
43 42
from . import app_settings, fields, managers
44 43

  
......
266 265

  
267 266

  
268 267
class Permission(models.Model):
269
    operation = models.ForeignKey(to=Operation, verbose_name=_('operation'), on_delete=models.CASCADE)
268
    operation = models.ForeignKey(
269
        to='a2_rbac.Operation', verbose_name=_('operation'), on_delete=models.CASCADE
270
    )
270 271
    ou = models.ForeignKey(
271 272
        to=rbac_utils.get_ou_model_name(),
272 273
        verbose_name=_('organizational unit'),
......
718 719
        return {'name': self.name, 'kind': self.kind, 'value': self.value}
719 720

  
720 721

  
722
class Operation(models.Model):
723
    slug = models.CharField(max_length=32, verbose_name=_('slug'), unique=True)
724

  
725
    def natural_key(self):
726
        return [self.slug]
727

  
728
    def __str__(self):
729
        return str(self._registry.get(self.slug, self.slug))
730

  
731
    def export_json(self):
732
        return {'slug': self.slug}
733

  
734
    @property
735
    def name(self):
736
        return str(self)
737

  
738
    @classmethod
739
    def register(cls, name, slug):
740
        cls._registry[slug] = name
741
        return cls(slug=slug)
742

  
743
    _registry = {}
744

  
745
    objects = rbac_managers.OperationManager()
746

  
747

  
748
Operation._meta.natural_key = ['slug']
749

  
750

  
721 751
GenericRelation(Permission, content_type_field='target_ct', object_id_field='target_id').contribute_to_class(
722 752
    ContentType, 'admin_perms'
723 753
)
src/authentic2/data_transfer.py
24 24
from django.utils.text import format_lazy
25 25
from django.utils.translation import gettext_lazy as _
26 26

  
27
from authentic2.a2_rbac.models import OrganizationalUnit, Permission, Role, RoleAttribute, RoleParenting
27
from authentic2.a2_rbac.models import (
28
    Operation,
29
    OrganizationalUnit,
30
    Permission,
31
    Role,
32
    RoleAttribute,
33
    RoleParenting,
34
)
28 35
from authentic2.a2_rbac.utils import get_default_ou
29 36
from authentic2.decorators import errorcollector
30 37
from authentic2.utils.lazy import lazy_join
31
from django_rbac.models import Operation
32 38

  
33 39

  
34 40
def update_model(obj, d):
src/authentic2/manager/forms.py
31 31
from django.utils.translation import pgettext
32 32
from django_select2.forms import HeavySelect2Widget
33 33

  
34
from authentic2.a2_rbac.models import OrganizationalUnit, Permission, Role, RoleAttribute
34
from authentic2.a2_rbac.models import Operation, OrganizationalUnit, Permission, Role, RoleAttribute
35 35
from authentic2.a2_rbac.utils import generate_slug, get_default_ou
36 36
from authentic2.custom_user.backends import DjangoRBACBackend
37 37
from authentic2.forms.fields import (
......
46 46
from authentic2.passwords import generate_password, get_min_password_strength
47 47
from authentic2.utils.misc import send_email_change_email, send_password_reset_mail, send_templated_mail
48 48
from authentic2.validators import EmailValidator
49
from django_rbac.models import Operation
50 49

  
51 50
from . import app_settings, fields, utils
52 51

  
src/django_rbac/migrations/0010_delete_operation.py
1
# Generated by Django 2.2.26 on 2022-10-11 14:43
2

  
3
from django.db import migrations
4

  
5

  
6
class Migration(migrations.Migration):
7

  
8
    dependencies = [
9
        ('django_rbac', '0009_auto_20221004_1343'),
10
        ('a2_rbac', '0033_remove_old_operation_fk'),
11
    ]
12

  
13
    operations = [
14
        migrations.DeleteModel(
15
            name='Operation',
16
        ),
17
    ]
src/django_rbac/models.py
1
from django.db import models
2
from django.utils.translation import gettext_lazy as _
3

  
4
from . import managers
5

  
6

  
7
class Operation(models.Model):
8
    slug = models.CharField(max_length=32, verbose_name=_('slug'), unique=True)
9

  
10
    def natural_key(self):
11
        return [self.slug]
12

  
13
    def __str__(self):
14
        return str(self._registry.get(self.slug, self.slug))
15

  
16
    def export_json(self):
17
        return {'slug': self.slug}
18

  
19
    @property
20
    def name(self):
21
        return str(self)
22

  
23
    @classmethod
24
    def register(cls, name, slug):
25
        cls._registry[slug] = name
26
        return cls(slug=slug)
27

  
28
    _registry = {}
29

  
30
    objects = managers.OperationManager()
31

  
32

  
33
Operation._meta.natural_key = ['slug']
src/django_rbac/utils.py
77 77

  
78 78

  
79 79
def get_operation(operation_tpl):
80
    from . import models
80
    from authentic2.a2_rbac import models
81 81

  
82 82
    operation, dummy = models.Operation.objects.get_or_create(slug=operation_tpl.slug)
83 83
    return operation
tests/test_a2_rbac.py
19 19
from django.core.exceptions import ValidationError
20 20
from django.core.management import call_command
21 21

  
22
from authentic2.a2_rbac.models import CHANGE_OP, MANAGE_MEMBERS_OP
22
from authentic2.a2_rbac.models import CHANGE_OP, MANAGE_MEMBERS_OP, Operation
23 23
from authentic2.a2_rbac.models import OrganizationalUnit as OU
24 24
from authentic2.a2_rbac.models import Permission, Role, RoleAttribute
25 25
from authentic2.a2_rbac.utils import get_default_ou
26 26
from authentic2.custom_user.models import User
27 27
from authentic2.models import Service
28 28
from authentic2.utils.misc import get_hex_uuid
29
from django_rbac.models import Operation
30 29
from tests.utils import login, request_select2, scoped_db_fixture
31 30

  
32 31

  
......
691 690
        def test_direct(self, db, fixture):
692 691
            assert set(fixture.role.children(direct=True)) == {fixture.role, fixture.child}
693 692
            assert fixture.role.children(include_self=False, direct=True).get() == fixture.child
693

  
694

  
695
def test_a2_rbac_operation_migration(migration, settings):
696
    migrate_from = [
697
        ('a2_rbac', '0030_organizationalunit_min_password_strength'),
698
        ('django_rbac', '0009_auto_20221004_1343'),
699
    ]
700
    migrate_to = [('a2_rbac', '0033_remove_old_operation_fk')]
701

  
702
    old_apps = migration.before(migrate_from)
703
    ContentType = old_apps.get_model('contenttypes', 'ContentType')
704
    Operation = old_apps.get_model('django_rbac', 'Operation')
705
    Permission = old_apps.get_model('a2_rbac', 'Permission')
706

  
707
    # check objects created by signal handlers
708
    base_operation = Operation.objects.get(slug='view')
709
    base_permission = Permission.objects.filter(operation=base_operation).first()
710

  
711
    # check other objects
712
    new_operation = Operation.objects.create(slug='test')
713
    Permission.objects.create(
714
        operation=new_operation,
715
        target_ct=ContentType.objects.get_for_model(ContentType),
716
        target_id=ContentType.objects.get_for_model(User).pk,
717
    )
718

  
719
    new_apps = migration.apply(migrate_to)
720
    ContentType = new_apps.get_model('contenttypes', 'ContentType')
721
    Operation = new_apps.get_model('a2_rbac', 'Operation')
722
    Permission = new_apps.get_model('a2_rbac', 'Permission')
723

  
724
    base_operation = Operation.objects.get(slug='view')
725
    assert (
726
        Permission.objects.filter(
727
            operation_id=base_operation,
728
            target_ct_id=base_permission.target_ct.pk,
729
            target_id=base_permission.target_id,
730
        ).count()
731
        == 1
732
    )
733

  
734
    new_operation = Operation.objects.get(slug=new_operation.slug)
735
    assert (
736
        Permission.objects.filter(
737
            operation=new_operation,
738
            target_ct=ContentType.objects.get_for_model(ContentType),
739
            target_id=ContentType.objects.get_for_model(User).pk,
740
        ).count()
741
        == 1
742
    )
tests/test_commands.py
31 31
    ADMIN_OP,
32 32
    MANAGE_MEMBERS_OP,
33 33
    VIEW_OP,
34
    Operation,
34 35
    OrganizationalUnit,
35 36
    Permission,
36 37
    Role,
......
40 41
from authentic2.custom_user.models import DeletedUser
41 42
from authentic2.models import UserExternalId
42 43
from authentic2_auth_oidc.models import OIDCAccount, OIDCProvider
43
from django_rbac.models import Operation
44 44
from django_rbac.utils import get_operation
45 45

  
46 46
from .utils import call_command, login
tests/test_rbac.py
20 20
from django.db.models import Q
21 21
from django.test.utils import CaptureQueriesContext
22 22

  
23
from authentic2.a2_rbac import models
23 24
from authentic2.custom_user import backends
24
from django_rbac import models, utils
25
from django_rbac import utils
25 26

  
26 27
OU = OrganizationalUnit = utils.get_ou_model()
27 28
Permission = utils.get_permission_model()
28
-