mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-22 18:56:15 +02:00
Merge pull request #2255 from cisagov/rjm/2006-associated-domains-requests
Issue #2006: Display associated domains and requests on user change form - [backup]
This commit is contained in:
commit
265609208f
5 changed files with 179 additions and 16 deletions
|
@ -15,6 +15,7 @@ from django.contrib.contenttypes.models import ContentType
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from dateutil.relativedelta import relativedelta # type: ignore
|
from dateutil.relativedelta import relativedelta # type: ignore
|
||||||
from epplibwrapper.errors import ErrorCode, RegistryError
|
from epplibwrapper.errors import ErrorCode, RegistryError
|
||||||
|
from registrar.models.user_domain_role import UserDomainRole
|
||||||
from waffle.admin import FlagAdmin
|
from waffle.admin import FlagAdmin
|
||||||
from waffle.models import Sample, Switch
|
from waffle.models import Sample, Switch
|
||||||
from registrar.models import Contact, Domain, DomainRequest, DraftDomain, User, Website
|
from registrar.models import Contact, Domain, DomainRequest, DraftDomain, User, Website
|
||||||
|
@ -588,6 +589,7 @@ class MyUserAdmin(BaseUserAdmin, ImportExportModelAdmin):
|
||||||
resource_classes = [UserResource]
|
resource_classes = [UserResource]
|
||||||
|
|
||||||
form = MyUserAdminForm
|
form = MyUserAdminForm
|
||||||
|
change_form_template = "django/admin/user_change_form.html"
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Contains meta information about this class"""
|
"""Contains meta information about this class"""
|
||||||
|
@ -627,7 +629,7 @@ class MyUserAdmin(BaseUserAdmin, ImportExportModelAdmin):
|
||||||
None,
|
None,
|
||||||
{"fields": ("username", "password", "status", "verification_type")},
|
{"fields": ("username", "password", "status", "verification_type")},
|
||||||
),
|
),
|
||||||
("Personal Info", {"fields": ("first_name", "middle_name", "last_name", "title", "email", "phone")}),
|
("Personal info", {"fields": ("first_name", "middle_name", "last_name", "title", "email", "phone")}),
|
||||||
(
|
(
|
||||||
"Permissions",
|
"Permissions",
|
||||||
{
|
{
|
||||||
|
@ -706,8 +708,6 @@ class MyUserAdmin(BaseUserAdmin, ImportExportModelAdmin):
|
||||||
ordering = ["first_name", "last_name", "email"]
|
ordering = ["first_name", "last_name", "email"]
|
||||||
search_help_text = "Search by first name, last name, or email."
|
search_help_text = "Search by first name, last name, or email."
|
||||||
|
|
||||||
change_form_template = "django/admin/email_clipboard_change_form.html"
|
|
||||||
|
|
||||||
def get_search_results(self, request, queryset, search_term):
|
def get_search_results(self, request, queryset, search_term):
|
||||||
"""
|
"""
|
||||||
Override for get_search_results. This affects any upstream model using autocomplete_fields,
|
Override for get_search_results. This affects any upstream model using autocomplete_fields,
|
||||||
|
@ -787,6 +787,23 @@ class MyUserAdmin(BaseUserAdmin, ImportExportModelAdmin):
|
||||||
# users who might not belong to groups
|
# users who might not belong to groups
|
||||||
return self.analyst_readonly_fields
|
return self.analyst_readonly_fields
|
||||||
|
|
||||||
|
def change_view(self, request, object_id, form_url="", extra_context=None):
|
||||||
|
"""Add user's related domains and requests to context"""
|
||||||
|
obj = self.get_object(request, object_id)
|
||||||
|
|
||||||
|
domain_requests = DomainRequest.objects.filter(creator=obj).exclude(
|
||||||
|
Q(status=DomainRequest.DomainRequestStatus.STARTED) | Q(status=DomainRequest.DomainRequestStatus.WITHDRAWN)
|
||||||
|
)
|
||||||
|
sort_by = request.GET.get("sort_by", "requested_domain__name")
|
||||||
|
domain_requests = domain_requests.order_by(sort_by)
|
||||||
|
|
||||||
|
user_domain_roles = UserDomainRole.objects.filter(user=obj)
|
||||||
|
domain_ids = user_domain_roles.values_list("domain_id", flat=True)
|
||||||
|
domains = Domain.objects.filter(id__in=domain_ids).exclude(state=Domain.State.DELETED)
|
||||||
|
|
||||||
|
extra_context = {"domain_requests": domain_requests, "domains": domains}
|
||||||
|
return super().change_view(request, object_id, form_url, extra_context)
|
||||||
|
|
||||||
|
|
||||||
class HostIPInline(admin.StackedInline):
|
class HostIPInline(admin.StackedInline):
|
||||||
"""Edit an ip address on the host page."""
|
"""Edit an ip address on the host page."""
|
||||||
|
|
|
@ -130,7 +130,7 @@ html[data-theme="light"] {
|
||||||
// Sets darker color on delete page links.
|
// Sets darker color on delete page links.
|
||||||
// Remove when dark mode successfully applies to Django delete page.
|
// Remove when dark mode successfully applies to Django delete page.
|
||||||
.delete-confirmation .content a:not(.button) {
|
.delete-confirmation .content a:not(.button) {
|
||||||
color: #005288;
|
color: color('primary');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,7 +159,7 @@ html[data-theme="dark"] {
|
||||||
// Sets darker color on delete page links.
|
// Sets darker color on delete page links.
|
||||||
// Remove when dark mode successfully applies to Django delete page.
|
// Remove when dark mode successfully applies to Django delete page.
|
||||||
.delete-confirmation .content a:not(.button) {
|
.delete-confirmation .content a:not(.button) {
|
||||||
color: #005288;
|
color: color('primary');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,6 +186,14 @@ div#content > h2 {
|
||||||
margin: units(2) 0 units(1) 0;
|
margin: units(2) 0 units(1) 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.module ul.padding-0 {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module ul.margin-0 {
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
.change-list {
|
.change-list {
|
||||||
.usa-table--striped tbody tr:nth-child(odd) td,
|
.usa-table--striped tbody tr:nth-child(odd) td,
|
||||||
.usa-table--striped tbody tr:nth-child(odd) th,
|
.usa-table--striped tbody tr:nth-child(odd) th,
|
||||||
|
@ -732,7 +740,7 @@ div.dja__model-description{
|
||||||
|
|
||||||
a, a:link, a:visited {
|
a, a:link, a:visited {
|
||||||
font-size: medium;
|
font-size: medium;
|
||||||
color: #005288 !important;
|
color: color('primary') !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.dja__model-description--no-overflow {
|
&.dja__model-description--no-overflow {
|
||||||
|
@ -761,3 +769,7 @@ div.dja__model-description{
|
||||||
.usa-summary-box h3 {
|
.usa-summary-box h3 {
|
||||||
color: #{$dhs-blue-60};
|
color: #{$dhs-blue-60};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.module caption, .inline-group h2 {
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
|
36
src/registrar/templates/django/admin/user_change_form.html
Normal file
36
src/registrar/templates/django/admin/user_change_form.html
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
{% extends 'django/admin/email_clipboard_change_form.html' %}
|
||||||
|
{% load i18n static %}
|
||||||
|
|
||||||
|
{% block after_related_objects %}
|
||||||
|
<div class="module aligned padding-3">
|
||||||
|
<h2>Associated requests and domains</h2>
|
||||||
|
<div class="grid-row grid-gap mobile:padding-x-1 desktop:padding-x-4">
|
||||||
|
<div class="mobile:grid-col-12 tablet:grid-col-6 desktop:grid-col-4">
|
||||||
|
<h3>Domain requests</h3>
|
||||||
|
<ul class="margin-0 padding-0">
|
||||||
|
{% for domain_request in domain_requests %}
|
||||||
|
<li>
|
||||||
|
<a href="{% url 'admin:registrar_domainrequest_change' domain_request.pk %}">
|
||||||
|
{{ domain_request.requested_domain }}
|
||||||
|
</a>
|
||||||
|
({{ domain_request.status }})
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="mobile:grid-col-12 tablet:grid-col-6 desktop:grid-col-4">
|
||||||
|
<h3>Domains</h3>
|
||||||
|
<ul class="margin-0 padding-0">
|
||||||
|
{% for domain in domains %}
|
||||||
|
<li>
|
||||||
|
<a href="{% url 'admin:registrar_domain_change' domain.pk %}">
|
||||||
|
{{ domain.name }}
|
||||||
|
</a>
|
||||||
|
({{ domain.state }})
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -667,7 +667,7 @@ class MockDb(TestCase):
|
||||||
is_election_board=False,
|
is_election_board=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
meoward_user = get_user_model().objects.create(
|
self.meoward_user = get_user_model().objects.create(
|
||||||
username="meoward_username", first_name="first_meoward", last_name="last_meoward", email="meoward@rocks.com"
|
username="meoward_username", first_name="first_meoward", last_name="last_meoward", email="meoward@rocks.com"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -676,7 +676,7 @@ class MockDb(TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
_, created = UserDomainRole.objects.get_or_create(
|
_, created = UserDomainRole.objects.get_or_create(
|
||||||
user=meoward_user, domain=self.domain_1, role=UserDomainRole.Roles.MANAGER
|
user=self.meoward_user, domain=self.domain_1, role=UserDomainRole.Roles.MANAGER
|
||||||
)
|
)
|
||||||
|
|
||||||
_, created = UserDomainRole.objects.get_or_create(
|
_, created = UserDomainRole.objects.get_or_create(
|
||||||
|
@ -688,19 +688,21 @@ class MockDb(TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
_, created = UserDomainRole.objects.get_or_create(
|
_, created = UserDomainRole.objects.get_or_create(
|
||||||
user=meoward_user, domain=self.domain_2, role=UserDomainRole.Roles.MANAGER
|
user=self.meoward_user, domain=self.domain_2, role=UserDomainRole.Roles.MANAGER
|
||||||
)
|
)
|
||||||
|
|
||||||
_, created = UserDomainRole.objects.get_or_create(
|
_, created = UserDomainRole.objects.get_or_create(
|
||||||
user=meoward_user, domain=self.domain_11, role=UserDomainRole.Roles.MANAGER
|
user=self.meoward_user, domain=self.domain_11, role=UserDomainRole.Roles.MANAGER
|
||||||
)
|
)
|
||||||
|
|
||||||
_, created = UserDomainRole.objects.get_or_create(
|
_, created = UserDomainRole.objects.get_or_create(
|
||||||
user=meoward_user, domain=self.domain_12, role=UserDomainRole.Roles.MANAGER
|
user=self.meoward_user, domain=self.domain_12, role=UserDomainRole.Roles.MANAGER
|
||||||
)
|
)
|
||||||
|
|
||||||
_, created = DomainInvitation.objects.get_or_create(
|
_, created = DomainInvitation.objects.get_or_create(
|
||||||
email=meoward_user.email, domain=self.domain_1, status=DomainInvitation.DomainInvitationStatus.RETRIEVED
|
email=self.meoward_user.email,
|
||||||
|
domain=self.domain_1,
|
||||||
|
status=DomainInvitation.DomainInvitationStatus.RETRIEVED,
|
||||||
)
|
)
|
||||||
|
|
||||||
_, created = DomainInvitation.objects.get_or_create(
|
_, created = DomainInvitation.objects.get_or_create(
|
||||||
|
|
|
@ -47,6 +47,7 @@ from registrar.models import (
|
||||||
from registrar.models.user_domain_role import UserDomainRole
|
from registrar.models.user_domain_role import UserDomainRole
|
||||||
from registrar.models.verified_by_staff import VerifiedByStaff
|
from registrar.models.verified_by_staff import VerifiedByStaff
|
||||||
from .common import (
|
from .common import (
|
||||||
|
MockDb,
|
||||||
MockSESClient,
|
MockSESClient,
|
||||||
AuditedAdminMockData,
|
AuditedAdminMockData,
|
||||||
completed_domain_request,
|
completed_domain_request,
|
||||||
|
@ -3438,16 +3439,19 @@ class TestListHeaderAdmin(TestCase):
|
||||||
User.objects.all().delete()
|
User.objects.all().delete()
|
||||||
|
|
||||||
|
|
||||||
class TestMyUserAdmin(TestCase):
|
class TestMyUserAdmin(MockDb):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
admin_site = AdminSite()
|
admin_site = AdminSite()
|
||||||
self.admin = MyUserAdmin(model=get_user_model(), admin_site=admin_site)
|
self.admin = MyUserAdmin(model=get_user_model(), admin_site=admin_site)
|
||||||
self.client = Client(HTTP_HOST="localhost:8080")
|
self.client = Client(HTTP_HOST="localhost:8080")
|
||||||
self.superuser = create_superuser()
|
self.superuser = create_superuser()
|
||||||
|
self.staffuser = create_user()
|
||||||
self.test_helper = GenericTestHelper(admin=self.admin)
|
self.test_helper = GenericTestHelper(admin=self.admin)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
super().tearDown()
|
super().tearDown()
|
||||||
|
DomainRequest.objects.all().delete()
|
||||||
User.objects.all().delete()
|
User.objects.all().delete()
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
|
@ -3472,7 +3476,7 @@ class TestMyUserAdmin(TestCase):
|
||||||
"""
|
"""
|
||||||
Tests for the correct helper text on this page
|
Tests for the correct helper text on this page
|
||||||
"""
|
"""
|
||||||
user = create_user()
|
user = self.staffuser
|
||||||
|
|
||||||
p = "adminpass"
|
p = "adminpass"
|
||||||
self.client.login(username="superuser", password=p)
|
self.client.login(username="superuser", password=p)
|
||||||
|
@ -3493,10 +3497,11 @@ class TestMyUserAdmin(TestCase):
|
||||||
]
|
]
|
||||||
self.test_helper.assert_response_contains_distinct_values(response, expected_values)
|
self.test_helper.assert_response_contains_distinct_values(response, expected_values)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
def test_list_display_without_username(self):
|
def test_list_display_without_username(self):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
request = self.client.request().wsgi_request
|
request = self.client.request().wsgi_request
|
||||||
request.user = create_user()
|
request.user = self.staffuser
|
||||||
|
|
||||||
list_display = self.admin.get_list_display(request)
|
list_display = self.admin.get_list_display(request)
|
||||||
expected_list_display = [
|
expected_list_display = [
|
||||||
|
@ -3522,7 +3527,7 @@ class TestMyUserAdmin(TestCase):
|
||||||
def test_get_fieldsets_cisa_analyst(self):
|
def test_get_fieldsets_cisa_analyst(self):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
request = self.client.request().wsgi_request
|
request = self.client.request().wsgi_request
|
||||||
request.user = create_user()
|
request.user = self.staffuser
|
||||||
fieldsets = self.admin.get_fieldsets(request)
|
fieldsets = self.admin.get_fieldsets(request)
|
||||||
expected_fieldsets = (
|
expected_fieldsets = (
|
||||||
(
|
(
|
||||||
|
@ -3540,6 +3545,97 @@ class TestMyUserAdmin(TestCase):
|
||||||
)
|
)
|
||||||
self.assertEqual(fieldsets, expected_fieldsets)
|
self.assertEqual(fieldsets, expected_fieldsets)
|
||||||
|
|
||||||
|
def test_analyst_can_see_related_domains_and_requests_in_user_form(self):
|
||||||
|
"""Tests if an analyst can see the related domains and domain requests for a user in that user's form"""
|
||||||
|
|
||||||
|
# From MockDb, we have self.meoward_user which we'll use as creator
|
||||||
|
# Create fake domain requests
|
||||||
|
domain_request_started = completed_domain_request(
|
||||||
|
status=DomainRequest.DomainRequestStatus.STARTED, user=self.meoward_user, name="started.gov"
|
||||||
|
)
|
||||||
|
domain_request_submitted = completed_domain_request(
|
||||||
|
status=DomainRequest.DomainRequestStatus.SUBMITTED, user=self.meoward_user, name="submitted.gov"
|
||||||
|
)
|
||||||
|
domain_request_in_review = completed_domain_request(
|
||||||
|
status=DomainRequest.DomainRequestStatus.IN_REVIEW, user=self.meoward_user, name="in-review.gov"
|
||||||
|
)
|
||||||
|
domain_request_withdrawn = completed_domain_request(
|
||||||
|
status=DomainRequest.DomainRequestStatus.WITHDRAWN, user=self.meoward_user, name="withdrawn.gov"
|
||||||
|
)
|
||||||
|
domain_request_approved = completed_domain_request(
|
||||||
|
status=DomainRequest.DomainRequestStatus.APPROVED, user=self.meoward_user, name="approved.gov"
|
||||||
|
)
|
||||||
|
domain_request_rejected = completed_domain_request(
|
||||||
|
status=DomainRequest.DomainRequestStatus.REJECTED, user=self.meoward_user, name="rejected.gov"
|
||||||
|
)
|
||||||
|
domain_request_ineligible = completed_domain_request(
|
||||||
|
status=DomainRequest.DomainRequestStatus.INELIGIBLE, user=self.meoward_user, name="ineligible.gov"
|
||||||
|
)
|
||||||
|
|
||||||
|
# From MockDb, we have sel.meoward_user who's admin on
|
||||||
|
# self.domain_1 - READY
|
||||||
|
# self.domain_2 - DNS_NEEDED
|
||||||
|
# self.domain_11 - READY
|
||||||
|
# self.domain_12 - READY
|
||||||
|
# DELETED:
|
||||||
|
domain_deleted, _ = Domain.objects.get_or_create(
|
||||||
|
name="domain_deleted.gov", state=Domain.State.DELETED, deleted=timezone.make_aware(datetime(2024, 4, 2))
|
||||||
|
)
|
||||||
|
_, created = UserDomainRole.objects.get_or_create(
|
||||||
|
user=self.meoward_user, domain=domain_deleted, role=UserDomainRole.Roles.MANAGER
|
||||||
|
)
|
||||||
|
|
||||||
|
p = "userpass"
|
||||||
|
self.client.login(username="staffuser", password=p)
|
||||||
|
response = self.client.get(
|
||||||
|
"/admin/registrar/user/{}/change/".format(self.meoward_user.id),
|
||||||
|
follow=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Make sure the page loaded and contains the expected domain request names and links to the domain requests
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
self.assertContains(response, domain_request_submitted.requested_domain.name)
|
||||||
|
expected_href = reverse("admin:registrar_domainrequest_change", args=[domain_request_submitted.pk])
|
||||||
|
self.assertContains(response, expected_href)
|
||||||
|
|
||||||
|
self.assertContains(response, domain_request_in_review.requested_domain.name)
|
||||||
|
expected_href = reverse("admin:registrar_domainrequest_change", args=[domain_request_in_review.pk])
|
||||||
|
self.assertContains(response, expected_href)
|
||||||
|
|
||||||
|
self.assertContains(response, domain_request_approved.requested_domain.name)
|
||||||
|
expected_href = reverse("admin:registrar_domainrequest_change", args=[domain_request_approved.pk])
|
||||||
|
self.assertContains(response, expected_href)
|
||||||
|
|
||||||
|
self.assertContains(response, domain_request_rejected.requested_domain.name)
|
||||||
|
expected_href = reverse("admin:registrar_domainrequest_change", args=[domain_request_rejected.pk])
|
||||||
|
self.assertContains(response, expected_href)
|
||||||
|
|
||||||
|
self.assertContains(response, domain_request_ineligible.requested_domain.name)
|
||||||
|
expected_href = reverse("admin:registrar_domainrequest_change", args=[domain_request_ineligible.pk])
|
||||||
|
self.assertContains(response, expected_href)
|
||||||
|
|
||||||
|
# We filter out those requests
|
||||||
|
# STARTED
|
||||||
|
self.assertNotContains(response, domain_request_started.requested_domain.name)
|
||||||
|
expected_href = reverse("admin:registrar_domainrequest_change", args=[domain_request_started.pk])
|
||||||
|
self.assertNotContains(response, expected_href)
|
||||||
|
|
||||||
|
# WITHDRAWN
|
||||||
|
self.assertNotContains(response, domain_request_withdrawn.requested_domain.name)
|
||||||
|
expected_href = reverse("admin:registrar_domainrequest_change", args=[domain_request_withdrawn.pk])
|
||||||
|
self.assertNotContains(response, expected_href)
|
||||||
|
|
||||||
|
# Make sure the page contains the expected domain names and links to the domains
|
||||||
|
self.assertContains(response, self.domain_1.name)
|
||||||
|
expected_href = reverse("admin:registrar_domain_change", args=[self.domain_1.pk])
|
||||||
|
self.assertContains(response, expected_href)
|
||||||
|
|
||||||
|
# We filter out DELETED
|
||||||
|
self.assertNotContains(response, domain_deleted.name)
|
||||||
|
expected_href = reverse("admin:registrar_domain_change", args=[domain_deleted.pk])
|
||||||
|
self.assertNotContains(response, expected_href)
|
||||||
|
|
||||||
|
|
||||||
class AuditedAdminTest(TestCase):
|
class AuditedAdminTest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue