Skip to content

Commit

Permalink
Change auditee/auditor certifying official (#2891)
Browse files Browse the repository at this point in the history
* Add GET side of page for changing Auditor Certifying Official.

* Add POST side of page for changing Auditor Certifying Official.

* Remove stray import.

* Add page for changing Auditee Certifying Official.

* Use variable for role in template instead of hard-coding.

* Typo correction.
  • Loading branch information
tadhg-ohiggins authored and jperson1 committed Dec 8, 2023
1 parent 8b65c09 commit 7bd8af9
Show file tree
Hide file tree
Showing 5 changed files with 351 additions and 0 deletions.
44 changes: 44 additions & 0 deletions backend/audit/templates/audit/manage-submission-change-access.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{% extends "base.html" %}
{% load static %}
{% block content %}
<div class="grid-container margin-top-6">
<div class="grid-row">
<form class="grid-col-8 grid-offset-2"
id="auditor-certification-step-2"
method="post">
{% csrf_token %}
<fieldset class="usa-fieldset padding-bottom-2">
<h1 id="auditor-certification">Change {{ friendly_role }}</h1>
<h3>Current {{ friendly_role }}</h3>

<p>Name: {{ certifier_name }}</p>

<p>Email: <code>{{ email }}</code></p>

<hr />

{% if errors %}
<ul>
{% for error in errors %}
<li class="usa-error-message">{{ error }}</li>
{% endfor %}
</ul>
{% endif %}

<label class="usa-label" for="fullname">Name of new {{ friendly_role }}</label>
<input class="usa-input"
id="name"
name="fullname"
placeholder="[name]" />
<label class="usa-label" for="email">Email address of new {{ friendly_role }}</label>
<input class="usa-input"
id="email"
name="email"
placeholder="[email address]" />
</fieldset>
<button class="usa-button margin-bottom-8 margin-top-4" id="continue">Remove {{ email }} from the {{ friendly_role }} role for this submission and replace them with the above user.</button>
</form>
</div>
</div>
{% include "audit-metadata.html" %}
{% endblock content %}
165 changes: 165 additions & 0 deletions backend/audit/test_manage_submission_access_view.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
from django.contrib.auth.models import User as DjangoUser
from django.test import TestCase
from django.urls import reverse

from model_bakery import baker
from .models import (
Access,
DeletedAccess,
SingleAuditChecklist,
User,
)


def _make_test_users_by_email(emails: list[str]) -> list[DjangoUser]:
return [baker.make(User, email=email) for email in emails]


def _make_access(sac: SingleAuditChecklist, role: str, user: DjangoUser) -> Access:
return baker.make(Access, user=user, email=user.email, sac=sac, role=role)


def _make_user_and_sac(**kwargs):
user = baker.make(User)
sac = baker.make(SingleAuditChecklist, **kwargs)
return user, sac


class ChangeAuditorCertifyingOfficialViewTests(TestCase):
"""
GET and POST tests for changing auditor certifying official.
"""

role = "certifying_auditor_contact"
other_role = "certifying_auditee_contact"
view = "audit:ChangeAuditorCertifyingOfficial"

def test_basic_get(self):
"""
A user should be able to access this page for a SAC they're associated with.
"""
user, sac = _make_user_and_sac()
baker.make(Access, user=user, sac=sac, role="editor")
sac.general_information = {"auditee_uei": "YESIAMAREALUEI"}
sac.save()
current_cac = baker.make(Access, sac=sac, role=self.role)

self.client.force_login(user)
url = reverse(self.view, kwargs={"report_id": sac.report_id})
response = self.client.get(url)

self.assertEqual(response.status_code, 200)
self.assertIn("YESIAMAREALUEI", response.content.decode("UTF-8"))
self.assertIn(current_cac.email, response.content.decode("UTF-8"))

def test_basic_post(self):
"""
Submitting the form with a new email address should delete the existing
Access, create a DeletedAccess, and create a new Access.
"""
user = baker.make(User, email="[email protected]")
sac = baker.make(SingleAuditChecklist)
baker.make(Access, user=user, sac=sac, role="editor")
baker.make(
Access,
sac=sac,
role=self.other_role,
email="[email protected]",
)
sac.general_information = {"auditee_uei": "YESIAMAREALUEI"}
sac.save()
current_cac = baker.make(Access, sac=sac, role=self.role)

self.client.force_login(user)

data = {
"fullname": "The New CAC",
"email": "[email protected]",
}

url = reverse(self.view, kwargs={"report_id": sac.report_id})
response = self.client.post(url, data=data)
self.assertEqual(302, response.status_code)

newaccess = Access.objects.get(
sac=sac, fullname=data["fullname"], email=data["email"]
)
self.assertEqual(self.role, newaccess.role)
oldaccess = DeletedAccess.objects.get(
sac=sac,
fullname=current_cac.fullname,
email=current_cac.email,
)
self.assertEqual(self.role, oldaccess.role)

def test_bad_email_post(self):
"""
Submitting an email address that's already in use for the other role should
result in a 400 and returning to the form page.
"""
new_email = "[email protected]"
user = baker.make(User, email="[email protected]")
sac = baker.make(SingleAuditChecklist)
baker.make(Access, user=user, sac=sac, role="editor")
baker.make(Access, sac=sac, role=self.other_role, email=new_email)
baker.make(Access, sac=sac, role=self.role)
sac.general_information = {"auditee_uei": "YESIAMAREALUEI"}
sac.save()

self.client.force_login(user)

data = {"fullname": "The New CAC", "email": new_email}

url = reverse(self.view, kwargs={"report_id": sac.report_id})
response = self.client.post(url, data=data)
self.assertEqual(400, response.status_code)

def test_login_required(self):
"""When an unauthenticated request is made"""

response = self.client.get(
reverse(
self.view,
kwargs={"report_id": "12345"},
)
)

self.assertEqual(response.status_code, 403)

def test_bad_report_id_returns_403(self):
"""
When a request is made for a malformed or nonexistent report_id,
a 403 error should be returned
"""
user = baker.make(User)

self.client.force_login(user)

response = self.client.get(
reverse(self.view, kwargs={"report_id": "this is not a report id"})
)

self.assertEqual(response.status_code, 403)

def test_inaccessible_audit_returns_403(self):
"""When a request is made for an audit that is inaccessible for this user, a 403 error should be returned"""
user, sac = _make_user_and_sac()

self.client.force_login(user)
response = self.client.post(
reverse(self.view, kwargs={"report_id": sac.report_id})
)

self.assertEqual(response.status_code, 403)


class ChangeAuditeeCertifyingOfficialViewTests(
ChangeAuditorCertifyingOfficialViewTests
):
"""
GET and POST tests for changing auditee certifying official.
"""

role = "certifying_auditee_contact"
other_role = "certifying_auditor_contact"
view = "audit:ChangeAuditeeCertifyingOfficial"
10 changes: 10 additions & 0 deletions backend/audit/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,16 @@ def camel_to_hyphen(raw: str) -> str:
views.UnlockAfterCertificationView.as_view(),
name="UnlockAfterCertification",
),
path(
"manage-submission/auditor-certifying-official/<str:report_id>",
views.ChangeAuditorCertifyingOfficialView.as_view(),
name="ChangeAuditorCertifyingOfficial",
),
path(
"manage-submission/auditee-certifying-official/<str:report_id>",
views.ChangeAuditeeCertifyingOfficialView.as_view(),
name="ChangeAuditeeCertifyingOfficial",
),
]

for form_section in FORM_SECTIONS:
Expand Down
6 changes: 6 additions & 0 deletions backend/audit/views/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
from .home import Home
from .manage_submission_access import (
ChangeAuditeeCertifyingOfficialView,
ChangeAuditorCertifyingOfficialView,
)
from .no_robots import no_robots
from .submission_progress_view import ( # noqa
SubmissionProgressView,
Expand Down Expand Up @@ -31,6 +35,8 @@
AuditorCertificationStep1View,
AuditorCertificationStep2View,
CertificationView,
ChangeAuditeeCertifyingOfficialView,
ChangeAuditorCertifyingOfficialView,
CrossValidationView,
EditSubmission,
ExcelFileHandlerView,
Expand Down
126 changes: 126 additions & 0 deletions backend/audit/views/manage_submission_access.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
from django import forms
from django.db import transaction
from django.shortcuts import redirect, render, reverse
from django.views import generic

from audit.mixins import (
SingleAuditChecklistAccessRequiredMixin,
)
from audit.models import (
ACCESS_ROLES,
Access,
SingleAuditChecklist,
)


class ChangeAccessForm(forms.Form):
"""
Form for changing access. The view class, not this class, has the responsibility for handling whether we’re deleting access (in the cases where only one user can have the role) or adding access (in the cases where multiple users can have the role).
"""

fullname = forms.CharField()
email = forms.EmailField()
# email_confirm = forms.EmailField()

# def clean(self):
# cleaned = super().clean()
# if cleaned.get("email") != cleaned.get("email_confirm"):
# raise ValidationError(
# "Email address and confirmed email address must match"
# )


class ChangeAuditorCertifyingOfficialView(
SingleAuditChecklistAccessRequiredMixin, generic.View
):
"""
View for changing the auditor certifying official
"""

role = "certifying_auditor_contact"
other_role = "certifying_auditee_contact"

def get(self, request, *args, **kwargs):
"""
Show the current auditor certifying official and the form.
"""
report_id = kwargs["report_id"]
sac = SingleAuditChecklist.objects.get(report_id=report_id)
access = Access.objects.get(sac=sac, role=self.role)
friendly_role = [r for r in ACCESS_ROLES if r[0] == self.role][0][1]
context = {
"role": self.role,
"friendly_role": friendly_role,
"auditee_uei": sac.general_information["auditee_uei"],
"auditee_name": sac.general_information.get("auditee_name"),
"certifier_name": access.fullname,
"email": access.email,
"report_id": report_id,
"errors": [],
}

return render(request, "audit/manage-submission-change-access.html", context)

def post(self, request, *args, **kwargs):
"""
Change the current auditor certifying official and redirect to submission
progress.
"""
report_id = kwargs["report_id"]
sac = SingleAuditChecklist.objects.get(report_id=report_id)
form = ChangeAccessForm(request.POST)
form.full_clean()
url = reverse("audit:SubmissionProgress", kwargs={"report_id": report_id})

if not self.other_role:
Access(
sac=sac,
role=self.role,
fullname=form.cleaned_data["fullname"],
email=form.cleaned_data["email"],
).save()
return redirect(url)

access = Access.objects.get(sac=sac, role=self.role)
other_access = Access.objects.get(sac=sac, role=self.other_role)
if form.cleaned_data["email"] == other_access.email:
friendly_role = [r for r in ACCESS_ROLES if r[0] == self.role][0][1]
context = {
"role": self.role,
"friendly_role": friendly_role,
"auditee_uei": sac.general_information["auditee_uei"],
"auditee_name": sac.general_information.get("auditee_name"),
"certifier_name": access.fullname,
"email": access.email,
"report_id": report_id,
"errors": [
"Cannot use the same email address for both certifying officials."
],
}
return render(
request,
"audit/manage-submission-change-access.html",
context,
status=400,
)

with transaction.atomic():
access.delete(removing_user=request.user, removal_event="access-change")

Access(
sac=sac,
role=self.role,
fullname=form.cleaned_data["fullname"],
email=form.cleaned_data["email"],
).save()

return redirect(url)


class ChangeAuditeeCertifyingOfficialView(ChangeAuditorCertifyingOfficialView):
"""
View for changing the auditee certifying official
"""

role = "certifying_auditee_contact"
other_role = "certifying_auditor_contact"

0 comments on commit 7bd8af9

Please sign in to comment.