Repository: cisagov/manage.get.gov

Issue:
Django admin: Remove "draft domains" & "websites" sections from the navigation for analysts
### Issue description

Remove "Draft domains" section from the navigation for analysts. 

This section displays the domain names that appear in a domain request. Cameron noted this section of the admin does not need to be exposed to analysts anymore.

### Acceptance criteria

- [x] "Draft domains" is removed from the navigation for users with "analyst" permission
- [x] Analyst is still able to edit the "requested domain" via the domain-request page.
- [x] "Websites" is removed from the navigation for users with "analyst" permission
- [x] Analyst is still able to edit "alternative domains" via the domain-request page. (Note: "current websites" are no longer editable via the domain-request page for analysts; that should remain as is)

### Additional context

This is an update that came out of the [recent audit](https://docs.google.com/spreadsheets/d/1dXe28ZpyghFpxB_T-GZsDpFUKb96AdztsNPhFL0BvpE/edit#gid=616839927) of Django field labels. And from user feedback gathered during the Diary Study.

### Links to other issues

Related to: #1924 


Discussion:
This ticket is accepted to the backlog because it was split from a current backlog ticket. 
Added "refinement" label. Need to determine how the field can still be edited within a request ... after the section is removed from navigation. Also determine whether the "websites" section should be removed from navigation, as well. 

Resolving patch:
diff --git a/src/registrar/admin.py b/src/registrar/admin.py
--- a/src/registrar/admin.py
+++ b/src/registrar/admin.py
@@ -779,6 +779,46 @@ class WebsiteAdmin(ListHeaderAdmin):
     ]
     search_help_text = "Search by website."
 
+    def get_model_perms(self, request):
+        """
+        Return empty perms dict thus hiding the model from admin index.
+        """
+        superuser_perm = request.user.has_perm("registrar.full_access_permission")
+        analyst_perm = request.user.has_perm("registrar.analyst_access_permission")
+        if analyst_perm and not superuser_perm:
+            return {}
+        return super().get_model_perms(request)
+
+    def has_change_permission(self, request, obj=None):
+        """
+        Allow analysts to access the change form directly via URL.
+        """
+        superuser_perm = request.user.has_perm("registrar.full_access_permission")
+        analyst_perm = request.user.has_perm("registrar.analyst_access_permission")
+        if analyst_perm and not superuser_perm:
+            return True
+        return super().has_change_permission(request, obj)
+
+    def response_change(self, request, obj):
+        """
+        Override to redirect users back to the previous page after saving.
+        """
+        superuser_perm = request.user.has_perm("registrar.full_access_permission")
+        analyst_perm = request.user.has_perm("registrar.analyst_access_permission")
+        return_path = request.GET.get("return_path")
+
+        # First, call the super method to perform the standard operations and capture the response
+        response = super().response_change(request, obj)
+
+        # Don't redirect to the website page on save if the user is an analyst.
+        # Rather, just redirect back to the originating page.
+        if (analyst_perm and not superuser_perm) and return_path:
+            # Redirect to the return path if it exists
+            return HttpResponseRedirect(return_path)
+
+        # If no redirection is needed, return the original response
+        return response
+
 
 class UserDomainRoleAdmin(ListHeaderAdmin):
     """Custom user domain role admin class."""
@@ -1466,7 +1506,10 @@ class DomainInformationInline(admin.StackedInline):
     def has_change_permission(self, request, obj=None):
         """Custom has_change_permission override so that we can specify that
         analysts can edit this through this inline, but not through the model normally"""
-        if request.user.has_perm("registrar.analyst_access_permission"):
+
+        superuser_perm = request.user.has_perm("registrar.full_access_permission")
+        analyst_perm = request.user.has_perm("registrar.analyst_access_permission")
+        if analyst_perm and not superuser_perm:
             return True
         return super().has_change_permission(request, obj)
 
@@ -1896,6 +1939,46 @@ class DraftDomainAdmin(ListHeaderAdmin):
     # in autocomplete_fields for user
     ordering = ["name"]
 
+    def get_model_perms(self, request):
+        """
+        Return empty perms dict thus hiding the model from admin index.
+        """
+        superuser_perm = request.user.has_perm("registrar.full_access_permission")
+        analyst_perm = request.user.has_perm("registrar.analyst_access_permission")
+        if analyst_perm and not superuser_perm:
+            return {}
+        return super().get_model_perms(request)
+
+    def has_change_permission(self, request, obj=None):
+        """
+        Allow analysts to access the change form directly via URL.
+        """
+        superuser_perm = request.user.has_perm("registrar.full_access_permission")
+        analyst_perm = request.user.has_perm("registrar.analyst_access_permission")
+        if analyst_perm and not superuser_perm:
+            return True
+        return super().has_change_permission(request, obj)
+
+    def response_change(self, request, obj):
+        """
+        Override to redirect users back to the previous page after saving.
+        """
+        superuser_perm = request.user.has_perm("registrar.full_access_permission")
+        analyst_perm = request.user.has_perm("registrar.analyst_access_permission")
+        return_path = request.GET.get("return_path")
+
+        # First, call the super method to perform the standard operations and capture the response
+        response = super().response_change(request, obj)
+
+        # Don't redirect to the website page on save if the user is an analyst.
+        # Rather, just redirect back to the originating page.
+        if (analyst_perm and not superuser_perm) and return_path:
+            # Redirect to the return path if it exists
+            return HttpResponseRedirect(return_path)
+
+        # If no redirection is needed, return the original response
+        return response
+
 
 class PublicContactAdmin(ListHeaderAdmin):
     """Custom PublicContact admin class."""
diff --git a/src/registrar/migrations/0084_create_groups_v11.py b/src/registrar/migrations/0084_create_groups_v11.py
new file mode 100644
--- /dev/null
+++ b/src/registrar/migrations/0084_create_groups_v11.py
@@ -0,0 +1,37 @@
+# This migration creates the create_full_access_group and create_cisa_analyst_group groups
+# It is dependent on 0079 (which populates federal agencies)
+# If permissions on the groups need changing, edit CISA_ANALYST_GROUP_PERMISSIONS
+# in the user_group model then:
+# [NOT RECOMMENDED]
+# step 1: docker-compose exec app ./manage.py migrate --fake registrar 0035_contenttypes_permissions
+# step 2: docker-compose exec app ./manage.py migrate registrar 0036_create_groups
+# step 3: fake run the latest migration in the migrations list
+# [RECOMMENDED]
+# Alternatively:
+# step 1: duplicate the migration that loads data
+# step 2: docker-compose exec app ./manage.py migrate
+
+from django.db import migrations
+from registrar.models import UserGroup
+from typing import Any
+
+
+# For linting: RunPython expects a function reference,
+# so let's give it one
+def create_groups(apps, schema_editor) -> Any:
+    UserGroup.create_cisa_analyst_group(apps, schema_editor)
+    UserGroup.create_full_access_group(apps, schema_editor)
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("registrar", "0083_alter_contact_email_alter_publiccontact_email"),
+    ]
+
+    operations = [
+        migrations.RunPython(
+            create_groups,
+            reverse_code=migrations.RunPython.noop,
+            atomic=True,
+        ),
+    ]
diff --git a/src/registrar/models/user_group.py b/src/registrar/models/user_group.py
--- a/src/registrar/models/user_group.py
+++ b/src/registrar/models/user_group.py
@@ -41,11 +41,6 @@ def create_cisa_analyst_group(apps, schema_editor):
                 "model": "domain",
                 "permissions": ["view_domain"],
             },
-            {
-                "app_label": "registrar",
-                "model": "draftdomain",
-                "permissions": ["change_draftdomain"],
-            },
             {
                 "app_label": "registrar",
                 "model": "user",
@@ -56,11 +51,6 @@ def create_cisa_analyst_group(apps, schema_editor):
                 "model": "domaininvitation",
                 "permissions": ["add_domaininvitation", "view_domaininvitation"],
             },
-            {
-                "app_label": "registrar",
-                "model": "website",
-                "permissions": ["change_website"],
-            },
             {
                 "app_label": "registrar",
                 "model": "userdomainrole",


Test patch:
diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py
--- a/src/registrar/tests/test_admin.py
+++ b/src/registrar/tests/test_admin.py
@@ -21,7 +21,16 @@
     UserDomainRoleAdmin,
     VerifiedByStaffAdmin,
 )
-from registrar.models import Domain, DomainRequest, DomainInformation, User, DomainInvitation, Contact, Website
+from registrar.models import (
+    Domain,
+    DomainRequest,
+    DomainInformation,
+    User,
+    DomainInvitation,
+    Contact,
+    Website,
+    DraftDomain,
+)
 from registrar.models.user_domain_role import UserDomainRole
 from registrar.models.verified_by_staff import VerifiedByStaff
 from .common import (
@@ -703,6 +712,126 @@ def setUp(self):
         )
         self.mock_client = MockSESClient()
 
+    @less_console_noise_decorator
+    def test_analyst_can_see_and_edit_alternative_domain(self):
+        """Tests if an analyst can still see and edit the alternative domain field"""
+
+        # Create fake creator
+        _creator = User.objects.create(
+            username="MrMeoward",
+            first_name="Meoward",
+            last_name="Jones",
+        )
+
+        # Create a fake domain request
+        _domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW, user=_creator)
+
+        fake_website = Website.objects.create(website="thisisatest.gov")
+        _domain_request.alternative_domains.add(fake_website)
+        _domain_request.save()
+
+        p = "userpass"
+        self.client.login(username="staffuser", password=p)
+        response = self.client.get(
+            "/admin/registrar/domainrequest/{}/change/".format(_domain_request.pk),
+            follow=True,
+        )
+
+        # Make sure the page loaded, and that we're on the right page
+        self.assertEqual(response.status_code, 200)
+        self.assertContains(response, _domain_request.requested_domain.name)
+
+        # Test if the page has the alternative domain
+        self.assertContains(response, "thisisatest.gov")
+
+        # Check that the page contains the url we expect
+        expected_href = reverse("admin:registrar_website_change", args=[fake_website.id])
+        self.assertContains(response, expected_href)
+
+        # Navigate to the website to ensure that we can still edit it
+        response = self.client.get(
+            "/admin/registrar/website/{}/change/".format(fake_website.pk),
+            follow=True,
+        )
+
+        # Make sure the page loaded, and that we're on the right page
+        self.assertEqual(response.status_code, 200)
+        self.assertContains(response, "thisisatest.gov")
+
+    @less_console_noise_decorator
+    def test_analyst_can_see_and_edit_requested_domain(self):
+        """Tests if an analyst can still see and edit the requested domain field"""
+
+        # Create fake creator
+        _creator = User.objects.create(
+            username="MrMeoward",
+            first_name="Meoward",
+            last_name="Jones",
+        )
+
+        # Create a fake domain request
+        _domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW, user=_creator)
+
+        p = "userpass"
+        self.client.login(username="staffuser", password=p)
+        response = self.client.get(
+            "/admin/registrar/domainrequest/{}/change/".format(_domain_request.pk),
+            follow=True,
+        )
+
+        # Filter to get the latest from the DB (rather than direct assignment)
+        requested_domain = DraftDomain.objects.filter(name=_domain_request.requested_domain.name).get()
+
+        # Make sure the page loaded, and that we're on the right page
+        self.assertEqual(response.status_code, 200)
+        self.assertContains(response, requested_domain.name)
+
+        # Check that the page contains the url we expect
+        expected_href = reverse("admin:registrar_draftdomain_change", args=[requested_domain.id])
+        self.assertContains(response, expected_href)
+
+        # Navigate to the website to ensure that we can still edit it
+        response = self.client.get(
+            "/admin/registrar/draftdomain/{}/change/".format(requested_domain.pk),
+            follow=True,
+        )
+
+        # Make sure the page loaded, and that we're on the right page
+        self.assertEqual(response.status_code, 200)
+        self.assertContains(response, "city.gov")
+
+    @less_console_noise_decorator
+    def test_analyst_can_see_current_websites(self):
+        """Tests if an analyst can still see current website field"""
+
+        # Create fake creator
+        _creator = User.objects.create(
+            username="MrMeoward",
+            first_name="Meoward",
+            last_name="Jones",
+        )
+
+        # Create a fake domain request
+        _domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW, user=_creator)
+
+        fake_website = Website.objects.create(website="thisisatest.gov")
+        _domain_request.current_websites.add(fake_website)
+        _domain_request.save()
+
+        p = "userpass"
+        self.client.login(username="staffuser", password=p)
+        response = self.client.get(
+            "/admin/registrar/domainrequest/{}/change/".format(_domain_request.pk),
+            follow=True,
+        )
+
+        # Make sure the page loaded, and that we're on the right page
+        self.assertEqual(response.status_code, 200)
+        self.assertContains(response, _domain_request.requested_domain.name)
+
+        # Test if the page has the current website
+        self.assertContains(response, "thisisatest.gov")
+
     def test_domain_sortable(self):
         """Tests if the DomainRequest sorts by domain correctly"""
         with less_console_noise():
diff --git a/src/registrar/tests/test_migrations.py b/src/registrar/tests/test_migrations.py
--- a/src/registrar/tests/test_migrations.py
+++ b/src/registrar/tests/test_migrations.py
@@ -37,7 +37,6 @@ def test_groups_created(self):
             "add_domaininvitation",
             "view_domaininvitation",
             "change_domainrequest",
-            "change_draftdomain",
             "add_federalagency",
             "change_federalagency",
             "delete_federalagency",
@@ -48,7 +47,6 @@ def test_groups_created(self):
             "add_verifiedbystaff",
             "change_verifiedbystaff",
             "delete_verifiedbystaff",
-            "change_website",
         ]
 
         # Get the codenames of actual permissions associated with the group