Sep 16 - Optimize code, part 2

This commit is contained in:
2026-09-16 14:31:41 -04:00
parent 2c5627354e
commit 13b56fb1d1
9 changed files with 632 additions and 140 deletions
+106 -24
View File
@@ -8,6 +8,7 @@ Routes: /dashboard, /project/<id>/qr-codes, /dashboard/search,
"""
from flask import abort, Blueprint, render_template, request, redirect, flash, session, jsonify, url_for
from datetime import datetime, timedelta, date, time
from sqlalchemy import or_
from extensions import db, logger_handler
from models.attendance import AttendanceData
@@ -15,7 +16,27 @@ from models.project import Project
from models.qrcode import QRCode
from models.user import User
from logger_handler import log_user_activity, log_database_operations
from utils.helpers import login_required
from utils.helpers import login_required, load_project_manager_scope
def _project_manager_qr_filter():
"""
(is_pm, qr_filter) for the session user — the QR codes a Project Manager may
see (§4). qr_filter is None for everyone else, and a never-true condition for
a PM with no assignments.
"""
is_pm, allowed_project_ids, allowed_location_names = load_project_manager_scope()
if not is_pm:
return False, None
scope = []
if allowed_project_ids:
scope.append(QRCode.project_id.in_(allowed_project_ids))
if allowed_location_names:
scope.append(QRCode.location.in_(allowed_location_names))
if not scope:
return True, QRCode.id.is_(None) # assigned nothing → sees nothing
return True, or_(*scope)
bp = Blueprint('dashboard', __name__)
@@ -34,7 +55,12 @@ def dashboard():
# Build QR codes query with filters
qr_query = QRCode.query
# Project Managers only see QR codes in their assigned projects/locations
is_pm, pm_qr_filter = _project_manager_qr_filter()
if pm_qr_filter is not None:
qr_query = qr_query.filter(pm_qr_filter)
# Apply name filter if provided
if search_name:
qr_query = qr_query.filter(QRCode.name.ilike(f'%{search_name}%'))
@@ -47,7 +73,16 @@ def dashboard():
# Execute query
qr_codes = qr_query.order_by(QRCode.created_date.desc()).all()
projects = Project.query.order_by(Project.name.asc()).all()
project_query = Project.query
if is_pm:
# Only the projects the PM is assigned to, plus those behind their locations
visible_project_ids = {qr.project_id for qr in qr_codes if qr.project_id}
_, allowed_project_ids, _ = load_project_manager_scope()
visible_project_ids.update(allowed_project_ids)
project_query = (project_query.filter(Project.id.in_(sorted(visible_project_ids)))
if visible_project_ids else project_query.filter(Project.id.is_(None)))
projects = project_query.order_by(Project.name.asc()).all()
# Log dashboard access with filter info
filter_info = []
@@ -87,7 +122,16 @@ def project_qr_codes(project_id):
project = db.session.get(Project, project_id)
if project is None:
abort(404)
# Project Managers may only open their own projects (§4)
is_pm, allowed_project_ids, _ = load_project_manager_scope()
if is_pm and project_id not in allowed_project_ids:
logger_handler.logger.warning(
f"Project Manager {session.get('username')} tried to open project {project_id}"
)
flash('You do not have permission to view that project.', 'error')
return redirect(url_for('dashboard.dashboard'))
# Get search parameters from URL
search_name = request.args.get('search_name', '').strip()
search_status = request.args.get('search_status', '').strip()
@@ -154,36 +198,59 @@ def search_qr_codes():
def dashboard_stats_api():
"""API endpoint for dashboard statistics"""
try:
# Project Manager scope (§4): every count below is restricted to the QR
# codes / locations they are assigned to.
is_pm, allowed_project_ids, allowed_location_names = load_project_manager_scope()
_, pm_qr_filter = _project_manager_qr_filter()
qr_base_query = QRCode.query.filter_by(active_status=True)
attendance_base_query = AttendanceData.query
location_base_query = db.session.query(AttendanceData.location_name)
project_base_query = Project.query.filter_by(active_status=True)
if is_pm:
qr_base_query = qr_base_query.filter(pm_qr_filter)
attendance_scope = []
if allowed_project_ids:
attendance_scope.append(AttendanceData.qr_code_id.in_(
db.session.query(QRCode.id).filter(QRCode.project_id.in_(allowed_project_ids))
))
project_base_query = project_base_query.filter(Project.id.in_(allowed_project_ids))
else:
project_base_query = project_base_query.filter(Project.id.is_(None))
if allowed_location_names:
attendance_scope.append(AttendanceData.location_name.in_(allowed_location_names))
attendance_condition = or_(*attendance_scope) if attendance_scope else AttendanceData.id.is_(None)
attendance_base_query = attendance_base_query.filter(attendance_condition)
location_base_query = location_base_query.filter(attendance_condition)
# Get current stats
total_qr_codes = QRCode.query.filter_by(active_status=True).count()
# Today's check-ins
today = datetime.utcnow().date()
today_checkins = AttendanceData.query.filter(
total_qr_codes = qr_base_query.count()
# Today's check-ins — local date, matching how check-ins are stored
today = datetime.now().date()
today_checkins = attendance_base_query.filter(
AttendanceData.check_in_date == today
).count()
# Active projects
active_projects = Project.query.filter_by(active_status=True).count()
active_projects = project_base_query.count()
# Unique locations
unique_locations = db.session.query(
AttendanceData.location_name
).distinct().count()
unique_locations = location_base_query.distinct().count()
# Calculate trends (compared to last month)
last_month = datetime.utcnow() - timedelta(days=30)
last_month = datetime.now() - timedelta(days=30)
# QR codes trend
old_qr_count = QRCode.query.filter(
QRCode.created_date <= last_month,
QRCode.active_status == True
old_qr_count = qr_base_query.filter(
QRCode.created_date <= last_month
).count()
qr_change = ((total_qr_codes - old_qr_count) / max(old_qr_count, 1)) * 100
# Check-ins trend (yesterday)
yesterday = today - timedelta(days=1)
yesterday_checkins = AttendanceData.query.filter(
yesterday_checkins = attendance_base_query.filter(
AttendanceData.check_in_date == yesterday
).count()
checkin_change = ((today_checkins - yesterday_checkins) / max(yesterday_checkins, 1)) * 100
@@ -214,12 +281,27 @@ def dashboard_realtime_api():
"""API endpoint for real-time dashboard data"""
try:
# Get recent activity (last 10 check-ins)
recent_activity = db.session.query(
recent_query = db.session.query(
AttendanceData.employee_id,
AttendanceData.location_name,
AttendanceData.check_in_time,
AttendanceData.check_in_date
).order_by(
)
# Project Managers only see activity at their own projects/locations (§4)
is_pm, allowed_project_ids, allowed_location_names = load_project_manager_scope()
if is_pm:
recent_scope = []
if allowed_project_ids:
recent_scope.append(AttendanceData.qr_code_id.in_(
db.session.query(QRCode.id).filter(QRCode.project_id.in_(allowed_project_ids))
))
if allowed_location_names:
recent_scope.append(AttendanceData.location_name.in_(allowed_location_names))
recent_query = recent_query.filter(or_(*recent_scope) if recent_scope
else AttendanceData.id.is_(None))
recent_activity = recent_query.order_by(
AttendanceData.check_in_date.desc(),
AttendanceData.check_in_time.desc()
).limit(10).all()