CMMC-Audit/project_review_audit.py
2026-05-27 14:45:29 -06:00

116 lines
4.1 KiB
Python

import threading
from django.shortcuts import get_object_or_404
from coldfront.core.project.models import Project, ProjectReview
from .models import AuditEvent
from .resolvers import project_label, project_review_label
from .utils import log_event
PATCH_MARKER = "_carc_audit_project_review_patch_installed"
_state = threading.local()
def _suppressed_project_review_ids():
ids = getattr(_state, "suppressed_project_review_ids", None)
if ids is None:
ids = set()
_state.suppressed_project_review_ids = ids
return ids
def is_project_review_status_suppressed(project_review_pk):
return project_review_pk in _suppressed_project_review_ids()
def _status_name(project_review):
return getattr(project_review.status, "name", None)
def _review_values(project_review):
return {
"project_id": project_review.project_id,
"status": _status_name(project_review),
"reason_for_not_updating_project": project_review.reason_for_not_updating_project or "",
}
def install_project_review_audit_patch():
from coldfront.core.project import views as project_views
if getattr(project_views, PATCH_MARKER, False):
return
original_review_post = project_views.ProjectReviewView.post
original_complete_get = project_views.ProjectReviewCompleteView.get
def review_post(self, request, *args, **kwargs):
project = get_object_or_404(Project, pk=self.kwargs.get("pk"))
before_pk = (
ProjectReview.objects.filter(project=project)
.order_by("-pk")
.values_list("pk", flat=True)
.first()
)
response = original_review_post(self, request, *args, **kwargs)
project_review = (
ProjectReview.objects.filter(project=project)
.select_related("status", "project")
.order_by("-pk")
.first()
)
if project_review and project_review.pk != before_pk:
log_event(
AuditEvent.Action.PROJECT_REVIEW_SUBMITTED,
project_review,
new_values=_review_values(project_review),
message=(
f"Project review submitted for {project_label(project_review.project)} "
f"with status {_status_name(project_review)}"
),
actor=getattr(request, "user", None),
request=request,
source=AuditEvent.Source.COLDFRONT_WORKFLOW,
target_repr=project_review_label(project_review),
)
return response
def complete_get(self, request, project_review_pk):
project_review = get_object_or_404(
ProjectReview.objects.select_related("status", "project"),
pk=project_review_pk,
)
old_status = _status_name(project_review)
suppressed_ids = _suppressed_project_review_ids()
suppressed_ids.add(project_review_pk)
try:
response = original_complete_get(self, request, project_review_pk)
finally:
suppressed_ids.discard(project_review_pk)
project_review.refresh_from_db()
project_review = ProjectReview.objects.select_related("status", "project").get(pk=project_review.pk)
new_status = _status_name(project_review)
if old_status != new_status and new_status == "Completed":
log_event(
AuditEvent.Action.PROJECT_REVIEW_COMPLETED,
project_review,
old_values={"status": old_status, "project_id": project_review.project_id},
new_values={"status": new_status, "project_id": project_review.project_id},
message=f"Project review completed for {project_label(project_review.project)}",
actor=getattr(request, "user", None),
request=request,
source=AuditEvent.Source.COLDFRONT_WORKFLOW,
target_repr=project_review_label(project_review),
)
return response
project_views.ProjectReviewView.post = review_post
project_views.ProjectReviewCompleteView.get = complete_get
setattr(project_views, PATCH_MARKER, True)