[Django]-Remove old permissions in django

18πŸ‘

βœ…

To start, make an empty migration file:

python manage.py makemigrations --empty yourappname

Change the migration (this is an example, adjust to your needs):

# -*- coding: utf-8 -*-
from __future__ import unicode_literals    
from django.db import migrations    


def add_permissions(apps, schema_editor):
    pass


def remove_permissions(apps, schema_editor):
    """Reverse the above additions of permissions."""
    ContentType = apps.get_model('contenttypes.ContentType')
    Permission = apps.get_model('auth.Permission')
    content_type = ContentType.objects.get(
        model='somemodel',
        app_label='yourappname',
    )
    # This cascades to Group
    Permission.objects.filter(
        content_type=content_type,
        codename__in=('add_somemodel', 'change_somemodel', 'delete_somemodel'),
    ).delete()

class Migration(migrations.Migration):
    dependencies = [
        ('yourappname', '0001_initial'),
    ]
    operations = [
        migrations.RunPython(remove_permissions, add_permissions),
    ]
πŸ‘€allcaps

24πŸ‘

Permissions have foreign keys to content types under the hood, so removing the content types for the models that no longer exist will also remove the permissions for those models.

Fortunately, Django also provides a manage.py command to remove old content types: remove_stale_contenttypes. Running that command will list the content types that no longer exist and the related objects (including permissions) that will be deleted, allowing you to review the changes and approve them.

$ manage.py remove_stale_contenttypes
Some content types in your database are stale and can be deleted.
Any objects that depend on these content types will also be deleted.
The content types and dependent objects that would be deleted are:

    - Content type for stale_app.removed_model
    - 4 auth.Permission object(s)

This list doesn't include any cascade deletions to data outside of Django's
models (uncommon).

Are you sure you want to delete these content types?
If you're unsure, answer 'no'.
Type 'yes' to continue, or 'no' to cancel:
πŸ‘€tari

3πŸ‘

I did it this way:

import re 

for perm in Permission.objects.all():
    if re.match( r".+modelname.+permissionname.+",str(perm)):
        print(perm)
        perm.delete()
πŸ‘€Maks

3πŸ‘

If you have custom or model based (default) permissions you wish to remove you could write a command like this to accomplish this task:

from django.conf import settings
from django.contrib.auth.models import Permission
from django.core.management.base import BaseCommand
import django.apps

class Command(BaseCommand):
    help = 'Remove custom permissions that are no longer in models'

    def handle(self, *args, **options):
        # get the db name needed for removal...
        database_name = input('Database Name: ')

        default_perm_names = list()
        # are real perms in db, may not be accurate
        db_custom_perm_names = list()
        # will be used to ensure they are correct.
        meta_custom_perm_names = list()

        default_and_custom_perms = list()

        for model in django.apps.apps.get_models():
            # add to models found to fix perms from removed models
            app_label = model._meta.app_label
            lower_model_name = model._meta.model_name

            all_model_permissions = Permission.objects.using(database_name).filter(content_type__app_label=app_label, content_type__model=lower_model_name)


            default_and_custom_perms.extend([x for x in all_model_permissions])
            # get the custom meta permissions, these should be in the meta of the class
            # will be a list or tuple or list, [0=codename, 1=name]
            meta_permissions = model._meta.permissions

            if meta_permissions:
                for perm in all_model_permissions:
                    # will be the model name from the content type, this is how django makes default perms
                    # we are trying to remove them so now we can figure out which ones are default by provided name
                    model_name_lower = perm.content_type.name
                    # default_perms =  ['add', 'change', 'view', 'delete', 'undelete']
                    # append them to the list of default names
                    default_perm_names.append(f'Can add {model_name_lower}')
                    default_perm_names.append(f'Can change {model_name_lower}')
                    default_perm_names.append(f'Can view {model_name_lower}')
                    default_perm_names.append(f'Can delete {model_name_lower}')
                    default_perm_names.append(f'Can undelete {model_name_lower}')
                    # will mean this is a custom perm...so add it
                    if not perm.name in default_perm_names:
                        db_custom_perm_names.append(perm.name)

                # the perms to ensure are correct...
                for model_perm in meta_permissions:
                    # get the meta perm, will be a list or tuple or list, [0=codename, 1=name]
                    custom_perm = Permission.objects.using(database_name).get(codename=model_perm[0], name=model_perm[1])
                    meta_custom_perm_names.append(custom_perm.name)


        perms_to_remove = [perm for perm in db_custom_perm_names if perm not in meta_custom_perm_names]
        if not perms_to_remove:
            print('There are no stale custom permissions to remove.')


        # print(perms_to_remove)
        # now remove the custom permissions that were removed from the model
        for actual_permission_to_remove in Permission.objects.using(database_name).filter(name__in=perms_to_remove):
            # print(actual_permission_to_remove)
            actual_permission_to_remove.delete(using=database_name)
            print(actual_permission_to_remove, '...deleted')

        for perm in [x for x in Permission.objects.using(database_name)]:
            # loop all perms...if it is not in the model perms it does not exist...
            if perm.content_type.model not in [x.content_type.model for x in default_and_custom_perms]:
                perm.delete(using=database_name)
                print(perm, 'regular permission...deleted')

If you also wish to ensure that the default permissions are added from Django you can add this snippet in the command:

    from django.apps import apps
    from django.contrib.auth.management import create_permissions
    from apps.client.models import ClientInformation
    # add all permissions the django way
    # get the db name needed from settings.py
    database_name = 'default' # or whatever DB you are looking for
    print(f'adding all permissions if not there to {database_name}')
    for app_config in apps.get_app_configs():
        # print(app_config)
        app_config.models_module = True
        create_permissions(app_config, using=database_name)
        app_config.models_module = None

Then call via python manage.py fix_permissions if you name your command file fix_permissions.py

πŸ‘€ViaTech

0πŸ‘

I’ve reworked @ViaTech’s code to use Django’s contrib.auth.management._get_all_permissions() functions which makes it more straight forward:

from typing import List, Set, Tuple

import django.apps

# noinspection PyProtectedMember
from django.contrib.auth.management import _get_all_permissions
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from django.core.management.base import BaseCommand
from django.db import DEFAULT_DB_ALIAS


class Command(BaseCommand):
    help = "Remove custom permissions that are no longer defined in models"

    def add_arguments(self, parser):
        parser.add_argument(
            "--database",
            default=DEFAULT_DB_ALIAS,
            help=f'Specifies the database to use. Default is "{DEFAULT_DB_ALIAS}".',
        )
        parser.add_argument(
            "--dry",
            action="store_true",
            help="Do a dry run not actually deleting any permissions",
        )

    def handle(self, *args, **options) -> str:
        using = options["database"]

        # This will hold the permissions that models have defined,
        # i.e. default permissions plus additional custom permissions:
        #       (content_type.pk, codename)
        defined_perms: List[Tuple[int, str]] = []

        for model in django.apps.apps.get_models():
            ctype = ContentType.objects.db_manager(using).get_for_model(
                model, for_concrete_model=False
            )

            # noinspection PyProtectedMember
            for (codename, _) in _get_all_permissions(model._meta):
                defined_perms.append((ctype.id, codename))

        # All permissions in current database (including stale ones)
        all_perms = Permission.objects.using(using).all()

        stale_perm_pks: Set[int] = set()
        for perm in all_perms:
            if (perm.content_type.pk, perm.codename) not in defined_perms:
                stale_perm_pks.add(perm.pk)
                self.stdout.write(f"Delete permission: {perm}")

        # Delete all stale permissions
        if options["dry"]:
            result = f"DRY RUN: {len(stale_perm_pks)} stale permissions NOT deleted"
        else:
            if stale_perm_pks:
                Permission.objects.filter(pk__in=stale_perm_pks).delete()
            result = f"{len(stale_perm_pks)} stale permissions deleted"

        return result
πŸ‘€kdb

Leave a comment