from django import forms from django.contrib.auth import get_user_model from django.contrib.auth.models import Permission from django.contrib.contenttypes.models import ContentType from django.db.models import Q from perfieldperms.utils import get_non_pfp_perms, get_unpermitted_fields, list_fields_for_ctype # Forms for permission allocation view def pfp_perm_select_form_factory(permissions_qs): """ Generate a form with options for permissions that depend on what model, permissions, and maybe fields a user has selected in the filters. Pass in a queryset of the permissions we want to use. Return a formset using this form. """ class PFPSelectForm(forms.Form): role_name = forms.CharField(widget=forms.HiddenInput) role_id = forms.CharField(widget=forms.HiddenInput) permissions = forms.ModelMultipleChoiceField( queryset=permissions_qs, required=False, widget=forms.CheckboxSelectMultiple, ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields['role_name'].widget.attrs['readonly'] = True self.fields['role_id'].widget.attrs['readonly'] = True return forms.formset_factory(PFPSelectForm, extra=0, max_num=0) class PFPRoleFilterForm(forms.Form): """ Form that contains the fields used to filter roles in permission management grid. """ prefix = 'pfp_rfilter' role_type = forms.ModelChoiceField(queryset=ContentType.objects.none()) def __init__(self, *args, **kwargs): """ Extend super to set the role_type queryset to allowed ContentTypes.""" super().__init__(*args, **kwargs) user_model = get_user_model() roles_qs = ContentType.objects.filter( Q(app_label=user_model._meta.app_label, model=user_model.__name__.lower()) | Q(pfproletype__isnull=False) ).distinct() self.fields['role_type'].queryset = roles_qs class PFPPermFilterForm(forms.Form): """ Form that contains the fields used to filter permissions in permission management grid. """ prefix = 'pfp_pfilter' model = forms.ModelChoiceField(queryset=ContentType.objects.all()) show_pfps = forms.BooleanField( required=False, label='field level permissions', ) permissions = forms.ModelMultipleChoiceField( queryset=Permission.objects.none(), required=False ) model_fields = forms.MultipleChoiceField(choices=[], required=False) def __init__(self, *args, **kwargs): """ Extend super to change the queryset for the model field, and configure other fields based on the contents of keyword arg "initial". """ super().__init__(*args, **kwargs) models_qs = ContentType.objects.filter(pfpfiltertype__isnull=False) if models_qs: self.fields['model'].queryset = models_qs if 'initial' in kwargs: initial = kwargs['initial'] if 'model' in initial: model_ctype = initial['model'] if 'show_pfps' in initial and initial['show_pfps']: permissions_qs = Permission.objects.filter( content_type=model_ctype) else: permissions_qs = get_non_pfp_perms(model_ctype) self.fields['permissions'].queryset = permissions_qs self.fields['model_fields'].choices = list_fields_for_ctype(model_ctype) # ModelForms for concrete models class PFPRoleTypesForm(forms.ModelForm): """Prevents attempt to duplicate an existing entry by restricting choices.""" role_ctype = forms.ModelChoiceField( queryset=ContentType.objects.filter( pfproletype__isnull=True )) class PFPContentTypesForm(forms.ModelForm): """Prevents attempt to duplicate an existing entry by restricting choices.""" content_type = forms.ModelChoiceField( queryset=ContentType.objects.filter( pfpcontenttype__isnull=True )) # Base ModelForm Classes class PFPModelForm(forms.ModelForm): """ Extension to ModelForm that disables or removes fields based on user permissions. Attributes ---------- disabled_fields : List, optional A list of field names to disable disable_fields : bool, optional If True, disable fields on the form, otherwise completely remove them from the form disabled_field_help_text : str, optional A string to use as help text for disabled fields. Parameters ---------- disabled_fields : List, optional A list of field names to disable disable_fields : bool, optional If True, disable fields on the form, otherwise completely remove them from the form user : User, optional A user object which will be used to determine which fields to disable on the form. """ disable_fields = True disabled_fields = None disabled_field_help_text = 'This field is disabled as you lack required permissions.' def __init__(self, *args, **kwargs): # Process args and overwrite class settings with those passed as kwargs if self.disabled_fields is None: self.disabled_fields = [] self.disable_fields = kwargs.pop('disable_fields', self.disable_fields) self.disabled_fields = kwargs.pop('disabled_fields', self.disabled_fields) user = kwargs.pop('user', None) super().__init__(*args, **kwargs) # If we have a user work out which fields they don't have access to and merge that list # with any preconfigured fields. if user: # If instance.pk is None it's a new unsaved object so 'add' permissions apply, # otherwise it's an existing object and we're changing. if hasattr(self.instance, 'pk') and self.instance.pk is not None: user_disabled_fields = get_unpermitted_fields( self._meta.model, user, obj=self.instance, ) else: user_disabled_fields = get_unpermitted_fields(self._meta.model, user) self.disabled_fields = list(set(self.disabled_fields + user_disabled_fields)) self._filter_fields() def _filter_fields(self): """Do the field filtering.""" # Iterate through fields, disabling or removing if the user lacks sufficient permission form_fields = list(self.fields) for fname in self.disabled_fields: if fname in form_fields: if self.disable_fields: self.fields[fname].disabled = True self.fields[fname].help_text = self.disabled_field_help_text else: del self.fields[fname]