Sep 16 - Optimize code, part 2
This commit is contained in:
+106
-24
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user