160 lines
5.6 KiB
Python
160 lines
5.6 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from datetime import datetime
|
|
|
|
from django.core.paginator import Page, Paginator
|
|
from django.urls import reverse
|
|
|
|
from apps.notifications.models import DigestOutbox, ReminderOutbox
|
|
from apps.profiles.models import ProfileRevision
|
|
from apps.sources.models import SourceRun
|
|
|
|
from ..models import ApplicationTimelineEvent, Feedback, JobVersion
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ActivityEvent:
|
|
occurred_at: datetime
|
|
event_type: str
|
|
title: str
|
|
detail: str
|
|
tone: str
|
|
url: str
|
|
object_label: str
|
|
|
|
|
|
ACTIVITY_FILTERS = (
|
|
("all", "Alle activiteit"),
|
|
("source", "Bronruns"),
|
|
("job", "Vacatures"),
|
|
("application", "Sollicitaties"),
|
|
("feedback", "Feedback"),
|
|
("notification", "Notificaties"),
|
|
("profile", "Profiel"),
|
|
)
|
|
|
|
|
|
def _source_events() -> list[ActivityEvent]:
|
|
events = []
|
|
for run in SourceRun.objects.select_related("source").order_by("-started_at")[:100]:
|
|
detail = (
|
|
f"{run.extracted_count} geëxtraheerd · {run.created_count} nieuw · "
|
|
f"{run.duplicate_count} duplicaat"
|
|
)
|
|
if run.error_category:
|
|
detail = f"{run.error_category}: {run.error_message or 'geen foutdetail'}"
|
|
events.append(
|
|
ActivityEvent(
|
|
run.started_at,
|
|
"source",
|
|
f"Bronrun {run.get_status_display().lower()}",
|
|
detail,
|
|
"positive" if run.status == SourceRun.Status.SUCCESS else "attention",
|
|
reverse("sources:list") + f"#source-details-{run.source_id}",
|
|
run.source.name,
|
|
)
|
|
)
|
|
return events
|
|
|
|
|
|
def _job_events() -> list[ActivityEvent]:
|
|
return [
|
|
ActivityEvent(
|
|
version.created_at,
|
|
"job",
|
|
"Vacature-inhoud gewijzigd" if version.changed_fields else "Vacatureversie opgeslagen",
|
|
", ".join(version.changed_fields)
|
|
if version.changed_fields
|
|
else "Bronmoment vastgelegd",
|
|
"neutral",
|
|
reverse("jobs:detail", kwargs={"pk": version.job_id}),
|
|
version.job.original_title,
|
|
)
|
|
for version in JobVersion.objects.select_related("job").order_by("-created_at")[:100]
|
|
]
|
|
|
|
|
|
def build_activity_page(
|
|
*, user, event_type: str = "all", query: str = "", page: int | str = 1, per_page: int = 20
|
|
) -> Page:
|
|
"""Combine bounded trustworthy events into a user-safe read-only timeline."""
|
|
|
|
events: list[ActivityEvent] = []
|
|
if event_type in {"all", "source"}:
|
|
events.extend(_source_events())
|
|
if event_type in {"all", "job"}:
|
|
events.extend(_job_events())
|
|
if event_type in {"all", "feedback"}:
|
|
for item in (
|
|
Feedback.objects.filter(user=user).select_related("job").order_by("-created_at")[:100]
|
|
):
|
|
events.append(
|
|
ActivityEvent(
|
|
item.created_at,
|
|
"feedback",
|
|
item.get_action_display(),
|
|
item.reason or "Gebruikersactie opgeslagen",
|
|
"neutral",
|
|
reverse("jobs:detail", kwargs={"pk": item.job_id}),
|
|
item.job.original_title,
|
|
)
|
|
)
|
|
if event_type in {"all", "application"}:
|
|
for item in (
|
|
ApplicationTimelineEvent.objects.filter(user=user)
|
|
.select_related("application__job")
|
|
.order_by("-created_at")[:100]
|
|
):
|
|
events.append(
|
|
ActivityEvent(
|
|
item.created_at,
|
|
"application",
|
|
item.get_event_type_display(),
|
|
"Sollicitatiedossier bijgewerkt",
|
|
"positive" if item.event_type == item.EventType.STATUS_CHANGED else "neutral",
|
|
reverse("jobs:application-edit", kwargs={"pk": item.application_id}),
|
|
item.application.job.original_title,
|
|
)
|
|
)
|
|
if event_type in {"all", "notification"}:
|
|
for model, label in ((DigestOutbox, "Samenvatting"), (ReminderOutbox, "Herinnering")):
|
|
for item in model.objects.filter(profile__user=user).order_by("-created_at")[:60]:
|
|
events.append(
|
|
ActivityEvent(
|
|
item.created_at,
|
|
"notification",
|
|
f"{label} {item.get_status_display().lower()}",
|
|
item.subject,
|
|
"positive" if item.status == item.Status.SENT else "attention",
|
|
reverse("dashboard:system"),
|
|
label,
|
|
)
|
|
)
|
|
if event_type in {"all", "profile"}:
|
|
for item in (
|
|
ProfileRevision.objects.filter(profile__user=user)
|
|
.select_related("profile")
|
|
.order_by("-created_at")[:60]
|
|
):
|
|
events.append(
|
|
ActivityEvent(
|
|
item.created_at,
|
|
"profile",
|
|
f"Zoekprofiel versie {item.version}",
|
|
item.reason or "Profielconfiguratie opgeslagen",
|
|
"neutral",
|
|
reverse("profiles:edit", kwargs={"pk": item.profile_id}),
|
|
item.profile.name,
|
|
)
|
|
)
|
|
normalized_query = query.strip().casefold()
|
|
if normalized_query:
|
|
events = [
|
|
item
|
|
for item in events
|
|
if normalized_query in f"{item.title} {item.detail} {item.object_label}".casefold()
|
|
]
|
|
events.sort(key=lambda item: item.occurred_at, reverse=True)
|
|
return Paginator(events, per_page).get_page(page)
|