from django.conf import settings 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 transaction from perfieldperms.models import PerFieldPermission from perfieldperms.utils import list_fields_for_ctype class Command(BaseCommand): """ Used to pre-generate permissions for models. Sources models from the PFPContentType table, or from the setting PFP_MODELS. If both sources are configured merges the lists. For each model listed gets a list of model level permissions, and for each model level perm generates a set of per-field permissions corresponding to each field on the model. These permissions take the form: __ If fields have been changed permissions are added/removed as appropriate. If you want to exclude a model level permissions add it to the setting PFP_IGNOREPERMS. Settings: PFP_MODELS -- an iterable of two tuples (app_label, model name) of models you want pfps created for. PFP_IGNORE_PERMS -- a dict of dicts of iterables of permissions you want ignored when creating pfps. Structured: {app_label: {model name: [, ,..]} } PFP_IGNORE_DELETE -- By default perfieldperms doesn't create field level delete permissions as this doesn't necessarily make sense. Set to False if you want to create delete pfps. PFP_IGNORE_VIEW -- By default perfieldperms doesn't create field level view permissions as this doesn't necessarily make sense. Set to False if you want to create view pfps. """ help = 'Generates permissions for fields on selected models.' requires_migrations_checks = True @staticmethod def _get_perms(content_type): model = content_type.model perms = Permission.objects.filter( content_type=content_type, perfieldpermission__isnull=True, ) excluded_perms = [] if (not hasattr(settings, 'PFP_IGNORE_DELETE') or settings.PFP_IGNORE_DELETE): excluded_perms.append('delete_{}'.format(model)) if (not hasattr(settings, 'PFP_IGNORE_VIEW') or settings.PFP_IGNORE_VIEW): excluded_perms.append('view_{}'.format(model)) if excluded_perms: perms = perms.exclude(codename__in=excluded_perms) return perms def handle(self, *args, **options): verbose = options['verbosity'] > 1 # Get all the models to create permissions for models = set() if hasattr(settings, 'PFP_MODELS'): models.update(set(settings.PFP_MODELS)) model_ctypes = ContentType.objects.filter(pfpcontenttype__isnull=False) models.update(set([(ctype.app_label, ctype.model) for ctype in model_ctypes])) deleted = 0 existing = 0 added = 0 for app_label, model in models: ignore_perms = [] if (hasattr(settings, 'PFP_IGNORE_PERMS') and app_label in settings.PFP_IGNORE_PERMS and model in settings.PFP_IGNORE_PERMS[app_label]): ignore_perms = settings.PFP_IGNORE_PERMS[app_label][model] ctype = ContentType.objects.get(app_label=app_label, model=model) model_fields = set([field for field, name in list_fields_for_ctype(ctype)]) perms = self._get_perms(ctype) # Remove perms for removed fields, add permissions for new fields with transaction.atomic(): for perm in perms: pfps = PerFieldPermission.objects.filter(model_permission=perm) if perm.codename in ignore_perms: if verbose: for pfp in pfps: self.stdout.write( 'Deleted permission {} for ignored permission {}.{} - ' '{}'.format(pfp.codename, app_label, model, pfp.field_name) ) if pfps: # pfps are a multi-table subclass so divide by 2 to get actual number # deleted deleted += pfps.delete()[0] / 2 else: fields = model_fields.copy() pfps = set(pfps) existing += len(pfps) # Delete any permissions for fields that don't exist anymore while pfps: pfp = pfps.pop() if pfp.field_name in fields: fields.remove(pfp.field_name) else: pfp.delete() deleted += 1 verbose and self.stdout.write( 'Deleted permission {} for removed field {}.{} - ' '{}'.format(pfp.codename, app_label, model, pfp.field_name) ) # Add new permissions for field in fields: new_pfp = PerFieldPermission( content_type=ctype, codename='{}__{}'.format(perm.codename, field), name='{} - {}'.format(perm.name, field), field_name=field, model_permission=perm, ) new_pfp.full_clean() new_pfp.save() added += 1 verbose and self.stdout.write( 'Added new permission {} for field {}.{} - {}'.format( new_pfp.codename, app_label, model, new_pfp.field_name ) ) if verbose: return ('Permissions added: {}\nPermissions deleted: {}\nExisting permissions ' 'skipped: {}'.format(added, deleted, existing))