From 0d05225c6d82ca4889a663c685b99dd09b851da9 Mon Sep 17 00:00:00 2001 From: Neil Martinsen-Burrell Date: Mon, 20 Mar 2023 12:55:02 -0500 Subject: [PATCH 01/14] Users on domain detail page --- src/registrar/templates/domain_detail.html | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/registrar/templates/domain_detail.html b/src/registrar/templates/domain_detail.html index 5d0624209..bf1de466d 100644 --- a/src/registrar/templates/domain_detail.html +++ b/src/registrar/templates/domain_detail.html @@ -9,6 +9,13 @@

{{ domain.name }}

Active: {% if domain.is_active %}Yes{% else %}No{% endif %}

+ +

Users

+ {% endblock %} {# content #} From ab1eb0ead18f6bb3594a3ab931890bff4e568f46 Mon Sep 17 00:00:00 2001 From: Neil Martinsen-Burrell Date: Tue, 21 Mar 2023 09:00:35 -0500 Subject: [PATCH 02/14] Domain management sidebar --- src/registrar/config/urls.py | 1 + src/registrar/templates/domain_base.html | 24 ++++++++ src/registrar/templates/domain_detail.html | 24 ++------ src/registrar/templates/domain_sidebar.html | 61 +++++++++++++++++++++ src/registrar/templates/domain_users.html | 10 ++++ src/registrar/views/domain.py | 6 ++ 6 files changed, 106 insertions(+), 20 deletions(-) create mode 100644 src/registrar/templates/domain_base.html create mode 100644 src/registrar/templates/domain_sidebar.html create mode 100644 src/registrar/templates/domain_users.html diff --git a/src/registrar/config/urls.py b/src/registrar/config/urls.py index 53cd5c374..dcf592570 100644 --- a/src/registrar/config/urls.py +++ b/src/registrar/config/urls.py @@ -62,6 +62,7 @@ urlpatterns = [ name="todo", ), path("domain/", views.DomainView.as_view(), name="domain"), + path("domain//users", views.DomainUsersView.as_view(), name="domain-users"), ] diff --git a/src/registrar/templates/domain_base.html b/src/registrar/templates/domain_base.html new file mode 100644 index 000000000..2f5074e1b --- /dev/null +++ b/src/registrar/templates/domain_base.html @@ -0,0 +1,24 @@ +{% extends "dashboard_base.html" %} + +{% block title %}Domain {{ domain.name }}{% endblock %} + +{% block content %} +
+
+
+ {% include 'domain_sidebar.html' %} +
+ +
+
+ +

Domain {{ domain.name }}

+ + {% block domain_content %} + {% endblock %} {# domain_content #} + +
+
+
+
+{% endblock %} {# content #} diff --git a/src/registrar/templates/domain_detail.html b/src/registrar/templates/domain_detail.html index bf1de466d..9cbbd4f91 100644 --- a/src/registrar/templates/domain_detail.html +++ b/src/registrar/templates/domain_detail.html @@ -1,21 +1,5 @@ -{% extends "dashboard_base.html" %} +{% extends "domain_base.html" %} - -{% block title %}Domain {{ domain.name }}{% endblock %} - -{% block content %} -
-
-

{{ domain.name }}

- -

Active: {% if domain.is_active %}Yes{% else %}No{% endif %}

- -

Users

-
    - {% for user in domain.users.all %} -
  • {{ user }} <{{ user.email }}>
  • - {% endfor %} -
-
-
-{% endblock %} {# content #} +{% block domain_content %} +

Active: {% if domain.is_active %}Yes{% else %}No{% endif %}

+{% endblock %} {# domain_content #} diff --git a/src/registrar/templates/domain_sidebar.html b/src/registrar/templates/domain_sidebar.html new file mode 100644 index 000000000..ae8444bea --- /dev/null +++ b/src/registrar/templates/domain_sidebar.html @@ -0,0 +1,61 @@ +{% load static url_helpers %} + +
+ +
diff --git a/src/registrar/templates/domain_users.html b/src/registrar/templates/domain_users.html new file mode 100644 index 000000000..bb3b5f528 --- /dev/null +++ b/src/registrar/templates/domain_users.html @@ -0,0 +1,10 @@ +{% extends "domain_base.html" %} + +{% block domain_content %} +

Users

+
    + {% for user in domain.users.all %} +
  • {{ user }} <{{ user.email }}>
  • + {% endfor %} +
+{% endblock %} {# domain_content #} diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index e2553ce44..600709dc4 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -11,3 +11,9 @@ class DomainView(DomainPermission, DetailView): model = Domain template_name = "domain_detail.html" context_object_name = "domain" + + +class DomainUsersView(DomainPermission, DetailView): + model = Domain + template_name = "domain_users.html" + context_object_name = "domain" From 96b95bae8ae5eed2b55321f154a8157f8050c853 Mon Sep 17 00:00:00 2001 From: Neil Martinsen-Burrell Date: Tue, 21 Mar 2023 10:00:25 -0500 Subject: [PATCH 03/14] Fixtures for approved domains --- src/registrar/fixtures.py | 23 +++++++++++++++++++++++ src/registrar/management/commands/load.py | 3 ++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/registrar/fixtures.py b/src/registrar/fixtures.py index 9f54b20a7..9f971ce18 100644 --- a/src/registrar/fixtures.py +++ b/src/registrar/fixtures.py @@ -107,6 +107,10 @@ class DomainApplicationFixture: "status": "investigating", "organization_name": "Example - In Investigation", }, + { + "status": "investigating", + "organization_name": "Example - Approved", + }, ] @classmethod @@ -249,3 +253,22 @@ class DomainApplicationFixture: cls._set_many_to_many_relations(da, app) except Exception as e: logger.warning(e) + +class DomainFixture(DomainApplicationFixture): + + """Create one domain and permissions on it for each user.""" + + @classmethod + def load(cls): + try: + users = list(User.objects.all()) # force evaluation to catch db errors + except Exception as e: + logger.warning(e) + return + + for user in users: + # approve one of each users investigating status domains + application = DomainApplication.objects.filter(creator=user, status=DomainApplication.INVESTIGATING).last() + logger.debug(f"Approving {application} for {user}") + application.approve() + application.save() diff --git a/src/registrar/management/commands/load.py b/src/registrar/management/commands/load.py index 0203a2e75..e48d3f211 100644 --- a/src/registrar/management/commands/load.py +++ b/src/registrar/management/commands/load.py @@ -3,7 +3,7 @@ import logging from django.core.management.base import BaseCommand from auditlog.context import disable_auditlog # type: ignore -from registrar.fixtures import UserFixture, DomainApplicationFixture +from registrar.fixtures import UserFixture, DomainApplicationFixture, DomainFixture logger = logging.getLogger(__name__) @@ -15,4 +15,5 @@ class Command(BaseCommand): with disable_auditlog(): UserFixture.load() DomainApplicationFixture.load() + DomainFixture.load() logger.info("All fixtures loaded.") From 7c852de7438f4961c15bc5db29605ba9fe1d1d22 Mon Sep 17 00:00:00 2001 From: Neil Martinsen-Burrell Date: Tue, 21 Mar 2023 10:17:56 -0500 Subject: [PATCH 04/14] Styling user management with a table? --- .../_theme/_uswds-theme-custom-styles.scss | 2 ++ src/registrar/templates/domain_base.html | 4 +-- src/registrar/templates/domain_detail.html | 3 +- src/registrar/templates/domain_users.html | 35 +++++++++++++++---- 4 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/registrar/assets/sass/_theme/_uswds-theme-custom-styles.scss b/src/registrar/assets/sass/_theme/_uswds-theme-custom-styles.scss index 1f8f4bd4d..1bf43ec63 100644 --- a/src/registrar/assets/sass/_theme/_uswds-theme-custom-styles.scss +++ b/src/registrar/assets/sass/_theme/_uswds-theme-custom-styles.scss @@ -222,7 +222,9 @@ section.dashboard { p { margin-bottom: 0; } +} +.dotgov-table { .usa-table { width: 100%; diff --git a/src/registrar/templates/domain_base.html b/src/registrar/templates/domain_base.html index 2f5074e1b..539949c44 100644 --- a/src/registrar/templates/domain_base.html +++ b/src/registrar/templates/domain_base.html @@ -1,4 +1,4 @@ -{% extends "dashboard_base.html" %} +{% extends "base.html" %} {% block title %}Domain {{ domain.name }}{% endblock %} @@ -12,9 +12,9 @@
+ {% block domain_content %}

Domain {{ domain.name }}

- {% block domain_content %} {% endblock %} {# domain_content #}
diff --git a/src/registrar/templates/domain_detail.html b/src/registrar/templates/domain_detail.html index 9cbbd4f91..dd6faa1be 100644 --- a/src/registrar/templates/domain_detail.html +++ b/src/registrar/templates/domain_detail.html @@ -1,5 +1,6 @@ {% extends "domain_base.html" %} {% block domain_content %} -

Active: {% if domain.is_active %}Yes{% else %}No{% endif %}

+ {{ block.super }} +

Active: {% if domain.is_active %}Yes{% else %}No{% endif %}

{% endblock %} {# domain_content #} diff --git a/src/registrar/templates/domain_users.html b/src/registrar/templates/domain_users.html index bb3b5f528..6f2f83ff8 100644 --- a/src/registrar/templates/domain_users.html +++ b/src/registrar/templates/domain_users.html @@ -1,10 +1,33 @@ {% extends "domain_base.html" %} +{% block title %}User management{% endblock %} + {% block domain_content %} -

Users

-
    - {% for user in domain.users.all %} -
  • {{ user }} <{{ user.email }}>
  • - {% endfor %} -
+

User management

+ + {% if domain.permissions %} + + + + + + + + + + {% for permission in domain.permissions.all %} + + + + + {% endfor %} + +
Domain users
EmailRole
+ {{ permission.user.email }} + {{ permission.role|title }}
+
+ {% endif %} {% endblock %} {# domain_content #} From 6ea24a4c2c05af74e67340563e9ceb372c5f0c02 Mon Sep 17 00:00:00 2001 From: igorkorenfeld Date: Tue, 21 Mar 2023 12:48:56 -0400 Subject: [PATCH 05/14] Restore to base style --- .../assets/sass/_theme/_uswds-theme-custom-styles.scss | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/registrar/assets/sass/_theme/_uswds-theme-custom-styles.scss b/src/registrar/assets/sass/_theme/_uswds-theme-custom-styles.scss index 1bf43ec63..1f8f4bd4d 100644 --- a/src/registrar/assets/sass/_theme/_uswds-theme-custom-styles.scss +++ b/src/registrar/assets/sass/_theme/_uswds-theme-custom-styles.scss @@ -222,9 +222,7 @@ section.dashboard { p { margin-bottom: 0; } -} -.dotgov-table { .usa-table { width: 100%; From dd5d30a7f1b9a7943e2a22b97e50ea181a2e3100 Mon Sep 17 00:00:00 2001 From: Neil Martinsen-Burrell Date: Tue, 21 Mar 2023 12:45:15 -0500 Subject: [PATCH 06/14] Fix liniting errors --- src/registrar/config/urls.py | 5 ++ src/registrar/fixtures.py | 5 +- src/registrar/templates/domain_add_user.html | 24 +++++++++ src/registrar/templates/domain_sidebar.html | 2 +- src/registrar/templates/domain_users.html | 8 +++ src/registrar/templatetags/url_helpers.py | 7 +++ src/registrar/views/__init__.py | 2 +- src/registrar/views/domain.py | 57 +++++++++++++++++++- 8 files changed, 106 insertions(+), 4 deletions(-) create mode 100644 src/registrar/templates/domain_add_user.html diff --git a/src/registrar/config/urls.py b/src/registrar/config/urls.py index dcf592570..0d0ec89f5 100644 --- a/src/registrar/config/urls.py +++ b/src/registrar/config/urls.py @@ -63,6 +63,11 @@ urlpatterns = [ ), path("domain/", views.DomainView.as_view(), name="domain"), path("domain//users", views.DomainUsersView.as_view(), name="domain-users"), + path( + "domain//users/add", + views.DomainAddUserView.as_view(), + name="domain-users-add", + ), ] diff --git a/src/registrar/fixtures.py b/src/registrar/fixtures.py index 9f971ce18..4edca7cf6 100644 --- a/src/registrar/fixtures.py +++ b/src/registrar/fixtures.py @@ -254,6 +254,7 @@ class DomainApplicationFixture: except Exception as e: logger.warning(e) + class DomainFixture(DomainApplicationFixture): """Create one domain and permissions on it for each user.""" @@ -268,7 +269,9 @@ class DomainFixture(DomainApplicationFixture): for user in users: # approve one of each users investigating status domains - application = DomainApplication.objects.filter(creator=user, status=DomainApplication.INVESTIGATING).last() + application = DomainApplication.objects.filter( + creator=user, status=DomainApplication.INVESTIGATING + ).last() logger.debug(f"Approving {application} for {user}") application.approve() application.save() diff --git a/src/registrar/templates/domain_add_user.html b/src/registrar/templates/domain_add_user.html new file mode 100644 index 000000000..87b141570 --- /dev/null +++ b/src/registrar/templates/domain_add_user.html @@ -0,0 +1,24 @@ +{% extends "domain_base.html" %} +{% load static field_helpers %} + +{% block title %}Add another user{% endblock %} + +{% block domain_content %} +

Add another user

+ +

You can add another user to help manage your domain. They will need to sign + into the .gov registrar with their Login.gov account. +

+ +
+ {% csrf_token %} + + {% input_with_errors form.email %} + + +
+ +{% endblock %} {# domain_content #} diff --git a/src/registrar/templates/domain_sidebar.html b/src/registrar/templates/domain_sidebar.html index ae8444bea..7f3a66be6 100644 --- a/src/registrar/templates/domain_sidebar.html +++ b/src/registrar/templates/domain_sidebar.html @@ -51,7 +51,7 @@
  • {% url 'domain-users' pk=domain.id as url %} User management diff --git a/src/registrar/templates/domain_users.html b/src/registrar/templates/domain_users.html index 6f2f83ff8..d4ee4e361 100644 --- a/src/registrar/templates/domain_users.html +++ b/src/registrar/templates/domain_users.html @@ -1,4 +1,5 @@ {% extends "domain_base.html" %} +{% load static %} {% block title %}User management{% endblock %} @@ -30,4 +31,11 @@ aria-live="polite" >
  • {% endif %} + + + Add another user + + {% endblock %} {# domain_content #} diff --git a/src/registrar/templatetags/url_helpers.py b/src/registrar/templatetags/url_helpers.py index 63ff9db6c..6201e61eb 100644 --- a/src/registrar/templatetags/url_helpers.py +++ b/src/registrar/templatetags/url_helpers.py @@ -8,3 +8,10 @@ register = template.Library() def namespaced_url(namespace, name="", **kwargs): """Get a URL, given its Django namespace and name.""" return reverse(f"{namespace}:{name}", kwargs=kwargs) + + +@register.filter("startswith") +def startswith(text, starts): + if isinstance(text, str): + return text.startswith(starts) + return False diff --git a/src/registrar/views/__init__.py b/src/registrar/views/__init__.py index 696c4640d..e1ae2cc32 100644 --- a/src/registrar/views/__init__.py +++ b/src/registrar/views/__init__.py @@ -1,5 +1,5 @@ from .application import * -from .domain import * +from .domain import DomainView, DomainUsersView, DomainAddUserView from .health import * from .index import * from .whoami import * diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index 600709dc4..48cd9443b 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -1,8 +1,13 @@ """View for a single Domain.""" +from django import forms +from django.db import IntegrityError +from django.shortcuts import redirect +from django.urls import reverse from django.views.generic import DetailView +from django.views.generic.edit import FormMixin -from registrar.models import Domain +from registrar.models import Domain, User, UserDomainRole from .utility import DomainPermission @@ -17,3 +22,53 @@ class DomainUsersView(DomainPermission, DetailView): model = Domain template_name = "domain_users.html" context_object_name = "domain" + + +class DomainAddUserForm(DomainPermission, forms.Form): + + """Form for adding a user to a domain.""" + + email = forms.EmailField(label="Email") + + def clean_email(self): + requested_email = self.cleaned_data["email"] + try: + User.objects.get(email=requested_email) + except User.DoesNotExist: + # TODO: send an invitation email to a non-existent user + raise forms.ValidationError("That user does not exist in this system.") + return requested_email + + +class DomainAddUserView(DomainPermission, FormMixin, DetailView): + template_name = "domain_add_user.html" + model = Domain + form_class = DomainAddUserForm + + def get_success_url(self): + return reverse("domain-users", kwargs={"pk": self.object.pk}) + + def post(self, request, *args, **kwargs): + self.object = self.get_object() + form = self.get_form() + if form.is_valid(): + return self.form_valid(form) + else: + return self.form_invalid(form) + + def form_valid(self, form): + """Add the specified user on this domain.""" + requested_email = form.cleaned_data["email"] + # look up a user with that email + # they should exist because we checked in clean_email + requested_user = User.objects.get(email=requested_email) + + try: + UserDomainRole.objects.create( + user=requested_user, domain=self.object, role=UserDomainRole.Roles.ADMIN + ) + except IntegrityError: + # User already has the desired role! Do nothing?? + pass + + return redirect(self.get_success_url()) From 0662031835194c2e2e4a77462d6e797b6f072bce Mon Sep 17 00:00:00 2001 From: Neil Martinsen-Burrell Date: Tue, 21 Mar 2023 13:18:22 -0500 Subject: [PATCH 07/14] Add success message --- src/registrar/templates/domain_base.html | 18 ++++++++++++++---- src/registrar/views/domain.py | 2 ++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/registrar/templates/domain_base.html b/src/registrar/templates/domain_base.html index 539949c44..d7bf655bd 100644 --- a/src/registrar/templates/domain_base.html +++ b/src/registrar/templates/domain_base.html @@ -12,12 +12,22 @@
    - {% block domain_content %} -

    Domain {{ domain.name }}

    + {% if messages %} + {% for message in messages %} +
    +
    + {{ message }} +
    +
    + {% endfor %} + {% endif %} - {% endblock %} {# domain_content #} + {% block domain_content %} +

    Domain {{ domain.name }}

    -
    + {% endblock %} {# domain_content #} + +
    diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index 48cd9443b..dc8ccc369 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -1,6 +1,7 @@ """View for a single Domain.""" from django import forms +from django.contrib import messages from django.db import IntegrityError from django.shortcuts import redirect from django.urls import reverse @@ -71,4 +72,5 @@ class DomainAddUserView(DomainPermission, FormMixin, DetailView): # User already has the desired role! Do nothing?? pass + messages.success(self.request, f"Added user {requested_email}.") return redirect(self.get_success_url()) From 9defc9d16108a30028d0216ce603b3ba6a75d492 Mon Sep 17 00:00:00 2001 From: Neil Martinsen-Burrell Date: Tue, 21 Mar 2023 14:27:35 -0500 Subject: [PATCH 08/14] Unit tests for user management views/forms --- src/registrar/tests/test_views.py | 75 ++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index 22f2e2ac9..c621cf986 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -1018,7 +1018,7 @@ class DomainApplicationTests(TestWithUser, WebTest): # self.assertNotContains(page, "VALUE") -class TestDomainPermissions(TestWithUser): +class TestWithDomainPermissions(TestWithUser): def setUp(self): super().setUp() self.domain, _ = Domain.objects.get_or_create(name="igorville.gov") @@ -1034,24 +1034,50 @@ class TestDomainPermissions(TestWithUser): pass super().tearDown() + +class TestDomainPermissions(TestWithDomainPermissions): def test_not_logged_in(self): """Not logged in gets a redirect to Login.""" response = self.client.get(reverse("domain", kwargs={"pk": self.domain.id})) self.assertEqual(response.status_code, 302) + response = self.client.get( + reverse("domain-users", kwargs={"pk": self.domain.id}) + ) + self.assertEqual(response.status_code, 302) + + response = self.client.get( + reverse("domain-users-add", kwargs={"pk": self.domain.id}) + ) + self.assertEqual(response.status_code, 302) + def test_no_domain_role(self): """Logged in but no role gets 403 Forbidden.""" self.client.force_login(self.user) self.role.delete() # user no longer has a role on this domain + with less_console_noise(): response = self.client.get(reverse("domain", kwargs={"pk": self.domain.id})) self.assertEqual(response.status_code, 403) + with less_console_noise(): + response = self.client.get( + reverse("domain-users", kwargs={"pk": self.domain.id}) + ) + self.assertEqual(response.status_code, 403) -class TestDomainDetail(TestDomainPermissions, WebTest): + with less_console_noise(): + response = self.client.get( + reverse("domain-users-add", kwargs={"pk": self.domain.id}) + ) + self.assertEqual(response.status_code, 403) + + +class TestDomainDetail(TestWithDomainPermissions, WebTest): def setUp(self): super().setUp() self.app.set_user(self.user.username) + self.client.force_login(self.user) def test_domain_detail_link_works(self): home_page = self.app.get("/") @@ -1059,3 +1085,48 @@ class TestDomainDetail(TestDomainPermissions, WebTest): # click the "Edit" link detail_page = home_page.click("Edit") self.assertContains(detail_page, "igorville.gov") + + def test_domain_user_management(self): + response = self.client.get( + reverse("domain-users", kwargs={"pk": self.domain.id}) + ) + self.assertContains(response, "User management") + + def test_domain_user_management_add_link(self): + """Button to get to user add page works.""" + management_page = self.app.get( + reverse("domain-users", kwargs={"pk": self.domain.id}) + ) + add_page = management_page.click("Add another user") + self.assertContains(add_page, "Add another user") + + def test_domain_user_add(self): + response = self.client.get( + reverse("domain-users-add", kwargs={"pk": self.domain.id}) + ) + self.assertContains(response, "Add another user") + + def test_domain_user_add_form(self): + """Adding a user works.""" + other_user, _ = get_user_model().objects.get_or_create( + email="mayor@igorville.gov" + ) + add_page = self.app.get( + reverse("domain-users-add", kwargs={"pk": self.domain.id}) + ) + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + + add_page.form["email"] = "mayor@igorville.gov" + + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + success_result = add_page.form.submit() + + self.assertEqual(success_result.status_code, 302) + self.assertEqual( + success_result["Location"], + reverse("domain-users", kwargs={"pk": self.domain.id}), + ) + + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + success_page = success_result.follow() + self.assertContains(success_page, "mayor@igorville.gov") From 1330110babad7d5ec342fe83cc4461be9a3d41cd Mon Sep 17 00:00:00 2001 From: Neil Martinsen-Burrell Date: Tue, 21 Mar 2023 14:50:46 -0500 Subject: [PATCH 09/14] Add breadcrumbs in domain management --- src/registrar/templates/domain_add_user.html | 7 +++++++ src/registrar/templates/domain_base.html | 8 ++++++++ src/registrar/templates/domain_users.html | 7 +++++++ 3 files changed, 22 insertions(+) diff --git a/src/registrar/templates/domain_add_user.html b/src/registrar/templates/domain_add_user.html index 87b141570..1a1c45aa3 100644 --- a/src/registrar/templates/domain_add_user.html +++ b/src/registrar/templates/domain_add_user.html @@ -4,6 +4,13 @@ {% block title %}Add another user{% endblock %} {% block domain_content %} +

    + + Back to user management + +

    Add another user

    You can add another user to help manage your domain. They will need to sign diff --git a/src/registrar/templates/domain_base.html b/src/registrar/templates/domain_base.html index d7bf655bd..3dcb79bb2 100644 --- a/src/registrar/templates/domain_base.html +++ b/src/registrar/templates/domain_base.html @@ -1,4 +1,5 @@ {% extends "base.html" %} +{% load static %} {% block title %}Domain {{ domain.name }}{% endblock %} @@ -23,6 +24,13 @@ {% endif %} {% block domain_content %} +

    + + Back to manage your domains +

    +

    Domain {{ domain.name }}

    {% endblock %} {# domain_content #} diff --git a/src/registrar/templates/domain_users.html b/src/registrar/templates/domain_users.html index d4ee4e361..989ac31e4 100644 --- a/src/registrar/templates/domain_users.html +++ b/src/registrar/templates/domain_users.html @@ -4,6 +4,13 @@ {% block title %}User management{% endblock %} {% block domain_content %} +

    + + Back to manage your domains +

    +

    User management

    {% if domain.permissions %} From 38f3b5ed87448e6d6f45d9c4dd41e7c06772e4d6 Mon Sep 17 00:00:00 2001 From: igorkorenfeld Date: Tue, 21 Mar 2023 16:50:12 -0400 Subject: [PATCH 10/14] Place table styling outside of dashboard, update class names for tables --- .../_theme/_uswds-theme-custom-styles.scss | 103 ++++++++++-------- src/registrar/templates/domain_users.html | 2 +- src/registrar/templates/home.html | 4 +- 3 files changed, 61 insertions(+), 48 deletions(-) diff --git a/src/registrar/assets/sass/_theme/_uswds-theme-custom-styles.scss b/src/registrar/assets/sass/_theme/_uswds-theme-custom-styles.scss index 1f8f4bd4d..8514b2ab8 100644 --- a/src/registrar/assets/sass/_theme/_uswds-theme-custom-styles.scss +++ b/src/registrar/assets/sass/_theme/_uswds-theme-custom-styles.scss @@ -223,27 +223,41 @@ section.dashboard { margin-bottom: 0; } - .usa-table { - width: 100%; + @include at-media(mobile-lg) { + margin-top: units(5); - a { - display: flex; - align-items: flex-start; + h2 { + margin-bottom: units(3); + } + } +} + + +.dotgov-table { + width: 100%; + + a { + display: flex; + align-items: flex-start; + color: color('primary'); + + &:visited { color: color('primary'); + } - &:visited { - color: color('primary'); - } - .usa-icon { - // align icon with x height - margin-top: units(0.5); - margin-right: units(0.5); - } + .usa-icon { + // align icon with x height + margin-top: units(0.5); + margin-right: units(0.5); } } - // Table on small mobile - .usa-table--stacked { + th[data-sortable]:not([aria-sort]) .usa-table__header__button { + right: auto; + } +} + +.dotgov-table--stacked { td, th { padding: units(1) units(2) units(2px) 0; border: none; @@ -268,46 +282,45 @@ section.dashboard { color: color('primary-darker'); padding-bottom: units(2px); } - } +} - @include at-media(mobile-lg) { - margin-top: units(5); +@include at-media(mobile-lg) { - h2 { - margin-bottom: units(3); - } - - .usa-table tr { + .dotgov-table { + tr { border: none; } - .usa-table { + td, th { + border-bottom: 1px solid color('base-light'); + } + + thead th { + color: color('primary-darker'); + border-bottom: 2px solid color('base-light'); + } + + tbody tr:last-of-type { td, th { - border-bottom: 1px solid color('base-light'); + border-bottom: 0; } + } + + tbody th { + word-break: break-word; + } - thead th { - color: color('primary-darker'); - border-bottom: 2px solid color('base-light'); - } + td, th, + .usa-tabel th{ + padding: units(2) units(2) units(2) 0; + } - tbody tr:last-of-type { - td, th { - border-bottom: 0; - } - } + th:first-of-type { + padding-left: 0; + } - td, th { - padding: units(2); - } - - th:first-of-type { - padding-left: 0; - } - - thead tr:first-child th:first-child { - border-top: none; - } + thead tr:first-child th:first-child { + border-top: none; } } } diff --git a/src/registrar/templates/domain_users.html b/src/registrar/templates/domain_users.html index 6f2f83ff8..541b31b84 100644 --- a/src/registrar/templates/domain_users.html +++ b/src/registrar/templates/domain_users.html @@ -6,7 +6,7 @@

    User management

    {% if domain.permissions %} - +
    diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index f15f1d1a8..fecef0c57 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -17,7 +17,7 @@

    Registered domains

    {% if domains %} -
    Domain users
    +
    @@ -56,7 +56,7 @@

    Active domain requests

    {% if domain_applications %} -
    Your domain applications
    +
    From f33bc3810e80fc5bcb0d7dbe71b653f07cb69440 Mon Sep 17 00:00:00 2001 From: Michelle Rago <60157596+michelle-rago@users.noreply.github.com> Date: Wed, 22 Mar 2023 10:02:08 -0400 Subject: [PATCH 11/14] Updated the text re: exporting a csv file --- src/registrar/templates/home.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index f15f1d1a8..c6166e665 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -111,7 +111,7 @@

    Export domains

    -

    If you would like to analyze your list of domains further, you can download the list of domains and their statuses as csv file

    +

    Download a list of your domains and their statuses as a csv file.

    Export domains as csv From 7718dbc374661a4fc97c37ed66838a2871e88521 Mon Sep 17 00:00:00 2001 From: Neil Martinsen-Burrell Date: Thu, 23 Mar 2023 14:03:08 -0500 Subject: [PATCH 12/14] Review feedback: docstrings, move out form --- src/registrar/forms/__init__.py | 1 + src/registrar/forms/domain.py | 21 +++++++++++++++++++++ src/registrar/views/domain.py | 13 +++++++++++++ 3 files changed, 35 insertions(+) create mode 100644 src/registrar/forms/domain.py diff --git a/src/registrar/forms/__init__.py b/src/registrar/forms/__init__.py index bd0426884..d48dd037b 100644 --- a/src/registrar/forms/__init__.py +++ b/src/registrar/forms/__init__.py @@ -1 +1,2 @@ from .application_wizard import * +from .domain import DomainAddUserForm diff --git a/src/registrar/forms/domain.py b/src/registrar/forms/domain.py new file mode 100644 index 000000000..6a2229961 --- /dev/null +++ b/src/registrar/forms/domain.py @@ -0,0 +1,21 @@ +"""Forms for domain management.""" + +from django import forms + +from registrar.models import User + + +class DomainAddUserForm(forms.Form): + + """Form for adding a user to a domain.""" + + email = forms.EmailField(label="Email") + + def clean_email(self): + requested_email = self.cleaned_data["email"] + try: + User.objects.get(email=requested_email) + except User.DoesNotExist: + # TODO: send an invitation email to a non-existent user + raise forms.ValidationError("That user does not exist in this system.") + return requested_email diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index dc8ccc369..150efab81 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -14,12 +14,18 @@ from .utility import DomainPermission class DomainView(DomainPermission, DetailView): + + """Domain detail overview page.""" + model = Domain template_name = "domain_detail.html" context_object_name = "domain" class DomainUsersView(DomainPermission, DetailView): + + """User management page in the domain details.""" + model = Domain template_name = "domain_users.html" context_object_name = "domain" @@ -42,6 +48,13 @@ class DomainAddUserForm(DomainPermission, forms.Form): class DomainAddUserView(DomainPermission, FormMixin, DetailView): + + """Inside of a domain's user management, a form for adding users. + + Multiple inheritance is used here for permissions, form handling, and + details of the individual domain. + """ + template_name = "domain_add_user.html" model = Domain form_class = DomainAddUserForm From 6f41d18deca8323f016c81d5ffaec623f7bd94b2 Mon Sep 17 00:00:00 2001 From: Neil Martinsen-Burrell Date: Thu, 23 Mar 2023 14:28:34 -0500 Subject: [PATCH 13/14] Fix OWASP Zap error --- src/zap.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zap.conf b/src/zap.conf index 640883adc..09d309cfe 100644 --- a/src/zap.conf +++ b/src/zap.conf @@ -48,7 +48,7 @@ 10038 OUTOFSCOPE http://app:8080/public/img/.* 10038 OUTOFSCOPE http://app:8080/public/css/.* 10038 OUTOFSCOPE http://app:8080/public/js/.* -10038 OUTOFSCOPE http://app:8080/(robots.txt|sitemap.xml|TODO|edit/) +10038 OUTOFSCOPE http://app:8080/(robots.txt|sitemap.xml|TODO|edit|users/) # This URL always returns 404, so include it as well. 10038 OUTOFSCOPE http://app:8080/todo # OIDC isn't configured in the test environment and DEBUG=True so this gives a 500 without CSP headers From d27da27e143257194e4a0b3557a9e9cd474e25bb Mon Sep 17 00:00:00 2001 From: Neil Martinsen-Burrell Date: Thu, 23 Mar 2023 14:42:51 -0500 Subject: [PATCH 14/14] Fix OWASP Zap error --- src/zap.conf | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/zap.conf b/src/zap.conf index 09d309cfe..ba0ef6a89 100644 --- a/src/zap.conf +++ b/src/zap.conf @@ -48,7 +48,9 @@ 10038 OUTOFSCOPE http://app:8080/public/img/.* 10038 OUTOFSCOPE http://app:8080/public/css/.* 10038 OUTOFSCOPE http://app:8080/public/js/.* -10038 OUTOFSCOPE http://app:8080/(robots.txt|sitemap.xml|TODO|edit|users/) +10038 OUTOFSCOPE http://app:8080/(robots.txt|sitemap.xml|TODO|edit/) +10038 OUTOFSCOPE http://app:8080/users +10038 OUTOFSCOPE http://app:8080/users/add # This URL always returns 404, so include it as well. 10038 OUTOFSCOPE http://app:8080/todo # OIDC isn't configured in the test environment and DEBUG=True so this gives a 500 without CSP headers
    Your domain applications