mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-08-13 13:09:41 +02:00
Did it the endpoint method way which was sadly incorrect
This commit is contained in:
parent
4b523a75ce
commit
95d912ecbc
8 changed files with 114 additions and 60 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
|
||||||
|
@ -1478,26 +1479,6 @@ class BaseInvitationAdmin(ListHeaderAdmin):
|
||||||
return response
|
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):
|
class DomainInvitationAdmin(BaseInvitationAdmin):
|
||||||
"""Custom domain invitation admin class."""
|
"""Custom domain invitation admin class."""
|
||||||
|
|
||||||
|
@ -1527,18 +1508,29 @@ class DomainInvitationAdmin(BaseInvitationAdmin):
|
||||||
|
|
||||||
search_help_text = "Search by email or domain."
|
search_help_text = "Search by email or domain."
|
||||||
|
|
||||||
# # Mark the FSM field 'status' as readonly
|
# Mark the FSM field 'status' as readonly
|
||||||
# # to allow admin users to create Domain Invitations
|
# to allow admin users to create Domain Invitations
|
||||||
# # without triggering the FSM Transition Not Allowed
|
# without triggering the FSM Transition Not Allowed
|
||||||
# # error.
|
# error.
|
||||||
# readonly_fields = ["status"]
|
readonly_fields = ["status"]
|
||||||
|
|
||||||
readonly_fields = []
|
|
||||||
|
|
||||||
autocomplete_fields = ["domain"]
|
autocomplete_fields = ["domain"]
|
||||||
|
|
||||||
change_form_template = "django/admin/domain_invitation_change_form.html"
|
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):
|
def save_model(self, request, obj, form, change):
|
||||||
"""
|
"""
|
||||||
Override the save_model method.
|
Override the save_model method.
|
||||||
|
|
|
@ -498,6 +498,22 @@ 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;
|
||||||
|
}
|
||||||
|
|
||||||
.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');
|
||||||
|
|
|
@ -78,10 +78,6 @@ class DomainInvitation(TimeStampedModel):
|
||||||
@transition(field="status", source=DomainInvitationStatus.INVITED, target=DomainInvitationStatus.CANCELED)
|
@transition(field="status", source=DomainInvitationStatus.INVITED, target=DomainInvitationStatus.CANCELED)
|
||||||
def cancel_invitation(self):
|
def cancel_invitation(self):
|
||||||
"""When an invitation is canceled, change the status to canceled"""
|
"""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
|
pass
|
||||||
|
|
||||||
@transition(field="status", source=DomainInvitationStatus.CANCELED, target=DomainInvitationStatus.INVITED)
|
@transition(field="status", source=DomainInvitationStatus.CANCELED, target=DomainInvitationStatus.INVITED)
|
||||||
|
|
|
@ -15,9 +15,24 @@
|
||||||
</ul>
|
</ul>
|
||||||
{% else %}
|
{% else %}
|
||||||
<ul>
|
<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>
|
<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="#">
|
||||||
|
@ -32,4 +47,3 @@
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
|
||||||
|
|
|
@ -7,8 +7,6 @@ This is using a custom implementation fieldset.html (see admin/fieldset.html)
|
||||||
{% block field_other %}
|
{% block field_other %}
|
||||||
{% if field.field.name == "email" %}
|
{% if field.field.name == "email" %}
|
||||||
{% include "admin/input_with_clipboard.html" with field=field.field %}
|
{% 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 %}
|
{% else %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -12,6 +12,7 @@ from registrar.models import (
|
||||||
Domain,
|
Domain,
|
||||||
DomainRequest,
|
DomainRequest,
|
||||||
DomainInformation,
|
DomainInformation,
|
||||||
|
DomainInvitation,
|
||||||
User,
|
User,
|
||||||
Host,
|
Host,
|
||||||
Portfolio,
|
Portfolio,
|
||||||
|
@ -30,6 +31,9 @@ from .common import (
|
||||||
)
|
)
|
||||||
from unittest.mock import ANY, call, patch
|
from unittest.mock import ANY, call, patch
|
||||||
|
|
||||||
|
from django.contrib.messages import get_messages
|
||||||
|
|
||||||
|
|
||||||
import boto3_mocking # type: ignore
|
import boto3_mocking # type: ignore
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
@ -495,6 +499,63 @@ class TestDomainInformationInline(MockEppLib):
|
||||||
self.assertIn("poopy@gov.gov", domain_managers)
|
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):
|
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