Squashed commit of the following:
commit 1eeea9f83ad9230a5c1f7a75662770eaab0df837 Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 21:15:41 2025 +0200 Disable resuming of old time entries. commit 3e3ec2f01cb7943622b819a19179388078ae1315 Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 20:59:19 2025 +0200 Refactor db migrations. commit 15a51a569da36c6b7c9e01ab17b6fdbdee6ad994 Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 19:58:04 2025 +0200 Apply new style for Time Tracking view. commit 77e5278b303e060d2b03853b06277f8aa567ae68 Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 18:06:04 2025 +0200 Allow direct registrations as a Company. commit 188a8772757cbef374243d3a5f29e4440ddecabe Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 18:04:45 2025 +0200 Add email invitation feature. commit d9ebaa02aa01b518960a20dccdd5a327d82f30c6 Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 17:12:32 2025 +0200 Apply common style for Company, User, Team management pages. commit 81149caf4d8fc6317e2ab1b4f022b32fc5aa6d22 Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 16:44:32 2025 +0200 Move export functions to own module. commit 1a26e19338e73f8849c671471dd15cc3c1b1fe82 Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 15:51:15 2025 +0200 Split up models.py. commit 61f1ccd10f721b0ff4dc1eccf30c7a1ee13f204d Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 12:05:28 2025 +0200 Move utility function into own modules. commit 84b341ed35e2c5387819a8b9f9d41eca900ae79f Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 11:44:24 2025 +0200 Refactor auth functions use. commit 923e311e3da5b26d85845c2832b73b7b17c48adb Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 11:35:52 2025 +0200 Refactor route nameing and fix bugs along the way. commit f0a5c4419c340e62a2615c60b2a9de28204d2995 Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 10:34:33 2025 +0200 Fix URL endpoints in announcement template. commit b74d74542a1c8dc350749e4788a9464d067a88b5 Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 09:25:53 2025 +0200 Move announcements to own module. commit 9563a28021ac46c82c04fe4649b394dbf96f92c7 Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 09:16:30 2025 +0200 Combine Company view and edit templates. commit 6687c373e681d54e4deab6b2582fed5cea9aadf6 Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 08:17:42 2025 +0200 Move Users, Company and System Administration to own modules. commit 8b7894a2e3eb84bb059f546648b6b9536fea724e Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 07:40:57 2025 +0200 Move Teams and Projects to own modules. commit d11bf059d99839ecf1f5d7020b8c8c8a2454c00b Author: Jens Luedicke <jens@luedicke.me> Date: Mon Jul 7 07:09:33 2025 +0200 Move Tasks and Sprints to own modules.
This commit is contained in:
511
routes/system_admin.py
Normal file
511
routes/system_admin.py
Normal file
@@ -0,0 +1,511 @@
|
||||
"""
|
||||
System Administrator routes
|
||||
"""
|
||||
|
||||
from flask import Blueprint, render_template, request, redirect, url_for, flash, g, current_app
|
||||
from models import (db, Company, User, Role, Team, Project, TimeEntry, SystemSettings,
|
||||
SystemEvent, BrandingSettings, Task, SubTask, TaskDependency, Sprint,
|
||||
Comment, UserPreferences, UserDashboard, WorkConfig, CompanySettings,
|
||||
CompanyWorkConfig, ProjectCategory)
|
||||
from routes.auth import system_admin_required
|
||||
from flask import session
|
||||
from sqlalchemy import func
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
import os
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
system_admin_bp = Blueprint('system_admin', __name__, url_prefix='/system-admin')
|
||||
|
||||
|
||||
@system_admin_bp.route('/dashboard')
|
||||
@system_admin_required
|
||||
def system_admin_dashboard():
|
||||
"""System Administrator Dashboard - view all data across companies"""
|
||||
|
||||
# Global statistics
|
||||
total_companies = Company.query.count()
|
||||
total_users = User.query.count()
|
||||
total_teams = Team.query.count()
|
||||
total_projects = Project.query.count()
|
||||
total_time_entries = TimeEntry.query.count()
|
||||
|
||||
# System admin count
|
||||
system_admins = User.query.filter_by(role=Role.SYSTEM_ADMIN).count()
|
||||
regular_admins = User.query.filter_by(role=Role.ADMIN).count()
|
||||
|
||||
# Recent activity (last 7 days)
|
||||
week_ago = datetime.now() - timedelta(days=7)
|
||||
|
||||
recent_users = User.query.filter(User.created_at >= week_ago).count()
|
||||
recent_companies = Company.query.filter(Company.created_at >= week_ago).count()
|
||||
recent_time_entries = TimeEntry.query.filter(TimeEntry.arrival_time >= week_ago).count()
|
||||
|
||||
# Top companies by user count
|
||||
top_companies = db.session.query(
|
||||
Company.name,
|
||||
Company.id,
|
||||
db.func.count(User.id).label('user_count')
|
||||
).join(User).group_by(Company.id).order_by(db.func.count(User.id).desc()).limit(5).all()
|
||||
|
||||
# Recent companies
|
||||
recent_companies_list = Company.query.order_by(Company.created_at.desc()).limit(5).all()
|
||||
|
||||
# System health checks
|
||||
orphaned_users = User.query.filter_by(company_id=None).count()
|
||||
orphaned_time_entries = TimeEntry.query.filter_by(user_id=None).count()
|
||||
blocked_users = User.query.filter_by(is_blocked=True).count()
|
||||
|
||||
return render_template('system_admin_dashboard.html',
|
||||
title='System Administrator Dashboard',
|
||||
total_companies=total_companies,
|
||||
total_users=total_users,
|
||||
total_teams=total_teams,
|
||||
total_projects=total_projects,
|
||||
total_time_entries=total_time_entries,
|
||||
system_admins=system_admins,
|
||||
regular_admins=regular_admins,
|
||||
recent_users=recent_users,
|
||||
recent_companies=recent_companies,
|
||||
recent_time_entries=recent_time_entries,
|
||||
top_companies=top_companies,
|
||||
recent_companies_list=recent_companies_list,
|
||||
orphaned_users=orphaned_users,
|
||||
orphaned_time_entries=orphaned_time_entries,
|
||||
blocked_users=blocked_users)
|
||||
|
||||
|
||||
@system_admin_bp.route('/companies')
|
||||
@system_admin_required
|
||||
def system_admin_companies():
|
||||
"""System admin view of all companies"""
|
||||
# Get filter parameters
|
||||
search_query = request.args.get('search', '')
|
||||
|
||||
# Base query
|
||||
query = Company.query
|
||||
|
||||
# Apply search filter
|
||||
if search_query:
|
||||
query = query.filter(
|
||||
db.or_(
|
||||
Company.name.ilike(f'%{search_query}%'),
|
||||
Company.slug.ilike(f'%{search_query}%')
|
||||
)
|
||||
)
|
||||
|
||||
# Get all companies
|
||||
companies = query.order_by(Company.created_at.desc()).all()
|
||||
|
||||
# Create a paginated response object
|
||||
from flask_sqlalchemy import Pagination
|
||||
page = request.args.get('page', 1, type=int)
|
||||
per_page = 20
|
||||
|
||||
# Paginate companies
|
||||
companies_paginated = query.paginate(page=page, per_page=per_page, error_out=False)
|
||||
|
||||
# Calculate statistics for each company
|
||||
company_stats = {}
|
||||
for company in companies_paginated.items:
|
||||
company_stats[company.id] = {
|
||||
'user_count': User.query.filter_by(company_id=company.id).count(),
|
||||
'admin_count': User.query.filter_by(company_id=company.id, role=Role.ADMIN).count(),
|
||||
'team_count': Team.query.filter_by(company_id=company.id).count(),
|
||||
'project_count': Project.query.filter_by(company_id=company.id).count(),
|
||||
'active_projects': Project.query.filter_by(company_id=company.id, is_active=True).count(),
|
||||
}
|
||||
|
||||
return render_template('system_admin_companies.html',
|
||||
title='System Admin - Companies',
|
||||
companies=companies_paginated,
|
||||
company_stats=company_stats,
|
||||
search_query=search_query)
|
||||
|
||||
|
||||
@system_admin_bp.route('/companies/<int:company_id>')
|
||||
@system_admin_required
|
||||
def system_admin_company_detail(company_id):
|
||||
"""System admin detailed view of a specific company"""
|
||||
company = Company.query.get_or_404(company_id)
|
||||
|
||||
# Get recent time entries count
|
||||
week_ago = datetime.now() - timedelta(days=7)
|
||||
recent_time_entries = TimeEntry.query.join(User).filter(
|
||||
User.company_id == company.id,
|
||||
TimeEntry.arrival_time >= week_ago
|
||||
).count()
|
||||
|
||||
# Get role distribution
|
||||
role_counts = {}
|
||||
for role in Role:
|
||||
count = User.query.filter_by(company_id=company.id, role=role).count()
|
||||
if count > 0:
|
||||
role_counts[role.value] = count
|
||||
|
||||
# Get users list
|
||||
users = User.query.filter_by(company_id=company.id).order_by(User.created_at.desc()).all()
|
||||
|
||||
# Get teams list
|
||||
teams = Team.query.filter_by(company_id=company.id).all()
|
||||
|
||||
# Get projects list
|
||||
projects = Project.query.filter_by(company_id=company.id).order_by(Project.created_at.desc()).all()
|
||||
|
||||
return render_template('system_admin_company_detail.html',
|
||||
title=f'Company Details - {company.name}',
|
||||
company=company,
|
||||
users=users,
|
||||
teams=teams,
|
||||
projects=projects,
|
||||
recent_time_entries=recent_time_entries,
|
||||
role_counts=role_counts)
|
||||
|
||||
|
||||
@system_admin_bp.route('/companies/<int:company_id>/delete', methods=['POST'])
|
||||
@system_admin_required
|
||||
def delete_company(company_id):
|
||||
"""System Admin: Delete a company and all its data"""
|
||||
company = Company.query.get_or_404(company_id)
|
||||
company_name = company.name
|
||||
|
||||
try:
|
||||
# Delete all related data in the correct order to avoid foreign key constraints
|
||||
|
||||
# Delete comments (must be before tasks)
|
||||
Comment.query.filter(Comment.task_id.in_(
|
||||
db.session.query(Task.id).join(Project).filter(Project.company_id == company_id)
|
||||
)).delete(synchronize_session=False)
|
||||
|
||||
# Delete subtasks
|
||||
SubTask.query.filter(SubTask.task_id.in_(
|
||||
db.session.query(Task.id).join(Project).filter(Project.company_id == company_id)
|
||||
)).delete(synchronize_session=False)
|
||||
|
||||
# Delete task dependencies
|
||||
TaskDependency.query.filter(
|
||||
TaskDependency.blocked_task_id.in_(
|
||||
db.session.query(Task.id).join(Project).filter(Project.company_id == company_id)
|
||||
) | TaskDependency.blocking_task_id.in_(
|
||||
db.session.query(Task.id).join(Project).filter(Project.company_id == company_id)
|
||||
)
|
||||
).delete(synchronize_session=False)
|
||||
|
||||
# Delete tasks
|
||||
Task.query.filter(Task.project_id.in_(
|
||||
db.session.query(Project.id).filter(Project.company_id == company_id)
|
||||
)).delete(synchronize_session=False)
|
||||
|
||||
# Delete sprints
|
||||
Sprint.query.filter(Sprint.project_id.in_(
|
||||
db.session.query(Project.id).filter(Project.company_id == company_id)
|
||||
)).delete(synchronize_session=False)
|
||||
|
||||
# Delete time entries
|
||||
TimeEntry.query.filter(TimeEntry.user_id.in_(
|
||||
db.session.query(User.id).filter(User.company_id == company_id)
|
||||
)).delete(synchronize_session=False)
|
||||
|
||||
# Delete projects
|
||||
Project.query.filter_by(company_id=company_id).delete()
|
||||
|
||||
# Delete teams
|
||||
Team.query.filter_by(company_id=company_id).delete()
|
||||
|
||||
# Delete user preferences, dashboards, and work configs
|
||||
UserPreferences.query.filter(UserPreferences.user_id.in_(
|
||||
db.session.query(User.id).filter(User.company_id == company_id)
|
||||
)).delete(synchronize_session=False)
|
||||
|
||||
UserDashboard.query.filter(UserDashboard.user_id.in_(
|
||||
db.session.query(User.id).filter(User.company_id == company_id)
|
||||
)).delete(synchronize_session=False)
|
||||
|
||||
WorkConfig.query.filter(WorkConfig.user_id.in_(
|
||||
db.session.query(User.id).filter(User.company_id == company_id)
|
||||
)).delete(synchronize_session=False)
|
||||
|
||||
# Delete users
|
||||
User.query.filter_by(company_id=company_id).delete()
|
||||
|
||||
# Delete company settings and work config
|
||||
CompanySettings.query.filter_by(company_id=company_id).delete()
|
||||
CompanyWorkConfig.query.filter_by(company_id=company_id).delete()
|
||||
|
||||
# Delete project categories
|
||||
ProjectCategory.query.filter_by(company_id=company_id).delete()
|
||||
|
||||
# Finally, delete the company
|
||||
db.session.delete(company)
|
||||
db.session.commit()
|
||||
|
||||
flash(f'Company "{company_name}" and all its data have been permanently deleted.', 'success')
|
||||
logger.info(f"System admin {g.user.username} deleted company {company_name} (ID: {company_id})")
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
logger.error(f"Error deleting company {company_id}: {str(e)}")
|
||||
flash(f'Error deleting company: {str(e)}', 'error')
|
||||
return redirect(url_for('system_admin.system_admin_company_detail', company_id=company_id))
|
||||
|
||||
return redirect(url_for('system_admin.system_admin_companies'))
|
||||
|
||||
|
||||
@system_admin_bp.route('/time-entries')
|
||||
@system_admin_required
|
||||
def system_admin_time_entries():
|
||||
"""System Admin: View time entries across all companies"""
|
||||
page = request.args.get('page', 1, type=int)
|
||||
company_filter = request.args.get('company', '')
|
||||
per_page = 50
|
||||
|
||||
# Build query properly with explicit joins
|
||||
query = db.session.query(
|
||||
TimeEntry,
|
||||
User.username,
|
||||
Company.name.label('company_name'),
|
||||
Project.name.label('project_name')
|
||||
).join(
|
||||
User, TimeEntry.user_id == User.id
|
||||
).join(
|
||||
Company, User.company_id == Company.id
|
||||
).outerjoin(
|
||||
Project, TimeEntry.project_id == Project.id
|
||||
)
|
||||
|
||||
# Apply company filter
|
||||
if company_filter:
|
||||
query = query.filter(Company.id == company_filter)
|
||||
|
||||
# Order by arrival time (newest first)
|
||||
query = query.order_by(TimeEntry.arrival_time.desc())
|
||||
|
||||
# Paginate
|
||||
entries = query.paginate(page=page, per_page=per_page, error_out=False)
|
||||
|
||||
# Get companies for filter dropdown
|
||||
companies = Company.query.order_by(Company.name).all()
|
||||
|
||||
# Get today's date for the template
|
||||
today = datetime.now().date()
|
||||
|
||||
return render_template('system_admin_time_entries.html',
|
||||
title='System Admin - Time Entries',
|
||||
entries=entries,
|
||||
companies=companies,
|
||||
current_company=company_filter,
|
||||
today=today)
|
||||
|
||||
|
||||
@system_admin_bp.route('/settings', methods=['GET', 'POST'])
|
||||
@system_admin_required
|
||||
def system_admin_settings():
|
||||
"""System Admin: Global system settings"""
|
||||
if request.method == 'POST':
|
||||
# Update system settings
|
||||
registration_enabled = request.form.get('registration_enabled') == 'on'
|
||||
email_verification = request.form.get('email_verification_required') == 'on'
|
||||
tracking_script_enabled = request.form.get('tracking_script_enabled') == 'on'
|
||||
tracking_script_code = request.form.get('tracking_script_code', '')
|
||||
|
||||
# Update or create settings
|
||||
reg_setting = SystemSettings.query.filter_by(key='registration_enabled').first()
|
||||
if reg_setting:
|
||||
reg_setting.value = 'true' if registration_enabled else 'false'
|
||||
else:
|
||||
reg_setting = SystemSettings(
|
||||
key='registration_enabled',
|
||||
value='true' if registration_enabled else 'false',
|
||||
description='Controls whether new user registration is allowed'
|
||||
)
|
||||
db.session.add(reg_setting)
|
||||
|
||||
email_setting = SystemSettings.query.filter_by(key='email_verification_required').first()
|
||||
if email_setting:
|
||||
email_setting.value = 'true' if email_verification else 'false'
|
||||
else:
|
||||
email_setting = SystemSettings(
|
||||
key='email_verification_required',
|
||||
value='true' if email_verification else 'false',
|
||||
description='Controls whether email verification is required for new accounts'
|
||||
)
|
||||
db.session.add(email_setting)
|
||||
|
||||
tracking_enabled_setting = SystemSettings.query.filter_by(key='tracking_script_enabled').first()
|
||||
if tracking_enabled_setting:
|
||||
tracking_enabled_setting.value = 'true' if tracking_script_enabled else 'false'
|
||||
else:
|
||||
tracking_enabled_setting = SystemSettings(
|
||||
key='tracking_script_enabled',
|
||||
value='true' if tracking_script_enabled else 'false',
|
||||
description='Controls whether custom tracking script is enabled'
|
||||
)
|
||||
db.session.add(tracking_enabled_setting)
|
||||
|
||||
tracking_code_setting = SystemSettings.query.filter_by(key='tracking_script_code').first()
|
||||
if tracking_code_setting:
|
||||
tracking_code_setting.value = tracking_script_code
|
||||
else:
|
||||
tracking_code_setting = SystemSettings(
|
||||
key='tracking_script_code',
|
||||
value=tracking_script_code,
|
||||
description='Custom tracking script code (HTML/JavaScript)'
|
||||
)
|
||||
db.session.add(tracking_code_setting)
|
||||
|
||||
db.session.commit()
|
||||
flash('System settings updated successfully.', 'success')
|
||||
return redirect(url_for('system_admin.system_admin_settings'))
|
||||
|
||||
# Get current settings
|
||||
settings = {}
|
||||
all_settings = SystemSettings.query.all()
|
||||
for setting in all_settings:
|
||||
if setting.key == 'registration_enabled':
|
||||
settings['registration_enabled'] = setting.value == 'true'
|
||||
elif setting.key == 'email_verification_required':
|
||||
settings['email_verification_required'] = setting.value == 'true'
|
||||
elif setting.key == 'tracking_script_enabled':
|
||||
settings['tracking_script_enabled'] = setting.value == 'true'
|
||||
elif setting.key == 'tracking_script_code':
|
||||
settings['tracking_script_code'] = setting.value
|
||||
|
||||
# System statistics
|
||||
total_companies = Company.query.count()
|
||||
total_users = User.query.count()
|
||||
total_system_admins = User.query.filter_by(role=Role.SYSTEM_ADMIN).count()
|
||||
|
||||
return render_template('system_admin_settings.html',
|
||||
title='System Administrator Settings',
|
||||
settings=settings,
|
||||
total_companies=total_companies,
|
||||
total_users=total_users,
|
||||
total_system_admins=total_system_admins)
|
||||
|
||||
|
||||
@system_admin_bp.route('/health')
|
||||
@system_admin_required
|
||||
def system_admin_health():
|
||||
"""System Admin: System health check and event log"""
|
||||
# Get system health summary
|
||||
health_summary = SystemEvent.get_system_health_summary()
|
||||
|
||||
# Get recent events (last 7 days)
|
||||
recent_events = SystemEvent.get_recent_events(days=7, limit=100)
|
||||
|
||||
# Get events by severity for quick stats
|
||||
errors = SystemEvent.get_events_by_severity('error', days=7, limit=20)
|
||||
warnings = SystemEvent.get_events_by_severity('warning', days=7, limit=20)
|
||||
|
||||
# System metrics
|
||||
now = datetime.now()
|
||||
|
||||
# Database connection test
|
||||
db_healthy = True
|
||||
db_error = None
|
||||
try:
|
||||
db.session.execute('SELECT 1')
|
||||
except Exception as e:
|
||||
db_healthy = False
|
||||
db_error = str(e)
|
||||
SystemEvent.log_event(
|
||||
'database_check_failed',
|
||||
f'Database health check failed: {str(e)}',
|
||||
'system',
|
||||
'error'
|
||||
)
|
||||
|
||||
# Application uptime (approximate based on first event)
|
||||
first_event = SystemEvent.query.order_by(SystemEvent.timestamp.asc()).first()
|
||||
uptime_start = first_event.timestamp if first_event else now
|
||||
uptime_duration = now - uptime_start
|
||||
|
||||
# Recent activity stats
|
||||
today = now.date()
|
||||
today_events = SystemEvent.query.filter(
|
||||
func.date(SystemEvent.timestamp) == today
|
||||
).count()
|
||||
|
||||
# Log the health check
|
||||
SystemEvent.log_event(
|
||||
'system_health_check',
|
||||
f'System health check performed by {session.get("username", "unknown")}',
|
||||
'system',
|
||||
'info',
|
||||
user_id=session.get('user_id'),
|
||||
ip_address=request.remote_addr,
|
||||
user_agent=request.headers.get('User-Agent')
|
||||
)
|
||||
|
||||
return render_template('system_admin_health.html',
|
||||
title='System Health Check',
|
||||
health_summary=health_summary,
|
||||
recent_events=recent_events,
|
||||
errors=errors,
|
||||
warnings=warnings,
|
||||
db_healthy=db_healthy,
|
||||
db_error=db_error,
|
||||
uptime_duration=uptime_duration,
|
||||
today_events=today_events)
|
||||
|
||||
|
||||
@system_admin_bp.route('/branding', methods=['GET', 'POST'])
|
||||
@system_admin_required
|
||||
def branding():
|
||||
"""System Admin: Branding settings"""
|
||||
if request.method == 'POST':
|
||||
branding = BrandingSettings.get_current()
|
||||
|
||||
# Handle form data
|
||||
branding.app_name = request.form.get('app_name', g.branding.app_name).strip()
|
||||
branding.logo_alt_text = request.form.get('logo_alt_text', '').strip()
|
||||
branding.primary_color = request.form.get('primary_color', '#007bff').strip()
|
||||
|
||||
# Handle imprint settings
|
||||
branding.imprint_enabled = 'imprint_enabled' in request.form
|
||||
branding.imprint_title = request.form.get('imprint_title', 'Imprint').strip()
|
||||
branding.imprint_content = request.form.get('imprint_content', '').strip()
|
||||
|
||||
branding.updated_by_id = g.user.id
|
||||
|
||||
# Handle logo upload
|
||||
if 'logo_file' in request.files:
|
||||
logo_file = request.files['logo_file']
|
||||
if logo_file and logo_file.filename:
|
||||
# Create uploads directory if it doesn't exist
|
||||
upload_dir = os.path.join(current_app.static_folder, 'uploads', 'branding')
|
||||
os.makedirs(upload_dir, exist_ok=True)
|
||||
|
||||
# Save the file with a timestamp to avoid conflicts
|
||||
import time
|
||||
filename = f"logo_{int(time.time())}_{logo_file.filename}"
|
||||
logo_path = os.path.join(upload_dir, filename)
|
||||
logo_file.save(logo_path)
|
||||
branding.logo_filename = filename
|
||||
|
||||
# Handle favicon upload
|
||||
if 'favicon_file' in request.files:
|
||||
favicon_file = request.files['favicon_file']
|
||||
if favicon_file and favicon_file.filename:
|
||||
# Create uploads directory if it doesn't exist
|
||||
upload_dir = os.path.join(current_app.static_folder, 'uploads', 'branding')
|
||||
os.makedirs(upload_dir, exist_ok=True)
|
||||
|
||||
# Save the file with a timestamp to avoid conflicts
|
||||
import time
|
||||
filename = f"favicon_{int(time.time())}_{favicon_file.filename}"
|
||||
favicon_path = os.path.join(upload_dir, filename)
|
||||
favicon_file.save(favicon_path)
|
||||
branding.favicon_filename = filename
|
||||
|
||||
db.session.commit()
|
||||
flash('Branding settings updated successfully.', 'success')
|
||||
return redirect(url_for('system_admin.branding'))
|
||||
|
||||
# Get current branding settings
|
||||
branding = BrandingSettings.get_current()
|
||||
|
||||
return render_template('system_admin_branding.html',
|
||||
title='System Administrator - Branding Settings',
|
||||
branding=branding)
|
||||
Reference in New Issue
Block a user