Flatten budget-app contents into repo root
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
"""The activity log: everything users did, newest first (administrators)."""
|
||||
import csv
|
||||
import io
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from fastapi.responses import Response
|
||||
from sqlalchemy import func, or_, select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ..db import get_db
|
||||
from ..models import AuditEntry, BudgetYear, Department, User
|
||||
from ..security import require
|
||||
from .mail import _date
|
||||
|
||||
router = APIRouter(prefix="/api", tags=["activity"])
|
||||
|
||||
|
||||
def _query(q, user_id, entity, action, year_id, date_from, date_to):
|
||||
query = select(AuditEntry)
|
||||
if user_id:
|
||||
query = query.where(AuditEntry.user_id == user_id)
|
||||
if entity:
|
||||
query = query.where(AuditEntry.entity == entity)
|
||||
if action:
|
||||
query = query.where(AuditEntry.action == action)
|
||||
if year_id:
|
||||
query = query.where(AuditEntry.year_id == year_id)
|
||||
if date_from:
|
||||
query = query.where(AuditEntry.at >= _date(date_from))
|
||||
if date_to:
|
||||
query = query.where(AuditEntry.at < _date(date_to, end=True))
|
||||
if q:
|
||||
like = f"%{q.strip().lower()}%"
|
||||
query = query.where(or_(func.lower(AuditEntry.summary).like(like),
|
||||
AuditEntry.user_id.in_(select(User.id).where(func.lower(User.name).like(like)))))
|
||||
return query
|
||||
|
||||
|
||||
def _rows(db: Session, entries: list[AuditEntry]) -> list[dict]:
|
||||
year_ids = {a.year_id for a in entries if a.year_id}
|
||||
labels = {y.id: f"{d.name} {y.year}" for y, d in db.execute(
|
||||
select(BudgetYear, Department).join(Department).where(BudgetYear.id.in_(year_ids)))} if year_ids else {}
|
||||
return [{"id": a.id, "at": a.at.isoformat(), "user": a.user.name if a.user else None, "user_id": a.user_id,
|
||||
"entity": a.entity, "entity_id": a.entity_id, "action": a.action, "summary": a.summary,
|
||||
"changes": a.changes, "year_id": a.year_id,
|
||||
"budget": labels.get(a.year_id) if a.year_id else None,
|
||||
"budget_exists": a.year_id in labels} for a in entries]
|
||||
|
||||
|
||||
@router.get("/activity")
|
||||
def activity(q: str | None = None, user_id: int | None = None, entity: str | None = None, action: str | None = None,
|
||||
year_id: int | None = None, date_from: str | None = None, date_to: str | None = None,
|
||||
limit: int = 100, offset: int = 0, db: Session = Depends(get_db), _: User = Depends(require("admin"))):
|
||||
query = _query(q, user_id, entity, action, year_id, date_from, date_to)
|
||||
total = db.scalar(select(func.count()).select_from(query.subquery()))
|
||||
entries = db.scalars(query.order_by(AuditEntry.at.desc(), AuditEntry.id.desc())
|
||||
.limit(min(max(limit, 1), 500)).offset(max(offset, 0))).all()
|
||||
kinds = db.execute(select(AuditEntry.entity, AuditEntry.action).distinct()).all()
|
||||
return {"total": total, "rows": _rows(db, entries),
|
||||
"entities": sorted({e for e, _ in kinds}), "actions": sorted({a for _, a in kinds})}
|
||||
|
||||
|
||||
@router.get("/activity.csv")
|
||||
def activity_csv(q: str | None = None, user_id: int | None = None, entity: str | None = None,
|
||||
action: str | None = None, year_id: int | None = None, date_from: str | None = None,
|
||||
date_to: str | None = None, db: Session = Depends(get_db), _: User = Depends(require("admin"))):
|
||||
entries = db.scalars(_query(q, user_id, entity, action, year_id, date_from, date_to)
|
||||
.order_by(AuditEntry.at.desc(), AuditEntry.id.desc())).all()
|
||||
out = io.StringIO()
|
||||
w = csv.writer(out, delimiter=";")
|
||||
w.writerow(["date", "user", "area", "action", "budget", "summary", "changes"])
|
||||
for r in _rows(db, entries):
|
||||
w.writerow([r["at"], r["user"] or "", r["entity"], r["action"], r["budget"] or "", r["summary"],
|
||||
"" if not r["changes"] else str(r["changes"])])
|
||||
return Response("" + out.getvalue(), media_type="text/csv; charset=utf-8",
|
||||
headers={"Content-Disposition": 'attachment; filename="activity_log.csv"'})
|
||||
Reference in New Issue
Block a user