feat: replace pipeline profile and source health

This commit is contained in:
Jens
2026-07-22 20:10:34 +02:00
parent 3371455255
commit 4fa2a34447
14 changed files with 282 additions and 130 deletions
+25
View File
@@ -274,6 +274,31 @@ def track_application_changes(
return events
def change_application_status(*, application: Application, user, status: str) -> Application:
"""Persist an accessible pipeline status change and its audit event."""
valid_statuses = {value for value, _label in Application.Status.choices}
if status not in valid_statuses:
raise ValueError("Ongeldige sollicitatiestatus.")
previous = application.status
if previous == status:
return application
with transaction.atomic():
application.status = status
update_fields = ["status", "updated_at"]
if status == Application.Status.APPLIED and application.applied_at is None:
application.applied_at = timezone.now()
update_fields.append("applied_at")
application.save(update_fields=update_fields)
_record_timeline_event(
application=application,
user=user,
event_type=ApplicationTimelineEvent.EventType.STATUS_CHANGED,
metadata={"from": previous, "to": status},
)
return application
def build_print_html(application: Application) -> str:
snapshot = application.snapshot if isinstance(application.snapshot, dict) else {}
job = application.job
+2
View File
@@ -9,6 +9,7 @@ from .views import (
application_delete,
application_export,
application_print,
application_status,
job_feedback,
)
@@ -17,6 +18,7 @@ urlpatterns = [
path("skills/", SkillInsightsView.as_view(), name="skill-insights"),
path("applications/", ApplicationListView.as_view(), name="applications"),
path("applications/<int:pk>/", ApplicationUpdateView.as_view(), name="application-edit"),
path("applications/<int:pk>/status/", application_status, name="application-status"),
path("applications/<int:pk>/export/", application_export, name="application-export"),
path("applications/<int:pk>/print/", application_print, name="application-print"),
path("applications/<int:pk>/delete/", application_delete, name="application-delete"),
+38 -3
View File
@@ -7,6 +7,7 @@ from django.db.models import Count, F, Q
from django.http import Http404, HttpResponse
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse
from django.utils import timezone
from django.utils.http import url_has_allowed_host_and_scheme
from django.views.decorators.http import require_POST
from django.views.generic import DetailView, ListView, TemplateView, UpdateView
@@ -18,6 +19,7 @@ from .models import Application, Feedback, JobPosting, ScoreRun
from .services.applications import (
build_application_export,
build_print_html,
change_application_status,
delete_application_dossier,
track_application_changes,
)
@@ -206,8 +208,10 @@ class ApplicationListView(LoginRequiredMixin, ListView):
context_object_name = "applications"
def get_queryset(self):
return Application.objects.filter(user=self.request.user).select_related(
"job", "job__employer"
return (
Application.objects.filter(user=self.request.user)
.select_related("job", "job__employer")
.prefetch_related("timeline_events")
)
def get_context_data(self, **kwargs):
@@ -222,6 +226,16 @@ class ApplicationListView(LoginRequiredMixin, ListView):
}
for status, label in Application.Status.choices
]
context["status_choices"] = Application.Status.choices
context["open_count"] = sum(
item.status not in {Application.Status.REJECTED, Application.Status.WITHDRAWN}
for item in applications
)
context["follow_up_count"] = sum(
bool(item.follow_up_date and item.follow_up_date <= timezone.localdate())
for item in applications
)
context["today"] = timezone.localdate()
return context
@@ -231,7 +245,11 @@ class ApplicationUpdateView(LoginRequiredMixin, UpdateView):
template_name = "applications/edit.html"
def get_queryset(self):
return Application.objects.filter(user=self.request.user).select_related("job")
return (
Application.objects.filter(user=self.request.user)
.select_related("job", "job__employer")
.prefetch_related("timeline_events")
)
def form_valid(self, form):
previous = (
@@ -269,6 +287,23 @@ def application_export(request, pk: int):
return response
@login_required
@require_POST
def application_status(request, pk: int):
application = get_object_or_404(Application, pk=pk, user=request.user)
try:
change_application_status(
application=application,
user=request.user,
status=request.POST.get("status", ""),
)
except ValueError as exc:
messages.error(request, str(exc))
else:
messages.success(request, "Pipelinestatus bijgewerkt.")
return redirect("jobs:applications")
@login_required
def application_print(request, pk: int):
application = get_object_or_404(