Did it the endpoint method way which was sadly incorrect

This commit is contained in:
Rebecca Hsieh 2025-02-06 13:04:53 -08:00
parent 4b523a75ce
commit 95d912ecbc
No known key found for this signature in database
8 changed files with 114 additions and 60 deletions

View file

@ -11,6 +11,7 @@ from django.db.models import (
Value,
When,
)
from django.db.models.functions import Concat, Coalesce
from django.http import HttpResponseRedirect
from registrar.models.federal_agency import FederalAgency
@ -24,7 +25,7 @@ from registrar.utility.admin_helpers import (
from django.conf import settings
from django.contrib.messages import get_messages
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 registrar.models import DomainInformation, Portfolio, UserPortfolioPermission, DomainInvitation
from registrar.models.utility.portfolio_helper import UserPortfolioPermissionChoices, UserPortfolioRoleChoices
@ -1478,26 +1479,6 @@ class BaseInvitationAdmin(ListHeaderAdmin):
return response
# class DomainInvitationAdminForm(forms.ModelForm):
# """Custom form for DomainInvitation in admin to only allow cancellations."""
# STATUS_CHOICES = [
# ("", "------"), # no action
# ("canceled", "Canceled"),
# ]
# status = forms.ChoiceField(choices=STATUS_CHOICES, required=False, label="Status")
# class Meta:
# model = models.DomainInvitation
# fields = "__all__"
# def clean_status(self):
# # Clean status - we purposely dont edit anything so we dont mess with the state
# status = self.cleaned_data.get("status")
# return status
class DomainInvitationAdmin(BaseInvitationAdmin):
"""Custom domain invitation admin class."""
@ -1527,18 +1508,29 @@ class DomainInvitationAdmin(BaseInvitationAdmin):
search_help_text = "Search by email or domain."
# # Mark the FSM field 'status' as readonly
# # to allow admin users to create Domain Invitations
# # without triggering the FSM Transition Not Allowed
# # error.
# readonly_fields = ["status"]
readonly_fields = []
# Mark the FSM field 'status' as readonly
# to allow admin users to create Domain Invitations
# without triggering the FSM Transition Not Allowed
# error.
readonly_fields = ["status"]
autocomplete_fields = ["domain"]
change_form_template = "django/admin/domain_invitation_change_form.html"
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
return super().change_view(request, object_id, form_url, extra_context)
def save_model(self, request, obj, form, change):
"""
Override the save_model method.

View file

@ -498,6 +498,22 @@ input[type=submit].button--dja-toolbar:focus, input[type=submit].button--dja-too
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;
}
.module--custom {
a {
font-size: 13px;

View file

@ -46,7 +46,6 @@ body {
background-color: color('gray-1');
}
.section-outlined {
background-color: color('white');
border: 1px solid color('base-lighter');

View file

@ -78,10 +78,6 @@ class DomainInvitation(TimeStampedModel):
@transition(field="status", source=DomainInvitationStatus.INVITED, target=DomainInvitationStatus.CANCELED)
def cancel_invitation(self):
"""When an invitation is canceled, change the status to canceled"""
# print("***** IN CANCEL_INVITATION SECTION")
# logger.info(f"Invitation for {self.email} to {self.domain} has been canceled.")
# print("WHEN INVITATION IS CANCELED > CHANGE STATUS TO CANCELED")
# Send email here maybe?
pass
@transition(field="status", source=DomainInvitationStatus.CANCELED, target=DomainInvitationStatus.INVITED)

View file

@ -15,13 +15,28 @@
</ul>
{% else %}
<ul>
{% if opts.model_name == 'domaininvitation' %}
<li>
<form method="post" action="{% url 'invitation-cancel' pk=invitation.id %}">
{% csrf_token %}
<button type="submit" class="usa-button--dja">
<svg class="usa-icon">
<use xlink:href="{%static 'img/sprite.svg'%}#cancel"></use>
</svg>
<span>{% translate "Cancel invitation" %}</span>
</button>
</form>
</li>
{% endif %}
<li>
<a href="{% add_preserved_filters history_url %}">{% translate "History" %}</a>
</li>
{% if opts.model_name == 'domainrequest' %}
<li>
<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>
</svg>
<!-- the span is targeted in JS, do not remove -->
@ -31,5 +46,4 @@
{% endif %}
</ul>
{% endif %}
{% endblock %}
{% endblock %}

View file

@ -1,22 +0,0 @@
{% load static %}
<div class="admin-icon-group">
<span id="status-field">{{ field.value | capfirst }}</span>
<input aria-hidden="true" class="display-none" value="">
<button
class="usa-button--dja usa-button usa-button__small-text usa-button--unstyled padding-left-1 usa-button--icon copy-to-clipboard"
type="button"
>
<div class="no-outline-on-click">
<svg
class="usa-icon"
>
<use aria-hidden="true" xlink:href="{%static 'img/sprite.svg'%}#highlight_off"></use>
</svg>
<!-- the span is targeted in JS, do not remove -->
<span>Cancel invitation</span>
</div>
</button>
</div>

View file

@ -7,8 +7,6 @@ This is using a custom implementation fieldset.html (see admin/fieldset.html)
{% block field_other %}
{% if field.field.name == "email" %}
{% include "admin/input_with_clipboard.html" with field=field.field %}
{% elif field.field.name == "status" %}
{% include "admin/status_with_clipboard.html" with field=field.field %}
{% else %}
{{ block.super }}
{% endif %}

View file

@ -12,6 +12,7 @@ from registrar.models import (
Domain,
DomainRequest,
DomainInformation,
DomainInvitation,
User,
Host,
Portfolio,
@ -30,6 +31,9 @@ from .common import (
)
from unittest.mock import ANY, call, patch
from django.contrib.messages import get_messages
import boto3_mocking # type: ignore
import logging
@ -495,6 +499,63 @@ class TestDomainInformationInline(MockEppLib):
self.assertIn("poopy@gov.gov", domain_managers)
class DomainInvitationAdminTest(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_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 (a POST)
cancel_invitation_url = reverse("invitation-cancel", args=[invitation.id])
response = self.client.post(cancel_invitation_url, follow=True)
# 6.Confirm we're redirected to the domain managers page for the domain
expected_redirect_url = reverse("domain-users", args=[domain.id])
self.assertRedirects(response, expected_redirect_url)
# 7. Get the messages
messages = list(get_messages(response.wsgi_request))
message_texts = [str(message) for message in messages]
# 8. Check that the success banner text is in the messages
expected_message = f"Canceled invitation to {invitation.email}."
self.assertIn(expected_message, message_texts)
class TestDomainAdminWithClient(TestCase):
"""Test DomainAdmin class as super user.