merged main and resolved conflict

This commit is contained in:
Alysia Broddrick 2024-06-11 13:07:21 -07:00
commit 1c1ae51023
No known key found for this signature in database
GPG key ID: 03917052CD0F06B7
14 changed files with 461 additions and 110 deletions

View file

@ -25,6 +25,9 @@ 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/')
|| startsWith(github.head_ref, 'ag/')
outputs: outputs:
environment: ${{ steps.var.outputs.environment}} environment: ${{ steps.var.outputs.environment}}
runs-on: "ubuntu-latest" runs-on: "ubuntu-latest"

View file

@ -17,6 +17,8 @@ on:
- staging - staging
- development - development
- ag - ag
- litterbox
- hotgov
- cb - cb
- bob - bob
- meoward - meoward

View file

@ -17,6 +17,8 @@ on:
- staging - staging
- development - development
- ag - ag
- litterbox
- hotgov
- cb - cb
- bob - bob
- meoward - meoward

View file

@ -1,11 +1,15 @@
# Complete model documentation # Complete model documentation
This is an auto-generated diagram of our data models generated with the This is an auto-generated diagram of our data models generated with the
[django-model2puml](https://github.com/sen-den/django-model2puml) library [django-model2puml](https://github.com/sen-den/django-model2puml) library.
using the command
## How to generate the puml
1. Uncomment `puml_generator` from `INSTALLED_APPS` in settings.py and docker-compose down and up
2. Run the following command to generate a puml file
```bash ```bash
$ docker compose app ./manage.py generate_puml --include registrar docker compose exec app ./manage.py generate_puml --include registrar
``` ```
![Complete data models diagram](./models_diagram.svg) ![Complete data models diagram](./models_diagram.svg)
@ -13,12 +17,19 @@ $ docker compose app ./manage.py generate_puml --include registrar
<details> <details>
<summary>PlantUML source code</summary> <summary>PlantUML source code</summary>
To regenerate this image using Docker, run ## How To regenerate the database svg image
1. Copy your puml file contents into the bottom of this file and replace the current code marked by `plantuml`
2. Run the following command
```bash ```bash
$ docker run -v $(pwd):$(pwd) -w $(pwd) -it plantuml/plantuml -tsvg models_diagram.md docker run -v $(pwd):$(pwd) -w $(pwd) -it plantuml/plantuml -tsvg models_diagram.md
``` ```
3. Remove the puml file from earlier (if you still have it)
4. Commit the new image and the md file
```plantuml ```plantuml
@startuml @startuml
class "registrar.Contact <Registrar>" as registrar.Contact #d6f4e9 { class "registrar.Contact <Registrar>" as registrar.Contact #d6f4e9 {
@ -28,17 +39,97 @@ class "registrar.Contact <Registrar>" as registrar.Contact #d6f4e9 {
+ created_at (DateTimeField) + created_at (DateTimeField)
+ updated_at (DateTimeField) + updated_at (DateTimeField)
~ user (OneToOneField) ~ user (OneToOneField)
+ first_name (TextField) + first_name (CharField)
+ middle_name (TextField) + middle_name (CharField)
+ last_name (TextField) + last_name (CharField)
+ title (TextField) + title (CharField)
+ email (TextField) + email (EmailField)
+ phone (PhoneNumberField) + phone (PhoneNumberField)
-- --
} }
registrar.Contact -- registrar.User registrar.Contact -- registrar.User
class "registrar.Host <Registrar>" as registrar.Host #d6f4e9 {
host
--
+ id (BigAutoField)
+ created_at (DateTimeField)
+ updated_at (DateTimeField)
+ name (CharField)
~ domain (ForeignKey)
--
}
registrar.Host -- registrar.Domain
class "registrar.HostIP <Registrar>" as registrar.HostIP #d6f4e9 {
host ip
--
+ id (BigAutoField)
+ created_at (DateTimeField)
+ updated_at (DateTimeField)
+ address (CharField)
~ host (ForeignKey)
--
}
registrar.HostIP -- registrar.Host
class "registrar.PublicContact <Registrar>" as registrar.PublicContact #d6f4e9 {
public contact
--
+ id (BigAutoField)
+ created_at (DateTimeField)
+ updated_at (DateTimeField)
+ contact_type (CharField)
+ registry_id (CharField)
~ domain (ForeignKey)
+ name (CharField)
+ org (CharField)
+ street1 (CharField)
+ street2 (CharField)
+ street3 (CharField)
+ city (CharField)
+ sp (CharField)
+ pc (CharField)
+ cc (CharField)
+ email (EmailField)
+ voice (CharField)
+ fax (CharField)
+ pw (CharField)
--
}
registrar.PublicContact -- registrar.Domain
class "registrar.Domain <Registrar>" as registrar.Domain #d6f4e9 {
domain
--
+ id (BigAutoField)
+ created_at (DateTimeField)
+ updated_at (DateTimeField)
+ name (DomainField)
+ state (FSMField)
+ expiration_date (DateField)
+ security_contact_registry_id (TextField)
+ deleted (DateField)
+ first_ready (DateField)
--
}
class "registrar.FederalAgency <Registrar>" as registrar.FederalAgency #d6f4e9 {
Federal agency
--
+ id (BigAutoField)
+ created_at (DateTimeField)
+ updated_at (DateTimeField)
+ agency (CharField)
--
}
class "registrar.DomainRequest <Registrar>" as registrar.DomainRequest #d6f4e9 { class "registrar.DomainRequest <Registrar>" as registrar.DomainRequest #d6f4e9 {
domain request domain request
-- --
@ -46,24 +137,25 @@ class "registrar.DomainRequest <Registrar>" as registrar.DomainRequest #d6f4e9 {
+ created_at (DateTimeField) + created_at (DateTimeField)
+ updated_at (DateTimeField) + updated_at (DateTimeField)
+ status (FSMField) + status (FSMField)
+ rejection_reason (TextField)
~ federal_agency (ForeignKey)
~ creator (ForeignKey) ~ creator (ForeignKey)
~ investigator (ForeignKey) ~ investigator (ForeignKey)
+ generic_org_type (CharField)
+ is_election_board (BooleanField)
+ organization_type (CharField) + organization_type (CharField)
+ federally_recognized_tribe (BooleanField) + federally_recognized_tribe (BooleanField)
+ state_recognized_tribe (BooleanField) + state_recognized_tribe (BooleanField)
+ tribe_name (TextField) + tribe_name (CharField)
+ federal_agency (TextField)
+ federal_type (CharField) + federal_type (CharField)
+ is_election_board (BooleanField) + organization_name (CharField)
+ organization_name (TextField) + address_line1 (CharField)
+ address_line1 (TextField)
+ address_line2 (CharField) + address_line2 (CharField)
+ city (TextField) + city (CharField)
+ state_territory (CharField) + state_territory (CharField)
+ zipcode (CharField) + zipcode (CharField)
+ urbanization (TextField) + urbanization (CharField)
+ type_of_work (TextField) + about_your_organization (TextField)
+ more_organization_information (TextField)
~ authorizing_official (ForeignKey) ~ authorizing_official (ForeignKey)
~ approved_domain (OneToOneField) ~ approved_domain (OneToOneField)
~ requested_domain (OneToOneField) ~ requested_domain (OneToOneField)
@ -71,17 +163,23 @@ class "registrar.DomainRequest <Registrar>" as registrar.DomainRequest #d6f4e9 {
+ purpose (TextField) + purpose (TextField)
+ no_other_contacts_rationale (TextField) + no_other_contacts_rationale (TextField)
+ anything_else (TextField) + anything_else (TextField)
+ has_anything_else_text (BooleanField)
+ cisa_representative_email (EmailField)
+ has_cisa_representative (BooleanField)
+ is_policy_acknowledged (BooleanField) + is_policy_acknowledged (BooleanField)
+ submission_date (DateField)
+ notes (TextField)
# current_websites (ManyToManyField) # current_websites (ManyToManyField)
# alternative_domains (ManyToManyField) # alternative_domains (ManyToManyField)
# other_contacts (ManyToManyField) # other_contacts (ManyToManyField)
-- --
} }
registrar.DomainRequest -- registrar.FederalAgency
registrar.DomainRequest -- registrar.User registrar.DomainRequest -- registrar.User
registrar.DomainRequest -- registrar.User registrar.DomainRequest -- registrar.User
registrar.DomainRequest -- registrar.Contact registrar.DomainRequest -- registrar.Contact
registrar.DomainRequest -- registrar.DraftDomain
registrar.DomainRequest -- registrar.Domain registrar.DomainRequest -- registrar.Domain
registrar.DomainRequest -- registrar.DraftDomain
registrar.DomainRequest -- registrar.Contact registrar.DomainRequest -- registrar.Contact
registrar.DomainRequest *--* registrar.Website registrar.DomainRequest *--* registrar.Website
registrar.DomainRequest *--* registrar.Website registrar.DomainRequest *--* registrar.Website
@ -94,35 +192,37 @@ class "registrar.DomainInformation <Registrar>" as registrar.DomainInformation #
+ id (BigAutoField) + id (BigAutoField)
+ created_at (DateTimeField) + created_at (DateTimeField)
+ updated_at (DateTimeField) + updated_at (DateTimeField)
~ federal_agency (ForeignKey)
~ creator (ForeignKey) ~ creator (ForeignKey)
~ domain_request (OneToOneField) ~ domain_request (OneToOneField)
+ generic_org_type (CharField)
+ organization_type (CharField) + organization_type (CharField)
+ federally_recognized_tribe (BooleanField) + federally_recognized_tribe (BooleanField)
+ state_recognized_tribe (BooleanField) + state_recognized_tribe (BooleanField)
+ tribe_name (TextField) + tribe_name (CharField)
+ federal_agency (TextField)
+ federal_type (CharField) + federal_type (CharField)
+ is_election_board (BooleanField) + is_election_board (BooleanField)
+ organization_name (TextField) + organization_name (CharField)
+ address_line1 (TextField) + address_line1 (CharField)
+ address_line2 (CharField) + address_line2 (CharField)
+ city (TextField) + city (CharField)
+ state_territory (CharField) + state_territory (CharField)
+ zipcode (CharField) + zipcode (CharField)
+ urbanization (TextField) + urbanization (CharField)
+ type_of_work (TextField) + about_your_organization (TextField)
+ more_organization_information (TextField)
~ authorizing_official (ForeignKey) ~ authorizing_official (ForeignKey)
~ domain (OneToOneField) ~ domain (OneToOneField)
~ submitter (ForeignKey) ~ submitter (ForeignKey)
+ purpose (TextField) + purpose (TextField)
+ no_other_contacts_rationale (TextField) + no_other_contacts_rationale (TextField)
+ anything_else (TextField) + anything_else (TextField)
+ cisa_representative_email (EmailField)
+ is_policy_acknowledged (BooleanField) + is_policy_acknowledged (BooleanField)
+ security_email (EmailField) + notes (TextField)
# other_contacts (ManyToManyField) # other_contacts (ManyToManyField)
-- --
} }
registrar.DomainInformation -- registrar.FederalAgency
registrar.DomainInformation -- registrar.User registrar.DomainInformation -- registrar.User
registrar.DomainInformation -- registrar.DomainRequest registrar.DomainInformation -- registrar.DomainRequest
registrar.DomainInformation -- registrar.Contact registrar.DomainInformation -- registrar.Contact
@ -142,43 +242,6 @@ class "registrar.DraftDomain <Registrar>" as registrar.DraftDomain #d6f4e9 {
} }
class "registrar.Domain <Registrar>" as registrar.Domain #d6f4e9 {
domain
--
+ id (BigAutoField)
+ created_at (DateTimeField)
+ updated_at (DateTimeField)
+ name (CharField)
--
}
class "registrar.HostIP <Registrar>" as registrar.HostIP #d6f4e9 {
host ip
--
+ id (BigAutoField)
+ created_at (DateTimeField)
+ updated_at (DateTimeField)
+ address (CharField)
~ host (ForeignKey)
--
}
registrar.HostIP -- registrar.Host
class "registrar.Host <Registrar>" as registrar.Host #d6f4e9 {
host
--
+ id (BigAutoField)
+ created_at (DateTimeField)
+ updated_at (DateTimeField)
+ name (CharField)
~ domain (ForeignKey)
--
}
registrar.Host -- registrar.Domain
class "registrar.UserDomainRole <Registrar>" as registrar.UserDomainRole #d6f4e9 { class "registrar.UserDomainRole <Registrar>" as registrar.UserDomainRole #d6f4e9 {
user domain role user domain role
-- --
@ -208,47 +271,49 @@ class "registrar.DomainInvitation <Registrar>" as registrar.DomainInvitation #d6
registrar.DomainInvitation -- registrar.Domain registrar.DomainInvitation -- registrar.Domain
class "registrar.Nameserver <Registrar>" as registrar.Nameserver #d6f4e9 { class "registrar.TransitionDomain <Registrar>" as registrar.TransitionDomain #d6f4e9 {
nameserver transition domain
-- --
+ id (BigAutoField) + id (BigAutoField)
+ created_at (DateTimeField) + created_at (DateTimeField)
+ updated_at (DateTimeField) + updated_at (DateTimeField)
+ name (CharField) + username (CharField)
~ domain (ForeignKey) + domain_name (CharField)
~ host_ptr (OneToOneField) + status (CharField)
+ email_sent (BooleanField)
+ processed (BooleanField)
+ generic_org_type (CharField)
+ organization_name (CharField)
+ federal_type (CharField)
+ federal_agency (CharField)
+ epp_creation_date (DateField)
+ epp_expiration_date (DateField)
+ first_name (CharField)
+ middle_name (CharField)
+ last_name (CharField)
+ title (CharField)
+ email (EmailField)
+ phone (CharField)
+ address_line (CharField)
+ city (CharField)
+ state_territory (CharField)
+ zipcode (CharField)
-- --
} }
registrar.Nameserver -- registrar.Domain
registrar.Nameserver -- registrar.Host
class "registrar.PublicContact <Registrar>" as registrar.PublicContact #d6f4e9 { class "registrar.VerifiedByStaff <Registrar>" as registrar.VerifiedByStaff #d6f4e9 {
public contact verified by staff
-- --
+ id (BigAutoField) + id (BigAutoField)
+ created_at (DateTimeField) + created_at (DateTimeField)
+ updated_at (DateTimeField) + updated_at (DateTimeField)
+ contact_type (CharField) + email (EmailField)
+ registry_id (CharField) ~ requestor (ForeignKey)
~ domain (ForeignKey) + notes (TextField)
+ name (TextField)
+ org (TextField)
+ street1 (TextField)
+ street2 (TextField)
+ street3 (TextField)
+ city (TextField)
+ sp (TextField)
+ pc (TextField)
+ cc (TextField)
+ email (TextField)
+ voice (TextField)
+ fax (TextField)
+ pw (TextField)
-- --
} }
registrar.VerifiedByStaff -- registrar.User
registrar.PublicContact -- registrar.Domain
class "registrar.User <Registrar>" as registrar.User #d6f4e9 { class "registrar.User <Registrar>" as registrar.User #d6f4e9 {
@ -265,7 +330,11 @@ class "registrar.User <Registrar>" as registrar.User #d6f4e9 {
+ is_staff (BooleanField) + is_staff (BooleanField)
+ is_active (BooleanField) + is_active (BooleanField)
+ date_joined (DateTimeField) + date_joined (DateTimeField)
+ status (CharField)
+ phone (PhoneNumberField) + phone (PhoneNumberField)
+ middle_name (CharField)
+ title (CharField)
+ verification_type (CharField)
# groups (ManyToManyField) # groups (ManyToManyField)
# user_permissions (ManyToManyField) # user_permissions (ManyToManyField)
# domains (ManyToManyField) # domains (ManyToManyField)
@ -274,6 +343,17 @@ class "registrar.User <Registrar>" as registrar.User #d6f4e9 {
registrar.User *--* registrar.Domain registrar.User *--* registrar.Domain
class "registrar.UserGroup <Registrar>" as registrar.UserGroup #d6f4e9 {
User group
--
- id (AutoField)
+ name (CharField)
~ group_ptr (OneToOneField)
# permissions (ManyToManyField)
--
}
class "registrar.Website <Registrar>" as registrar.Website #d6f4e9 { class "registrar.Website <Registrar>" as registrar.Website #d6f4e9 {
website website
-- --
@ -285,6 +365,29 @@ class "registrar.Website <Registrar>" as registrar.Website #d6f4e9 {
} }
class "registrar.WaffleFlag <Registrar>" as registrar.WaffleFlag #d6f4e9 {
waffle flag
--
+ id (BigAutoField)
+ name (CharField)
+ everyone (BooleanField)
+ percent (DecimalField)
+ testing (BooleanField)
+ superusers (BooleanField)
+ staff (BooleanField)
+ authenticated (BooleanField)
+ languages (TextField)
+ rollout (BooleanField)
+ note (TextField)
+ created (DateTimeField)
+ modified (DateTimeField)
# groups (ManyToManyField)
# users (ManyToManyField)
--
}
registrar.WaffleFlag *--* registrar.User
@enduml @enduml
``` ```

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Before After
Before After

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
@ -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."""

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

@ -660,6 +660,8 @@ ALLOWED_HOSTS = [
"getgov-staging.app.cloud.gov", "getgov-staging.app.cloud.gov",
"getgov-development.app.cloud.gov", "getgov-development.app.cloud.gov",
"getgov-ag.app.cloud.gov", "getgov-ag.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

@ -106,6 +106,12 @@ class UserFixture:
"last_name": "Orr", "last_name": "Orr",
"email": "riley+320@truss.works", "email": "riley+320@truss.works",
}, },
{
"username": "76612d84-66b0-4ae9-9870-81e98b9858b6",
"first_name": "Anna",
"last_name": "Gingle",
"email": "annagingle@truss.works",
},
] ]
STAFF = [ STAFF = [
@ -194,6 +200,12 @@ class UserFixture:
"last_name": "Orr-Analyst", "last_name": "Orr-Analyst",
"email": "riley+321@truss.works", "email": "riley+321@truss.works",
}, },
{
"username": "e1e350b1-cfc1-4753-a6cb-3ae6d912f99c",
"first_name": "Anna-Analyst",
"last_name": "Gingle-Analyst",
"email": "annagingle+analyst@truss.works",
},
] ]
def load_users(cls, users, group_name, are_superusers=False): def load_users(cls, users, group_name, are_superusers=False):

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

@ -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,
@ -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):