Merge branch 'main' into za/1851-check-how-user-was-verified

This commit is contained in:
zandercymatics 2024-04-23 14:36:33 -06:00
commit cb030cb694
No known key found for this signature in database
GPG key ID: FF4636ABEC9682B7
15 changed files with 1623 additions and 81 deletions

View file

@ -48,6 +48,34 @@ class MyUserAdminForm(UserChangeForm):
"user_permissions": NoAutocompleteFilteredSelectMultiple("user_permissions", False),
}
def __init__(self, *args, **kwargs):
"""Custom init to modify the user form"""
super(MyUserAdminForm, self).__init__(*args, **kwargs)
self._override_base_help_texts()
def _override_base_help_texts(self):
"""
Used to override pre-existing help texts in AbstractUser.
This is done to avoid modifying the base AbstractUser class.
"""
is_superuser = self.fields.get("is_superuser")
is_staff = self.fields.get("is_staff")
password = self.fields.get("password")
if is_superuser is not None:
is_superuser.help_text = "For development purposes only; provides superuser access on the database level."
if is_staff is not None:
is_staff.help_text = "Designates whether the user can log in to this admin site."
if password is not None:
# Link is copied from the base implementation of UserChangeForm.
link = f"../../{self.instance.pk}/password/"
password.help_text = (
"Raw passwords are not stored, so they will not display here. "
f'You can change the password using <a href="{link}">this form</a>.'
)
class DomainInformationAdminForm(forms.ModelForm):
"""This form utilizes the custom widget for its class's ManyToMany UIs."""
@ -535,7 +563,7 @@ class MyUserAdmin(BaseUserAdmin):
analyst_fieldsets = (
(
None,
{"fields": ("password", "status", "verification_type")},
{"fields": ("status", "verification_type",)},
),
("Personal Info", {"fields": ("first_name", "last_name", "email")}),
(
@ -561,7 +589,6 @@ class MyUserAdmin(BaseUserAdmin):
# NOT all fields are readonly for admin, otherwise we would have
# set this at the permissions level. The exception is 'status'
analyst_readonly_fields = [
"password",
"Personal Info",
"first_name",
"last_name",
@ -1727,20 +1754,26 @@ class DomainAdmin(ListHeaderAdmin):
if domain is not None and hasattr(domain, "domain_info"):
extra_context["original_object"] = domain.domain_info
extra_context["state_help_message"] = Domain.State.get_admin_help_text(domain.state)
extra_context["domain_state"] = domain.get_state_display()
# Pass in what the an extended expiration date would be for the expiration date modal
self._set_expiration_date_context(domain, extra_context)
return super().changeform_view(request, object_id, form_url, extra_context)
def _set_expiration_date_context(self, domain, extra_context):
"""Given a domain, calculate the an extended expiration date
from the current registry expiration date."""
years_to_extend_by = self._get_calculated_years_for_exp_date(domain)
try:
curr_exp_date = domain.registry_expiration_date
except KeyError:
# No expiration date was found. Return none.
extra_context["extended_expiration_date"] = None
return super().changeform_view(request, object_id, form_url, extra_context)
else:
new_date = curr_exp_date + relativedelta(years=years_to_extend_by)
extra_context["extended_expiration_date"] = new_date
else:
extra_context["extended_expiration_date"] = None
return super().changeform_view(request, object_id, form_url, extra_context)
def response_change(self, request, obj):
# Create dictionary of action functions

View file

@ -159,6 +159,31 @@ class Domain(TimeStampedModel, DomainHelper):
return help_texts.get(state, "")
@classmethod
def get_admin_help_text(cls, state):
"""Returns a help message for a desired state for /admin. If none is found, an empty string is returned"""
admin_help_texts = {
cls.UNKNOWN: (
"The creator of the associated domain request has not logged in to "
"manage the domain since it was approved. "
'The state will switch to "DNS needed" after they access the domain in the registrar.'
),
cls.DNS_NEEDED: (
"Before this domain can be used, name server addresses need to be added within the registrar."
),
cls.READY: "This domain has name servers and is ready for use.",
cls.ON_HOLD: (
"While on hold, this domain won't resolve in DNS and "
"any infrastructure (like websites) will be offline."
),
cls.DELETED: (
"This domain was permanently removed from the registry. "
"The domain no longer resolves in DNS and any infrastructure (like websites) is offline."
),
}
return admin_help_texts.get(state, "")
class Cache(property):
"""
Python descriptor to turn class methods into properties.
@ -992,22 +1017,25 @@ class Domain(TimeStampedModel, DomainHelper):
blank=False,
default=None, # prevent saving without a value
unique=True,
verbose_name="domain",
help_text="Fully qualified domain name",
verbose_name="domain",
)
state = FSMField(
max_length=21,
choices=State.choices,
default=State.UNKNOWN,
protected=True, # cannot change state directly, particularly in Django admin
# cannot change state directly, particularly in Django admin
protected=True,
# This must be defined for custom state help messages,
# as otherwise the view will purge the help field as it does not exist.
help_text=" ",
verbose_name="domain state",
help_text="Very basic info about the lifecycle of this domain object",
)
expiration_date = DateField(
null=True,
help_text=("Duplication of registry's expiration date saved for ease of reporting"),
help_text=("Date the domain expires in the registry"),
)
security_contact_registry_id = TextField(
@ -1019,15 +1047,15 @@ class Domain(TimeStampedModel, DomainHelper):
deleted = DateField(
null=True,
editable=False,
help_text='Will appear blank unless the domain is in "deleted" state',
verbose_name="deleted on",
help_text="Deleted at date",
)
first_ready = DateField(
null=True,
editable=False,
help_text='Date when this domain first moved into "ready" state; date will never change',
verbose_name="first ready on",
help_text="The last time this domain moved into the READY state",
)
def isActive(self):

View file

@ -47,6 +47,7 @@ class DomainInformation(TimeStampedModel):
"registrar.User",
on_delete=models.PROTECT,
related_name="information_created",
help_text="Person who submitted the domain request",
)
domain_request = models.OneToOneField(
@ -55,7 +56,7 @@ class DomainInformation(TimeStampedModel):
blank=True,
null=True,
related_name="DomainRequest_info",
help_text="Associated domain request",
help_text="Request associated with this domain",
unique=True,
)
@ -73,7 +74,6 @@ class DomainInformation(TimeStampedModel):
null=True,
blank=True,
verbose_name="election office",
help_text="Is your organization an election office?",
)
# TODO - Ticket #1911: stub this data from DomainRequest
@ -82,30 +82,26 @@ class DomainInformation(TimeStampedModel):
choices=DomainRequest.OrgChoicesElectionOffice.choices,
null=True,
blank=True,
help_text="Type of organization - Election office",
help_text='"Election" appears after the org type if it\'s an election office.',
)
federally_recognized_tribe = models.BooleanField(
null=True,
help_text="Is the tribe federally recognized",
)
state_recognized_tribe = models.BooleanField(
null=True,
help_text="Is the tribe recognized by a state",
)
tribe_name = models.CharField(
null=True,
blank=True,
help_text="Name of tribe",
)
federal_agency = models.CharField(
choices=AGENCY_CHOICES,
null=True,
blank=True,
help_text="Federal agency",
)
federal_type = models.CharField(
@ -113,38 +109,32 @@ class DomainInformation(TimeStampedModel):
choices=BranchChoices.choices,
null=True,
blank=True,
help_text="Federal government branch",
)
is_election_board = models.BooleanField(
null=True,
blank=True,
verbose_name="election office",
help_text="Is your organization an election office?",
)
organization_name = models.CharField(
null=True,
blank=True,
help_text="Organization name",
db_index=True,
)
address_line1 = models.CharField(
null=True,
blank=True,
help_text="Street address",
verbose_name="address line 1",
)
address_line2 = models.CharField(
null=True,
blank=True,
help_text="Street address line 2 (optional)",
verbose_name="address line 2",
)
city = models.CharField(
null=True,
blank=True,
help_text="City",
)
state_territory = models.CharField(
max_length=2,
@ -152,27 +142,24 @@ class DomainInformation(TimeStampedModel):
null=True,
blank=True,
verbose_name="state / territory",
help_text="State, territory, or military post",
)
zipcode = models.CharField(
max_length=10,
null=True,
blank=True,
help_text="Zip code",
verbose_name="zip code",
db_index=True,
verbose_name="zip code",
)
urbanization = models.CharField(
null=True,
blank=True,
help_text="Urbanization (required for Puerto Rico only)",
help_text="Required for Puerto Rico only",
verbose_name="urbanization",
)
about_your_organization = models.TextField(
null=True,
blank=True,
help_text="Information about your organization",
)
authorizing_official = models.ForeignKey(
@ -190,7 +177,6 @@ class DomainInformation(TimeStampedModel):
null=True,
# Access this information via Domain as "domain.domain_info"
related_name="domain_info",
help_text="Domain to which this information belongs",
)
# This is the contact information provided by the domain requestor. The
@ -201,6 +187,7 @@ class DomainInformation(TimeStampedModel):
blank=True,
related_name="submitted_domain_requests_information",
on_delete=models.PROTECT,
help_text='Person listed under "your contact information" in the request form',
)
purpose = models.TextField(
@ -219,13 +206,12 @@ class DomainInformation(TimeStampedModel):
no_other_contacts_rationale = models.TextField(
null=True,
blank=True,
help_text="Reason for listing no additional contacts",
help_text="Required if creator does not list other employees",
)
anything_else = models.TextField(
null=True,
blank=True,
help_text="Anything else?",
)
is_policy_acknowledged = models.BooleanField(
@ -237,7 +223,6 @@ class DomainInformation(TimeStampedModel):
notes = models.TextField(
null=True,
blank=True,
help_text="Notes about the request",
)
def __str__(self):

View file

@ -464,6 +464,7 @@ class DomainRequest(TimeStampedModel):
"registrar.User",
on_delete=models.PROTECT,
related_name="domain_requests_created",
help_text="Person who submitted the domain request; will not receive email updates",
)
investigator = models.ForeignKey(
@ -481,14 +482,12 @@ class DomainRequest(TimeStampedModel):
choices=OrganizationChoices.choices,
null=True,
blank=True,
help_text="Type of organization",
)
is_election_board = models.BooleanField(
null=True,
blank=True,
verbose_name="election office",
help_text="Is your organization an election office?",
)
# TODO - Ticket #1911: stub this data from DomainRequest
@ -497,30 +496,26 @@ class DomainRequest(TimeStampedModel):
choices=OrgChoicesElectionOffice.choices,
null=True,
blank=True,
help_text="Type of organization - Election office",
help_text='"Election" appears after the org type if it\'s an election office.',
)
federally_recognized_tribe = models.BooleanField(
null=True,
help_text="Is the tribe federally recognized",
)
state_recognized_tribe = models.BooleanField(
null=True,
help_text="Is the tribe recognized by a state",
)
tribe_name = models.CharField(
null=True,
blank=True,
help_text="Name of tribe",
)
federal_agency = models.CharField(
choices=AGENCY_CHOICES,
null=True,
blank=True,
help_text="Federal agency",
)
federal_type = models.CharField(
@ -528,32 +523,27 @@ class DomainRequest(TimeStampedModel):
choices=BranchChoices.choices,
null=True,
blank=True,
help_text="Federal government branch",
)
organization_name = models.CharField(
null=True,
blank=True,
help_text="Organization name",
db_index=True,
)
address_line1 = models.CharField(
null=True,
blank=True,
help_text="Street address",
verbose_name="Address line 1",
)
address_line2 = models.CharField(
null=True,
blank=True,
help_text="Street address line 2 (optional)",
verbose_name="Address line 2",
)
city = models.CharField(
null=True,
blank=True,
help_text="City",
)
state_territory = models.CharField(
max_length=2,
@ -561,26 +551,23 @@ class DomainRequest(TimeStampedModel):
null=True,
blank=True,
verbose_name="state / territory",
help_text="State, territory, or military post",
)
zipcode = models.CharField(
max_length=10,
null=True,
blank=True,
verbose_name="zip code",
help_text="Zip code",
db_index=True,
)
urbanization = models.CharField(
null=True,
blank=True,
help_text="Urbanization (required for Puerto Rico only)",
help_text="Required for Puerto Rico only",
)
about_your_organization = models.TextField(
null=True,
blank=True,
help_text="Information about your organization",
)
authorizing_official = models.ForeignKey(
@ -603,7 +590,7 @@ class DomainRequest(TimeStampedModel):
"Domain",
null=True,
blank=True,
help_text="The approved domain",
help_text="Domain associated with this request; will be blank until request is approved",
related_name="domain_request",
on_delete=models.SET_NULL,
)
@ -612,7 +599,6 @@ class DomainRequest(TimeStampedModel):
"DraftDomain",
null=True,
blank=True,
help_text="The requested domain",
related_name="domain_request",
on_delete=models.PROTECT,
)
@ -621,6 +607,7 @@ class DomainRequest(TimeStampedModel):
"registrar.Website",
blank=True,
related_name="alternatives+",
help_text="Other domain names the creator provided for consideration",
)
# This is the contact information provided by the domain requestor. The
@ -631,12 +618,12 @@ class DomainRequest(TimeStampedModel):
blank=True,
related_name="submitted_domain_requests",
on_delete=models.PROTECT,
help_text='Person listed under "your contact information" in the request form; will receive email updates',
)
purpose = models.TextField(
null=True,
blank=True,
help_text="Purpose of your domain",
)
other_contacts = models.ManyToManyField(
@ -649,13 +636,12 @@ class DomainRequest(TimeStampedModel):
no_other_contacts_rationale = models.TextField(
null=True,
blank=True,
help_text="Reason for listing no additional contacts",
help_text="Required if creator does not list other employees",
)
anything_else = models.TextField(
null=True,
blank=True,
help_text="Anything else?",
)
is_policy_acknowledged = models.BooleanField(
@ -676,7 +662,6 @@ class DomainRequest(TimeStampedModel):
notes = models.TextField(
null=True,
blank=True,
help_text="Notes about this request",
)
def sync_organization_type(self):

View file

@ -22,14 +22,13 @@ class Host(TimeStampedModel):
default=None, # prevent saving without a value
unique=False,
verbose_name="host name",
help_text="Fully qualified domain name",
)
domain = models.ForeignKey(
"registrar.Domain",
on_delete=models.PROTECT,
related_name="host", # access this Host via the Domain as `domain.host`
help_text="Domain to which this host belongs",
help_text="Domain associated with this host",
)
def __str__(self):

View file

@ -21,12 +21,11 @@ class HostIP(TimeStampedModel):
default=None, # prevent saving without a value
validators=[validate_ipv46_address],
verbose_name="IP address",
help_text="IP address",
)
host = models.ForeignKey(
"registrar.Host",
on_delete=models.PROTECT,
related_name="ip", # access this HostIP via the Host as `host.ip`
help_text="Host to which this IP address belongs",
help_text="IP associated with this host",
)

View file

@ -58,6 +58,7 @@ class User(AbstractUser):
null=True, # Allow the field to be null
blank=True, # Allow the field to be blank
verbose_name="user status",
help_text='Users in "restricted" status cannot make updates in the registrar or start a new request.',
)
domains = models.ManyToManyField(

View file

@ -164,7 +164,7 @@ class CreateOrUpdateOrganizationTypeHelper:
# There is no avenue for this to occur in the UI,
# as such - this can only occur if the object is initialized in this way.
# Or if there are pre-existing data.
logger.warning(
logger.debug(
"create_or_update_organization_type() -> is_election_board "
f"cannot exist for {generic_org_type}. Setting to None."
)

View file

@ -9,7 +9,6 @@ class VerifiedByStaff(TimeStampedModel):
email = models.EmailField(
null=False,
blank=False,
help_text="Email",
db_index=True,
)
@ -19,12 +18,12 @@ class VerifiedByStaff(TimeStampedModel):
blank=True,
on_delete=models.SET_NULL,
related_name="verifiedby_user",
help_text="Person who verified this user",
)
notes = models.TextField(
null=False,
blank=False,
help_text="Notes",
)
class Meta:

View file

@ -12,7 +12,7 @@ class Website(TimeStampedModel):
website = models.CharField(
max_length=255,
null=False,
help_text="",
help_text="An alternative domain or current website listed on a domain request",
)
def __str__(self) -> str:

View file

@ -33,7 +33,10 @@
{% endif %}
</div>
</div>
{{ block.super }}
{% for fieldset in adminform %}
{% include "django/admin/includes/domain_fieldset.html" with state_help_message=state_help_message %}
{% endfor %}
{% endblock %}
{% block submit_buttons_bottom %}

View file

@ -67,9 +67,16 @@ This is using a custom implementation fieldset.html (see admin/fieldset.html)
{% endwith %}
{% endblock field_readonly %}
{% block help_text %}
<div class="help margin-bottom-1" {% if field.field.id_for_label %} id="{{ field.field.id_for_label }}_helptext"{% endif %}>
<div>{{ field.field.help_text|safe }}</div>
</div>
{% endblock help_text %}
{% block after_help_text %}
{% if field.field.name == "creator" %}
<div class="flex-container">
<div class="flex-container tablet:margin-top-1">
<label aria-label="Creator contact details"></label>
{% include "django/admin/includes/contact_detail_list.html" with user=original_object.creator no_title_top_padding=field.is_readonly %}
</div>

View file

@ -0,0 +1,21 @@
{% extends "admin/fieldset.html" %}
{% load static url_helpers %}
{% block field_readonly %}
{% if field.field.name == "state" %}
<div class="readonly">{{ domain_state }}</div>
{% else %}
<div class="readonly">{{ field.contents }}</div>
{% endif %}
{% endblock %}
{% block help_text %}
<div class="help margin-bottom-1" {% if field.field.id_for_label %} id="{{ field.field.id_for_label }}_helptext"{% endif %}>
{% if field.field.name == "state" %}
<div>{{ state_help_message }}</div>
{% else %}
<div>{{ field.field.help_text|safe }}</div>
{% endif %}
</div>
{% endblock help_text %}

View file

@ -18,6 +18,7 @@ from registrar.admin import (
AuditedAdmin,
ContactAdmin,
DomainInformationAdmin,
MyHostAdmin,
UserDomainRoleAdmin,
VerifiedByStaffAdmin,
)
@ -76,6 +77,13 @@ class TestDomainAdmin(MockEppLib, WebTest):
self.app.set_user(self.superuser.username)
self.client.force_login(self.superuser)
# Add domain data
self.ready_domain, _ = Domain.objects.get_or_create(name="fakeready.gov", state=Domain.State.READY)
self.unknown_domain, _ = Domain.objects.get_or_create(name="fakeunknown.gov", state=Domain.State.UNKNOWN)
self.dns_domain, _ = Domain.objects.get_or_create(name="fakedns.gov", state=Domain.State.DNS_NEEDED)
self.hold_domain, _ = Domain.objects.get_or_create(name="fakehold.gov", state=Domain.State.ON_HOLD)
self.deleted_domain, _ = Domain.objects.get_or_create(name="fakedeleted.gov", state=Domain.State.DELETED)
# Contains some test tools
self.test_helper = GenericTestHelper(
factory=self.factory,
@ -159,6 +167,68 @@ class TestDomainAdmin(MockEppLib, WebTest):
# Test for the copy link
self.assertContains(response, "usa-button__clipboard")
@less_console_noise_decorator
def test_helper_text(self):
"""
Tests for the correct helper text on this page
"""
# Create a ready domain with a preset expiration date
domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY)
p = "adminpass"
self.client.login(username="superuser", password=p)
response = self.client.get(
"/admin/registrar/domain/{}/change/".format(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, domain.name)
# These should exist in the response
expected_values = [
("expiration_date", "Date the domain expires in the registry"),
("first_ready_at", 'Date when this domain first moved into "ready" state; date will never change'),
("deleted_at", 'Will appear blank unless the domain is in "deleted" state'),
]
self.test_helper.assert_response_contains_distinct_values(response, expected_values)
@less_console_noise_decorator
def test_helper_text_state(self):
"""
Tests for the correct state helper text on this page
"""
# We don't need to check for all text content, just a portion of it
expected_unknown_domain_message = "The creator of the associated domain request has not logged in to"
expected_dns_message = "Before this domain can be used, name server addresses need"
expected_hold_message = "While on hold, this domain"
expected_deleted_message = "This domain was permanently removed from the registry."
expected_messages = [
(self.ready_domain, "This domain has name servers and is ready for use."),
(self.unknown_domain, expected_unknown_domain_message),
(self.dns_domain, expected_dns_message),
(self.hold_domain, expected_hold_message),
(self.deleted_domain, expected_deleted_message),
]
p = "adminpass"
self.client.login(username="superuser", password=p)
for domain, message in expected_messages:
with self.subTest(domain_state=domain.state):
response = self.client.get(
"/admin/registrar/domain/{}/change/".format(domain.id),
)
# Make sure the page loaded, and that we're on the right page
self.assertEqual(response.status_code, 200)
self.assertContains(response, domain.name)
# Check that the right help text exists
self.assertContains(response, message)
@patch("registrar.admin.DomainAdmin._get_current_date", return_value=date(2024, 1, 1))
def test_extend_expiration_date_button(self, mock_date_today):
"""
@ -782,6 +852,41 @@ class TestDomainRequestAdmin(MockEppLib):
)
self.mock_client = MockSESClient()
@less_console_noise_decorator
def test_helper_text(self):
"""
Tests for the correct helper text on this page
"""
# Create a fake domain request and domain
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
p = "adminpass"
self.client.login(username="superuser", 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)
# These should exist in the response
expected_values = [
("creator", "Person who submitted the domain request; will not receive email updates"),
(
"submitter",
'Person listed under "your contact information" in the request form; will receive email updates',
),
("approved_domain", "Domain associated with this request; will be blank until request is approved"),
("no_other_contacts_rationale", "Required if creator does not list other employees"),
("alternative_domains", "Other domain names the creator provided for consideration"),
("no_other_contacts_rationale", "Required if creator does not list other employees"),
("Urbanization", "Required for Puerto Rico only"),
]
self.test_helper.assert_response_contains_distinct_values(response, expected_values)
@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"""
@ -2315,6 +2420,54 @@ class DomainInvitationAdminTest(TestCase):
self.assertContains(response, retrieved_html, count=1)
class TestHostAdmin(TestCase):
def setUp(self):
"""Setup environment for a mock admin user"""
super().setUp()
self.site = AdminSite()
self.factory = RequestFactory()
self.admin = MyHostAdmin(model=Host, admin_site=self.site)
self.client = Client(HTTP_HOST="localhost:8080")
self.superuser = create_superuser()
self.test_helper = GenericTestHelper(
factory=self.factory,
user=self.superuser,
admin=self.admin,
url="/admin/registrar/Host/",
model=Host,
)
def tearDown(self):
super().tearDown()
Host.objects.all().delete()
Domain.objects.all().delete()
@less_console_noise_decorator
def test_helper_text(self):
"""
Tests for the correct helper text on this page
"""
domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY)
# Create a fake host
host, _ = Host.objects.get_or_create(name="ns1.test.gov", domain=domain)
p = "adminpass"
self.client.login(username="superuser", password=p)
response = self.client.get(
"/admin/registrar/host/{}/change/".format(host.pk),
follow=True,
)
# Make sure the page loaded
self.assertEqual(response.status_code, 200)
# These should exist in the response
expected_values = [
("domain", "Domain associated with this host"),
]
self.test_helper.assert_response_contains_distinct_values(response, expected_values)
class TestDomainInformationAdmin(TestCase):
def setUp(self):
"""Setup environment for a mock admin user"""
@ -2367,6 +2520,38 @@ class TestDomainInformationAdmin(TestCase):
Contact.objects.all().delete()
User.objects.all().delete()
@less_console_noise_decorator
def test_helper_text(self):
"""
Tests for the correct helper text on this page
"""
# Create a fake domain request and domain
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
domain_request.approve()
domain_info = DomainInformation.objects.filter(domain=domain_request.approved_domain).get()
p = "adminpass"
self.client.login(username="superuser", password=p)
response = self.client.get(
"/admin/registrar/domaininformation/{}/change/".format(domain_info.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_info.domain.name)
# These should exist in the response
expected_values = [
("creator", "Person who submitted the domain request"),
("submitter", 'Person listed under "your contact information" in the request form'),
("domain_request", "Request associated with this domain"),
("no_other_contacts_rationale", "Required if creator does not list other employees"),
("urbanization", "Required for Puerto Rico only"),
]
self.test_helper.assert_response_contains_distinct_values(response, expected_values)
@less_console_noise_decorator
def test_other_contacts_has_readonly_link(self):
"""Tests if the readonly other_contacts field has links"""
@ -2711,7 +2896,7 @@ class UserDomainRoleAdminTest(TestCase):
self.assertContains(response, "Joe Jones AntarcticPolarBears@example.com</a></th>", count=1)
class ListHeaderAdminTest(TestCase):
class TestListHeaderAdmin(TestCase):
def setUp(self):
self.site = AdminSite()
self.factory = RequestFactory()
@ -2784,10 +2969,43 @@ class ListHeaderAdminTest(TestCase):
User.objects.all().delete()
class MyUserAdminTest(TestCase):
class TestMyUserAdmin(TestCase):
def setUp(self):
admin_site = AdminSite()
self.admin = MyUserAdmin(model=get_user_model(), admin_site=admin_site)
self.client = Client(HTTP_HOST="localhost:8080")
self.superuser = create_superuser()
self.test_helper = GenericTestHelper(admin=self.admin)
def tearDown(self):
super().tearDown()
User.objects.all().delete()
@less_console_noise_decorator
def test_helper_text(self):
"""
Tests for the correct helper text on this page
"""
user = create_user()
p = "adminpass"
self.client.login(username="superuser", password=p)
response = self.client.get(
"/admin/registrar/user/{}/change/".format(user.pk),
follow=True,
)
# Make sure the page loaded
self.assertEqual(response.status_code, 200)
# These should exist in the response
expected_values = [
("password", "Raw passwords are not stored, so they will not display here."),
("status", 'Users in "restricted" status cannot make updates in the registrar or start a new request.'),
("is_staff", "Designates whether the user can log in to this admin site"),
("is_superuser", "For development purposes only; provides superuser access on the database level"),
]
self.test_helper.assert_response_contains_distinct_values(response, expected_values)
def test_list_display_without_username(self):
with less_console_noise():
@ -2809,8 +3027,9 @@ class MyUserAdminTest(TestCase):
def test_get_fieldsets_superuser(self):
with less_console_noise():
request = self.client.request().wsgi_request
request.user = create_superuser()
request.user = self.superuser
fieldsets = self.admin.get_fieldsets(request)
expected_fieldsets = super(MyUserAdmin, self.admin).get_fieldsets(request)
self.assertEqual(fieldsets, expected_fieldsets)
@ -2820,16 +3039,13 @@ class MyUserAdminTest(TestCase):
request.user = create_user()
fieldsets = self.admin.get_fieldsets(request)
expected_fieldsets = (
(None, {"fields": ("password", "status", "verification_type")}),
(None, {"fields": ("status", "verification_type",)}),
("Personal Info", {"fields": ("first_name", "last_name", "email")}),
("Permissions", {"fields": ("is_active", "groups")}),
("Important dates", {"fields": ("last_login", "date_joined")}),
)
self.assertEqual(fieldsets, expected_fieldsets)
def tearDown(self):
User.objects.all().delete()
class AuditedAdminTest(TestCase):
def setUp(self):
@ -3303,10 +3519,43 @@ class ContactAdminTest(TestCase):
User.objects.all().delete()
class VerifiedByStaffAdminTestCase(TestCase):
class TestVerifiedByStaffAdmin(TestCase):
def setUp(self):
super().setUp()
self.site = AdminSite()
self.superuser = create_superuser()
self.admin = VerifiedByStaffAdmin(model=VerifiedByStaff, admin_site=self.site)
self.factory = RequestFactory()
self.client = Client(HTTP_HOST="localhost:8080")
self.test_helper = GenericTestHelper(admin=self.admin)
def tearDown(self):
super().tearDown()
VerifiedByStaff.objects.all().delete()
User.objects.all().delete()
@less_console_noise_decorator
def test_helper_text(self):
"""
Tests for the correct helper text on this page
"""
vip_instance, _ = VerifiedByStaff.objects.get_or_create(email="test@example.com", notes="Test Notes")
p = "adminpass"
self.client.login(username="superuser", password=p)
response = self.client.get(
"/admin/registrar/verifiedbystaff/{}/change/".format(vip_instance.pk),
follow=True,
)
# Make sure the page loaded
self.assertEqual(response.status_code, 200)
# These should exist in the response
expected_values = [
("requestor", "Person who verified this user"),
]
self.test_helper.assert_response_contains_distinct_values(response, expected_values)
def test_save_model_sets_user_field(self):
with less_console_noise():