forms.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. from django import forms
  2. from django.contrib.auth.models import Group, User
  3. from django.contrib.admin.widgets import FilteredSelectMultiple
  4. from django.contrib.contenttypes.models import ContentType
  5. from django.core.exceptions import FieldError, ValidationError
  6. from users.constants import CONSTRAINT_TOKEN_USER, OBJECTPERMISSION_OBJECT_TYPES
  7. from users.models import ObjectPermission, Token
  8. from utilities.forms.fields import ContentTypeMultipleChoiceField
  9. from utilities.permissions import qs_filter_from_constraints
  10. __all__ = (
  11. 'GroupAdminForm',
  12. 'ObjectPermissionForm',
  13. 'TokenAdminForm',
  14. )
  15. class GroupAdminForm(forms.ModelForm):
  16. users = forms.ModelMultipleChoiceField(
  17. queryset=User.objects.all(),
  18. required=False,
  19. widget=FilteredSelectMultiple('users', False)
  20. )
  21. class Meta:
  22. model = Group
  23. fields = ('name', 'users')
  24. def __init__(self, *args, **kwargs):
  25. super(GroupAdminForm, self).__init__(*args, **kwargs)
  26. if self.instance.pk:
  27. self.fields['users'].initial = self.instance.user_set.all()
  28. def save_m2m(self):
  29. self.instance.user_set.set(self.cleaned_data['users'])
  30. def save(self, *args, **kwargs):
  31. instance = super(GroupAdminForm, self).save()
  32. self.save_m2m()
  33. return instance
  34. class TokenAdminForm(forms.ModelForm):
  35. key = forms.CharField(
  36. required=False,
  37. help_text="If no key is provided, one will be generated automatically."
  38. )
  39. class Meta:
  40. fields = [
  41. 'user', 'key', 'write_enabled', 'expires', 'description', 'allowed_ips'
  42. ]
  43. model = Token
  44. class ObjectPermissionForm(forms.ModelForm):
  45. object_types = ContentTypeMultipleChoiceField(
  46. queryset=ContentType.objects.all(),
  47. limit_choices_to=OBJECTPERMISSION_OBJECT_TYPES
  48. )
  49. can_view = forms.BooleanField(required=False)
  50. can_add = forms.BooleanField(required=False)
  51. can_change = forms.BooleanField(required=False)
  52. can_delete = forms.BooleanField(required=False)
  53. class Meta:
  54. model = ObjectPermission
  55. exclude = []
  56. help_texts = {
  57. 'actions': 'Actions granted in addition to those listed above',
  58. 'constraints': 'JSON expression of a queryset filter that will return only permitted objects. Leave null '
  59. 'to match all objects of this type. A list of multiple objects will result in a logical OR '
  60. 'operation.'
  61. }
  62. labels = {
  63. 'actions': 'Additional actions'
  64. }
  65. widgets = {
  66. 'constraints': forms.Textarea(attrs={'class': 'vLargeTextField'})
  67. }
  68. def __init__(self, *args, **kwargs):
  69. super().__init__(*args, **kwargs)
  70. # Make the actions field optional since the admin form uses it only for non-CRUD actions
  71. self.fields['actions'].required = False
  72. # Order group and user fields
  73. self.fields['groups'].queryset = self.fields['groups'].queryset.order_by('name')
  74. self.fields['users'].queryset = self.fields['users'].queryset.order_by('username')
  75. # Check the appropriate checkboxes when editing an existing ObjectPermission
  76. if self.instance.pk:
  77. for action in ['view', 'add', 'change', 'delete']:
  78. if action in self.instance.actions:
  79. self.fields[f'can_{action}'].initial = True
  80. self.instance.actions.remove(action)
  81. def clean(self):
  82. super().clean()
  83. object_types = self.cleaned_data.get('object_types')
  84. constraints = self.cleaned_data.get('constraints')
  85. # Append any of the selected CRUD checkboxes to the actions list
  86. if not self.cleaned_data.get('actions'):
  87. self.cleaned_data['actions'] = list()
  88. for action in ['view', 'add', 'change', 'delete']:
  89. if self.cleaned_data[f'can_{action}'] and action not in self.cleaned_data['actions']:
  90. self.cleaned_data['actions'].append(action)
  91. # At least one action must be specified
  92. if not self.cleaned_data['actions']:
  93. raise ValidationError("At least one action must be selected.")
  94. # Validate the specified model constraints by attempting to execute a query. We don't care whether the query
  95. # returns anything; we just want to make sure the specified constraints are valid.
  96. if object_types and constraints:
  97. # Normalize the constraints to a list of dicts
  98. if type(constraints) is not list:
  99. constraints = [constraints]
  100. for ct in object_types:
  101. model = ct.model_class()
  102. try:
  103. tokens = {
  104. CONSTRAINT_TOKEN_USER: 0, # Replace token with a null user ID
  105. }
  106. model.objects.filter(qs_filter_from_constraints(constraints, tokens)).exists()
  107. except FieldError as e:
  108. raise ValidationError({
  109. 'constraints': f'Invalid filter for {model}: {e}'
  110. })