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)