From 32c138032bf20cad5f51bf458d07bbd743394ae2 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 3 Jan 2024 15:11:42 -0700 Subject: [PATCH 01/40] Rough mock --- src/registrar/assets/sass/_theme/_tables.scss | 4 ++++ src/registrar/templates/domain_users.html | 23 +++++++++++++++++++ src/registrar/views/domain.py | 15 ++++++++++++ 3 files changed, 42 insertions(+) diff --git a/src/registrar/assets/sass/_theme/_tables.scss b/src/registrar/assets/sass/_theme/_tables.scss index 6dcc6f3bc..892427f82 100644 --- a/src/registrar/assets/sass/_theme/_tables.scss +++ b/src/registrar/assets/sass/_theme/_tables.scss @@ -25,6 +25,10 @@ color: color('primary-darker'); padding-bottom: units(2px); } + td.shift-action-button { + padding-right: 0; + transform: translateX(10px); + } } .dotgov-table { diff --git a/src/registrar/templates/domain_users.html b/src/registrar/templates/domain_users.html index 0eecd35b3..147c0bb8e 100644 --- a/src/registrar/templates/domain_users.html +++ b/src/registrar/templates/domain_users.html @@ -31,6 +31,7 @@ Email Role + Action @@ -40,6 +41,28 @@ {{ permission.user.email }} {{ permission.role|title }} + + Remove + {# Use data-force-action to take esc out of the equation and pass cancel_button_resets_ds_form to effectuate a reset in the view #} +
+
+ {% include 'includes/modal.html' with modal_heading="Warning: You are about to remove all DS records on your domain" modal_description="To fully disable DNSSEC: In addition to removing your DS records here you’ll also need to delete the DS records at your DNS host. To avoid causing your domain to appear offline you should wait to delete your DS records at your DNS host until the Time to Live (TTL) expires. This is often less than 24 hours, but confirm with your provider." modal_button=modal_button|safe %} +
+
+ {% endfor %} diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index 59b206993..aaad016a7 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -625,6 +625,21 @@ class DomainUsersView(DomainBaseView): template_name = "domain_users.html" + def get_context_data(self, **kwargs): + """The initial value for the form (which is a formset here).""" + context = super().get_context_data(**kwargs) + + # Create HTML for the modal button + modal_button = ( + '' + ) + + context["modal_button"] = modal_button + + return context + class DomainAddUserView(DomainFormBaseView): """Inside of a domain's user management, a form for adding users. From 64687ea7862f230b90263d98ef0d34602b0fb98b Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 4 Jan 2024 10:27:51 -0700 Subject: [PATCH 02/40] Some view logic --- src/registrar/config/urls.py | 5 +++ src/registrar/templates/domain_users.html | 6 ++-- src/registrar/views/__init__.py | 1 + src/registrar/views/domain.py | 13 ++++++++ src/registrar/views/utility/mixins.py | 30 ++++++++++++++++++ .../views/utility/permission_views.py | 31 +++++++++++++++++++ 6 files changed, 84 insertions(+), 2 deletions(-) diff --git a/src/registrar/config/urls.py b/src/registrar/config/urls.py index 607bf5f61..416cb7c36 100644 --- a/src/registrar/config/urls.py +++ b/src/registrar/config/urls.py @@ -138,6 +138,11 @@ urlpatterns = [ views.DomainInvitationDeleteView.as_view(http_method_names=["post"]), name="invitation-delete", ), + path( + "domain//users//delete", + views.DomainDeleteUserView.as_view(http_method_names=["post"]), + name="domain-user-delete", + ), ] # we normally would guard these with `if settings.DEBUG` but tests run with diff --git a/src/registrar/templates/domain_users.html b/src/registrar/templates/domain_users.html index 147c0bb8e..22ef88533 100644 --- a/src/registrar/templates/domain_users.html +++ b/src/registrar/templates/domain_users.html @@ -58,8 +58,10 @@ aria-describedby="Your DNSSEC records will be deleted from the registry." data-force-action > -
- {% include 'includes/modal.html' with modal_heading="Warning: You are about to remove all DS records on your domain" modal_description="To fully disable DNSSEC: In addition to removing your DS records here you’ll also need to delete the DS records at your DNS host. To avoid causing your domain to appear offline you should wait to delete your DS records at your DNS host until the Time to Live (TTL) expires. This is often less than 24 hours, but confirm with your provider." modal_button=modal_button|safe %} + + {% with heading="Are you sure you want to remove <"|add:permission.user.email|add:">?" %} + {% include 'includes/modal.html' with modal_heading=heading modal_description="<"|add:permission.user.email|add:"> will no longer be able to manage the domain "|add:domain.name|add:"." modal_button=modal_button|safe %} + {% endwith %}
diff --git a/src/registrar/views/__init__.py b/src/registrar/views/__init__.py index c1400d7c0..8785c9076 100644 --- a/src/registrar/views/__init__.py +++ b/src/registrar/views/__init__.py @@ -12,6 +12,7 @@ from .domain import ( DomainUsersView, DomainAddUserView, DomainInvitationDeleteView, + DomainDeleteUserView, ) from .health import * from .index import * diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index aaad016a7..8884f9436 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -33,6 +33,7 @@ from registrar.utility.errors import ( SecurityEmailErrorCodes, ) from registrar.models.utility.contact_error import ContactError +from registrar.views.utility.permission_views import UserDomainRolePermissionView from ..forms import ( ContactForm, @@ -753,3 +754,15 @@ class DomainInvitationDeleteView(DomainInvitationPermissionDeleteView, SuccessMe def get_success_message(self, cleaned_data): return f"Successfully canceled invitation for {self.object.email}." + + +class DomainDeleteUserView(UserDomainRolePermissionView, SuccessMessageMixin): + """Inside of a domain's user management, a form for deleting users. + """ + object: UserDomainRole # workaround for type mismatch in DeleteView + + def get_success_url(self): + return reverse("domain-users", kwargs={"pk": self.object.domain.id}) + + def get_success_message(self, cleaned_data): + return f"Successfully removed manager for {self.object.email}." diff --git a/src/registrar/views/utility/mixins.py b/src/registrar/views/utility/mixins.py index 0cf5970df..af8f66279 100644 --- a/src/registrar/views/utility/mixins.py +++ b/src/registrar/views/utility/mixins.py @@ -286,6 +286,36 @@ class DomainApplicationPermission(PermissionsLoginMixin): return True +class UserDomainRolePermission(PermissionsLoginMixin): + + """Permission mixin for UserDomainRole if user + has access, otherwise 403""" + + def has_permission(self): + """Check if this user has access to this domain application. + + The user is in self.request.user and the domain needs to be looked + up from the domain's primary key in self.kwargs["pk"] + """ + domain_pk = self.kwargs["pk"] + user_pk = self.kwargs["user_pk"] + print(f"here is the user: {self.request.user} and kwargs: {domain_pk}") + if not self.request.user.is_authenticated: + return False + print("User was authenticated!") + x = UserDomainRole.objects.filter( + id=user_pk + ).get() + print(x) + # TODO - exclude the creator from this + if not UserDomainRole.objects.filter( + domain__id=domain_pk, domain__permissions__user=self.request.user + ).exists(): + return False + + return True + + class DomainApplicationPermissionWithdraw(PermissionsLoginMixin): """Permission mixin that redirects to withdraw action on domain application diff --git a/src/registrar/views/utility/permission_views.py b/src/registrar/views/utility/permission_views.py index 1798ec79d..5c5ebc494 100644 --- a/src/registrar/views/utility/permission_views.py +++ b/src/registrar/views/utility/permission_views.py @@ -4,6 +4,7 @@ import abc # abstract base class from django.views.generic import DetailView, DeleteView, TemplateView from registrar.models import Domain, DomainApplication, DomainInvitation +from registrar.models.user_domain_role import UserDomainRole from .mixins import ( DomainPermission, @@ -11,6 +12,7 @@ from .mixins import ( DomainApplicationPermissionWithdraw, DomainInvitationPermission, ApplicationWizardPermission, + UserDomainRolePermission, ) import logging @@ -122,3 +124,32 @@ class DomainInvitationPermissionDeleteView(DomainInvitationPermission, DeleteVie model = DomainInvitation object: DomainInvitation # workaround for type mismatch in DeleteView + + +class UserDomainRolePermissionView(UserDomainRolePermission, DetailView, abc.ABC): + + """Abstract base view for UserDomainRole that enforces permissions. + + This abstract view cannot be instantiated. Actual views must specify + `template_name`. + """ + + # DetailView property for what model this is viewing + model = UserDomainRole + # variable name in template context for the model object + context_object_name = "userdomainrole" + + +class UserDomainRolePermissionDeleteView(UserDomainRolePermissionView, DeleteView, abc.ABC): + + """Abstract base view for domain application withdraw function + + This abstract view cannot be instantiated. Actual views must specify + `template_name`. + """ + + # DetailView property for what model this is viewing + model = UserDomainRole + # variable name in template context for the model object + context_object_name = "userdomainrole" + From 1710c902da22c7168cef3b1ee861984ed4f17a70 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 4 Jan 2024 11:39:47 -0700 Subject: [PATCH 03/40] Add delete --- src/registrar/views/domain.py | 10 ++++++++-- src/registrar/views/utility/mixins.py | 10 +++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index 8884f9436..80cc50a1d 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -33,7 +33,7 @@ from registrar.utility.errors import ( SecurityEmailErrorCodes, ) from registrar.models.utility.contact_error import ContactError -from registrar.views.utility.permission_views import UserDomainRolePermissionView +from registrar.views.utility.permission_views import UserDomainRolePermissionDeleteView, UserDomainRolePermissionView from ..forms import ( ContactForm, @@ -756,11 +756,17 @@ class DomainInvitationDeleteView(DomainInvitationPermissionDeleteView, SuccessMe return f"Successfully canceled invitation for {self.object.email}." -class DomainDeleteUserView(UserDomainRolePermissionView, SuccessMessageMixin): +class DomainDeleteUserView(UserDomainRolePermissionDeleteView, SuccessMessageMixin): """Inside of a domain's user management, a form for deleting users. """ object: UserDomainRole # workaround for type mismatch in DeleteView + def get_object(self, queryset=None): + """Custom get_object definition to grab a UserDomainRole object from a domain_id and user_id""" + domain_id = self.kwargs.get('pk') + user_id = self.kwargs.get('user_pk') + return UserDomainRole.objects.get(domain=domain_id, user=user_id) + def get_success_url(self): return reverse("domain-users", kwargs={"pk": self.object.domain.id}) diff --git a/src/registrar/views/utility/mixins.py b/src/registrar/views/utility/mixins.py index af8f66279..ca5dceeb8 100644 --- a/src/registrar/views/utility/mixins.py +++ b/src/registrar/views/utility/mixins.py @@ -299,17 +299,13 @@ class UserDomainRolePermission(PermissionsLoginMixin): """ domain_pk = self.kwargs["pk"] user_pk = self.kwargs["user_pk"] - print(f"here is the user: {self.request.user} and kwargs: {domain_pk}") + if not self.request.user.is_authenticated: return False - print("User was authenticated!") - x = UserDomainRole.objects.filter( - id=user_pk - ).get() - print(x) + # TODO - exclude the creator from this if not UserDomainRole.objects.filter( - domain__id=domain_pk, domain__permissions__user=self.request.user + user=user_pk, domain=domain_pk ).exists(): return False From b0d05dd5df386143fc1a798f8c1e7941660ba74b Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 4 Jan 2024 12:52:49 -0700 Subject: [PATCH 04/40] Update mixins.py --- src/registrar/views/utility/mixins.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/registrar/views/utility/mixins.py b/src/registrar/views/utility/mixins.py index ca5dceeb8..bfa9d7330 100644 --- a/src/registrar/views/utility/mixins.py +++ b/src/registrar/views/utility/mixins.py @@ -300,13 +300,26 @@ class UserDomainRolePermission(PermissionsLoginMixin): domain_pk = self.kwargs["pk"] user_pk = self.kwargs["user_pk"] + # Check if the user is authenticated if not self.request.user.is_authenticated: return False - # TODO - exclude the creator from this - if not UserDomainRole.objects.filter( - user=user_pk, domain=domain_pk - ).exists(): + # Check if the UserDomainRole object exists, then check + # if the user requesting the delete has permissions to do so + has_delete_permission = UserDomainRole.objects.filter( + user=user_pk, + domain=domain_pk, + domain__permissions__user=self.request.user, + ).exists() + if not has_delete_permission: + return False + + # Check if more than one manager exists on the domain. + # If only one exists, prevent this from happening + has_multiple_managers = len(UserDomainRole.objects.filter( + domain=domain_pk + )) > 1 + if not has_multiple_managers: return False return True From 232a3e2d06820b0f72c54f98411c98353cac1ba4 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 4 Jan 2024 13:09:04 -0700 Subject: [PATCH 05/40] Add success message --- src/registrar/views/domain.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index 80cc50a1d..da81ba6a3 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -756,19 +756,26 @@ class DomainInvitationDeleteView(DomainInvitationPermissionDeleteView, SuccessMe return f"Successfully canceled invitation for {self.object.email}." -class DomainDeleteUserView(UserDomainRolePermissionDeleteView, SuccessMessageMixin): +class DomainDeleteUserView(UserDomainRolePermissionDeleteView): """Inside of a domain's user management, a form for deleting users. """ object: UserDomainRole # workaround for type mismatch in DeleteView def get_object(self, queryset=None): """Custom get_object definition to grab a UserDomainRole object from a domain_id and user_id""" - domain_id = self.kwargs.get('pk') - user_id = self.kwargs.get('user_pk') + domain_id = self.kwargs.get("pk") + user_id = self.kwargs.get("user_pk") return UserDomainRole.objects.get(domain=domain_id, user=user_id) def get_success_url(self): return reverse("domain-users", kwargs={"pk": self.object.domain.id}) def get_success_message(self, cleaned_data): - return f"Successfully removed manager for {self.object.email}." + return f"Successfully removed manager for {self.object.user.email}." + + def form_valid(self, form): + """Delete the specified user on this domain.""" + super().form_valid(form) + messages.success(self.request, f"Successfully removed manager for {self.object.user.email}.") + + return redirect(self.get_success_url()) \ No newline at end of file From 9d11653386670edfe9b4660e70aeb9ba29741ff7 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 4 Jan 2024 13:14:44 -0700 Subject: [PATCH 06/40] Update domain_users.html --- src/registrar/templates/domain_users.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/templates/domain_users.html b/src/registrar/templates/domain_users.html index 22ef88533..20079f64f 100644 --- a/src/registrar/templates/domain_users.html +++ b/src/registrar/templates/domain_users.html @@ -55,7 +55,7 @@ class="usa-modal" id="toggle-user-alert-{{ forloop.counter }}" aria-labelledby="Are you sure you want to continue?" - aria-describedby="Your DNSSEC records will be deleted from the registry." + aria-describedby="User will be removed" data-force-action >
From 01a6de2392307a9a580e5150a629ee0fc0649fd4 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 4 Jan 2024 13:53:59 -0700 Subject: [PATCH 07/40] Clean up html --- src/registrar/templates/domain_users.html | 43 ++++++++++++----------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/src/registrar/templates/domain_users.html b/src/registrar/templates/domain_users.html index 20079f64f..b6798ac16 100644 --- a/src/registrar/templates/domain_users.html +++ b/src/registrar/templates/domain_users.html @@ -43,27 +43,28 @@ {{ permission.role|title }} Remove - {# Use data-force-action to take esc out of the equation and pass cancel_button_resets_ds_form to effectuate a reset in the view #} -
- - {% with heading="Are you sure you want to remove <"|add:permission.user.email|add:">?" %} - {% include 'includes/modal.html' with modal_heading=heading modal_description="<"|add:permission.user.email|add:"> will no longer be able to manage the domain "|add:domain.name|add:"." modal_button=modal_button|safe %} - {% endwith %} - -
+ id="button-toggle-user-alert-{{ forloop.counter }}" + href="#toggle-user-alert-{{ forloop.counter }}" + class="usa-button--unstyled" + aria-controls="toggle-user-alert-{{ forloop.counter }}" + data-open-modal + > + Remove + + +
+
+ {% with heading="Are you sure you want to remove <"|add:permission.user.email|add:">?" %} + {% include 'includes/modal.html' with modal_heading=heading modal_description="<"|add:permission.user.email|add:"> will no longer be able to manage the domain "|add:domain.name|add:"." modal_button=modal_button|safe %} + {% endwith %} +
+
{% endfor %} From 4c2718654585b1a3734bfd593f8133cff48737bf Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 8 Jan 2024 09:59:24 -0700 Subject: [PATCH 08/40] Add conditional logic --- src/registrar/templates/domain_users.html | 54 ++++++++++++----------- src/registrar/views/domain.py | 9 ++++ 2 files changed, 38 insertions(+), 25 deletions(-) diff --git a/src/registrar/templates/domain_users.html b/src/registrar/templates/domain_users.html index b6798ac16..5375b45e8 100644 --- a/src/registrar/templates/domain_users.html +++ b/src/registrar/templates/domain_users.html @@ -31,7 +31,9 @@ Email Role - Action + {% if can_delete_users %} + Action + {% endif %} @@ -41,31 +43,33 @@ {{ permission.user.email }} {{ permission.role|title }} - - - Remove - + {% if can_delete_users %} + + + Remove + -
-
- {% with heading="Are you sure you want to remove <"|add:permission.user.email|add:">?" %} - {% include 'includes/modal.html' with modal_heading=heading modal_description="<"|add:permission.user.email|add:"> will no longer be able to manage the domain "|add:domain.name|add:"." modal_button=modal_button|safe %} - {% endwith %} -
-
- +
+
+ {% with heading="Are you sure you want to remove <"|add:permission.user.email|add:">?" %} + {% include 'includes/modal.html' with modal_heading=heading modal_description="<"|add:permission.user.email|add:"> will no longer be able to manage the domain "|add:domain.name|add:"." modal_button=modal_button|safe %} + {% endwith %} +
+
+ + {% endif %} {% endfor %} diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index da81ba6a3..c8aa7083f 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -630,6 +630,15 @@ class DomainUsersView(DomainBaseView): """The initial value for the form (which is a formset here).""" context = super().get_context_data(**kwargs) + domain_pk = None + can_delete_users = False + if self.kwargs is not None and "pk" in self.kwargs: + domain_pk = self.kwargs["pk"] + # Prevent the end user from deleting themselves as a manager if they are the + # only manager that exists on a domain. + can_delete_users = UserDomainRole.objects.filter(domain__id=domain_pk).count() > 1 + context["can_delete_users"] = can_delete_users + # Create HTML for the modal button modal_button = ( '' - ) + return context + def _add_modal_buttons_to_context(self, context): + """Adds modal buttons (and their HTML) to the context""" + # Create HTML for the modal button + modal_button = self._create_modal_button_html( + button_name="delete_domain_manager", + button_text_content="Yes, remove domain manager", + classes=["usa-button", "usa-button--secondary"] + ) context["modal_button"] = modal_button + # Create HTML for the modal button when deleting yourself + modal_button_self= self._create_modal_button_html( + button_name="delete_domain_manager_self", + button_text_content="Yes, remove myself", + classes=["usa-button", "usa-button--secondary"] + ) + context["modal_button_self"] = modal_button_self + return context + def _create_modal_button_html(self, button_name: str, button_text_content: str, classes: List[str] | str): + """Template for modal submit buttons""" + + if isinstance(classes, list): + class_list = " ".join(classes) + elif isinstance(classes, str): + class_list = classes + + html_class = f'class="{class_list}"' if class_list else None + + modal_button = ( + '' + ) + return modal_button + class DomainAddUserView(DomainFormBaseView): """Inside of a domain's user management, a form for adding users. @@ -779,12 +822,30 @@ class DomainDeleteUserView(UserDomainRolePermissionDeleteView): def get_success_url(self): return reverse("domain-users", kwargs={"pk": self.object.domain.id}) - def get_success_message(self, cleaned_data): - return f"Successfully removed manager for {self.object.user.email}." + def get_success_message(self, delete_self = False): + if delete_self: + message = f"You are no longer managing the domain {self.object.domain}." + else: + message = f"Removed {self.object.user.email} as a manager for this domain." + return message def form_valid(self, form): """Delete the specified user on this domain.""" super().form_valid(form) - messages.success(self.request, f"Successfully removed manager for {self.object.user.email}.") - return redirect(self.get_success_url()) \ No newline at end of file + # Is the user deleting themselves? If so, display a different message + delete_self = self.request.user.email == self.object.user.email + + # Add a success message + messages.success(self.request, self.get_success_message(delete_self)) + + return redirect(self.get_success_url()) + + def post(self, request, *args, **kwargs): + response = super().post(request, *args, **kwargs) + + # If the user is deleting themselves, redirect to home + if self.request.user.email == self.object.user.email: + return redirect(reverse("home")) + + return response \ No newline at end of file From 40e91ead1f40939b7f28b636945a08a71506b55e Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 16 Jan 2024 10:56:56 -0700 Subject: [PATCH 14/40] Add logic for self deletion --- .../assets/sass/_theme/_buttons.scss | 9 +- src/registrar/templates/domain_users.html | 97 +++++++++++-------- src/registrar/views/domain.py | 10 +- 3 files changed, 69 insertions(+), 47 deletions(-) diff --git a/src/registrar/assets/sass/_theme/_buttons.scss b/src/registrar/assets/sass/_theme/_buttons.scss index 02089ec6d..c890b7a55 100644 --- a/src/registrar/assets/sass/_theme/_buttons.scss +++ b/src/registrar/assets/sass/_theme/_buttons.scss @@ -26,18 +26,21 @@ a.usa-button { text-decoration: none; } -a.usa-button.disabled-link { +a.usa-button.disabled-link, +a.usa-button--unstyled.disabled-link { background-color: #ccc !important; color: #454545 !important } -a.usa-button.disabled-link:hover { +a.usa-button.disabled-link:hover, +a.usa-button--unstyled.disabled-link { background-color: #ccc !important; cursor: not-allowed !important; color: #454545 !important } -a.usa-button.disabled-link:focus { +a.usa-button.disabled-link:focus, +a.usa-button--unstyled.disabled-link { background-color: #ccc !important; cursor: not-allowed !important; outline: none !important; diff --git a/src/registrar/templates/domain_users.html b/src/registrar/templates/domain_users.html index 4a0d25625..216c21942 100644 --- a/src/registrar/templates/domain_users.html +++ b/src/registrar/templates/domain_users.html @@ -16,10 +16,8 @@
  • There is no limit to the number of domain managers you can add.
  • After adding a domain manager, an email invitation will be sent to that user with instructions on how to set up an account.
  • -
  • To remove a domain manager, contact us for - assistance.
  • All domain managers must keep their contact information updated and be responsive if contacted by the .gov team.
  • +
  • Domains must have at least one domain manager. You can’t remove yourself as a domain manager if you’re the only one assigned to this domain. Add another domain manager before you remove yourself from this domain.
  • {% if domain.permissions %} @@ -31,9 +29,7 @@ Email Role - {% if can_delete_users %} - Action - {% endif %} + Action @@ -43,49 +39,66 @@ {{ permission.user.email }} {{ permission.role|title }} + {% if can_delete_users %} - - + Remove + + {# Display a custom message if the user is trying to delete themselves #} + {% if permission.user.email == current_user_email %} +
    - Remove - - {# Display a custom message if the user is trying to delete themselves #} - {% if permission.user.email == current_user_email %} -
    + {% with domain_name=domain.name|force_escape %} + {% include 'includes/modal.html' with modal_heading="Are you sure you want to remove yourself as a domain manager for "|add:domain_name|add:"?"|safe modal_description="You will no longer be able to manage the domain "|add:domain_name|add:"."|safe modal_button=modal_button_self|safe %} + {% endwith %} + +
    + {% else %} +
    -
    - {% with domain_name=domain.name|force_escape %} - {% include 'includes/modal.html' with modal_heading="Are you sure you want to remove yourself as a domain manager for "|add:domain_name|add:"?"|safe modal_description="You will no longer be able to manage the domain "|add:domain_name|add:"."|safe modal_button=modal_button_self|safe %} - {% endwith %} -
    -
    - {% else %} -
    -
    - {% with email=permission.user.email|force_escape domain_name=domain.name|force_escape %} - {% include 'includes/modal.html' with modal_heading="Are you sure you want to remove <"|add:email|add:">?"|safe modal_description="<"|add:email|add:"> will no longer be able to manage the domain "|add:domain_name|add:"."|safe modal_button=modal_button|safe %} - {% endwith %} -
    -
    - {% endif %} - + > +
    + {% with email=permission.user.email|default:permission.user|force_escape domain_name=domain.name|force_escape %} + {% include 'includes/modal.html' with modal_heading="Are you sure you want to remove <"|add:email|add:">?"|safe modal_description="<"|add:email|add:"> will no longer be able to manage the domain "|add:domain_name|add:"."|safe modal_button=modal_button|safe %} + {% endwith %} +
    +
    + {% endif %} + {% else %} + + Remove + {% endif %} + + {% comment %} + usa-tooltip disabled-link" + data-position="right" + title="Coming in 2024" + aria-disabled="true" + data-tooltip="true" + {% endcomment %} {% endfor %} diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index b55d490ce..0f894a985 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -820,13 +820,18 @@ class DomainDeleteUserView(UserDomainRolePermissionDeleteView): return UserDomainRole.objects.get(domain=domain_id, user=user_id) def get_success_url(self): + """Refreshes the page after a delete is successful""" return reverse("domain-users", kwargs={"pk": self.object.domain.id}) def get_success_message(self, delete_self = False): + """Returns confirmation content for the deletion event """ + email_or_name = self.object.user.email + if email_or_name is None: + email_or_name = self.object.user if delete_self: message = f"You are no longer managing the domain {self.object.domain}." else: - message = f"Removed {self.object.user.email} as a manager for this domain." + message = f"Removed {email_or_name} as a manager for this domain." return message def form_valid(self, form): @@ -842,10 +847,11 @@ class DomainDeleteUserView(UserDomainRolePermissionDeleteView): return redirect(self.get_success_url()) def post(self, request, *args, **kwargs): + """Custom post implementation to redirect to home in the event that the user deletes themselves""" response = super().post(request, *args, **kwargs) # If the user is deleting themselves, redirect to home - if self.request.user.email == self.object.user.email: + if self.request.user == self.object.user: return redirect(reverse("home")) return response \ No newline at end of file From 12d5905ef975597b493fa856c8880df081774e9e Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 16 Jan 2024 11:56:49 -0700 Subject: [PATCH 15/40] Add success message for self delete --- src/registrar/templates/dashboard_base.html | 26 ++++++++++----------- src/registrar/templates/home.html | 3 +++ src/registrar/tests/test_url_auth.py | 1 + src/registrar/views/domain.py | 13 ++++++++--- 4 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/registrar/templates/dashboard_base.html b/src/registrar/templates/dashboard_base.html index 27b5ea717..46b04570b 100644 --- a/src/registrar/templates/dashboard_base.html +++ b/src/registrar/templates/dashboard_base.html @@ -3,22 +3,22 @@ {% block wrapper %}
    - {% block messages %} - {% if messages %} -
      - {% for message in messages %} - - {{ message }} - - {% endfor %} -
    - {% endif %} - {% endblock %} - {% block section_nav %}{% endblock %} {% block hero %}{% endblock %} - {% block content %}{% endblock %} + {% block content %} + {% block messages %} + {% if messages %} +
      + {% for message in messages %} + + {{ message }} + + {% endfor %} +
    + {% endif %} + {% endblock %} + {% endblock %}
    {% block complementary %}{% endblock %}
    diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index 15835920b..d25cd3de8 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -10,6 +10,9 @@ {# the entire logged in page goes here #}
    + {% block messages %} + {% include "includes/form_messages.html" %} + {% endblock %}

    Manage your domains

    diff --git a/src/registrar/tests/test_url_auth.py b/src/registrar/tests/test_url_auth.py index 34f80ac44..3e0514a85 100644 --- a/src/registrar/tests/test_url_auth.py +++ b/src/registrar/tests/test_url_auth.py @@ -23,6 +23,7 @@ SAMPLE_KWARGS = { "content_type_id": "2", "object_id": "3", "domain": "whitehouse.gov", + "user_pk": "1", } # Our test suite will ignore some namespaces. diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index 0f894a985..e00c90b19 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -825,13 +825,19 @@ class DomainDeleteUserView(UserDomainRolePermissionDeleteView): def get_success_message(self, delete_self = False): """Returns confirmation content for the deletion event """ + + # Grab the text representation of the user we want to delete email_or_name = self.object.user.email - if email_or_name is None: + if email_or_name is None or email_or_name.strip() == "": email_or_name = self.object.user + + # If the user is deleting themselves, return a special message. + # If not, return something more generic. if delete_self: message = f"You are no longer managing the domain {self.object.domain}." else: message = f"Removed {email_or_name} as a manager for this domain." + return message def form_valid(self, form): @@ -839,7 +845,7 @@ class DomainDeleteUserView(UserDomainRolePermissionDeleteView): super().form_valid(form) # Is the user deleting themselves? If so, display a different message - delete_self = self.request.user.email == self.object.user.email + delete_self = self.request.user == self.object.user # Add a success message messages.success(self.request, self.get_success_message(delete_self)) @@ -851,7 +857,8 @@ class DomainDeleteUserView(UserDomainRolePermissionDeleteView): response = super().post(request, *args, **kwargs) # If the user is deleting themselves, redirect to home - if self.request.user == self.object.user: + delete_self = self.request.user == self.object.user + if delete_self: return redirect(reverse("home")) return response \ No newline at end of file From f16382e946f62e8a9b0813d4a26f75d20b808ed9 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 17 Jan 2024 09:41:19 -0700 Subject: [PATCH 16/40] Linting --- src/registrar/views/application.py | 12 +++++++- src/registrar/views/domain.py | 29 ++++++++----------- src/registrar/views/utility/mixins.py | 6 ++-- .../views/utility/permission_views.py | 1 - 4 files changed, 25 insertions(+), 23 deletions(-) diff --git a/src/registrar/views/application.py b/src/registrar/views/application.py index 23c8cf55e..cfb16336b 100644 --- a/src/registrar/views/application.py +++ b/src/registrar/views/application.py @@ -10,6 +10,7 @@ from django.contrib import messages from registrar.forms import application_wizard as forms from registrar.models import DomainApplication +from registrar.models.user import User from registrar.utility import StrEnum from registrar.views.utility import StepsHelper @@ -131,11 +132,19 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView): if self._application: return self._application + # For linter. The else block should never be hit, but if it does, + # there may be a UI consideration. That will need to be handled in another ticket. + creator = None + if self.request.user is not None and isinstance(self.request.user, User): + creator = self.request.user + else: + raise ValueError("Invalid value for User") + if self.has_pk(): id = self.storage["application_id"] try: self._application = DomainApplication.objects.get( - creator=self.request.user, # type: ignore + creator=creator, pk=id, ) return self._application @@ -476,6 +485,7 @@ class DotgovDomain(ApplicationWizard): self.application.save() return response + class Purpose(ApplicationWizard): template_name = "application_purpose.html" forms = [forms.PurposeForm] diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index e00c90b19..d4a7b8066 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -34,7 +34,7 @@ from registrar.utility.errors import ( SecurityEmailErrorCodes, ) from registrar.models.utility.contact_error import ContactError -from registrar.views.utility.permission_views import UserDomainRolePermissionDeleteView, UserDomainRolePermissionView +from registrar.views.utility.permission_views import UserDomainRolePermissionDeleteView from ..forms import ( ContactForm, @@ -643,7 +643,6 @@ class DomainUsersView(DomainBaseView): return context def _add_booleans_to_context(self, context): - # Determine if the current user can delete managers domain_pk = None can_delete_users = False @@ -660,17 +659,17 @@ class DomainUsersView(DomainBaseView): """Adds modal buttons (and their HTML) to the context""" # Create HTML for the modal button modal_button = self._create_modal_button_html( - button_name="delete_domain_manager", + button_name="delete_domain_manager", button_text_content="Yes, remove domain manager", - classes=["usa-button", "usa-button--secondary"] + classes=["usa-button", "usa-button--secondary"], ) context["modal_button"] = modal_button # Create HTML for the modal button when deleting yourself - modal_button_self= self._create_modal_button_html( + modal_button_self = self._create_modal_button_html( button_name="delete_domain_manager_self", button_text_content="Yes, remove myself", - classes=["usa-button", "usa-button--secondary"] + classes=["usa-button", "usa-button--secondary"], ) context["modal_button_self"] = modal_button_self @@ -686,11 +685,7 @@ class DomainUsersView(DomainBaseView): html_class = f'class="{class_list}"' if class_list else None - modal_button = ( - '' - ) + modal_button = '' return modal_button @@ -809,8 +804,8 @@ class DomainInvitationDeleteView(DomainInvitationPermissionDeleteView, SuccessMe class DomainDeleteUserView(UserDomainRolePermissionDeleteView): - """Inside of a domain's user management, a form for deleting users. - """ + """Inside of a domain's user management, a form for deleting users.""" + object: UserDomainRole # workaround for type mismatch in DeleteView def get_object(self, queryset=None): @@ -823,8 +818,8 @@ class DomainDeleteUserView(UserDomainRolePermissionDeleteView): """Refreshes the page after a delete is successful""" return reverse("domain-users", kwargs={"pk": self.object.domain.id}) - def get_success_message(self, delete_self = False): - """Returns confirmation content for the deletion event """ + def get_success_message(self, delete_self=False): + """Returns confirmation content for the deletion event""" # Grab the text representation of the user we want to delete email_or_name = self.object.user.email @@ -860,5 +855,5 @@ class DomainDeleteUserView(UserDomainRolePermissionDeleteView): delete_self = self.request.user == self.object.user if delete_self: return redirect(reverse("home")) - - return response \ No newline at end of file + + return response diff --git a/src/registrar/views/utility/mixins.py b/src/registrar/views/utility/mixins.py index bfa9d7330..980c0dad5 100644 --- a/src/registrar/views/utility/mixins.py +++ b/src/registrar/views/utility/mixins.py @@ -307,7 +307,7 @@ class UserDomainRolePermission(PermissionsLoginMixin): # Check if the UserDomainRole object exists, then check # if the user requesting the delete has permissions to do so has_delete_permission = UserDomainRole.objects.filter( - user=user_pk, + user=user_pk, domain=domain_pk, domain__permissions__user=self.request.user, ).exists() @@ -316,9 +316,7 @@ class UserDomainRolePermission(PermissionsLoginMixin): # Check if more than one manager exists on the domain. # If only one exists, prevent this from happening - has_multiple_managers = len(UserDomainRole.objects.filter( - domain=domain_pk - )) > 1 + has_multiple_managers = len(UserDomainRole.objects.filter(domain=domain_pk)) > 1 if not has_multiple_managers: return False diff --git a/src/registrar/views/utility/permission_views.py b/src/registrar/views/utility/permission_views.py index 5c5ebc494..295fbc65c 100644 --- a/src/registrar/views/utility/permission_views.py +++ b/src/registrar/views/utility/permission_views.py @@ -152,4 +152,3 @@ class UserDomainRolePermissionDeleteView(UserDomainRolePermissionView, DeleteVie model = UserDomainRole # variable name in template context for the model object context_object_name = "userdomainrole" - From 1a5c8930edbc591f9854c4795460a3a66d6e1352 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 17 Jan 2024 14:37:13 -0700 Subject: [PATCH 17/40] Button align --- src/registrar/assets/sass/_theme/_buttons.scss | 16 ++++++++++------ src/registrar/assets/sass/_theme/_tables.scss | 4 ---- src/registrar/templates/domain_users.html | 6 +++--- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/registrar/assets/sass/_theme/_buttons.scss b/src/registrar/assets/sass/_theme/_buttons.scss index c890b7a55..78c06f0f4 100644 --- a/src/registrar/assets/sass/_theme/_buttons.scss +++ b/src/registrar/assets/sass/_theme/_buttons.scss @@ -26,27 +26,31 @@ a.usa-button { text-decoration: none; } -a.usa-button.disabled-link, -a.usa-button--unstyled.disabled-link { +a.usa-button.disabled-link { background-color: #ccc !important; color: #454545 !important } -a.usa-button.disabled-link:hover, -a.usa-button--unstyled.disabled-link { +a.usa-button.disabled-link:hover{ background-color: #ccc !important; cursor: not-allowed !important; color: #454545 !important } -a.usa-button.disabled-link:focus, -a.usa-button--unstyled.disabled-link { +a.usa-button.disabled-link:focus { background-color: #ccc !important; cursor: not-allowed !important; outline: none !important; color: #454545 !important } +a.usa-button--unstyled.disabled-link, +a.usa-button--unstyled.disabled-link:hover, +a.usa-button--unstyled.disabled-link:focus { + cursor: not-allowed !important; + outline: none !important; +} + a.usa-button:not(.usa-button--unstyled, .usa-button--outline) { color: color('white'); } diff --git a/src/registrar/assets/sass/_theme/_tables.scss b/src/registrar/assets/sass/_theme/_tables.scss index 892427f82..6dcc6f3bc 100644 --- a/src/registrar/assets/sass/_theme/_tables.scss +++ b/src/registrar/assets/sass/_theme/_tables.scss @@ -25,10 +25,6 @@ color: color('primary-darker'); padding-bottom: units(2px); } - td.shift-action-button { - padding-right: 0; - transform: translateX(10px); - } } .dotgov-table { diff --git a/src/registrar/templates/domain_users.html b/src/registrar/templates/domain_users.html index 216c21942..e78d88781 100644 --- a/src/registrar/templates/domain_users.html +++ b/src/registrar/templates/domain_users.html @@ -29,7 +29,7 @@ Email Role - Action + Action @@ -39,7 +39,7 @@ {{ permission.user.email }} {{ permission.role|title }} - + {% if can_delete_users %} {{ invitation.created_at|date }} {{ invitation.status|title }} -

    + {% csrf_token %} From 234af2501fe803958a00859a01846fc795074f46 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 18 Jan 2024 15:01:49 -0700 Subject: [PATCH 18/40] Unit test --- src/registrar/tests/test_views.py | 70 +++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index 8f812b815..0b73f7bef 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -1443,6 +1443,76 @@ class TestDomainManagers(TestDomainOverview): response = self.client.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) self.assertContains(response, "Add a domain manager") + def test_domain_delete_link(self): + """Tests if the user delete link works""" + + # Add additional users + dummy_user_1 = User.objects.create( + username="macncheese", + email="cheese@igorville.com", + ) + dummy_user_2 = User.objects.create( + username="pastapizza", + email="pasta@igorville.com", + ) + + role_1 = UserDomainRole.objects.create( + user=dummy_user_1, domain=self.domain, role=UserDomainRole.Roles.MANAGER + ) + role_2 = UserDomainRole.objects.create( + user=dummy_user_2, domain=self.domain, role=UserDomainRole.Roles.MANAGER + ) + + response = self.client.get(reverse("domain-users", kwargs={"pk": self.domain.id})) + + # Make sure we're on the right page + self.assertContains(response, "Domain managers") + + # Make sure the desired user exists + self.assertContains(response, "cheese@igorville.com") + + # Delete dummy_user_1 + response = self.client.post( + reverse( + "domain-user-delete", + kwargs={ + "pk": self.domain.id, + "user_pk": dummy_user_1.id + } + ), + follow=True + ) + + # Grab the displayed messages + messages = list(response.context["messages"]) + self.assertEqual(len(messages), 1) + + # Ensure the error we recieve is in line with what we expect + message = messages[0] + self.assertEqual(message.message, "Removed cheese@igorville.com as a manager for this domain.") + self.assertEqual(message.tags, "success") + + # Check that role_1 deleted in the DB after the post + deleted_user_exists = UserDomainRole.objects.filter(id=role_1.id).exists() + self.assertFalse(deleted_user_exists) + + # Ensure that the current user wasn't deleted + current_user_exists = UserDomainRole.objects.filter(id=self.user.id).exists() + self.assertTrue(current_user_exists) + + # Ensure that the other userdomainrole was not deleted + role_2_exists = UserDomainRole.objects.filter(id=role_2.id).exists() + self.assertTrue(role_2_exists) + + # Check that the view no longer displays the deleted user + # TODO - why is this not working? + # self.assertNotContains(response, "cheese@igorville.com") + + @skip("TODO") + def test_domain_delete_self_redirects_home(self): + """Tests if deleting yourself redirects to home""" + raise + @boto3_mocking.patching def test_domain_user_add_form(self): """Adding an existing user works.""" From 474e70d1bd30398923d7930c9153f851e20842c8 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 18 Jan 2024 15:43:42 -0700 Subject: [PATCH 19/40] Linting on test case, stylistic changes --- src/registrar/templates/domain_users.html | 6 +++--- src/registrar/tests/test_views.py | 21 +++++---------------- 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/src/registrar/templates/domain_users.html b/src/registrar/templates/domain_users.html index e78d88781..fa94c5f51 100644 --- a/src/registrar/templates/domain_users.html +++ b/src/registrar/templates/domain_users.html @@ -29,7 +29,7 @@ Email Role - Action + Action @@ -137,8 +137,8 @@ {{ invitation.created_at|date }} {{ invitation.status|title }} -
    - {% csrf_token %} + + {% csrf_token %}
    diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index 0b73f7bef..f022ec09c 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -1456,31 +1456,20 @@ class TestDomainManagers(TestDomainOverview): email="pasta@igorville.com", ) - role_1 = UserDomainRole.objects.create( - user=dummy_user_1, domain=self.domain, role=UserDomainRole.Roles.MANAGER - ) - role_2 = UserDomainRole.objects.create( - user=dummy_user_2, domain=self.domain, role=UserDomainRole.Roles.MANAGER - ) + role_1 = UserDomainRole.objects.create(user=dummy_user_1, domain=self.domain, role=UserDomainRole.Roles.MANAGER) + role_2 = UserDomainRole.objects.create(user=dummy_user_2, domain=self.domain, role=UserDomainRole.Roles.MANAGER) response = self.client.get(reverse("domain-users", kwargs={"pk": self.domain.id})) # Make sure we're on the right page self.assertContains(response, "Domain managers") - + # Make sure the desired user exists self.assertContains(response, "cheese@igorville.com") # Delete dummy_user_1 response = self.client.post( - reverse( - "domain-user-delete", - kwargs={ - "pk": self.domain.id, - "user_pk": dummy_user_1.id - } - ), - follow=True + reverse("domain-user-delete", kwargs={"pk": self.domain.id, "user_pk": dummy_user_1.id}), follow=True ) # Grab the displayed messages @@ -1497,7 +1486,7 @@ class TestDomainManagers(TestDomainOverview): self.assertFalse(deleted_user_exists) # Ensure that the current user wasn't deleted - current_user_exists = UserDomainRole.objects.filter(id=self.user.id).exists() + current_user_exists = UserDomainRole.objects.filter(user=self.user.id).exists() self.assertTrue(current_user_exists) # Ensure that the other userdomainrole was not deleted From b23f361a0843e5ac9d563e02e843420be84b9038 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 18 Jan 2024 15:53:59 -0700 Subject: [PATCH 20/40] Update permission_views.py --- src/registrar/views/utility/permission_views.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/registrar/views/utility/permission_views.py b/src/registrar/views/utility/permission_views.py index 8c414f6ad..762612128 100644 --- a/src/registrar/views/utility/permission_views.py +++ b/src/registrar/views/utility/permission_views.py @@ -158,5 +158,8 @@ class UserDomainRolePermissionDeleteView(UserDomainRolePermissionView, DeleteVie # DetailView property for what model this is viewing model = UserDomainRole + # workaround for type mismatch in DeleteView + object: UserDomainRole + # variable name in template context for the model object context_object_name = "userdomainrole" From 816cbe23dd6e6b65e8edae4dd129049971fc775d Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 22 Jan 2024 08:06:33 -0700 Subject: [PATCH 21/40] Add unit tests --- src/registrar/templates/domain_users.html | 8 ++-- src/registrar/tests/test_views.py | 56 +++++++++++++++++++++-- 2 files changed, 57 insertions(+), 7 deletions(-) diff --git a/src/registrar/templates/domain_users.html b/src/registrar/templates/domain_users.html index fa94c5f51..e7659e409 100644 --- a/src/registrar/templates/domain_users.html +++ b/src/registrar/templates/domain_users.html @@ -28,7 +28,7 @@ Email - Role + Role Action @@ -125,8 +125,8 @@ Email Date created - Status - Action + Status + Action @@ -137,7 +137,7 @@ {{ invitation.created_at|date }} {{ invitation.status|title }} -
    + {% csrf_token %}
    diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index bac76b5e2..d692fd3dc 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -2543,6 +2543,7 @@ class TestDomainManagers(TestDomainOverview): super().tearDown() self.user.is_staff = False self.user.save() + User.objects.all().delete() def test_domain_managers(self): response = self.client.get(reverse("domain-users", kwargs={"pk": self.domain.id})) @@ -2609,13 +2610,62 @@ class TestDomainManagers(TestDomainOverview): self.assertTrue(role_2_exists) # Check that the view no longer displays the deleted user - # TODO - why is this not working? + # why is this not working? Its not in the response when printed? # self.assertNotContains(response, "cheese@igorville.com") - @skip("TODO") def test_domain_delete_self_redirects_home(self): """Tests if deleting yourself redirects to home""" - raise + # Add additional users + dummy_user_1 = User.objects.create( + username="macncheese", + email="cheese@igorville.com", + ) + dummy_user_2 = User.objects.create( + username="pastapizza", + email="pasta@igorville.com", + ) + + role_1 = UserDomainRole.objects.create(user=dummy_user_1, domain=self.domain, role=UserDomainRole.Roles.MANAGER) + role_2 = UserDomainRole.objects.create(user=dummy_user_2, domain=self.domain, role=UserDomainRole.Roles.MANAGER) + + response = self.client.get(reverse("domain-users", kwargs={"pk": self.domain.id})) + + # Make sure we're on the right page + self.assertContains(response, "Domain managers") + + # Make sure the desired user exists + self.assertContains(response, "info@example.com") + + # Make sure more than one UserDomainRole exists on this object + self.assertContains(response, "cheese@igorville.com") + + # Delete the current user + response = self.client.post( + reverse("domain-user-delete", kwargs={"pk": self.domain.id, "user_pk": self.user.id}), follow=True + ) + + # Check if we've been redirected to the home page + self.assertContains(response, "Manage your domains") + + # Grab the displayed messages + messages = list(response.context["messages"]) + self.assertEqual(len(messages), 1) + + # Ensure the error we recieve is in line with what we expect + message = messages[0] + self.assertEqual(message.message, "You are no longer managing the domain igorville.gov") + self.assertEqual(message.tags, "success") + + # Ensure that the current user was deleted + current_user_exists = UserDomainRole.objects.filter(user=self.user.id).exists() + self.assertFalse(current_user_exists) + + # Ensure that the other userdomainroles are not deleted + role_1_exists = UserDomainRole.objects.filter(id=role_1.id).exists() + self.assertTrue(role_1_exists) + + role_2_exists = UserDomainRole.objects.filter(id=role_2.id).exists() + self.assertTrue(role_2_exists) @boto3_mocking.patching def test_domain_user_add_form(self): From b334fba42190af6aa19a17cb5235b3b35eae9e93 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 22 Jan 2024 08:15:30 -0700 Subject: [PATCH 22/40] Fix unit test --- src/registrar/tests/test_views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index d692fd3dc..3cb9a7792 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -2653,7 +2653,7 @@ class TestDomainManagers(TestDomainOverview): # Ensure the error we recieve is in line with what we expect message = messages[0] - self.assertEqual(message.message, "You are no longer managing the domain igorville.gov") + self.assertEqual(message.message, "You are no longer managing the domain igorville.gov.") self.assertEqual(message.tags, "success") # Ensure that the current user was deleted From 4e667ea54640c37fad7934e0d3f467884b428ebd Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 22 Jan 2024 12:09:37 -0700 Subject: [PATCH 23/40] Minor styling change --- src/registrar/templates/domain_users.html | 2 +- src/registrar/templates/includes/form_messages.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/registrar/templates/domain_users.html b/src/registrar/templates/domain_users.html index e7659e409..cfcea717c 100644 --- a/src/registrar/templates/domain_users.html +++ b/src/registrar/templates/domain_users.html @@ -83,7 +83,7 @@ {% else %}
    - {{ message }} + {{ message }}
    From 8417c773683d7c8fe1c22a34981228ce41533d54 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 23 Jan 2024 13:21:52 -0700 Subject: [PATCH 24/40] Code cleanup --- src/registrar/assets/sass/_theme/_buttons.scss | 6 +++--- src/registrar/templates/dashboard_base.html | 6 +++--- src/registrar/templates/domain_users.html | 12 +++--------- src/registrar/tests/test_views.py | 4 ---- src/registrar/views/application.py | 8 -------- src/registrar/views/domain.py | 8 +++----- 6 files changed, 12 insertions(+), 32 deletions(-) diff --git a/src/registrar/assets/sass/_theme/_buttons.scss b/src/registrar/assets/sass/_theme/_buttons.scss index 78c06f0f4..b168551b5 100644 --- a/src/registrar/assets/sass/_theme/_buttons.scss +++ b/src/registrar/assets/sass/_theme/_buttons.scss @@ -31,7 +31,7 @@ a.usa-button.disabled-link { color: #454545 !important } -a.usa-button.disabled-link:hover{ +a.usa-button.disabled-link:hover { background-color: #ccc !important; cursor: not-allowed !important; color: #454545 !important @@ -47,8 +47,8 @@ a.usa-button.disabled-link:focus { a.usa-button--unstyled.disabled-link, a.usa-button--unstyled.disabled-link:hover, a.usa-button--unstyled.disabled-link:focus { - cursor: not-allowed !important; - outline: none !important; + cursor: not-allowed; + outline: none; } a.usa-button:not(.usa-button--unstyled, .usa-button--outline) { diff --git a/src/registrar/templates/dashboard_base.html b/src/registrar/templates/dashboard_base.html index 46b04570b..6dd2ce8fd 100644 --- a/src/registrar/templates/dashboard_base.html +++ b/src/registrar/templates/dashboard_base.html @@ -11,10 +11,10 @@ {% if messages %}
      {% for message in messages %} - - {{ message }} +
    • + {{ message }}
    • - {% endfor %} + {% endfor %}
    {% endif %} {% endblock %} diff --git a/src/registrar/templates/domain_users.html b/src/registrar/templates/domain_users.html index cfcea717c..e22800677 100644 --- a/src/registrar/templates/domain_users.html +++ b/src/registrar/templates/domain_users.html @@ -92,13 +92,6 @@
    {% endif %} - {% comment %} - usa-tooltip disabled-link" - data-position="right" - title="Coming in 2024" - aria-disabled="true" - data-tooltip="true" - {% endcomment %} {% endfor %} @@ -137,8 +130,9 @@ {{ invitation.created_at|date }} {{ invitation.status|title }} -
    - {% csrf_token %} + + + {% csrf_token %}
    diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index 5a32615a4..b128724a0 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -2740,10 +2740,6 @@ class TestDomainManagers(TestDomainOverview): role_2_exists = UserDomainRole.objects.filter(id=role_2.id).exists() self.assertTrue(role_2_exists) - # Check that the view no longer displays the deleted user - # why is this not working? Its not in the response when printed? - # self.assertNotContains(response, "cheese@igorville.com") - def test_domain_delete_self_redirects_home(self): """Tests if deleting yourself redirects to home""" # Add additional users diff --git a/src/registrar/views/application.py b/src/registrar/views/application.py index 4e7180cd7..031b93dee 100644 --- a/src/registrar/views/application.py +++ b/src/registrar/views/application.py @@ -481,14 +481,6 @@ class DotgovDomain(ApplicationWizard): context["federal_type"] = self.application.federal_type return context - def post(self, request, *args, **kwargs): - """Override for the post method to mark the DraftDomain as complete""" - response = super().post(request, *args, **kwargs) - # Set the DraftDomain to "complete" - self.application.requested_domain.is_incomplete = False - self.application.save() - return response - class Purpose(ApplicationWizard): template_name = "application_purpose.html" diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index bbf3d43a0..08ac0424d 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -651,13 +651,14 @@ class DomainUsersView(DomainBaseView): # Determine if the current user can delete managers domain_pk = None can_delete_users = False + if self.kwargs is not None and "pk" in self.kwargs: domain_pk = self.kwargs["pk"] # Prevent the end user from deleting themselves as a manager if they are the # only manager that exists on a domain. can_delete_users = UserDomainRole.objects.filter(domain__id=domain_pk).count() > 1 - context["can_delete_users"] = can_delete_users + context["can_delete_users"] = can_delete_users return context def _add_modal_buttons_to_context(self, context): @@ -689,7 +690,6 @@ class DomainUsersView(DomainBaseView): class_list = classes html_class = f'class="{class_list}"' if class_list else None - modal_button = '' return modal_button @@ -831,7 +831,7 @@ class DomainDeleteUserView(UserDomainRolePermissionDeleteView): if email_or_name is None or email_or_name.strip() == "": email_or_name = self.object.user - # If the user is deleting themselves, return a special message. + # If the user is deleting themselves, return a specific message. # If not, return something more generic. if delete_self: message = f"You are no longer managing the domain {self.object.domain}." @@ -842,14 +842,12 @@ class DomainDeleteUserView(UserDomainRolePermissionDeleteView): def form_valid(self, form): """Delete the specified user on this domain.""" - super().form_valid(form) # Is the user deleting themselves? If so, display a different message delete_self = self.request.user == self.object.user # Add a success message messages.success(self.request, self.get_success_message(delete_self)) - return redirect(self.get_success_url()) def post(self, request, *args, **kwargs): From f1c5e9668ddeb666583707c247e7b2c6ed5384be Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 23 Jan 2024 14:23:54 -0700 Subject: [PATCH 25/40] Add back accidental super deletion --- src/registrar/views/domain.py | 3 +++ src/registrar/views/utility/permission_views.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index 08ac0424d..188671f27 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -843,6 +843,9 @@ class DomainDeleteUserView(UserDomainRolePermissionDeleteView): def form_valid(self, form): """Delete the specified user on this domain.""" + # Delete the object + super().form_valid(form) + # Is the user deleting themselves? If so, display a different message delete_self = self.request.user == self.object.user diff --git a/src/registrar/views/utility/permission_views.py b/src/registrar/views/utility/permission_views.py index 762612128..a274db0d9 100644 --- a/src/registrar/views/utility/permission_views.py +++ b/src/registrar/views/utility/permission_views.py @@ -150,7 +150,7 @@ class UserDomainRolePermissionView(UserDomainRolePermission, DetailView, abc.ABC class UserDomainRolePermissionDeleteView(UserDomainRolePermissionView, DeleteView, abc.ABC): - """Abstract base view for domain application withdraw function + """Abstract base view for deleting a UserDomainRole. This abstract view cannot be instantiated. Actual views must specify `template_name`. From 33cf59530d1535eee900239d165b9b196f46d2c4 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 23 Jan 2024 14:34:59 -0700 Subject: [PATCH 26/40] Fix unit test Fixed unit test filtering too broadly --- src/registrar/tests/test_views.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index b128724a0..9b17357f5 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -2690,7 +2690,7 @@ class TestDomainManagers(TestDomainOverview): response = self.client.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) self.assertContains(response, "Add a domain manager") - def test_domain_delete_link(self): + def test_domain_user_delete_link(self): """Tests if the user delete link works""" # Add additional users @@ -2733,14 +2733,14 @@ class TestDomainManagers(TestDomainOverview): self.assertFalse(deleted_user_exists) # Ensure that the current user wasn't deleted - current_user_exists = UserDomainRole.objects.filter(user=self.user.id).exists() + current_user_exists = UserDomainRole.objects.filter(user=self.user.id, domain=self.domain).exists() self.assertTrue(current_user_exists) # Ensure that the other userdomainrole was not deleted role_2_exists = UserDomainRole.objects.filter(id=role_2.id).exists() self.assertTrue(role_2_exists) - def test_domain_delete_self_redirects_home(self): + def test_domain_user_delete_self_redirects_home(self): """Tests if deleting yourself redirects to home""" # Add additional users dummy_user_1 = User.objects.create( @@ -2784,7 +2784,7 @@ class TestDomainManagers(TestDomainOverview): self.assertEqual(message.tags, "success") # Ensure that the current user was deleted - current_user_exists = UserDomainRole.objects.filter(user=self.user.id).exists() + current_user_exists = UserDomainRole.objects.filter(user=self.user.id, domain=self.domain).exists() self.assertFalse(current_user_exists) # Ensure that the other userdomainroles are not deleted From 18c3c0d01e03c179248987ee5976fed143fc6247 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 26 Jan 2024 10:53:52 -0700 Subject: [PATCH 27/40] Add back important --- src/registrar/assets/sass/_theme/_buttons.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/registrar/assets/sass/_theme/_buttons.scss b/src/registrar/assets/sass/_theme/_buttons.scss index b168551b5..0576f8b47 100644 --- a/src/registrar/assets/sass/_theme/_buttons.scss +++ b/src/registrar/assets/sass/_theme/_buttons.scss @@ -47,8 +47,8 @@ a.usa-button.disabled-link:focus { a.usa-button--unstyled.disabled-link, a.usa-button--unstyled.disabled-link:hover, a.usa-button--unstyled.disabled-link:focus { - cursor: not-allowed; - outline: none; + cursor: not-allowed !important; + outline: none !important; } a.usa-button:not(.usa-button--unstyled, .usa-button--outline) { From c60f5bb6bcab59ee242ab9fb6afbd19916d01e90 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 26 Jan 2024 10:54:18 -0700 Subject: [PATCH 28/40] Undo important (my bad) --- src/registrar/assets/sass/_theme/_buttons.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/registrar/assets/sass/_theme/_buttons.scss b/src/registrar/assets/sass/_theme/_buttons.scss index 0576f8b47..b168551b5 100644 --- a/src/registrar/assets/sass/_theme/_buttons.scss +++ b/src/registrar/assets/sass/_theme/_buttons.scss @@ -47,8 +47,8 @@ a.usa-button.disabled-link:focus { a.usa-button--unstyled.disabled-link, a.usa-button--unstyled.disabled-link:hover, a.usa-button--unstyled.disabled-link:focus { - cursor: not-allowed !important; - outline: none !important; + cursor: not-allowed; + outline: none; } a.usa-button:not(.usa-button--unstyled, .usa-button--outline) { From 30f6c93aaa2498d22b727335c8005b3c5d1e679c Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 26 Jan 2024 11:15:34 -0700 Subject: [PATCH 29/40] Update _buttons.scss --- src/registrar/assets/sass/_theme/_buttons.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/registrar/assets/sass/_theme/_buttons.scss b/src/registrar/assets/sass/_theme/_buttons.scss index b168551b5..0576f8b47 100644 --- a/src/registrar/assets/sass/_theme/_buttons.scss +++ b/src/registrar/assets/sass/_theme/_buttons.scss @@ -47,8 +47,8 @@ a.usa-button.disabled-link:focus { a.usa-button--unstyled.disabled-link, a.usa-button--unstyled.disabled-link:hover, a.usa-button--unstyled.disabled-link:focus { - cursor: not-allowed; - outline: none; + cursor: not-allowed !important; + outline: none !important; } a.usa-button:not(.usa-button--unstyled, .usa-button--outline) { From d9610ae12dac880997837cc2656495203b1bab00 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 26 Jan 2024 12:13:09 -0700 Subject: [PATCH 30/40] UI changes --- src/registrar/assets/sass/_theme/_buttons.scss | 1 + src/registrar/templates/domain_users.html | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/registrar/assets/sass/_theme/_buttons.scss b/src/registrar/assets/sass/_theme/_buttons.scss index 0576f8b47..5148456e5 100644 --- a/src/registrar/assets/sass/_theme/_buttons.scss +++ b/src/registrar/assets/sass/_theme/_buttons.scss @@ -49,6 +49,7 @@ a.usa-button--unstyled.disabled-link:hover, a.usa-button--unstyled.disabled-link:focus { cursor: not-allowed !important; outline: none !important; + text-decoration: none !important; } a.usa-button:not(.usa-button--unstyled, .usa-button--outline) { diff --git a/src/registrar/templates/domain_users.html b/src/registrar/templates/domain_users.html index e22800677..b3d52bdd4 100644 --- a/src/registrar/templates/domain_users.html +++ b/src/registrar/templates/domain_users.html @@ -53,7 +53,7 @@ {# Display a custom message if the user is trying to delete themselves #} {% if permission.user.email == current_user_email %}
    {% with domain_name=domain.name|force_escape %} - {% include 'includes/modal.html' with modal_heading="Are you sure you want to remove yourself as a domain manager for "|add:domain_name|add:"?"|safe modal_description="You will no longer be able to manage the domain "|add:domain_name|add:"."|safe modal_button=modal_button_self|safe %} + {% include 'includes/modal.html' with modal_heading="Are you sure you want to remove yourself as a domain manager?" modal_description="You will no longer be able to manage the domain "|add:domain_name|add:"."|safe modal_button=modal_button_self|safe %} {% endwith %}
    {% else %}
    {% with email=permission.user.email|default:permission.user|force_escape domain_name=domain.name|force_escape %} - {% include 'includes/modal.html' with modal_heading="Are you sure you want to remove <"|add:email|add:">?"|safe modal_description="<"|add:email|add:"> will no longer be able to manage the domain "|add:domain_name|add:"."|safe modal_button=modal_button|safe %} + {% include 'includes/modal.html' with modal_heading="Are you sure you want to remove " heading_value="<"|add:email|add:">?" modal_description="<"|add:email|add:"> will no longer be able to manage the domain "|add:domain_name|add:"."|safe modal_button=modal_button|safe %} {% endwith %}
    From 0ba2cef9e97d32cc81df0e9903df54aaa143cb10 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 29 Jan 2024 09:32:03 -0700 Subject: [PATCH 31/40] PR suggestions --- src/registrar/tests/test_views.py | 77 ++++++++++++++++++- src/registrar/views/domain.py | 29 ++----- src/registrar/views/utility/mixins.py | 2 +- .../views/utility/permission_views.py | 18 +---- 4 files changed, 86 insertions(+), 40 deletions(-) diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index d37866091..ffe94199c 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -2690,8 +2690,8 @@ class TestDomainManagers(TestDomainOverview): response = self.client.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) self.assertContains(response, "Add a domain manager") - def test_domain_user_delete_link(self): - """Tests if the user delete link works""" + def test_domain_user_delete(self): + """Tests if deleting a domain manager works""" # Add additional users dummy_user_1 = User.objects.create( @@ -2740,6 +2740,79 @@ class TestDomainManagers(TestDomainOverview): role_2_exists = UserDomainRole.objects.filter(id=role_2.id).exists() self.assertTrue(role_2_exists) + def test_domain_user_delete_denied_if_no_permission(self): + """Deleting a domain manager is denied if the user has no permission to do so""" + + # Create a domain object + vip_domain = Domain.objects.create(name="freeman.gov") + + # Add users + dummy_user_1 = User.objects.create( + username="bagel", + email="bagel@igorville.com", + ) + dummy_user_2 = User.objects.create( + username="pastapizza", + email="pasta@igorville.com", + ) + + role_1 = UserDomainRole.objects.create(user=dummy_user_1, domain=vip_domain, role=UserDomainRole.Roles.MANAGER) + role_2 = UserDomainRole.objects.create(user=dummy_user_2, domain=vip_domain, role=UserDomainRole.Roles.MANAGER) + + response = self.client.get(reverse("domain-users", kwargs={"pk": vip_domain.id})) + + # Make sure that we can't access the domain manager page normally + self.assertEqual(response.status_code, 403) + + # Try to delete dummy_user_1 + response = self.client.post( + reverse("domain-user-delete", kwargs={"pk": vip_domain.id, "user_pk": dummy_user_1.id}), follow=True + ) + + # Ensure that we are denied access + self.assertEqual(response.status_code, 403) + + # Ensure that the user wasn't deleted + role_1_exists = UserDomainRole.objects.filter(id=role_1.id).exists() + self.assertTrue(role_1_exists) + + # Ensure that the other userdomainrole was not deleted + role_2_exists = UserDomainRole.objects.filter(id=role_2.id).exists() + self.assertTrue(role_2_exists) + + # Make sure that the current user wasn't deleted for some reason + current_user_exists = UserDomainRole.objects.filter(user=self.user.id, domain=self.domain).exists() + self.assertTrue(current_user_exists) + + def test_domain_user_delete_denied_if_last_man_standing(self): + """Deleting a domain manager is denied if the user is the only manager""" + + # Create a domain object + vip_domain = Domain.objects.create(name="olive-oil.gov") + + # Add the requesting user as the only manager on the domain + UserDomainRole.objects.create(user=self.user, domain=vip_domain, role=UserDomainRole.Roles.MANAGER) + + response = self.client.get(reverse("domain-users", kwargs={"pk": vip_domain.id})) + + # Make sure that we can still access the domain manager page normally + self.assertContains(response, "Domain managers") + + # Make sure that the logged in user exists + self.assertContains(response, "info@example.com") + + # Try to delete the current user + response = self.client.post( + reverse("domain-user-delete", kwargs={"pk": vip_domain.id, "user_pk": self.user.id}), follow=True + ) + + # Ensure that we are denied access + self.assertEqual(response.status_code, 403) + + # Make sure that the current user wasn't deleted + current_user_exists = UserDomainRole.objects.filter(user=self.user.id, domain=self.domain).exists() + self.assertTrue(current_user_exists) + def test_domain_user_delete_self_redirects_home(self): """Tests if deleting yourself redirects to home""" # Add additional users diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index fce971e66..313762ef1 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -6,7 +6,6 @@ inherit from `DomainPermissionView` (or DomainInvitationPermissionDeleteView). """ import logging -from typing import List from django.contrib import messages from django.contrib.messages.views import SuccessMessageMixin @@ -664,35 +663,23 @@ class DomainUsersView(DomainBaseView): def _add_modal_buttons_to_context(self, context): """Adds modal buttons (and their HTML) to the context""" # Create HTML for the modal button - modal_button = self._create_modal_button_html( - button_name="delete_domain_manager", - button_text_content="Yes, remove domain manager", - classes=["usa-button", "usa-button--secondary"], + modal_button = ( + '' ) context["modal_button"] = modal_button # Create HTML for the modal button when deleting yourself - modal_button_self = self._create_modal_button_html( - button_name="delete_domain_manager_self", - button_text_content="Yes, remove myself", - classes=["usa-button", "usa-button--secondary"], + modal_button_self = ( + '' ) context["modal_button_self"] = modal_button_self return context - def _create_modal_button_html(self, button_name: str, button_text_content: str, classes: List[str] | str): - """Template for modal submit buttons""" - - if isinstance(classes, list): - class_list = " ".join(classes) - elif isinstance(classes, str): - class_list = classes - - html_class = f'class="{class_list}"' if class_list else None - modal_button = '' - return modal_button - class DomainAddUserView(DomainFormBaseView): """Inside of a domain's user management, a form for adding users. diff --git a/src/registrar/views/utility/mixins.py b/src/registrar/views/utility/mixins.py index 980c0dad5..b2c4cb364 100644 --- a/src/registrar/views/utility/mixins.py +++ b/src/registrar/views/utility/mixins.py @@ -286,7 +286,7 @@ class DomainApplicationPermission(PermissionsLoginMixin): return True -class UserDomainRolePermission(PermissionsLoginMixin): +class UserDeleteDomainRolePermission(PermissionsLoginMixin): """Permission mixin for UserDomainRole if user has access, otherwise 403""" diff --git a/src/registrar/views/utility/permission_views.py b/src/registrar/views/utility/permission_views.py index a274db0d9..54c96d602 100644 --- a/src/registrar/views/utility/permission_views.py +++ b/src/registrar/views/utility/permission_views.py @@ -12,7 +12,7 @@ from .mixins import ( DomainApplicationPermissionWithdraw, DomainInvitationPermission, ApplicationWizardPermission, - UserDomainRolePermission, + UserDeleteDomainRolePermission, ) import logging @@ -134,21 +134,7 @@ class DomainApplicationPermissionDeleteView(DomainApplicationPermission, DeleteV object: DomainApplication -class UserDomainRolePermissionView(UserDomainRolePermission, DetailView, abc.ABC): - - """Abstract base view for UserDomainRole that enforces permissions. - - This abstract view cannot be instantiated. Actual views must specify - `template_name`. - """ - - # DetailView property for what model this is viewing - model = UserDomainRole - # variable name in template context for the model object - context_object_name = "userdomainrole" - - -class UserDomainRolePermissionDeleteView(UserDomainRolePermissionView, DeleteView, abc.ABC): +class UserDomainRolePermissionDeleteView(UserDeleteDomainRolePermission, DeleteView, abc.ABC): """Abstract base view for deleting a UserDomainRole. From d8d7f6381935ba7706e9de05a2c3da83f497ec40 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 29 Jan 2024 14:00:41 -0700 Subject: [PATCH 32/40] Fix typo in unit test --- src/registrar/tests/test_views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index ffe94199c..3bbcfcf01 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -2781,7 +2781,7 @@ class TestDomainManagers(TestDomainOverview): self.assertTrue(role_2_exists) # Make sure that the current user wasn't deleted for some reason - current_user_exists = UserDomainRole.objects.filter(user=self.user.id, domain=self.domain).exists() + current_user_exists = UserDomainRole.objects.filter(user=dummy_user_1.id, domain=vip_domain.id).exists() self.assertTrue(current_user_exists) def test_domain_user_delete_denied_if_last_man_standing(self): @@ -2810,7 +2810,7 @@ class TestDomainManagers(TestDomainOverview): self.assertEqual(response.status_code, 403) # Make sure that the current user wasn't deleted - current_user_exists = UserDomainRole.objects.filter(user=self.user.id, domain=self.domain).exists() + current_user_exists = UserDomainRole.objects.filter(user=self.user.id, domain=vip_domain.id).exists() self.assertTrue(current_user_exists) def test_domain_user_delete_self_redirects_home(self): From 468e7b58b4e070c44dfdeea3af834a350c9cb9ca Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 30 Jan 2024 13:10:55 -0700 Subject: [PATCH 33/40] Remove < > --- src/registrar/templates/domain_users.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/templates/domain_users.html b/src/registrar/templates/domain_users.html index b3d52bdd4..349e4125a 100644 --- a/src/registrar/templates/domain_users.html +++ b/src/registrar/templates/domain_users.html @@ -75,7 +75,7 @@ >
    {% with email=permission.user.email|default:permission.user|force_escape domain_name=domain.name|force_escape %} - {% include 'includes/modal.html' with modal_heading="Are you sure you want to remove " heading_value="<"|add:email|add:">?" modal_description="<"|add:email|add:"> will no longer be able to manage the domain "|add:domain_name|add:"."|safe modal_button=modal_button|safe %} + {% include 'includes/modal.html' with modal_heading="Are you sure you want to remove " heading_value="email|add:"?" modal_description=""|add:email|add:" will no longer be able to manage the domain "|add:domain_name|add:"."|safe modal_button=modal_button|safe %} {% endwith %}
    From f85dc682a3c3e30ac5efcdf059887193eed0c879 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 30 Jan 2024 13:16:38 -0700 Subject: [PATCH 34/40] Hotfix --- src/registrar/templates/domain_users.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/templates/domain_users.html b/src/registrar/templates/domain_users.html index 349e4125a..544cb7249 100644 --- a/src/registrar/templates/domain_users.html +++ b/src/registrar/templates/domain_users.html @@ -75,7 +75,7 @@ >
    {% with email=permission.user.email|default:permission.user|force_escape domain_name=domain.name|force_escape %} - {% include 'includes/modal.html' with modal_heading="Are you sure you want to remove " heading_value="email|add:"?" modal_description=""|add:email|add:" will no longer be able to manage the domain "|add:domain_name|add:"."|safe modal_button=modal_button|safe %} + {% include 'includes/modal.html' with modal_heading="Are you sure you want to remove " heading_value=email|add:"?" modal_description=""|add:email|add:" will no longer be able to manage the domain "|add:domain_name|add:"."|safe modal_button=modal_button|safe %} {% endwith %}
    From 6f5a1cf5e9bbd057d4ed91ed8d3152716235618f Mon Sep 17 00:00:00 2001 From: Rebecca Hsieh Date: Tue, 30 Jan 2024 13:06:04 -0800 Subject: [PATCH 35/40] Address quick comments --- src/registrar/assets/js/get-gov.js | 2 +- src/registrar/templates/application_dotgov_domain.html | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/registrar/assets/js/get-gov.js b/src/registrar/assets/js/get-gov.js index 52f88bb1d..587b95305 100644 --- a/src/registrar/assets/js/get-gov.js +++ b/src/registrar/assets/js/get-gov.js @@ -219,8 +219,8 @@ function validateFormsetInputs(e, availabilityButton) { // Run validators for each input inputs.forEach(input => { - runValidators(input); removeFormErrors(input, true); + runValidators(input); }); // Set the validate-for attribute on the button with the collected input IDs diff --git a/src/registrar/templates/application_dotgov_domain.html b/src/registrar/templates/application_dotgov_domain.html index f5b31fb15..39f9935c2 100644 --- a/src/registrar/templates/application_dotgov_domain.html +++ b/src/registrar/templates/application_dotgov_domain.html @@ -48,7 +48,6 @@ {% endwith %} {% endwith %}