Merge branch 'main' into gd/1900-add-action-needed-reason

This commit is contained in:
zandercymatics 2024-06-10 15:23:09 -06:00
commit f2f0ebc27c
No known key found for this signature in database
GPG key ID: FF4636ABEC9682B7
17 changed files with 394 additions and 22 deletions

View file

@ -25,6 +25,8 @@ jobs:
|| startsWith(github.head_ref, 'meoward/') || startsWith(github.head_ref, 'meoward/')
|| startsWith(github.head_ref, 'bob/') || startsWith(github.head_ref, 'bob/')
|| startsWith(github.head_ref, 'cb/') || startsWith(github.head_ref, 'cb/')
|| startsWith(github.head_ref, 'hotgov/')
|| startsWith(github.head_ref, 'litterbox/')
outputs: outputs:
environment: ${{ steps.var.outputs.environment}} environment: ${{ steps.var.outputs.environment}}
runs-on: "ubuntu-latest" runs-on: "ubuntu-latest"

View file

@ -16,6 +16,8 @@ on:
- stable - stable
- staging - staging
- development - development
- litterbox
- hotgov
- cb - cb
- bob - bob
- meoward - meoward

View file

@ -16,6 +16,8 @@ on:
options: options:
- staging - staging
- development - development
- litterbox
- hotgov
- cb - cb
- bob - bob
- meoward - meoward

View file

@ -0,0 +1,32 @@
---
applications:
- name: getgov-hotgov
buildpacks:
- python_buildpack
path: ../../src
instances: 1
memory: 512M
stack: cflinuxfs4
timeout: 180
command: ./run.sh
health-check-type: http
health-check-http-endpoint: /health
health-check-invocation-timeout: 40
env:
# Send stdout and stderr straight to the terminal without buffering
PYTHONUNBUFFERED: yup
# Tell Django where to find its configuration
DJANGO_SETTINGS_MODULE: registrar.config.settings
# Tell Django where it is being hosted
DJANGO_BASE_URL: https://getgov-hotgov.app.cloud.gov
# Tell Django how much stuff to log
DJANGO_LOG_LEVEL: INFO
# default public site location
GETGOV_PUBLIC_SITE_URL: https://get.gov
# Flag to disable/enable features in prod environments
IS_PRODUCTION: False
routes:
- route: getgov-hotgov.app.cloud.gov
services:
- getgov-credentials
- getgov-hotgov-database

View file

@ -0,0 +1,32 @@
---
applications:
- name: getgov-litterbox
buildpacks:
- python_buildpack
path: ../../src
instances: 1
memory: 512M
stack: cflinuxfs4
timeout: 180
command: ./run.sh
health-check-type: http
health-check-http-endpoint: /health
health-check-invocation-timeout: 40
env:
# Send stdout and stderr straight to the terminal without buffering
PYTHONUNBUFFERED: yup
# Tell Django where to find its configuration
DJANGO_SETTINGS_MODULE: registrar.config.settings
# Tell Django where it is being hosted
DJANGO_BASE_URL: https://getgov-litterbox.app.cloud.gov
# Tell Django how much stuff to log
DJANGO_LOG_LEVEL: INFO
# default public site location
GETGOV_PUBLIC_SITE_URL: https://get.gov
# Flag to disable/enable features in prod environments
IS_PRODUCTION: False
routes:
- route: getgov-litterbox.app.cloud.gov
services:
- getgov-credentials
- getgov-litterbox-database

View file

@ -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
@ -398,6 +399,39 @@ class CustomLogEntryAdmin(LogEntryAdmin):
change_form_template = "admin/change_form_no_submit.html" change_form_template = "admin/change_form_no_submit.html"
add_form_template = "admin/change_form_no_submit.html" add_form_template = "admin/change_form_no_submit.html"
# Select log entry to change -> Log entries
def changelist_view(self, request, extra_context=None):
if extra_context is None:
extra_context = {}
extra_context["tabtitle"] = "Log entries"
return super().changelist_view(request, extra_context=extra_context)
# #786: Skipping on updating audit log tab titles for now
# def change_view(self, request, object_id, form_url="", extra_context=None):
# if extra_context is None:
# extra_context = {}
# log_entry = self.get_object(request, object_id)
# if log_entry:
# # Reset title to empty string
# extra_context["subtitle"] = ""
# extra_context["tabtitle"] = ""
# object_repr = log_entry.object_repr # Hold name of the object
# changes = log_entry.changes
# # Check if this is a log entry for an addition and related to the contact model
# # Created [name] -> Created [name] contact | Change log entry
# if (
# all(new_value != "None" for field, (old_value, new_value) in changes.items())
# and log_entry.content_type.model == "contact"
# ):
# extra_context["subtitle"] = f"Created {object_repr} contact"
# extra_context["tabtitle"] = "Change log entry"
# return super().change_view(request, object_id, form_url, extra_context=extra_context)
class AdminSortFields: class AdminSortFields:
_name_sort = ["first_name", "last_name", "email"] _name_sort = ["first_name", "last_name", "email"]
@ -570,6 +604,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"""
@ -609,7 +644,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",
{ {
@ -688,8 +723,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,
@ -769,6 +802,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."""
@ -793,6 +843,14 @@ class MyHostAdmin(AuditedAdmin, ImportExportModelAdmin):
search_help_text = "Search by domain or host name." search_help_text = "Search by domain or host name."
inlines = [HostIPInline] inlines = [HostIPInline]
# Select host to change -> Host
def changelist_view(self, request, extra_context=None):
if extra_context is None:
extra_context = {}
extra_context["tabtitle"] = "Host"
# Get the filtered values
return super().changelist_view(request, extra_context=extra_context)
class HostIpResource(resources.ModelResource): class HostIpResource(resources.ModelResource):
"""defines how each field in the referenced model should be mapped to the corresponding fields in the """defines how each field in the referenced model should be mapped to the corresponding fields in the
@ -808,6 +866,14 @@ class HostIpAdmin(AuditedAdmin, ImportExportModelAdmin):
resource_classes = [HostIpResource] resource_classes = [HostIpResource]
model = models.HostIP model = models.HostIP
# Select host ip to change -> Host ip
def changelist_view(self, request, extra_context=None):
if extra_context is None:
extra_context = {}
extra_context["tabtitle"] = "Host IP"
# Get the filtered values
return super().changelist_view(request, extra_context=extra_context)
class ContactResource(resources.ModelResource): class ContactResource(resources.ModelResource):
"""defines how each field in the referenced model should be mapped to the corresponding fields in the """defines how each field in the referenced model should be mapped to the corresponding fields in the
@ -941,6 +1007,14 @@ class ContactAdmin(ListHeaderAdmin, ImportExportModelAdmin):
return super().change_view(request, object_id, form_url, extra_context=extra_context) return super().change_view(request, object_id, form_url, extra_context=extra_context)
# Select contact to change -> Contacts
def changelist_view(self, request, extra_context=None):
if extra_context is None:
extra_context = {}
extra_context["tabtitle"] = "Contacts"
# Get the filtered values
return super().changelist_view(request, extra_context=extra_context)
class WebsiteResource(resources.ModelResource): class WebsiteResource(resources.ModelResource):
"""defines how each field in the referenced model should be mapped to the corresponding fields in the """defines how each field in the referenced model should be mapped to the corresponding fields in the
@ -1058,6 +1132,21 @@ class UserDomainRoleAdmin(ListHeaderAdmin, ImportExportModelAdmin):
else: else:
return response return response
# User Domain manager [email] is manager on domain [domain name] ->
# Domain manager [email] on [domain name]
def changeform_view(self, request, object_id=None, form_url="", extra_context=None):
if extra_context is None:
extra_context = {}
if object_id:
obj = self.get_object(request, object_id)
if obj:
email = obj.user.email
domain_name = obj.domain.name
extra_context["subtitle"] = f"Domain manager {email} on {domain_name}"
return super().changeform_view(request, object_id, form_url, extra_context=extra_context)
class DomainInvitationAdmin(ListHeaderAdmin): class DomainInvitationAdmin(ListHeaderAdmin):
"""Custom domain invitation admin class.""" """Custom domain invitation admin class."""
@ -1094,6 +1183,14 @@ class DomainInvitationAdmin(ListHeaderAdmin):
change_form_template = "django/admin/email_clipboard_change_form.html" change_form_template = "django/admin/email_clipboard_change_form.html"
# Select domain invitations to change -> Domain invitations
def changelist_view(self, request, extra_context=None):
if extra_context is None:
extra_context = {}
extra_context["tabtitle"] = "Domain invitations"
# Get the filtered values
return super().changelist_view(request, extra_context=extra_context)
class DomainInformationResource(resources.ModelResource): class DomainInformationResource(resources.ModelResource):
"""defines how each field in the referenced model should be mapped to the corresponding fields in the """defines how each field in the referenced model should be mapped to the corresponding fields in the
@ -1234,6 +1331,14 @@ class DomainInformationAdmin(ListHeaderAdmin, ImportExportModelAdmin):
readonly_fields.extend([field for field in self.analyst_readonly_fields]) readonly_fields.extend([field for field in self.analyst_readonly_fields])
return readonly_fields # Read-only fields for analysts return readonly_fields # Read-only fields for analysts
# Select domain information to change -> Domain information
def changelist_view(self, request, extra_context=None):
if extra_context is None:
extra_context = {}
extra_context["tabtitle"] = "Domain information"
# Get the filtered values
return super().changelist_view(request, extra_context=extra_context)
class DomainRequestResource(FsmModelResource): class DomainRequestResource(FsmModelResource):
"""defines how each field in the referenced model should be mapped to the corresponding fields in the """defines how each field in the referenced model should be mapped to the corresponding fields in the
@ -1692,11 +1797,17 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
if next_char.isdigit(): if next_char.isdigit():
should_apply_default_filter = True should_apply_default_filter = True
# Select domain request to change -> Domain requests
if extra_context is None:
extra_context = {}
extra_context["tabtitle"] = "Domain requests"
if should_apply_default_filter: if should_apply_default_filter:
# modify the GET of the request to set the selected filter # modify the GET of the request to set the selected filter
modified_get = copy.deepcopy(request.GET) modified_get = copy.deepcopy(request.GET)
modified_get["status__in"] = "submitted,in review,action needed" modified_get["status__in"] = "submitted,in review,action needed"
request.GET = modified_get request.GET = modified_get
response = super().changelist_view(request, extra_context=extra_context) response = super().changelist_view(request, extra_context=extra_context)
return response return response
@ -2262,6 +2373,14 @@ class DraftDomainAdmin(ListHeaderAdmin, ImportExportModelAdmin):
# If no redirection is needed, return the original response # If no redirection is needed, return the original response
return response return response
# Select draft domain to change -> Draft domains
def changelist_view(self, request, extra_context=None):
if extra_context is None:
extra_context = {}
extra_context["tabtitle"] = "Draft domains"
# Get the filtered values
return super().changelist_view(request, extra_context=extra_context)
class PublicContactResource(resources.ModelResource): class PublicContactResource(resources.ModelResource):
"""defines how each field in the referenced model should be mapped to the corresponding fields in the """defines how each field in the referenced model should be mapped to the corresponding fields in the
@ -2306,6 +2425,20 @@ class PublicContactAdmin(ListHeaderAdmin, ImportExportModelAdmin):
change_form_template = "django/admin/email_clipboard_change_form.html" change_form_template = "django/admin/email_clipboard_change_form.html"
autocomplete_fields = ["domain"] autocomplete_fields = ["domain"]
def changeform_view(self, request, object_id=None, form_url="", extra_context=None):
if extra_context is None:
extra_context = {}
if object_id:
obj = self.get_object(request, object_id)
if obj:
name = obj.name
email = obj.email
registry_id = obj.registry_id
extra_context["subtitle"] = f"{name} <{email}> id: {registry_id}"
return super().changeform_view(request, object_id, form_url, extra_context=extra_context)
class VerifiedByStaffAdmin(ListHeaderAdmin): class VerifiedByStaffAdmin(ListHeaderAdmin):
list_display = ("email", "requestor", "truncated_notes", "created_at") list_display = ("email", "requestor", "truncated_notes", "created_at")
@ -2358,6 +2491,14 @@ class UserGroupAdmin(AuditedAdmin):
def user_group(self, obj): def user_group(self, obj):
return obj.name return obj.name
# Select user groups to change -> User groups
def changelist_view(self, request, extra_context=None):
if extra_context is None:
extra_context = {}
extra_context["tabtitle"] = "User groups"
# Get the filtered values
return super().changelist_view(request, extra_context=extra_context)
class WaffleFlagAdmin(FlagAdmin): class WaffleFlagAdmin(FlagAdmin):
"""Custom admin implementation of django-waffle's Flag class""" """Custom admin implementation of django-waffle's Flag class"""

View file

@ -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;
}

View file

@ -659,6 +659,8 @@ ALLOWED_HOSTS = [
"getgov-stable.app.cloud.gov", "getgov-stable.app.cloud.gov",
"getgov-staging.app.cloud.gov", "getgov-staging.app.cloud.gov",
"getgov-development.app.cloud.gov", "getgov-development.app.cloud.gov",
"getgov-litterbox.app.cloud.gov",
"getgov-hotgov.app.cloud.gov",
"getgov-cb.app.cloud.gov", "getgov-cb.app.cloud.gov",
"getgov-bob.app.cloud.gov", "getgov-bob.app.cloud.gov",
"getgov-meoward.app.cloud.gov", "getgov-meoward.app.cloud.gov",

View file

@ -26,7 +26,18 @@
<script type="application/javascript" src="{% static 'js/dja-collapse.js' %}" defer></script> <script type="application/javascript" src="{% static 'js/dja-collapse.js' %}" defer></script>
{% endblock %} {% endblock %}
{% block title %}{% if subtitle %}{{ subtitle }} | {% endif %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %} {% block title %}
{% if subtitle %}
{{ subtitle }} |
{% endif %}
{% if tabtitle %}
{{ tabtitle }} |
{% else %}
{{ title }} |
{% endif %}
{{ site_title|default:_('Django site admin') }}
{% endblock %}
{% block extrastyle %}{{ block.super }} {% block extrastyle %}{{ block.super }}
<link rel="stylesheet" type="text/css" href="{% static 'css/styles.css' %}" /> <link rel="stylesheet" type="text/css" href="{% static 'css/styles.css' %}" />
{% endblock %} {% endblock %}

View 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 %}

View file

@ -1,7 +1,7 @@
{% extends "domain_base.html" %} {% extends "domain_base.html" %}
{% load static field_helpers url_helpers %} {% load static field_helpers url_helpers %}
{% block title %}Domain authorizing official | {{ domain.name }} | {% endblock %} {% block title %}Authorizing official | {{ domain.name }} | {% endblock %}
{% block domain_content %} {% block domain_content %}
{# this is right after the messages block in the parent template #} {# this is right after the messages block in the parent template #}

View file

@ -1,7 +1,7 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load static %} {% load static %}
{% block title %}Domain: {{ domain.name }} | {% endblock %} {% block title %}{{ domain.name }} | {% endblock %}
{% block content %} {% block content %}
<div class="grid-container"> <div class="grid-container">

View file

@ -1,6 +1,8 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load static form_helpers url_helpers %} {% load static form_helpers url_helpers %}
{% block title %} Start a request | {% endblock %}
{% block content %} {% block content %}
<main id="main-content" class="grid-container"> <main id="main-content" class="grid-container">
<div class="grid-col desktop:grid-offset-2 desktop:grid-col-8"> <div class="grid-col desktop:grid-offset-2 desktop:grid-col-8">

View file

@ -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(

View file

@ -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,
@ -3494,16 +3495,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
@ -3528,7 +3532,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)
@ -3549,10 +3553,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 = [
@ -3578,7 +3583,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 = (
( (
@ -3596,6 +3601,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):

View file

@ -1087,7 +1087,7 @@ class TestDomainAuthorizingOfficial(TestDomainOverview):
"""Can load domain's authorizing official page.""" """Can load domain's authorizing official page."""
page = self.client.get(reverse("domain-authorizing-official", kwargs={"pk": self.domain.id})) page = self.client.get(reverse("domain-authorizing-official", kwargs={"pk": self.domain.id}))
# once on the sidebar, once in the title # once on the sidebar, once in the title
self.assertContains(page, "Authorizing official", count=2) self.assertContains(page, "Authorizing official", count=3)
def test_domain_authorizing_official_content(self): def test_domain_authorizing_official_content(self):
"""Authorizing official information appears on the page.""" """Authorizing official information appears on the page."""