- Add profile_image field to Profile model with default value - Update profile edit route to handle profile image file uploads - Add password change route with validation in auth module - Create change password template with form - Update profile template to include image upload with preview - Add password change link to admin sidebar - Update homepage to use dynamic profile image from database
352 lines
13 KiB
Python
352 lines
13 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright Hersel Giannella
|
|
|
|
"""
|
|
Admin dashboard routes for managing portfolio content
|
|
"""
|
|
|
|
from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify, current_app
|
|
from flask_login import login_required, current_user
|
|
from werkzeug.utils import secure_filename
|
|
from models import db, Profile, Skill, Project, ProjectTag, SocialLink
|
|
import os
|
|
|
|
route_admin = Blueprint('admin', __name__, url_prefix='/admin')
|
|
|
|
|
|
def allowed_file(filename):
|
|
"""Check if file extension is allowed"""
|
|
return '.' in filename and \
|
|
filename.rsplit('.', 1)[1].lower() in current_app.config['ALLOWED_EXTENSIONS']
|
|
|
|
|
|
def save_uploaded_file(file):
|
|
"""Save uploaded file and return the relative path"""
|
|
if file and allowed_file(file.filename):
|
|
filename = secure_filename(file.filename)
|
|
# Add timestamp to avoid collisions
|
|
from datetime import datetime
|
|
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S_')
|
|
filename = timestamp + filename
|
|
|
|
# Create upload folder if it doesn't exist
|
|
upload_folder = current_app.config['UPLOAD_FOLDER']
|
|
os.makedirs(upload_folder, exist_ok=True)
|
|
|
|
filepath = os.path.join(upload_folder, filename)
|
|
file.save(filepath)
|
|
|
|
# Return relative path from static folder
|
|
return f"img/{filename}"
|
|
return None
|
|
|
|
|
|
@route_admin.route('/')
|
|
@login_required
|
|
def dashboard():
|
|
"""Admin dashboard home"""
|
|
# Statistiche
|
|
stats = {
|
|
'projects': Project.query.count(),
|
|
'skills': Skill.query.count(),
|
|
'social_links': SocialLink.query.count(),
|
|
'published_projects': Project.query.filter_by(is_published=True).count()
|
|
}
|
|
return render_template('admin/dashboard.html', stats=stats)
|
|
|
|
|
|
# ============================================================================
|
|
# PROFILE MANAGEMENT
|
|
# ============================================================================
|
|
|
|
@route_admin.route('/profile')
|
|
@login_required
|
|
def profile_manage():
|
|
"""Manage profile information"""
|
|
profile = Profile.query.first()
|
|
return render_template('admin/profile.html', profile=profile)
|
|
|
|
|
|
@route_admin.route('/profile/edit', methods=['POST'])
|
|
@login_required
|
|
def profile_edit():
|
|
"""Edit profile information"""
|
|
profile = Profile.query.first()
|
|
if not profile:
|
|
flash('Profilo non trovato.', 'danger')
|
|
return redirect(url_for('admin.profile_manage'))
|
|
|
|
# Handle profile image upload
|
|
profile_image_url = request.form.get('profile_image', profile.profile_image)
|
|
if 'profile_image_file' in request.files:
|
|
file = request.files['profile_image_file']
|
|
if file.filename:
|
|
uploaded_path = save_uploaded_file(file)
|
|
if uploaded_path:
|
|
profile_image_url = uploaded_path
|
|
else:
|
|
flash('Formato immagine non valido. Usa: png, jpg, jpeg, gif, webp', 'danger')
|
|
return redirect(url_for('admin.profile_manage'))
|
|
|
|
profile.title = request.form.get('title', profile.title)
|
|
profile.lead_text = request.form.get('lead_text', profile.lead_text)
|
|
profile.description_1 = request.form.get('description_1', profile.description_1)
|
|
profile.description_2 = request.form.get('description_2', profile.description_2)
|
|
profile.years_experience = int(request.form.get('years_experience', profile.years_experience))
|
|
profile.cv_url = request.form.get('cv_url', profile.cv_url)
|
|
profile.profile_image = profile_image_url
|
|
|
|
db.session.commit()
|
|
flash('Profilo aggiornato con successo!', 'success')
|
|
return redirect(url_for('admin.profile_manage'))
|
|
|
|
|
|
# ============================================================================
|
|
# SKILLS MANAGEMENT
|
|
# ============================================================================
|
|
|
|
@route_admin.route('/skills')
|
|
@login_required
|
|
def skills_manage():
|
|
"""Manage skills"""
|
|
skills = Skill.query.order_by(Skill.display_order).all()
|
|
return render_template('admin/skills.html', skills=skills)
|
|
|
|
|
|
@route_admin.route('/skills/add', methods=['POST'])
|
|
@login_required
|
|
def skill_add():
|
|
"""Add new skill"""
|
|
skill = Skill(
|
|
name=request.form.get('name'),
|
|
icon_class=request.form.get('icon_class'),
|
|
category=request.form.get('category'),
|
|
display_order=int(request.form.get('display_order', 0)),
|
|
is_active=request.form.get('is_active') == 'on'
|
|
)
|
|
db.session.add(skill)
|
|
db.session.commit()
|
|
flash('Skill aggiunta con successo!', 'success')
|
|
return redirect(url_for('admin.skills_manage'))
|
|
|
|
|
|
@route_admin.route('/skills/<int:skill_id>/edit', methods=['POST'])
|
|
@login_required
|
|
def skill_edit(skill_id):
|
|
"""Edit skill"""
|
|
skill = Skill.query.get_or_404(skill_id)
|
|
skill.name = request.form.get('name', skill.name)
|
|
skill.icon_class = request.form.get('icon_class', skill.icon_class)
|
|
skill.category = request.form.get('category', skill.category)
|
|
skill.display_order = int(request.form.get('display_order', skill.display_order))
|
|
skill.is_active = request.form.get('is_active') == 'on'
|
|
|
|
db.session.commit()
|
|
flash('Skill aggiornata con successo!', 'success')
|
|
return redirect(url_for('admin.skills_manage'))
|
|
|
|
|
|
@route_admin.route('/skills/<int:skill_id>/delete', methods=['POST'])
|
|
@login_required
|
|
def skill_delete(skill_id):
|
|
"""Delete skill"""
|
|
skill = Skill.query.get_or_404(skill_id)
|
|
db.session.delete(skill)
|
|
db.session.commit()
|
|
flash('Skill eliminata con successo!', 'success')
|
|
return redirect(url_for('admin.skills_manage'))
|
|
|
|
|
|
# ============================================================================
|
|
# PROJECTS MANAGEMENT
|
|
# ============================================================================
|
|
|
|
@route_admin.route('/projects')
|
|
@login_required
|
|
def projects_manage():
|
|
"""Manage projects"""
|
|
projects = Project.query.order_by(Project.display_order).all()
|
|
return render_template('admin/projects.html', projects=projects)
|
|
|
|
|
|
@route_admin.route('/projects/add', methods=['GET', 'POST'])
|
|
@login_required
|
|
def project_add():
|
|
"""Add new project"""
|
|
if request.method == 'POST':
|
|
# Handle image upload
|
|
image_url = request.form.get('image_url') # Manual URL input
|
|
if 'image_file' in request.files:
|
|
file = request.files['image_file']
|
|
if file.filename: # Se è stato selezionato un file
|
|
uploaded_path = save_uploaded_file(file)
|
|
if uploaded_path:
|
|
image_url = uploaded_path
|
|
else:
|
|
flash('Formato immagine non valido. Usa: png, jpg, jpeg, gif, webp', 'danger')
|
|
return redirect(url_for('admin.project_add'))
|
|
|
|
project = Project(
|
|
title=request.form.get('title'),
|
|
description=request.form.get('description'),
|
|
image_url=image_url,
|
|
demo_url=request.form.get('demo_url'),
|
|
github_url=request.form.get('github_url'),
|
|
display_order=int(request.form.get('display_order', 0)),
|
|
animation_delay=request.form.get('animation_delay', '0s'),
|
|
is_published=request.form.get('is_published') == 'on'
|
|
)
|
|
db.session.add(project)
|
|
db.session.flush()
|
|
|
|
# Aggiungi tags
|
|
tags_input = request.form.get('tags', '')
|
|
if tags_input:
|
|
tags_list = [tag.strip() for tag in tags_input.split(',') if tag.strip()]
|
|
for idx, tag_name in enumerate(tags_list):
|
|
# Estrai colore se specificato (formato: "Python:bg-primary")
|
|
if ':' in tag_name:
|
|
tag_name, color = tag_name.split(':', 1)
|
|
else:
|
|
color = 'bg-primary'
|
|
|
|
tag = ProjectTag(
|
|
project_id=project.id,
|
|
name=tag_name.strip(),
|
|
color_class=color.strip(),
|
|
display_order=idx
|
|
)
|
|
db.session.add(tag)
|
|
|
|
db.session.commit()
|
|
flash('Progetto aggiunto con successo!', 'success')
|
|
return redirect(url_for('admin.projects_manage'))
|
|
|
|
return render_template('admin/project_form.html', project=None)
|
|
|
|
|
|
@route_admin.route('/projects/<int:project_id>/edit', methods=['GET', 'POST'])
|
|
@login_required
|
|
def project_edit(project_id):
|
|
"""Edit project"""
|
|
project = Project.query.get_or_404(project_id)
|
|
|
|
if request.method == 'POST':
|
|
# Handle image upload
|
|
image_url = request.form.get('image_url', project.image_url)
|
|
if 'image_file' in request.files:
|
|
file = request.files['image_file']
|
|
if file.filename: # Se è stato selezionato un file
|
|
uploaded_path = save_uploaded_file(file)
|
|
if uploaded_path:
|
|
image_url = uploaded_path
|
|
else:
|
|
flash('Formato immagine non valido. Usa: png, jpg, jpeg, gif, webp', 'danger')
|
|
return redirect(url_for('admin.project_edit', project_id=project_id))
|
|
|
|
project.title = request.form.get('title', project.title)
|
|
project.description = request.form.get('description', project.description)
|
|
project.image_url = image_url
|
|
project.demo_url = request.form.get('demo_url', project.demo_url)
|
|
project.github_url = request.form.get('github_url', project.github_url)
|
|
project.display_order = int(request.form.get('display_order', project.display_order))
|
|
project.animation_delay = request.form.get('animation_delay', project.animation_delay)
|
|
project.is_published = request.form.get('is_published') == 'on'
|
|
|
|
# Aggiorna tags
|
|
ProjectTag.query.filter_by(project_id=project.id).delete()
|
|
|
|
tags_input = request.form.get('tags', '')
|
|
if tags_input:
|
|
tags_list = [tag.strip() for tag in tags_input.split(',') if tag.strip()]
|
|
for idx, tag_name in enumerate(tags_list):
|
|
if ':' in tag_name:
|
|
tag_name, color = tag_name.split(':', 1)
|
|
else:
|
|
color = 'bg-primary'
|
|
|
|
tag = ProjectTag(
|
|
project_id=project.id,
|
|
name=tag_name.strip(),
|
|
color_class=color.strip(),
|
|
display_order=idx
|
|
)
|
|
db.session.add(tag)
|
|
|
|
db.session.commit()
|
|
flash('Progetto aggiornato con successo!', 'success')
|
|
return redirect(url_for('admin.projects_manage'))
|
|
|
|
return render_template('admin/project_form.html', project=project)
|
|
|
|
|
|
@route_admin.route('/projects/<int:project_id>/delete', methods=['POST'])
|
|
@login_required
|
|
def project_delete(project_id):
|
|
"""Delete project"""
|
|
project = Project.query.get_or_404(project_id)
|
|
db.session.delete(project)
|
|
db.session.commit()
|
|
flash('Progetto eliminato con successo!', 'success')
|
|
return redirect(url_for('admin.projects_manage'))
|
|
|
|
|
|
# ============================================================================
|
|
# SOCIAL LINKS MANAGEMENT
|
|
# ============================================================================
|
|
|
|
@route_admin.route('/social-links')
|
|
@login_required
|
|
def social_links_manage():
|
|
"""Manage social links"""
|
|
social_links = SocialLink.query.order_by(SocialLink.display_order).all()
|
|
return render_template('admin/social_links.html', social_links=social_links)
|
|
|
|
|
|
@route_admin.route('/social-links/add', methods=['POST'])
|
|
@login_required
|
|
def social_link_add():
|
|
"""Add new social link"""
|
|
link = SocialLink(
|
|
platform_name=request.form.get('platform_name'),
|
|
url=request.form.get('url'),
|
|
icon_class=request.form.get('icon_class'),
|
|
display_order=int(request.form.get('display_order', 0)),
|
|
animation_delay=request.form.get('animation_delay', '0s'),
|
|
is_active=request.form.get('is_active') == 'on'
|
|
)
|
|
db.session.add(link)
|
|
db.session.commit()
|
|
flash('Link social aggiunto con successo!', 'success')
|
|
return redirect(url_for('admin.social_links_manage'))
|
|
|
|
|
|
@route_admin.route('/social-links/<int:link_id>/edit', methods=['POST'])
|
|
@login_required
|
|
def social_link_edit(link_id):
|
|
"""Edit social link"""
|
|
link = SocialLink.query.get_or_404(link_id)
|
|
link.platform_name = request.form.get('platform_name', link.platform_name)
|
|
link.url = request.form.get('url', link.url)
|
|
link.icon_class = request.form.get('icon_class', link.icon_class)
|
|
link.display_order = int(request.form.get('display_order', link.display_order))
|
|
link.animation_delay = request.form.get('animation_delay', link.animation_delay)
|
|
link.is_active = request.form.get('is_active') == 'on'
|
|
|
|
db.session.commit()
|
|
flash('Link social aggiornato con successo!', 'success')
|
|
return redirect(url_for('admin.social_links_manage'))
|
|
|
|
|
|
@route_admin.route('/social-links/<int:link_id>/delete', methods=['POST'])
|
|
@login_required
|
|
def social_link_delete(link_id):
|
|
"""Delete social link"""
|
|
link = SocialLink.query.get_or_404(link_id)
|
|
db.session.delete(link)
|
|
db.session.commit()
|
|
flash('Link social eliminato con successo!', 'success')
|
|
return redirect(url_for('admin.social_links_manage'))
|