mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-05-19 19:09:22 +02:00
Add constraint on admin saving a domain application when its creator is ineligible, make all fields readonly when creator is ineligible
This commit is contained in:
parent
96ea396da4
commit
4b82f5e131
2 changed files with 163 additions and 71 deletions
|
@ -4,11 +4,43 @@ from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.http.response import HttpResponseRedirect
|
from django.http.response import HttpResponseRedirect
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
# from django.forms.widgets import CheckboxSelectMultiple
|
||||||
|
# from django import forms
|
||||||
from . import models
|
from . import models
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# class TextDisplayWidget(forms.Widget):
|
||||||
|
# def render(self, name, value, attrs=None, renderer=None):
|
||||||
|
# if value:
|
||||||
|
# choices_dict = dict(self.choices)
|
||||||
|
# selected_values = set(value)
|
||||||
|
# display_values = [choices_dict[val] for val in selected_values]
|
||||||
|
# return ', '.join(display_values)
|
||||||
|
# return ''
|
||||||
|
|
||||||
|
|
||||||
|
# class DomainApplicationForm(forms.ModelForm):
|
||||||
|
# class Meta:
|
||||||
|
# model = models.DomainApplication
|
||||||
|
# fields = '__all__'
|
||||||
|
|
||||||
|
# def __init__(self, *args, **kwargs):
|
||||||
|
# super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
# # Replace the current_websites field with text if a condition is met
|
||||||
|
# if self.instance and self.instance.creator.status == 'ineligible':
|
||||||
|
# self.fields['current_websites'].widget = TextDisplayWidget()
|
||||||
|
# self.fields['current_websites'].widget.choices = self.fields['current_websites'].choices
|
||||||
|
|
||||||
|
# self.fields['other_contacts'].widget = TextDisplayWidget()
|
||||||
|
# self.fields['other_contacts'].widget.choices = self.fields['other_contacts'].choices
|
||||||
|
|
||||||
|
# self.fields['alternative_domains'].widget = TextDisplayWidget()
|
||||||
|
# self.fields['alternative_domains'].widget.choices = self.fields['alternative_domains'].choices
|
||||||
|
|
||||||
|
|
||||||
class AuditedAdmin(admin.ModelAdmin):
|
class AuditedAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
"""Custom admin to make auditing easier."""
|
"""Custom admin to make auditing easier."""
|
||||||
|
@ -182,6 +214,10 @@ class DomainApplicationAdmin(ListHeaderAdmin):
|
||||||
|
|
||||||
"""Customize the applications listing view."""
|
"""Customize the applications listing view."""
|
||||||
|
|
||||||
|
# Set multi-selects 'read-only' (hide selects and show data)
|
||||||
|
# based on user perms and application creator's status
|
||||||
|
#form = DomainApplicationForm
|
||||||
|
|
||||||
# Columns
|
# Columns
|
||||||
list_display = [
|
list_display = [
|
||||||
"requested_domain",
|
"requested_domain",
|
||||||
|
@ -255,7 +291,7 @@ class DomainApplicationAdmin(ListHeaderAdmin):
|
||||||
]
|
]
|
||||||
|
|
||||||
# Read only that we'll leverage for CISA Analysts
|
# Read only that we'll leverage for CISA Analysts
|
||||||
readonly_fields = [
|
analyst_readonly_fields = [
|
||||||
"creator",
|
"creator",
|
||||||
"type_of_work",
|
"type_of_work",
|
||||||
"more_organization_information",
|
"more_organization_information",
|
||||||
|
@ -273,6 +309,7 @@ class DomainApplicationAdmin(ListHeaderAdmin):
|
||||||
|
|
||||||
# Trigger action when a fieldset is changed
|
# Trigger action when a fieldset is changed
|
||||||
def save_model(self, request, obj, form, change):
|
def save_model(self, request, obj, form, change):
|
||||||
|
if obj and obj.creator.status != "ineligible":
|
||||||
if change: # Check if the application is being edited
|
if change: # Check if the application is being edited
|
||||||
# Get the original application from the database
|
# Get the original application from the database
|
||||||
original_obj = models.DomainApplication.objects.get(pk=obj.pk)
|
original_obj = models.DomainApplication.objects.get(pk=obj.pk)
|
||||||
|
@ -311,14 +348,33 @@ class DomainApplicationAdmin(ListHeaderAdmin):
|
||||||
logger.warning("Unknown status selected in django admin")
|
logger.warning("Unknown status selected in django admin")
|
||||||
|
|
||||||
super().save_model(request, obj, form, change)
|
super().save_model(request, obj, form, change)
|
||||||
|
else:
|
||||||
|
messages.error(request, "This action is not permitted for applications with an ineligible creator.")
|
||||||
|
|
||||||
def get_readonly_fields(self, request, obj=None):
|
def get_readonly_fields(self, request, obj=None):
|
||||||
|
|
||||||
|
"""Set the read-only state on form elements.
|
||||||
|
We have 2 conditions that determine which fields are read-only:
|
||||||
|
admin user permissions and the application creator's status, so
|
||||||
|
we'll use the baseline readonly_fields and extend it as needed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
readonly_fields = list(self.readonly_fields)
|
||||||
|
|
||||||
|
# Check if the creator is ineligible
|
||||||
|
if obj and obj.creator.status == "ineligible":
|
||||||
|
# For fields like CharField, IntegerField, etc., the widget used is straightforward,
|
||||||
|
# and the readonly_fields list can control their behavior
|
||||||
|
readonly_fields.extend([field.name for field in self.model._meta.fields])
|
||||||
|
# Add the multi-select fields to readonly_fields:
|
||||||
|
# Complex fields like ManyToManyField require special handling
|
||||||
|
readonly_fields.extend(["current_websites", "other_contacts", "alternative_domains"])
|
||||||
|
|
||||||
if request.user.is_superuser:
|
if request.user.is_superuser:
|
||||||
# Superusers have full access, no fields are read-only
|
return readonly_fields
|
||||||
return []
|
|
||||||
else:
|
else:
|
||||||
# Regular users can only view the specified fields
|
readonly_fields.extend([field for field in self.analyst_readonly_fields])
|
||||||
return self.readonly_fields
|
return readonly_fields
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(models.User, MyUserAdmin)
|
admin.site.register(models.User, MyUserAdmin)
|
||||||
|
|
|
@ -4,6 +4,7 @@ from registrar.admin import DomainApplicationAdmin, ListHeaderAdmin, MyUserAdmin
|
||||||
from registrar.models import DomainApplication, DomainInformation, User
|
from registrar.models import DomainApplication, DomainInformation, User
|
||||||
from .common import completed_application, mock_user, create_superuser, create_user
|
from .common import completed_application, mock_user, create_superuser, create_user
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
@ -14,6 +15,9 @@ class TestDomainApplicationAdmin(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.site = AdminSite()
|
self.site = AdminSite()
|
||||||
self.factory = RequestFactory()
|
self.factory = RequestFactory()
|
||||||
|
self.admin = DomainApplicationAdmin(model=DomainApplication, admin_site=self.site)
|
||||||
|
self.superuser = create_superuser()
|
||||||
|
self.staffuser = create_user()
|
||||||
|
|
||||||
@boto3_mocking.patching
|
@boto3_mocking.patching
|
||||||
def test_save_model_sends_submitted_email(self):
|
def test_save_model_sends_submitted_email(self):
|
||||||
|
@ -33,14 +37,11 @@ class TestDomainApplicationAdmin(TestCase):
|
||||||
"/admin/registrar/domainapplication/{}/change/".format(application.pk)
|
"/admin/registrar/domainapplication/{}/change/".format(application.pk)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create an instance of the model admin
|
|
||||||
model_admin = DomainApplicationAdmin(DomainApplication, self.site)
|
|
||||||
|
|
||||||
# Modify the application's property
|
# Modify the application's property
|
||||||
application.status = DomainApplication.SUBMITTED
|
application.status = DomainApplication.SUBMITTED
|
||||||
|
|
||||||
# Use the model admin's save_model method
|
# Use the model admin's save_model method
|
||||||
model_admin.save_model(request, application, form=None, change=True)
|
self.admin.save_model(request, application, form=None, change=True)
|
||||||
|
|
||||||
# Access the arguments passed to send_email
|
# Access the arguments passed to send_email
|
||||||
call_args = mock_client_instance.send_email.call_args
|
call_args = mock_client_instance.send_email.call_args
|
||||||
|
@ -79,14 +80,11 @@ class TestDomainApplicationAdmin(TestCase):
|
||||||
"/admin/registrar/domainapplication/{}/change/".format(application.pk)
|
"/admin/registrar/domainapplication/{}/change/".format(application.pk)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create an instance of the model admin
|
|
||||||
model_admin = DomainApplicationAdmin(DomainApplication, self.site)
|
|
||||||
|
|
||||||
# Modify the application's property
|
# Modify the application's property
|
||||||
application.status = DomainApplication.IN_REVIEW
|
application.status = DomainApplication.IN_REVIEW
|
||||||
|
|
||||||
# Use the model admin's save_model method
|
# Use the model admin's save_model method
|
||||||
model_admin.save_model(request, application, form=None, change=True)
|
self.admin.save_model(request, application, form=None, change=True)
|
||||||
|
|
||||||
# Access the arguments passed to send_email
|
# Access the arguments passed to send_email
|
||||||
call_args = mock_client_instance.send_email.call_args
|
call_args = mock_client_instance.send_email.call_args
|
||||||
|
@ -125,14 +123,11 @@ class TestDomainApplicationAdmin(TestCase):
|
||||||
"/admin/registrar/domainapplication/{}/change/".format(application.pk)
|
"/admin/registrar/domainapplication/{}/change/".format(application.pk)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create an instance of the model admin
|
|
||||||
model_admin = DomainApplicationAdmin(DomainApplication, self.site)
|
|
||||||
|
|
||||||
# Modify the application's property
|
# Modify the application's property
|
||||||
application.status = DomainApplication.APPROVED
|
application.status = DomainApplication.APPROVED
|
||||||
|
|
||||||
# Use the model admin's save_model method
|
# Use the model admin's save_model method
|
||||||
model_admin.save_model(request, application, form=None, change=True)
|
self.admin.save_model(request, application, form=None, change=True)
|
||||||
|
|
||||||
# Access the arguments passed to send_email
|
# Access the arguments passed to send_email
|
||||||
call_args = mock_client_instance.send_email.call_args
|
call_args = mock_client_instance.send_email.call_args
|
||||||
|
@ -166,14 +161,11 @@ class TestDomainApplicationAdmin(TestCase):
|
||||||
"/admin/registrar/domainapplication/{}/change/".format(application.pk)
|
"/admin/registrar/domainapplication/{}/change/".format(application.pk)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create an instance of the model admin
|
|
||||||
model_admin = DomainApplicationAdmin(DomainApplication, self.site)
|
|
||||||
|
|
||||||
# Modify the application's property
|
# Modify the application's property
|
||||||
application.status = DomainApplication.APPROVED
|
application.status = DomainApplication.APPROVED
|
||||||
|
|
||||||
# Use the model admin's save_model method
|
# Use the model admin's save_model method
|
||||||
model_admin.save_model(request, application, form=None, change=True)
|
self.admin.save_model(request, application, form=None, change=True)
|
||||||
|
|
||||||
# Test that approved domain exists and equals requested domain
|
# Test that approved domain exists and equals requested domain
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
@ -198,14 +190,11 @@ class TestDomainApplicationAdmin(TestCase):
|
||||||
"/admin/registrar/domainapplication/{}/change/".format(application.pk)
|
"/admin/registrar/domainapplication/{}/change/".format(application.pk)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create an instance of the model admin
|
|
||||||
model_admin = DomainApplicationAdmin(DomainApplication, self.site)
|
|
||||||
|
|
||||||
# Modify the application's property
|
# Modify the application's property
|
||||||
application.status = DomainApplication.ACTION_NEEDED
|
application.status = DomainApplication.ACTION_NEEDED
|
||||||
|
|
||||||
# Use the model admin's save_model method
|
# Use the model admin's save_model method
|
||||||
model_admin.save_model(request, application, form=None, change=True)
|
self.admin.save_model(request, application, form=None, change=True)
|
||||||
|
|
||||||
# Access the arguments passed to send_email
|
# Access the arguments passed to send_email
|
||||||
call_args = mock_client_instance.send_email.call_args
|
call_args = mock_client_instance.send_email.call_args
|
||||||
|
@ -247,14 +236,11 @@ class TestDomainApplicationAdmin(TestCase):
|
||||||
"/admin/registrar/domainapplication/{}/change/".format(application.pk)
|
"/admin/registrar/domainapplication/{}/change/".format(application.pk)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create an instance of the model admin
|
|
||||||
model_admin = DomainApplicationAdmin(DomainApplication, self.site)
|
|
||||||
|
|
||||||
# Modify the application's property
|
# Modify the application's property
|
||||||
application.status = DomainApplication.REJECTED
|
application.status = DomainApplication.REJECTED
|
||||||
|
|
||||||
# Use the model admin's save_model method
|
# Use the model admin's save_model method
|
||||||
model_admin.save_model(request, application, form=None, change=True)
|
self.admin.save_model(request, application, form=None, change=True)
|
||||||
|
|
||||||
# Access the arguments passed to send_email
|
# Access the arguments passed to send_email
|
||||||
call_args = mock_client_instance.send_email.call_args
|
call_args = mock_client_instance.send_email.call_args
|
||||||
|
@ -288,20 +274,71 @@ class TestDomainApplicationAdmin(TestCase):
|
||||||
"/admin/registrar/domainapplication/{}/change/".format(application.pk)
|
"/admin/registrar/domainapplication/{}/change/".format(application.pk)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create an instance of the model admin
|
|
||||||
model_admin = DomainApplicationAdmin(DomainApplication, self.site)
|
|
||||||
|
|
||||||
# Modify the application's property
|
# Modify the application's property
|
||||||
application.status = DomainApplication.INELIGIBLE
|
application.status = DomainApplication.INELIGIBLE
|
||||||
|
|
||||||
# Use the model admin's save_model method
|
# Use the model admin's save_model method
|
||||||
model_admin.save_model(request, application, form=None, change=True)
|
self.admin.save_model(request, application, form=None, change=True)
|
||||||
|
|
||||||
# Test that approved domain exists and equals requested domain
|
# Test that approved domain exists and equals requested domain
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
application.creator.status, "ineligible"
|
application.creator.status, "ineligible"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_readonly_when_ineligible_creator(self):
|
||||||
|
application = completed_application(status=DomainApplication.IN_REVIEW)
|
||||||
|
application.creator.status = 'ineligible'
|
||||||
|
application.creator.save()
|
||||||
|
|
||||||
|
request = self.factory.get('/')
|
||||||
|
request.user = self.superuser
|
||||||
|
|
||||||
|
readonly_fields = self.admin.get_readonly_fields(request, application)
|
||||||
|
|
||||||
|
expected_fields = ['id', 'created_at', 'updated_at', 'status', 'creator', 'investigator', 'organization_type', 'federally_recognized_tribe', 'state_recognized_tribe', 'tribe_name', 'federal_agency', 'federal_type', 'is_election_board', 'organization_name', 'address_line1', 'address_line2', 'city', 'state_territory', 'zipcode', 'urbanization', 'type_of_work', 'more_organization_information', 'authorizing_official', 'approved_domain', 'requested_domain', 'submitter', 'purpose', 'no_other_contacts_rationale', 'anything_else', 'is_policy_acknowledged', 'current_websites', 'other_contacts', 'alternative_domains']
|
||||||
|
|
||||||
|
self.assertEqual(readonly_fields, expected_fields)
|
||||||
|
|
||||||
|
def test_readonly_fields_for_analyst(self):
|
||||||
|
request = self.factory.get('/') # Use the correct method and path
|
||||||
|
request.user = self.staffuser
|
||||||
|
|
||||||
|
readonly_fields = self.admin.get_readonly_fields(request)
|
||||||
|
|
||||||
|
expected_fields = ['creator', 'type_of_work', 'more_organization_information', 'address_line1', 'address_line2', 'zipcode', 'requested_domain', 'alternative_domains', 'purpose', 'submitter', 'no_other_contacts_rationale', 'anything_else', 'is_policy_acknowledged']
|
||||||
|
|
||||||
|
self.assertEqual(readonly_fields, expected_fields)
|
||||||
|
|
||||||
|
def test_readonly_fields_for_superuser(self):
|
||||||
|
request = self.factory.get('/') # Use the correct method and path
|
||||||
|
request.user = self.superuser
|
||||||
|
|
||||||
|
readonly_fields = self.admin.get_readonly_fields(request)
|
||||||
|
|
||||||
|
expected_fields = []
|
||||||
|
|
||||||
|
self.assertEqual(readonly_fields, expected_fields)
|
||||||
|
|
||||||
|
def test_saving_when_ineligible_creator(self):
|
||||||
|
# Create an instance of the model
|
||||||
|
application = completed_application(status=DomainApplication.IN_REVIEW)
|
||||||
|
application.creator.status = 'ineligible'
|
||||||
|
application.creator.save()
|
||||||
|
|
||||||
|
# Create a request object with a superuser
|
||||||
|
request = self.factory.get('/')
|
||||||
|
request.user = self.superuser
|
||||||
|
|
||||||
|
with patch('django.contrib.messages.error') as mock_error:
|
||||||
|
# Simulate saving the model
|
||||||
|
self.admin.save_model(request, application, None, False)
|
||||||
|
|
||||||
|
# Assert that the error message was called with the correct argument
|
||||||
|
mock_error.assert_called_once_with(request, "This action is not permitted for applications with an ineligible creator.")
|
||||||
|
|
||||||
|
# Assert that the status has not changed
|
||||||
|
self.assertEqual(application.status, DomainApplication.IN_REVIEW)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
DomainInformation.objects.all().delete()
|
DomainInformation.objects.all().delete()
|
||||||
DomainApplication.objects.all().delete()
|
DomainApplication.objects.all().delete()
|
||||||
|
@ -381,7 +418,6 @@ class ListHeaderAdminTest(TestCase):
|
||||||
DomainInformation.objects.all().delete()
|
DomainInformation.objects.all().delete()
|
||||||
DomainApplication.objects.all().delete()
|
DomainApplication.objects.all().delete()
|
||||||
User.objects.all().delete()
|
User.objects.all().delete()
|
||||||
self.superuser.delete()
|
|
||||||
|
|
||||||
|
|
||||||
class MyUserAdminTest(TestCase):
|
class MyUserAdminTest(TestCase):
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue