mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-27 21:16:28 +02:00
Merge branch 'main' into el/2232-admin-list-markup
This commit is contained in:
commit
e8ef9ead7d
17 changed files with 1795 additions and 4503 deletions
|
@ -4,7 +4,6 @@ Pull requests should be titled in the format of `#issue_number: Descriptive name
|
||||||
Pull requests including a migration should be suffixed with ` - MIGRATION`
|
Pull requests including a migration should be suffixed with ` - MIGRATION`
|
||||||
|
|
||||||
After creating a pull request, pull request submitters should:
|
After creating a pull request, pull request submitters should:
|
||||||
- Add at least 2 developers as PR reviewers (only 1 will need to approve).
|
|
||||||
- Message on Slack or in standup to notify the team that a PR is ready for review.
|
- Message on Slack or in standup to notify the team that a PR is ready for review.
|
||||||
- If any model was updated to modify/add/delete columns, run makemigrations and commit the associated migrations file.
|
- If any model was updated to modify/add/delete columns, run makemigrations and commit the associated migrations file.
|
||||||
|
|
||||||
|
|
1317
src/Pipfile.lock
generated
1317
src/Pipfile.lock
generated
File diff suppressed because it is too large
Load diff
4666
src/package-lock.json
generated
4666
src/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -17,7 +17,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.26.0",
|
"@babel/core": "^7.26.0",
|
||||||
"@babel/preset-env": "^7.26.0",
|
"@babel/preset-env": "^7.26.0",
|
||||||
"@uswds/compile": "1.1.0",
|
"@uswds/compile": "1.2.1",
|
||||||
"babel-loader": "^9.2.1",
|
"babel-loader": "^9.2.1",
|
||||||
"sass-loader": "^12.6.0",
|
"sass-loader": "^12.6.0",
|
||||||
"webpack": "^5.96.1",
|
"webpack": "^5.96.1",
|
||||||
|
|
|
@ -1434,6 +1434,20 @@ class DomainInvitationAdmin(ListHeaderAdmin):
|
||||||
# Get the filtered values
|
# Get the filtered values
|
||||||
return super().changelist_view(request, extra_context=extra_context)
|
return super().changelist_view(request, extra_context=extra_context)
|
||||||
|
|
||||||
|
def save_model(self, request, obj, form, change):
|
||||||
|
"""
|
||||||
|
Override the save_model method.
|
||||||
|
|
||||||
|
On creation of a new domain invitation, attempt to retrieve the invitation,
|
||||||
|
which will be successful if a single User exists for that email; otherwise, will
|
||||||
|
just continue to create the invitation.
|
||||||
|
"""
|
||||||
|
if not change and User.objects.filter(email=obj.email).count() == 1:
|
||||||
|
# Domain Invitation creation for an existing User
|
||||||
|
obj.retrieve()
|
||||||
|
# Call the parent save method to save the object
|
||||||
|
super().save_model(request, obj, form, change)
|
||||||
|
|
||||||
|
|
||||||
class PortfolioInvitationAdmin(ListHeaderAdmin):
|
class PortfolioInvitationAdmin(ListHeaderAdmin):
|
||||||
"""Custom portfolio invitation admin class."""
|
"""Custom portfolio invitation admin class."""
|
||||||
|
@ -2736,8 +2750,30 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def change_view(self, request, object_id, form_url="", extra_context=None):
|
def change_view(self, request, object_id, form_url="", extra_context=None):
|
||||||
"""Display restricted warning,
|
"""Display restricted warning, setup the auditlog trail and pass it in extra context,
|
||||||
Setup the auditlog trail and pass it in extra context."""
|
display warning that status cannot be changed from 'Approved' if domain is in Ready state"""
|
||||||
|
|
||||||
|
# Fetch the domain request instance
|
||||||
|
domain_request: models.DomainRequest = models.DomainRequest.objects.get(pk=object_id)
|
||||||
|
if domain_request.approved_domain and domain_request.approved_domain.state == models.Domain.State.READY:
|
||||||
|
domain = domain_request.approved_domain
|
||||||
|
# get change url for domain
|
||||||
|
app_label = domain_request.approved_domain._meta.app_label
|
||||||
|
model_name = domain._meta.model_name
|
||||||
|
obj_id = domain.id
|
||||||
|
change_url = reverse("admin:%s_%s_change" % (app_label, model_name), args=[obj_id])
|
||||||
|
|
||||||
|
message = format_html(
|
||||||
|
"The status of this domain request cannot be changed because it has been joined to a domain in Ready status: " # noqa: E501
|
||||||
|
"<a href='{}'>{}</a>",
|
||||||
|
mark_safe(change_url), # nosec
|
||||||
|
escape(str(domain)),
|
||||||
|
)
|
||||||
|
messages.warning(
|
||||||
|
request,
|
||||||
|
message,
|
||||||
|
)
|
||||||
|
|
||||||
obj = self.get_object(request, object_id)
|
obj = self.get_object(request, object_id)
|
||||||
self.display_restricted_warning(request, obj)
|
self.display_restricted_warning(request, obj)
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ export function handleRequestingEntityFieldset() {
|
||||||
const selectParent = select?.parentElement;
|
const selectParent = select?.parentElement;
|
||||||
const suborgContainer = document.getElementById("suborganization-container");
|
const suborgContainer = document.getElementById("suborganization-container");
|
||||||
const suborgDetailsContainer = document.getElementById("suborganization-container__details");
|
const suborgDetailsContainer = document.getElementById("suborganization-container__details");
|
||||||
|
const suborgAddtlInstruction = document.getElementById("suborganization-addtl-instruction");
|
||||||
const subOrgCreateNewOption = document.getElementById("option-to-add-suborg")?.value;
|
const subOrgCreateNewOption = document.getElementById("option-to-add-suborg")?.value;
|
||||||
// Make sure all crucial page elements exist before proceeding.
|
// Make sure all crucial page elements exist before proceeding.
|
||||||
// This more or less ensures that we are on the Requesting Entity page, and not elsewhere.
|
// This more or less ensures that we are on the Requesting Entity page, and not elsewhere.
|
||||||
|
@ -26,7 +27,13 @@ export function handleRequestingEntityFieldset() {
|
||||||
function toggleSuborganization(radio=null) {
|
function toggleSuborganization(radio=null) {
|
||||||
if (radio != null) requestingSuborganization = radio?.checked && radio.value === "True";
|
if (radio != null) requestingSuborganization = radio?.checked && radio.value === "True";
|
||||||
requestingSuborganization ? showElement(suborgContainer) : hideElement(suborgContainer);
|
requestingSuborganization ? showElement(suborgContainer) : hideElement(suborgContainer);
|
||||||
requestingNewSuborganization.value = requestingSuborganization && select.value === "other" ? "True" : "False";
|
if (select.options.length == 2) { // --Select-- and other are the only options
|
||||||
|
hideElement(selectParent); // Hide the select drop down and indicate requesting new suborg
|
||||||
|
hideElement(suborgAddtlInstruction); // Hide additional instruction related to the list
|
||||||
|
requestingNewSuborganization.value = "True";
|
||||||
|
} else {
|
||||||
|
requestingNewSuborganization.value = requestingSuborganization && select.value === "other" ? "True" : "False";
|
||||||
|
}
|
||||||
requestingNewSuborganization.value === "True" ? showElement(suborgDetailsContainer) : hideElement(suborgDetailsContainer);
|
requestingNewSuborganization.value === "True" ? showElement(suborgDetailsContainer) : hideElement(suborgDetailsContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -137,7 +137,7 @@ export class MembersTable extends BaseTable {
|
||||||
}
|
}
|
||||||
// This easter egg is only for fixtures that dont have names as we are displaying their emails
|
// This easter egg is only for fixtures that dont have names as we are displaying their emails
|
||||||
// All prod users will have emails linked to their account
|
// All prod users will have emails linked to their account
|
||||||
if (customTableOptions.needsAdditionalColumn) MembersTable.addMemberModal(num_domains, member.email || "Samwise Gamgee", member_delete_url, unique_id, row);
|
if (customTableOptions.needsAdditionalColumn) MembersTable.addMemberDeleteModal(num_domains, member.email || "Samwise Gamgee", member_delete_url, unique_id, row);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -417,24 +417,21 @@ export class MembersTable extends BaseTable {
|
||||||
* @param {string} submit_delete_url - `${member_type}-${member_id}/delete`
|
* @param {string} submit_delete_url - `${member_type}-${member_id}/delete`
|
||||||
* @param {HTMLElement} wrapper_element - The element to which the modal is appended
|
* @param {HTMLElement} wrapper_element - The element to which the modal is appended
|
||||||
*/
|
*/
|
||||||
static addMemberModal(num_domains, member_email, submit_delete_url, id, wrapper_element) {
|
static addMemberDeleteModal(num_domains, member_email, submit_delete_url, id, wrapper_element) {
|
||||||
let modalHeading = '';
|
|
||||||
let modalDescription = '';
|
|
||||||
|
|
||||||
if (num_domains == 0){
|
let modalHeading = ``;
|
||||||
|
let modalDescription = ``;
|
||||||
|
|
||||||
|
if (num_domains >= 0){
|
||||||
modalHeading = `Are you sure you want to delete ${member_email}?`;
|
modalHeading = `Are you sure you want to delete ${member_email}?`;
|
||||||
modalDescription = `They will no longer be able to access this organization.
|
modalDescription = `They will no longer be able to access this organization.
|
||||||
This action cannot be undone.`;
|
This action cannot be undone.`;
|
||||||
} else if (num_domains == 1) {
|
if (num_domains >= 1)
|
||||||
modalHeading = `Are you sure you want to delete ${member_email}?`;
|
{
|
||||||
modalDescription = `<b>${member_email}</b> currently manages ${num_domains} domain in the organization.
|
modalDescription = `<b>${member_email}</b> currently manages ${num_domains} domain${num_domains > 1 ? "s": ""} in the organization.
|
||||||
Removing them from the organization will remove all of their domains. They will no longer be able to
|
Removing them from the organization will remove them from all of their domains. They will no longer be able to
|
||||||
access this organization. This action cannot be undone.`;
|
access this organization. This action cannot be undone.`;
|
||||||
} else if (num_domains > 1) {
|
}
|
||||||
modalHeading = `Are you sure you want to delete ${member_email}?`;
|
|
||||||
modalDescription = `<b>${member_email}</b> currently manages ${num_domains} domains in the organization.
|
|
||||||
Removing them from the organization will remove all of their domains. They will no longer be able to
|
|
||||||
access this organization. This action cannot be undone.`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const modalSubmit = `
|
const modalSubmit = `
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from datetime import timedelta
|
from datetime import datetime, timedelta
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
|
@ -126,7 +126,22 @@ class DomainRequestFixture:
|
||||||
# TODO for a future ticket: Allow for more than just "federal" here
|
# TODO for a future ticket: Allow for more than just "federal" here
|
||||||
request.generic_org_type = request_dict["generic_org_type"] if "generic_org_type" in request_dict else "federal"
|
request.generic_org_type = request_dict["generic_org_type"] if "generic_org_type" in request_dict else "federal"
|
||||||
if request.status != "started":
|
if request.status != "started":
|
||||||
request.last_submitted_date = fake.date()
|
# Generate fake data for first_submitted_date and last_submitted_date
|
||||||
|
# First generate a random date set to be later than 2020 (or something)
|
||||||
|
# (if we just use fake.date() we might get years like 1970 or earlier)
|
||||||
|
earliest_date_allowed = datetime(2020, 1, 1).date()
|
||||||
|
end_date = datetime.today().date() # Today's date (latest allowed date)
|
||||||
|
days_range = (end_date - earliest_date_allowed).days
|
||||||
|
first_submitted_date = earliest_date_allowed + timedelta(days=random.randint(0, days_range)) # nosec
|
||||||
|
|
||||||
|
# Generate a random positive offset to ensure last_submitted_date is later
|
||||||
|
# (Start with 1 to ensure at least 1 day difference)
|
||||||
|
offset_days = random.randint(1, 30) # nosec
|
||||||
|
last_submitted_date = first_submitted_date + timedelta(days=offset_days)
|
||||||
|
|
||||||
|
# Convert back to strings before assigning
|
||||||
|
request.first_submitted_date = first_submitted_date.strftime("%Y-%m-%d")
|
||||||
|
request.last_submitted_date = last_submitted_date.strftime("%Y-%m-%d")
|
||||||
request.federal_type = (
|
request.federal_type = (
|
||||||
request_dict["federal_type"]
|
request_dict["federal_type"]
|
||||||
if "federal_type" in request_dict
|
if "federal_type" in request_dict
|
||||||
|
|
|
@ -159,9 +159,12 @@ class RequestingEntityYesNoForm(BaseYesNoForm):
|
||||||
"""Extend the initialization of the form from RegistrarForm __init__"""
|
"""Extend the initialization of the form from RegistrarForm __init__"""
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
if self.domain_request.portfolio:
|
if self.domain_request.portfolio:
|
||||||
|
choose_text = (
|
||||||
|
"(choose from list)" if self.domain_request.portfolio.portfolio_suborganizations.exists() else ""
|
||||||
|
)
|
||||||
self.form_choices = (
|
self.form_choices = (
|
||||||
(False, self.domain_request.portfolio),
|
(False, self.domain_request.portfolio),
|
||||||
(True, "A suborganization (choose from list)"),
|
(True, f"A suborganization {choose_text}"),
|
||||||
)
|
)
|
||||||
self.fields[self.field_name] = self.get_typed_choice_field()
|
self.fields[self.field_name] = self.get_typed_choice_field()
|
||||||
|
|
||||||
|
|
|
@ -6,11 +6,7 @@
|
||||||
<ul class="usa-list">
|
<ul class="usa-list">
|
||||||
<li>Be available </li>
|
<li>Be available </li>
|
||||||
<li>Relate to your organization’s name, location, and/or services </li>
|
<li>Relate to your organization’s name, location, and/or services </li>
|
||||||
{% if portfolio %}
|
|
||||||
<li>Be clear to the general public. Your domain name must not be easily confused with other organizations.</li>
|
|
||||||
{% else %}
|
|
||||||
<li>Be unlikely to mislead or confuse the general public (even if your domain is only intended for a specific audience) </li>
|
<li>Be unlikely to mislead or confuse the general public (even if your domain is only intended for a specific audience) </li>
|
||||||
{% endif %}
|
|
||||||
</ul>
|
</ul>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
|
@ -38,8 +38,9 @@
|
||||||
<div id="suborganization-container" class="margin-top-4">
|
<div id="suborganization-container" class="margin-top-4">
|
||||||
<h2>Add suborganization information</h2>
|
<h2>Add suborganization information</h2>
|
||||||
<p>
|
<p>
|
||||||
This information will be published in <a class="usa-link usa-link--always-blue" target="_blank" href="{% public_site_url 'about/data' %}">.gov’s public data</a>. If you don’t see your suborganization in the list,
|
This information will be published in <a class="usa-link usa-link--always-blue" target="_blank" href="{% public_site_url 'about/data' %}">.gov’s public data</a>.
|
||||||
select “other.”
|
<span id="suborganization-addtl-instruction"> If you don’t see your suborganization in the list,
|
||||||
|
select “other.”</span>
|
||||||
</p>
|
</p>
|
||||||
{% with attr_required=True %}
|
{% with attr_required=True %}
|
||||||
{% input_with_errors forms.1.sub_organization %}
|
{% input_with_errors forms.1.sub_organization %}
|
||||||
|
|
|
@ -131,13 +131,11 @@ class TestDomainInvitationAdmin(TestCase):
|
||||||
tests have available superuser, client, and admin
|
tests have available superuser, client, and admin
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def setUpClass(cls):
|
|
||||||
cls.factory = RequestFactory()
|
|
||||||
cls.admin = ListHeaderAdmin(model=DomainInvitationAdmin, admin_site=AdminSite())
|
|
||||||
cls.superuser = create_superuser()
|
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
self.factory = RequestFactory()
|
||||||
|
self.admin = ListHeaderAdmin(model=DomainInvitationAdmin, admin_site=AdminSite())
|
||||||
|
self.superuser = create_superuser()
|
||||||
|
self.domain = Domain.objects.create(name="example.com")
|
||||||
"""Create a client object"""
|
"""Create a client object"""
|
||||||
self.client = Client(HTTP_HOST="localhost:8080")
|
self.client = Client(HTTP_HOST="localhost:8080")
|
||||||
|
|
||||||
|
@ -145,9 +143,6 @@ class TestDomainInvitationAdmin(TestCase):
|
||||||
"""Delete all DomainInvitation objects"""
|
"""Delete all DomainInvitation objects"""
|
||||||
DomainInvitation.objects.all().delete()
|
DomainInvitation.objects.all().delete()
|
||||||
Contact.objects.all().delete()
|
Contact.objects.all().delete()
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def tearDownClass(self):
|
|
||||||
User.objects.all().delete()
|
User.objects.all().delete()
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
|
@ -168,6 +163,7 @@ class TestDomainInvitationAdmin(TestCase):
|
||||||
)
|
)
|
||||||
self.assertContains(response, "Show more")
|
self.assertContains(response, "Show more")
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
def test_get_filters(self):
|
def test_get_filters(self):
|
||||||
"""Ensures that our filters are displaying correctly"""
|
"""Ensures that our filters are displaying correctly"""
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
|
@ -192,6 +188,59 @@ class TestDomainInvitationAdmin(TestCase):
|
||||||
self.assertContains(response, invited_html, count=1)
|
self.assertContains(response, invited_html, count=1)
|
||||||
self.assertContains(response, retrieved_html, count=1)
|
self.assertContains(response, retrieved_html, count=1)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_save_model_user_exists(self):
|
||||||
|
"""Test saving a domain invitation when the user exists.
|
||||||
|
|
||||||
|
Should attempt to retrieve the domain invitation."""
|
||||||
|
# Create a user with the same email
|
||||||
|
User.objects.create_user(email="test@example.com", username="username")
|
||||||
|
|
||||||
|
# Create a domain invitation instance
|
||||||
|
invitation = DomainInvitation(email="test@example.com", domain=self.domain)
|
||||||
|
|
||||||
|
admin_instance = DomainInvitationAdmin(DomainInvitation, admin_site=None)
|
||||||
|
|
||||||
|
# Create a request object
|
||||||
|
request = self.factory.post("/admin/registrar/DomainInvitation/add/")
|
||||||
|
request.user = self.superuser
|
||||||
|
|
||||||
|
# Patch the retrieve method
|
||||||
|
with patch.object(DomainInvitation, "retrieve") as mock_retrieve:
|
||||||
|
admin_instance.save_model(request, invitation, form=None, change=False)
|
||||||
|
|
||||||
|
# Assert retrieve was called
|
||||||
|
mock_retrieve.assert_called_once()
|
||||||
|
|
||||||
|
# Assert the invitation was saved
|
||||||
|
self.assertEqual(DomainInvitation.objects.count(), 1)
|
||||||
|
self.assertEqual(DomainInvitation.objects.first().email, "test@example.com")
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_save_model_user_does_not_exist(self):
|
||||||
|
"""Test saving a domain invitation when the user does not exist.
|
||||||
|
|
||||||
|
Should not attempt to retrieve the domain invitation."""
|
||||||
|
# Create a domain invitation instance
|
||||||
|
invitation = DomainInvitation(email="nonexistent@example.com", domain=self.domain)
|
||||||
|
|
||||||
|
admin_instance = DomainInvitationAdmin(DomainInvitation, admin_site=None)
|
||||||
|
|
||||||
|
# Create a request object
|
||||||
|
request = self.factory.post("/admin/registrar/DomainInvitation/add/")
|
||||||
|
request.user = self.superuser
|
||||||
|
|
||||||
|
# Patch the retrieve method to ensure it is not called
|
||||||
|
with patch.object(DomainInvitation, "retrieve") as mock_retrieve:
|
||||||
|
admin_instance.save_model(request, invitation, form=None, change=False)
|
||||||
|
|
||||||
|
# Assert retrieve was not called
|
||||||
|
mock_retrieve.assert_not_called()
|
||||||
|
|
||||||
|
# Assert the invitation was saved
|
||||||
|
self.assertEqual(DomainInvitation.objects.count(), 1)
|
||||||
|
self.assertEqual(DomainInvitation.objects.first().email, "nonexistent@example.com")
|
||||||
|
|
||||||
|
|
||||||
class TestUserPortfolioPermissionAdmin(TestCase):
|
class TestUserPortfolioPermissionAdmin(TestCase):
|
||||||
"""Tests for the PortfolioInivtationAdmin class"""
|
"""Tests for the PortfolioInivtationAdmin class"""
|
||||||
|
|
|
@ -28,6 +28,8 @@ from registrar.models import (
|
||||||
AllowedEmail,
|
AllowedEmail,
|
||||||
Suborganization,
|
Suborganization,
|
||||||
)
|
)
|
||||||
|
from registrar.models.host import Host
|
||||||
|
from registrar.models.public_contact import PublicContact
|
||||||
from .common import (
|
from .common import (
|
||||||
MockSESClient,
|
MockSESClient,
|
||||||
completed_domain_request,
|
completed_domain_request,
|
||||||
|
@ -40,7 +42,7 @@ from .common import (
|
||||||
GenericTestHelper,
|
GenericTestHelper,
|
||||||
normalize_html,
|
normalize_html,
|
||||||
)
|
)
|
||||||
from unittest.mock import patch
|
from unittest.mock import ANY, patch
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
import boto3_mocking # type: ignore
|
import boto3_mocking # type: ignore
|
||||||
|
@ -80,6 +82,8 @@ class TestDomainRequestAdmin(MockEppLib):
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
super().tearDown()
|
super().tearDown()
|
||||||
|
Host.objects.all().delete()
|
||||||
|
PublicContact.objects.all().delete()
|
||||||
Domain.objects.all().delete()
|
Domain.objects.all().delete()
|
||||||
DomainInformation.objects.all().delete()
|
DomainInformation.objects.all().delete()
|
||||||
DomainRequest.objects.all().delete()
|
DomainRequest.objects.all().delete()
|
||||||
|
@ -2107,6 +2111,37 @@ class TestDomainRequestAdmin(MockEppLib):
|
||||||
"Cannot edit a domain request with a restricted creator.",
|
"Cannot edit a domain request with a restricted creator.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_approved_domain_request_with_ready_domain_has_warning_message(self):
|
||||||
|
"""Tests if the domain request has a warning message when the approved domain is in Ready state"""
|
||||||
|
# Create an instance of the model
|
||||||
|
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
|
||||||
|
# Approve the domain request
|
||||||
|
domain_request.approve()
|
||||||
|
domain_request.save()
|
||||||
|
|
||||||
|
# Add nameservers to get to Ready state
|
||||||
|
domain_request.approved_domain.nameservers = [
|
||||||
|
("ns1.city.gov", ["1.1.1.1"]),
|
||||||
|
("ns2.city.gov", ["1.1.1.2"]),
|
||||||
|
]
|
||||||
|
domain_request.approved_domain.save()
|
||||||
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
|
with patch("django.contrib.messages.warning") as mock_warning:
|
||||||
|
# Create a request object
|
||||||
|
self.client.force_login(self.superuser)
|
||||||
|
self.client.get(
|
||||||
|
"/admin/registrar/domainrequest/{}/change/".format(domain_request.pk),
|
||||||
|
follow=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Assert that the error message was called with the correct argument
|
||||||
|
mock_warning.assert_called_once_with(
|
||||||
|
ANY, # don't care about the request argument
|
||||||
|
f"The status of this domain request cannot be changed because it has been joined to a domain in Ready status: <a href='/admin/registrar/domain/{domain_request.approved_domain.id}/change/'>{domain_request.approved_domain.name}</a>", # noqa
|
||||||
|
)
|
||||||
|
|
||||||
def trigger_saving_approved_to_another_state(self, domain_is_active, another_state, rejection_reason=None):
|
def trigger_saving_approved_to_another_state(self, domain_is_active, another_state, rejection_reason=None):
|
||||||
"""Helper method that triggers domain request state changes from approved to another state,
|
"""Helper method that triggers domain request state changes from approved to another state,
|
||||||
with an associated domain that can be either active (READY) or not.
|
with an associated domain that can be either active (READY) or not.
|
||||||
|
|
|
@ -338,7 +338,7 @@ class GetRequestsJsonTest(TestWithUser, WebTest):
|
||||||
self.assertEqual(expected_domain_request.creator.email, creator[i])
|
self.assertEqual(expected_domain_request.creator.email, creator[i])
|
||||||
# Check action url, action label and svg icon
|
# Check action url, action label and svg icon
|
||||||
# Example domain requests will test each of below three scenarios
|
# Example domain requests will test each of below three scenarios
|
||||||
if creator[i] != self.user.email:
|
if creator[i] != self.user.email or not self.user.has_edit_request_portfolio_permission(self.portfolio):
|
||||||
# Test case where action is View
|
# Test case where action is View
|
||||||
self.assertEqual("View", action_labels[i])
|
self.assertEqual("View", action_labels[i])
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
|
|
@ -1952,7 +1952,7 @@ class DomainRequestGrowth(DomainRequestExport):
|
||||||
"Domain request",
|
"Domain request",
|
||||||
"Domain type",
|
"Domain type",
|
||||||
"Federal type",
|
"Federal type",
|
||||||
"Submitted at",
|
"First submitted date",
|
||||||
]
|
]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -1976,7 +1976,6 @@ class DomainRequestGrowth(DomainRequestExport):
|
||||||
start_date_formatted = format_start_date(start_date)
|
start_date_formatted = format_start_date(start_date)
|
||||||
end_date_formatted = format_end_date(end_date)
|
end_date_formatted = format_end_date(end_date)
|
||||||
return Q(
|
return Q(
|
||||||
status=DomainRequest.DomainRequestStatus.SUBMITTED,
|
|
||||||
last_submitted_date__lte=end_date_formatted,
|
last_submitted_date__lte=end_date_formatted,
|
||||||
last_submitted_date__gte=start_date_formatted,
|
last_submitted_date__gte=start_date_formatted,
|
||||||
)
|
)
|
||||||
|
|
|
@ -125,15 +125,6 @@ def serialize_domain_request(request, domain_request, user):
|
||||||
DomainRequest.DomainRequestStatus.WITHDRAWN,
|
DomainRequest.DomainRequestStatus.WITHDRAWN,
|
||||||
]
|
]
|
||||||
|
|
||||||
# Determine if the request is deletable
|
|
||||||
if not user.is_org_user(request):
|
|
||||||
is_deletable = domain_request.status in deletable_statuses
|
|
||||||
else:
|
|
||||||
portfolio = request.session.get("portfolio")
|
|
||||||
is_deletable = (
|
|
||||||
domain_request.status in deletable_statuses and user.has_edit_request_portfolio_permission(portfolio)
|
|
||||||
) and domain_request.creator == user
|
|
||||||
|
|
||||||
# Determine action label based on user permissions and request status
|
# Determine action label based on user permissions and request status
|
||||||
editable_statuses = [
|
editable_statuses = [
|
||||||
DomainRequest.DomainRequestStatus.STARTED,
|
DomainRequest.DomainRequestStatus.STARTED,
|
||||||
|
@ -141,11 +132,26 @@ def serialize_domain_request(request, domain_request, user):
|
||||||
DomainRequest.DomainRequestStatus.WITHDRAWN,
|
DomainRequest.DomainRequestStatus.WITHDRAWN,
|
||||||
]
|
]
|
||||||
|
|
||||||
if user.has_edit_request_portfolio_permission and domain_request.creator == user:
|
# No portfolio action_label
|
||||||
|
if domain_request.creator == user:
|
||||||
action_label = "Edit" if domain_request.status in editable_statuses else "Manage"
|
action_label = "Edit" if domain_request.status in editable_statuses else "Manage"
|
||||||
else:
|
else:
|
||||||
action_label = "View"
|
action_label = "View"
|
||||||
|
|
||||||
|
# No portfolio deletable
|
||||||
|
is_deletable = domain_request.status in deletable_statuses
|
||||||
|
|
||||||
|
# If we're working with a portfolio
|
||||||
|
if user.is_org_user(request):
|
||||||
|
portfolio = request.session.get("portfolio")
|
||||||
|
is_deletable = (
|
||||||
|
domain_request.status in deletable_statuses and user.has_edit_request_portfolio_permission(portfolio)
|
||||||
|
) and domain_request.creator == user
|
||||||
|
if user.has_edit_request_portfolio_permission(portfolio) and domain_request.creator == user:
|
||||||
|
action_label = "Edit" if domain_request.status in editable_statuses else "Manage"
|
||||||
|
else:
|
||||||
|
action_label = "View"
|
||||||
|
|
||||||
# Map the action label to corresponding URLs and icons
|
# Map the action label to corresponding URLs and icons
|
||||||
action_url_map = {
|
action_url_map = {
|
||||||
"Edit": reverse("edit-domain-request", kwargs={"id": domain_request.id}),
|
"Edit": reverse("edit-domain-request", kwargs={"id": domain_request.id}),
|
||||||
|
|
|
@ -1,68 +1,68 @@
|
||||||
-i https://pypi.python.org/simple
|
-i https://pypi.python.org/simple
|
||||||
annotated-types==0.7.0; python_version >= '3.8'
|
annotated-types==0.7.0; python_version >= '3.8'
|
||||||
asgiref==3.8.1; python_version >= '3.8'
|
asgiref==3.8.1; python_version >= '3.8'
|
||||||
boto3==1.35.41; python_version >= '3.8'
|
boto3==1.35.91; python_version >= '3.8'
|
||||||
botocore==1.35.41; python_version >= '3.8'
|
botocore==1.35.91; python_version >= '3.8'
|
||||||
cachetools==5.5.0; python_version >= '3.7'
|
cachetools==5.5.0; python_version >= '3.7'
|
||||||
certifi==2024.8.30; python_version >= '3.6'
|
certifi==2024.12.14; python_version >= '3.6'
|
||||||
cfenv==0.5.3
|
cfenv==0.5.3
|
||||||
cffi==1.17.1; platform_python_implementation != 'PyPy'
|
cffi==1.17.1; python_version >= '3.8'
|
||||||
charset-normalizer==3.4.0; python_full_version >= '3.7.0'
|
charset-normalizer==3.4.1; python_version >= '3.7'
|
||||||
cryptography==43.0.1; python_version >= '3.7'
|
cryptography==44.0.0; python_version >= '3.7' and python_full_version not in '3.9.0, 3.9.1'
|
||||||
defusedxml==0.7.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
|
defusedxml==0.7.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
|
||||||
diff-match-patch==20230430; python_version >= '3.7'
|
diff-match-patch==20241021; python_version >= '3.7'
|
||||||
dj-database-url==2.2.0
|
dj-database-url==2.3.0
|
||||||
dj-email-url==1.0.6
|
dj-email-url==1.0.6
|
||||||
django==4.2.17; python_version >= '3.8'
|
django==4.2.17; python_version >= '3.8'
|
||||||
django-admin-multiple-choice-list-filter==0.1.1
|
django-admin-multiple-choice-list-filter==0.1.1
|
||||||
django-allow-cidr==0.7.1
|
django-allow-cidr==0.7.1
|
||||||
django-auditlog==3.0.0; python_version >= '3.8'
|
django-auditlog==3.0.0; python_version >= '3.8'
|
||||||
django-cache-url==3.4.5
|
django-cache-url==3.4.5
|
||||||
django-cors-headers==4.5.0; python_version >= '3.9'
|
django-cors-headers==4.6.0; python_version >= '3.9'
|
||||||
django-csp==3.8
|
django-csp==3.8
|
||||||
django-fsm==2.8.1
|
django-fsm==2.8.1
|
||||||
django-import-export==4.1.1; python_version >= '3.8'
|
django-import-export==4.3.3; python_version >= '3.9'
|
||||||
django-login-required-middleware==0.9.0
|
django-login-required-middleware==0.9.0
|
||||||
django-phonenumber-field[phonenumberslite]==8.0.0; python_version >= '3.8'
|
django-phonenumber-field[phonenumberslite]==8.0.0; python_version >= '3.8'
|
||||||
django-waffle==4.1.0; python_version >= '3.8'
|
django-waffle==4.2.0; python_version >= '3.8'
|
||||||
django-widget-tweaks==1.5.0; python_version >= '3.8'
|
django-widget-tweaks==1.5.0; python_version >= '3.8'
|
||||||
environs[django]==11.0.0; python_version >= '3.8'
|
environs[django]==11.2.1; python_version >= '3.8'
|
||||||
faker==30.3.0; python_version >= '3.8'
|
faker==33.1.0; python_version >= '3.8'
|
||||||
fred-epplib@ git+https://github.com/cisagov/epplib.git@d56d183f1664f34c40ca9716a3a9a345f0ef561c
|
fred-epplib @ git+https://github.com/cisagov/epplib.git@d56d183f1664f34c40ca9716a3a9a345f0ef561c
|
||||||
furl==2.1.3
|
furl==2.1.3
|
||||||
future==1.0.0; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'
|
future==1.0.0; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'
|
||||||
gevent==24.10.2; python_version >= '3.9'
|
gevent==24.11.1; python_version >= '3.9'
|
||||||
greenlet==3.1.1; python_version >= '3.7'
|
greenlet==3.1.1; python_version >= '3.7'
|
||||||
gunicorn==23.0.0; python_version >= '3.7'
|
gunicorn==23.0.0; python_version >= '3.7'
|
||||||
idna==3.10; python_version >= '3.6'
|
idna==3.10; python_version >= '3.6'
|
||||||
jmespath==1.0.1; python_version >= '3.7'
|
jmespath==1.0.1; python_version >= '3.7'
|
||||||
lxml==5.3.0; python_version >= '3.6'
|
lxml==5.3.0; python_version >= '3.6'
|
||||||
mako==1.3.5; python_version >= '3.8'
|
mako==1.3.8; python_version >= '3.8'
|
||||||
markupsafe==3.0.1; python_version >= '3.9'
|
markupsafe==3.0.2; python_version >= '3.9'
|
||||||
marshmallow==3.22.0; python_version >= '3.8'
|
marshmallow==3.23.2; python_version >= '3.9'
|
||||||
oic==1.7.0; python_version ~= '3.8'
|
oic==1.7.0; python_version ~= '3.8'
|
||||||
orderedmultidict==1.0.1
|
orderedmultidict==1.0.1
|
||||||
packaging==24.1; python_version >= '3.8'
|
packaging==24.2; python_version >= '3.8'
|
||||||
phonenumberslite==8.13.47
|
phonenumberslite==8.13.52
|
||||||
psycopg2-binary==2.9.9; python_version >= '3.7'
|
psycopg2-binary==2.9.10; python_version >= '3.8'
|
||||||
pycparser==2.22; python_version >= '3.8'
|
pycparser==2.22; python_version >= '3.8'
|
||||||
pycryptodomex==3.21.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'
|
pycryptodomex==3.21.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'
|
||||||
pydantic==2.9.2; python_version >= '3.8'
|
pydantic==2.10.4; python_version >= '3.8'
|
||||||
pydantic-core==2.23.4; python_version >= '3.8'
|
pydantic-core==2.27.2; python_version >= '3.8'
|
||||||
pydantic-settings==2.5.2; python_version >= '3.8'
|
pydantic-settings==2.7.1; python_version >= '3.8'
|
||||||
pyjwkest==1.4.2
|
pyjwkest==1.4.2
|
||||||
python-dateutil==2.9.0.post0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
|
python-dateutil==2.9.0.post0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'
|
||||||
python-dotenv==1.0.1; python_version >= '3.8'
|
python-dotenv==1.0.1; python_version >= '3.8'
|
||||||
pyzipper==0.3.6; python_version >= '3.4'
|
pyzipper==0.3.6; python_version >= '3.4'
|
||||||
requests==2.32.3; python_version >= '3.8'
|
requests==2.32.3; python_version >= '3.8'
|
||||||
s3transfer==0.10.3; python_version >= '3.8'
|
s3transfer==0.10.4; python_version >= '3.8'
|
||||||
setuptools==75.1.0; python_version >= '3.8'
|
setuptools==75.6.0; python_version >= '3.9'
|
||||||
six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
|
six==1.17.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'
|
||||||
sqlparse==0.5.1; python_version >= '3.8'
|
sqlparse==0.5.3; python_version >= '3.8'
|
||||||
tablib[html,ods,xls,xlsx,yaml]==3.5.0; python_version >= '3.8'
|
tablib==3.7.0; python_version >= '3.9'
|
||||||
tblib==3.0.0; python_version >= '3.8'
|
tblib==3.0.0; python_version >= '3.8'
|
||||||
typing-extensions==4.12.2; python_version >= '3.8'
|
typing-extensions==4.12.2; python_version >= '3.8'
|
||||||
urllib3==2.2.3; python_version >= '3.8'
|
urllib3==2.3.0; python_version >= '3.9'
|
||||||
whitenoise==6.7.0; python_version >= '3.8'
|
whitenoise==6.8.2; python_version >= '3.9'
|
||||||
zope.event==5.0; python_version >= '3.7'
|
zope.event==5.0; python_version >= '3.7'
|
||||||
zope.interface==7.1.0; python_version >= '3.8'
|
zope.interface==7.2; python_version >= '3.8'
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue