mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-08-04 08:52:16 +02:00
Merge branch 'main' into dk/1298-adjango-admin-searchable-dropdown
This commit is contained in:
commit
690a9b5560
13 changed files with 147 additions and 35 deletions
|
@ -2,6 +2,9 @@
|
|||
from django.apps import apps
|
||||
from django.views.decorators.http import require_http_methods
|
||||
from django.http import JsonResponse
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from registrar.templatetags.url_helpers import public_site_url
|
||||
|
||||
import requests
|
||||
|
||||
|
@ -18,8 +21,13 @@ DOMAIN_API_MESSAGES = {
|
|||
" For example, if you want www.city.gov, you would enter “city”"
|
||||
" (without the quotes).",
|
||||
"extra_dots": "Enter the .gov domain you want without any periods.",
|
||||
"unavailable": "That domain isn’t available. Try entering another one."
|
||||
" Contact us if you need help coming up with a domain.",
|
||||
# message below is considered safe; no user input can be inserted into the message
|
||||
# body; public_site_url() function reads from local app settings and therefore safe
|
||||
"unavailable": mark_safe( # nosec
|
||||
"That domain isn’t available. "
|
||||
"<a class='usa-link' href='{}' target='_blank'>"
|
||||
"Read more about choosing your .gov domain.</a>".format(public_site_url("domains/choosing"))
|
||||
),
|
||||
"invalid": "Enter a domain using only letters, numbers, or hyphens (though we don't recommend using hyphens).",
|
||||
"success": "That domain is available!",
|
||||
"error": "Error finding domain availability.",
|
||||
|
|
|
@ -115,14 +115,14 @@ function inlineToast(el, id, style, msg) {
|
|||
toast.className = `usa-alert usa-alert--${style} usa-alert--slim`;
|
||||
toastBody.classList.add("usa-alert__body");
|
||||
p.classList.add("usa-alert__text");
|
||||
p.innerText = msg;
|
||||
p.innerHTML = msg;
|
||||
toastBody.appendChild(p);
|
||||
toast.appendChild(toastBody);
|
||||
el.parentNode.insertBefore(toast, el.nextSibling);
|
||||
} else {
|
||||
// update and show the existing message div
|
||||
toast.className = `usa-alert usa-alert--${style} usa-alert--slim`;
|
||||
toast.querySelector("div p").innerText = msg;
|
||||
toast.querySelector("div p").innerHTML = msg;
|
||||
makeVisible(toast);
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -67,6 +67,7 @@ class DomainNameserverForm(forms.Form):
|
|||
ip = cleaned_data.get("ip", None)
|
||||
# remove ANY spaces in the ip field
|
||||
ip = ip.replace(" ", "")
|
||||
cleaned_data["ip"] = ip
|
||||
domain = cleaned_data.get("domain", "")
|
||||
|
||||
ip_list = self.extract_ip_list(ip)
|
||||
|
@ -117,8 +118,34 @@ class DomainNameserverForm(forms.Form):
|
|||
self.add_error("ip", str(e))
|
||||
|
||||
|
||||
class BaseNameserverFormset(forms.BaseFormSet):
|
||||
def clean(self):
|
||||
"""
|
||||
Check for duplicate entries in the formset.
|
||||
"""
|
||||
if any(self.errors):
|
||||
# Don't bother validating the formset unless each form is valid on its own
|
||||
return
|
||||
|
||||
data = []
|
||||
duplicates = []
|
||||
|
||||
for form in self.forms:
|
||||
if form.cleaned_data:
|
||||
value = form.cleaned_data["server"]
|
||||
if value in data:
|
||||
form.add_error(
|
||||
"server",
|
||||
NameserverError(code=nsErrorCodes.DUPLICATE_HOST, nameserver=value),
|
||||
)
|
||||
duplicates.append(value)
|
||||
else:
|
||||
data.append(value)
|
||||
|
||||
|
||||
NameserverFormset = formset_factory(
|
||||
DomainNameserverForm,
|
||||
formset=BaseNameserverFormset,
|
||||
extra=1,
|
||||
max_num=13,
|
||||
validate_max=True,
|
||||
|
|
|
@ -25,8 +25,8 @@
|
|||
{% if domain.permissions %}
|
||||
<section class="section--outlined">
|
||||
<table class="usa-table usa-table--borderless usa-table--stacked dotgov-table--stacked dotgov-table">
|
||||
<h2 class> Active users </h2>
|
||||
<caption class="sr-only">Domain users</caption>
|
||||
<h2 class> Domain managers </h2>
|
||||
<caption class="sr-only">Domain managers</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-sortable scope="col" role="columnheader">Email</th>
|
||||
|
|
|
@ -860,15 +860,9 @@ class MockEppLib(TestCase):
|
|||
case commands.UpdateDomain:
|
||||
return self.mockUpdateDomainCommands(_request, cleaned)
|
||||
case commands.CreateHost:
|
||||
return MagicMock(
|
||||
res_data=[self.mockDataHostChange],
|
||||
code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY,
|
||||
)
|
||||
return self.mockCreateHostCommands(_request, cleaned)
|
||||
case commands.UpdateHost:
|
||||
return MagicMock(
|
||||
res_data=[self.mockDataHostChange],
|
||||
code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY,
|
||||
)
|
||||
return self.mockUpdateHostCommands(_request, cleaned)
|
||||
case commands.DeleteHost:
|
||||
return MagicMock(
|
||||
res_data=[self.mockDataHostChange],
|
||||
|
@ -883,6 +877,28 @@ class MockEppLib(TestCase):
|
|||
case _:
|
||||
return MagicMock(res_data=[self.mockDataInfoHosts])
|
||||
|
||||
def mockCreateHostCommands(self, _request, cleaned):
|
||||
test_ws_ip = common.Ip(addr="1.1. 1.1")
|
||||
addrs_submitted = getattr(_request, "addrs", [])
|
||||
if test_ws_ip in addrs_submitted:
|
||||
raise RegistryError(code=ErrorCode.PARAMETER_VALUE_RANGE_ERROR)
|
||||
else:
|
||||
return MagicMock(
|
||||
res_data=[self.mockDataHostChange],
|
||||
code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY,
|
||||
)
|
||||
|
||||
def mockUpdateHostCommands(self, _request, cleaned):
|
||||
test_ws_ip = common.Ip(addr="1.1. 1.1")
|
||||
addrs_submitted = getattr(_request, "addrs", [])
|
||||
if test_ws_ip in addrs_submitted:
|
||||
raise RegistryError(code=ErrorCode.PARAMETER_VALUE_RANGE_ERROR)
|
||||
else:
|
||||
return MagicMock(
|
||||
res_data=[self.mockDataHostChange],
|
||||
code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY,
|
||||
)
|
||||
|
||||
def mockUpdateDomainCommands(self, _request, cleaned):
|
||||
if getattr(_request, "name", None) == "dnssec-invalid.gov":
|
||||
raise RegistryError(code=ErrorCode.PARAMETER_VALUE_RANGE_ERROR)
|
||||
|
|
|
@ -1219,6 +1219,8 @@ class TestDomainOverview(TestWithDomainPermissions, WebTest):
|
|||
self.app.set_user(self.user.username)
|
||||
self.client.force_login(self.user)
|
||||
|
||||
|
||||
class TestDomainDetail(TestDomainOverview):
|
||||
def test_domain_detail_link_works(self):
|
||||
home_page = self.app.get("/")
|
||||
self.assertContains(home_page, "igorville.gov")
|
||||
|
@ -1227,7 +1229,7 @@ class TestDomainOverview(TestWithDomainPermissions, WebTest):
|
|||
self.assertContains(detail_page, "igorville.gov")
|
||||
self.assertContains(detail_page, "Status")
|
||||
|
||||
def test_domain_overview_blocked_for_ineligible_user(self):
|
||||
def test_domain_detail_blocked_for_ineligible_user(self):
|
||||
"""We could easily duplicate this test for all domain management
|
||||
views, but a single url test should be solid enough since all domain
|
||||
management pages share the same permissions class"""
|
||||
|
@ -1239,7 +1241,7 @@ class TestDomainOverview(TestWithDomainPermissions, WebTest):
|
|||
response = self.client.get(reverse("domain", kwargs={"pk": self.domain.id}))
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_domain_overview_allowed_for_on_hold(self):
|
||||
def test_domain_detail_allowed_for_on_hold(self):
|
||||
"""Test that the domain overview page displays for on hold domain"""
|
||||
home_page = self.app.get("/")
|
||||
self.assertContains(home_page, "on-hold.gov")
|
||||
|
@ -1248,7 +1250,7 @@ class TestDomainOverview(TestWithDomainPermissions, WebTest):
|
|||
detail_page = self.client.get(reverse("domain", kwargs={"pk": self.domain_on_hold.id}))
|
||||
self.assertNotContains(detail_page, "Edit")
|
||||
|
||||
def test_domain_see_just_nameserver(self):
|
||||
def test_domain_detail_see_just_nameserver(self):
|
||||
home_page = self.app.get("/")
|
||||
self.assertContains(home_page, "justnameserver.com")
|
||||
|
||||
|
@ -1259,7 +1261,7 @@ class TestDomainOverview(TestWithDomainPermissions, WebTest):
|
|||
self.assertContains(detail_page, "ns1.justnameserver.com")
|
||||
self.assertContains(detail_page, "ns2.justnameserver.com")
|
||||
|
||||
def test_domain_see_nameserver_and_ip(self):
|
||||
def test_domain_detail_see_nameserver_and_ip(self):
|
||||
home_page = self.app.get("/")
|
||||
self.assertContains(home_page, "nameserverwithip.gov")
|
||||
|
||||
|
@ -1275,7 +1277,7 @@ class TestDomainOverview(TestWithDomainPermissions, WebTest):
|
|||
self.assertContains(detail_page, "(1.2.3.4,")
|
||||
self.assertContains(detail_page, "2.3.4.5)")
|
||||
|
||||
def test_domain_with_no_information_or_application(self):
|
||||
def test_domain_detail_with_no_information_or_application(self):
|
||||
"""Test that domain management page returns 200 and displays error
|
||||
when no domain information or domain application exist"""
|
||||
# have to use staff user for this test
|
||||
|
@ -1506,6 +1508,62 @@ class TestDomainNameservers(TestDomainOverview):
|
|||
status_code=200,
|
||||
)
|
||||
|
||||
def test_domain_nameservers_form_submit_duplicate_host(self):
|
||||
"""Nameserver form catches error when host is duplicated.
|
||||
|
||||
Uses self.app WebTest because we need to interact with forms.
|
||||
"""
|
||||
# initial nameservers page has one server with two ips
|
||||
nameservers_page = self.app.get(reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id}))
|
||||
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||
# attempt to submit the form with duplicate host names of fake.host.com
|
||||
nameservers_page.form["form-0-ip"] = ""
|
||||
nameservers_page.form["form-1-server"] = "fake.host.com"
|
||||
with less_console_noise(): # swallow log warning message
|
||||
result = nameservers_page.form.submit()
|
||||
# form submission was a post with an error, response should be a 200
|
||||
# error text appears twice, once at the top of the page, once around
|
||||
# the required field. remove duplicate entry
|
||||
self.assertContains(
|
||||
result,
|
||||
str(NameserverError(code=NameserverErrorCodes.DUPLICATE_HOST)),
|
||||
count=2,
|
||||
status_code=200,
|
||||
)
|
||||
|
||||
def test_domain_nameservers_form_submit_whitespace(self):
|
||||
"""Nameserver form removes whitespace from ip.
|
||||
|
||||
Uses self.app WebTest because we need to interact with forms.
|
||||
"""
|
||||
nameserver1 = "ns1.igorville.gov"
|
||||
nameserver2 = "ns2.igorville.gov"
|
||||
valid_ip = "1.1. 1.1"
|
||||
# initial nameservers page has one server with two ips
|
||||
# have to throw an error in order to test that the whitespace has been stripped from ip
|
||||
nameservers_page = self.app.get(reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id}))
|
||||
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||
# attempt to submit the form without one host and an ip with whitespace
|
||||
nameservers_page.form["form-0-server"] = nameserver1
|
||||
nameservers_page.form["form-1-ip"] = valid_ip
|
||||
nameservers_page.form["form-1-server"] = nameserver2
|
||||
with less_console_noise(): # swallow log warning message
|
||||
result = nameservers_page.form.submit()
|
||||
# form submission was a post with an ip address which has been stripped of whitespace,
|
||||
# response should be a 302 to success page
|
||||
self.assertEqual(result.status_code, 302)
|
||||
self.assertEqual(
|
||||
result["Location"],
|
||||
reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id}),
|
||||
)
|
||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||
page = result.follow()
|
||||
# in the event of a generic nameserver error from registry error, there will be a 302
|
||||
# with an error message displayed, so need to follow 302 and test for success message
|
||||
self.assertContains(page, "The name servers for this domain have been updated")
|
||||
|
||||
def test_domain_nameservers_form_submit_glue_record_not_allowed(self):
|
||||
"""Nameserver form catches error when IP is present
|
||||
but host not subdomain.
|
||||
|
@ -1597,7 +1655,7 @@ class TestDomainNameservers(TestDomainOverview):
|
|||
"""
|
||||
nameserver1 = "ns1.igorville.gov"
|
||||
nameserver2 = "ns2.igorville.gov"
|
||||
invalid_ip = "127.0.0.1"
|
||||
valid_ip = "127.0.0.1"
|
||||
# initial nameservers page has one server with two ips
|
||||
nameservers_page = self.app.get(reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id}))
|
||||
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||
|
@ -1606,7 +1664,7 @@ class TestDomainNameservers(TestDomainOverview):
|
|||
# only one has ips
|
||||
nameservers_page.form["form-0-server"] = nameserver1
|
||||
nameservers_page.form["form-1-server"] = nameserver2
|
||||
nameservers_page.form["form-1-ip"] = invalid_ip
|
||||
nameservers_page.form["form-1-ip"] = valid_ip
|
||||
with less_console_noise(): # swallow log warning message
|
||||
result = nameservers_page.form.submit()
|
||||
# form submission was a successful post, response should be a 302
|
||||
|
|
|
@ -68,7 +68,8 @@ class NameserverErrorCodes(IntEnum):
|
|||
- 4 TOO_MANY_HOSTS more than the max allowed host values
|
||||
- 5 MISSING_HOST host is missing for a nameserver
|
||||
- 6 INVALID_HOST host is invalid for a nameserver
|
||||
- 7 BAD_DATA bad data input for nameserver
|
||||
- 7 DUPLICATE_HOST host is a duplicate
|
||||
- 8 BAD_DATA bad data input for nameserver
|
||||
"""
|
||||
|
||||
MISSING_IP = 1
|
||||
|
@ -77,7 +78,8 @@ class NameserverErrorCodes(IntEnum):
|
|||
TOO_MANY_HOSTS = 4
|
||||
MISSING_HOST = 5
|
||||
INVALID_HOST = 6
|
||||
BAD_DATA = 7
|
||||
DUPLICATE_HOST = 7
|
||||
BAD_DATA = 8
|
||||
|
||||
|
||||
class NameserverError(Exception):
|
||||
|
@ -93,6 +95,7 @@ class NameserverError(Exception):
|
|||
NameserverErrorCodes.TOO_MANY_HOSTS: ("Too many hosts provided, you may not have more than 13 nameservers."),
|
||||
NameserverErrorCodes.MISSING_HOST: ("Name server must be provided to enter IP address."),
|
||||
NameserverErrorCodes.INVALID_HOST: ("Enter a name server in the required format, like ns1.example.com"),
|
||||
NameserverErrorCodes.DUPLICATE_HOST: ("Remove duplicate entry"),
|
||||
NameserverErrorCodes.BAD_DATA: (
|
||||
"There’s something wrong with the name server information you provided. "
|
||||
"If you need help email us at help@get.gov."
|
||||
|
|
|
@ -197,7 +197,7 @@ class DomainOrgNameAddressView(DomainFormBaseView):
|
|||
"""The form is valid, save the organization name and mailing address."""
|
||||
form.save()
|
||||
|
||||
messages.success(self.request, "The organization name and mailing address has been updated.")
|
||||
messages.success(self.request, "The organization information has been updated.")
|
||||
|
||||
# superclass has the redirect
|
||||
return super().form_valid(form)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue