mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-08-15 05:54:11 +02:00
Merge pull request #3477 from cisagov/rh/3348-domain-dropdown
#3348: Cancel invitation within Django Admin - [GD]
This commit is contained in:
commit
f6e0600cc9
6 changed files with 165 additions and 5 deletions
|
@ -11,6 +11,7 @@ from django.db.models import (
|
||||||
Value,
|
Value,
|
||||||
When,
|
When,
|
||||||
)
|
)
|
||||||
|
|
||||||
from django.db.models.functions import Concat, Coalesce
|
from django.db.models.functions import Concat, Coalesce
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
from registrar.models.federal_agency import FederalAgency
|
from registrar.models.federal_agency import FederalAgency
|
||||||
|
@ -24,7 +25,7 @@ from registrar.utility.admin_helpers import (
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.messages import get_messages
|
from django.contrib.messages import get_messages
|
||||||
from django.contrib.admin.helpers import AdminForm
|
from django.contrib.admin.helpers import AdminForm
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect, get_object_or_404
|
||||||
from django_fsm import get_available_FIELD_transitions, FSMField
|
from django_fsm import get_available_FIELD_transitions, FSMField
|
||||||
from registrar.models import DomainInformation, Portfolio, UserPortfolioPermission, DomainInvitation
|
from registrar.models import DomainInformation, Portfolio, UserPortfolioPermission, DomainInvitation
|
||||||
from registrar.models.utility.portfolio_helper import UserPortfolioPermissionChoices, UserPortfolioRoleChoices
|
from registrar.models.utility.portfolio_helper import UserPortfolioPermissionChoices, UserPortfolioRoleChoices
|
||||||
|
@ -1533,6 +1534,27 @@ class DomainInvitationAdmin(BaseInvitationAdmin):
|
||||||
# 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 change_view(self, request, object_id, form_url="", extra_context=None):
|
||||||
|
"""Override the change_view to add the invitation obj for the change_form_object_tools template"""
|
||||||
|
|
||||||
|
if extra_context is None:
|
||||||
|
extra_context = {}
|
||||||
|
|
||||||
|
# Get the domain invitation object
|
||||||
|
invitation = get_object_or_404(DomainInvitation, id=object_id)
|
||||||
|
extra_context["invitation"] = invitation
|
||||||
|
|
||||||
|
if request.method == "POST" and "cancel_invitation" in request.POST:
|
||||||
|
if invitation.status == DomainInvitation.DomainInvitationStatus.INVITED:
|
||||||
|
invitation.cancel_invitation()
|
||||||
|
invitation.save(update_fields=["status"])
|
||||||
|
messages.success(request, _("Invitation canceled successfully."))
|
||||||
|
|
||||||
|
# Redirect back to the change view
|
||||||
|
return redirect(reverse("admin:registrar_domaininvitation_change", args=[object_id]))
|
||||||
|
|
||||||
|
return super().change_view(request, object_id, form_url, extra_context)
|
||||||
|
|
||||||
def delete_view(self, request, object_id, extra_context=None):
|
def delete_view(self, request, object_id, extra_context=None):
|
||||||
"""
|
"""
|
||||||
Custom delete_view to perform additional actions or customize the template.
|
Custom delete_view to perform additional actions or customize the template.
|
||||||
|
@ -1551,6 +1573,7 @@ class DomainInvitationAdmin(BaseInvitationAdmin):
|
||||||
which will be successful if a single User exists for that email; otherwise, will
|
which will be successful if a single User exists for that email; otherwise, will
|
||||||
just continue to create the invitation.
|
just continue to create the invitation.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not change:
|
if not change:
|
||||||
domain = obj.domain
|
domain = obj.domain
|
||||||
domain_org = getattr(domain.domain_info, "portfolio", None)
|
domain_org = getattr(domain.domain_info, "portfolio", None)
|
||||||
|
|
|
@ -498,6 +498,28 @@ input[type=submit].button--dja-toolbar:focus, input[type=submit].button--dja-too
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.object-tools li button {
|
||||||
|
font-family: Source Sans Pro Web, Helvetica Neue, Helvetica, Roboto, Arial, sans-serif;
|
||||||
|
text-transform: none !important;
|
||||||
|
font-size: 14px !important;
|
||||||
|
display: block;
|
||||||
|
float: left;
|
||||||
|
padding: 3px 12px;
|
||||||
|
background: var(--object-tools-bg) !important;
|
||||||
|
color: var(--object-tools-fg);
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 0.6875rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
border-radius: 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
border: none;
|
||||||
|
line-height: 20px;
|
||||||
|
&:focus, &:hover{
|
||||||
|
background: var(--object-tools-hover-bg) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.module--custom {
|
.module--custom {
|
||||||
a {
|
a {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
|
|
@ -46,7 +46,6 @@ body {
|
||||||
background-color: color('gray-1');
|
background-color: color('gray-1');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.section-outlined {
|
.section-outlined {
|
||||||
background-color: color('white');
|
background-color: color('white');
|
||||||
border: 1px solid color('base-lighter');
|
border: 1px solid color('base-lighter');
|
||||||
|
|
|
@ -15,13 +15,28 @@
|
||||||
</ul>
|
</ul>
|
||||||
{% else %}
|
{% else %}
|
||||||
<ul>
|
<ul>
|
||||||
|
{% if opts.model_name == 'domaininvitation' %}
|
||||||
|
{% if invitation.status == invitation.DomainInvitationStatus.INVITED %}
|
||||||
|
<li>
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="cancel_invitation" value="true">
|
||||||
|
<button type="submit" class="usa-button--dja">
|
||||||
|
Cancel invitation
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<a href="{% add_preserved_filters history_url %}">{% translate "History" %}</a>
|
<a href="{% add_preserved_filters history_url %}">{% translate "History" %}</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
{% if opts.model_name == 'domainrequest' %}
|
{% if opts.model_name == 'domainrequest' %}
|
||||||
<li>
|
<li>
|
||||||
<a id="id-copy-to-clipboard-summary" class="usa-button--dja" type="button" href="#">
|
<a id="id-copy-to-clipboard-summary" class="usa-button--dja" type="button" href="#">
|
||||||
<svg class="usa-icon" >
|
<svg class="usa-icon">
|
||||||
<use xlink:href="{%static 'img/sprite.svg'%}#content_copy"></use>
|
<use xlink:href="{%static 'img/sprite.svg'%}#content_copy"></use>
|
||||||
</svg>
|
</svg>
|
||||||
<!-- the span is targeted in JS, do not remove -->
|
<!-- the span is targeted in JS, do not remove -->
|
||||||
|
@ -32,4 +47,3 @@
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ from registrar.models import (
|
||||||
Domain,
|
Domain,
|
||||||
DomainRequest,
|
DomainRequest,
|
||||||
DomainInformation,
|
DomainInformation,
|
||||||
|
DomainInvitation,
|
||||||
User,
|
User,
|
||||||
Host,
|
Host,
|
||||||
Portfolio,
|
Portfolio,
|
||||||
|
@ -495,6 +496,107 @@ class TestDomainInformationInline(MockEppLib):
|
||||||
self.assertIn("poopy@gov.gov", domain_managers)
|
self.assertIn("poopy@gov.gov", domain_managers)
|
||||||
|
|
||||||
|
|
||||||
|
class TestDomainInvitationAdmin(TestCase):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super().setUpClass()
|
||||||
|
cls.staffuser = create_user(email="staffdomainmanager@meoward.com", is_staff=True)
|
||||||
|
cls.site = AdminSite()
|
||||||
|
cls.admin = DomainAdmin(model=Domain, admin_site=cls.site)
|
||||||
|
cls.factory = RequestFactory()
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.client = Client(HTTP_HOST="localhost:8080")
|
||||||
|
self.client.force_login(self.staffuser)
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
def test_successful_cancel_invitation_flow_in_admin(self):
|
||||||
|
"""Testing canceling a domain invitation in Django Admin."""
|
||||||
|
|
||||||
|
# 1. Create a domain and assign staff user role + domain manager
|
||||||
|
domain = Domain.objects.create(name="cancelinvitationflowviaadmin.gov")
|
||||||
|
UserDomainRole.objects.create(user=self.staffuser, domain=domain, role="manager")
|
||||||
|
|
||||||
|
# 2. Invite a domain manager to the above domain
|
||||||
|
invitation = DomainInvitation.objects.create(
|
||||||
|
email="inviteddomainmanager@meoward.com",
|
||||||
|
domain=domain,
|
||||||
|
status=DomainInvitation.DomainInvitationStatus.INVITED,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 3. Go to the Domain Invitations list in /admin
|
||||||
|
domain_invitation_list_url = reverse("admin:registrar_domaininvitation_changelist")
|
||||||
|
response = self.client.get(domain_invitation_list_url)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
# 4. Go to the change view of that invitation and make sure you can see the button
|
||||||
|
domain_invitation_change_url = reverse("admin:registrar_domaininvitation_change", args=[invitation.id])
|
||||||
|
response = self.client.get(domain_invitation_change_url)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, "Cancel invitation")
|
||||||
|
|
||||||
|
# 5. Click the cancel invitation button
|
||||||
|
response = self.client.post(domain_invitation_change_url, {"cancel_invitation": "true"}, follow=True)
|
||||||
|
|
||||||
|
# 6. Make sure we're redirect back to the change view page in /admin
|
||||||
|
self.assertRedirects(response, domain_invitation_change_url)
|
||||||
|
|
||||||
|
# 7. Confirm cancellation confirmation message appears
|
||||||
|
expected_message = f"Invitation for {invitation.email} on {domain.name} is canceled"
|
||||||
|
self.assertContains(response, expected_message)
|
||||||
|
|
||||||
|
def test_no_cancel_invitation_button_in_retrieved_state(self):
|
||||||
|
"""Shouldn't be able to see the "Cancel invitation" button if invitation is RETRIEVED state"""
|
||||||
|
|
||||||
|
# 1. Create a domain and assign staff user role + domain manager
|
||||||
|
domain = Domain.objects.create(name="retrieved.gov")
|
||||||
|
UserDomainRole.objects.create(user=self.staffuser, domain=domain, role="manager")
|
||||||
|
|
||||||
|
# 2. Invite a domain manager to the above domain and NOT in invited state
|
||||||
|
invitation = DomainInvitation.objects.create(
|
||||||
|
email="retrievedinvitation@meoward.com",
|
||||||
|
domain=domain,
|
||||||
|
status=DomainInvitation.DomainInvitationStatus.RETRIEVED,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 3. Go to the Domain Invitations list in /admin
|
||||||
|
domain_invitation_list_url = reverse("admin:registrar_domaininvitation_changelist")
|
||||||
|
response = self.client.get(domain_invitation_list_url)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
# 4. Go to the change view of that invitation and make sure you CANNOT see the button
|
||||||
|
domain_invitation_change_url = reverse("admin:registrar_domaininvitation_change", args=[invitation.id])
|
||||||
|
response = self.client.get(domain_invitation_change_url)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertNotContains(response, "Cancel invitation")
|
||||||
|
|
||||||
|
def test_no_cancel_invitation_button_in_canceled_state(self):
|
||||||
|
"""Shouldn't be able to see the "Cancel invitation" button if invitation is CANCELED state"""
|
||||||
|
|
||||||
|
# 1. Create a domain and assign staff user role + domain manager
|
||||||
|
domain = Domain.objects.create(name="canceled.gov")
|
||||||
|
UserDomainRole.objects.create(user=self.staffuser, domain=domain, role="manager")
|
||||||
|
|
||||||
|
# 2. Invite a domain manager to the above domain and NOT in invited state
|
||||||
|
invitation = DomainInvitation.objects.create(
|
||||||
|
email="canceledinvitation@meoward.com",
|
||||||
|
domain=domain,
|
||||||
|
status=DomainInvitation.DomainInvitationStatus.CANCELED,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 3. Go to the Domain Invitations list in /admin
|
||||||
|
domain_invitation_list_url = reverse("admin:registrar_domaininvitation_changelist")
|
||||||
|
response = self.client.get(domain_invitation_list_url)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
# 4. Go to the change view of that invitation and make sure you CANNOT see the button
|
||||||
|
domain_invitation_change_url = reverse("admin:registrar_domaininvitation_change", args=[invitation.id])
|
||||||
|
response = self.client.get(domain_invitation_change_url)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertNotContains(response, "Cancel invitation")
|
||||||
|
|
||||||
|
|
||||||
class TestDomainAdminWithClient(TestCase):
|
class TestDomainAdminWithClient(TestCase):
|
||||||
"""Test DomainAdmin class as super user.
|
"""Test DomainAdmin class as super user.
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue