Complete dynamic site implementation: routes, templates, updated requirements and docker setup
This commit is contained in:
123
routes/auth.py
Normal file
123
routes/auth.py
Normal file
@@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Authentication Routes
|
||||
|
||||
from quart import Blueprint, request, render_template, redirect, url_for, session, flash
|
||||
from models.user import User
|
||||
from utils.auth import login_user, logout_user, get_current_user
|
||||
from utils.validators import validate_email, validate_password, validate_username
|
||||
from utils.helpers import flash_message
|
||||
|
||||
auth_bp = Blueprint('auth', __name__, url_prefix='/auth')
|
||||
|
||||
@auth_bp.route('/login', methods=['GET', 'POST'])
|
||||
async def login():
|
||||
"""Login page"""
|
||||
if request.method == 'GET':
|
||||
return await render_template('auth/login.html')
|
||||
|
||||
form_data = await request.form
|
||||
username_or_email = form_data.get('username', '').strip()
|
||||
password = form_data.get('password', '')
|
||||
|
||||
if not username_or_email or not password:
|
||||
flash_message('Username/Email e password sono richiesti', 'error')
|
||||
return await render_template('auth/login.html')
|
||||
|
||||
# Authenticate user
|
||||
user = await User.authenticate(username_or_email, password)
|
||||
|
||||
if user:
|
||||
login_user(user)
|
||||
flash_message(f'Benvenuto, {user.full_name}!', 'success')
|
||||
|
||||
# Redirect to dashboard if admin, home otherwise
|
||||
if user.is_admin:
|
||||
return redirect(url_for('dashboard.index'))
|
||||
else:
|
||||
return redirect(url_for('home.index'))
|
||||
else:
|
||||
flash_message('Credenziali non valide', 'error')
|
||||
return await render_template('auth/login.html')
|
||||
|
||||
@auth_bp.route('/register', methods=['GET', 'POST'])
|
||||
async def register():
|
||||
"""Registration page"""
|
||||
if request.method == 'GET':
|
||||
return await render_template('auth/register.html')
|
||||
|
||||
form_data = await request.form
|
||||
username = form_data.get('username', '').strip()
|
||||
email = form_data.get('email', '').strip()
|
||||
password = form_data.get('password', '')
|
||||
confirm_password = form_data.get('confirm_password', '')
|
||||
first_name = form_data.get('first_name', '').strip()
|
||||
last_name = form_data.get('last_name', '').strip()
|
||||
|
||||
errors = {}
|
||||
|
||||
# Validate inputs
|
||||
is_valid, error = validate_username(username)
|
||||
if not is_valid:
|
||||
errors['username'] = error
|
||||
|
||||
is_valid, error = validate_email(email)
|
||||
if not is_valid:
|
||||
errors['email'] = error
|
||||
|
||||
is_valid, error = validate_password(password)
|
||||
if not is_valid:
|
||||
errors['password'] = error
|
||||
|
||||
if password != confirm_password:
|
||||
errors['confirm_password'] = 'Le password non coincidono'
|
||||
|
||||
# Check if user already exists
|
||||
if not errors:
|
||||
existing_user = await User.find_by_username(username)
|
||||
if existing_user:
|
||||
errors['username'] = 'Username già in uso'
|
||||
|
||||
existing_email = await User.find_by_email(email)
|
||||
if existing_email:
|
||||
errors['email'] = 'Email già registrata'
|
||||
|
||||
if errors:
|
||||
for field, error in errors.items():
|
||||
flash_message(error, 'error')
|
||||
return await render_template('auth/register.html')
|
||||
|
||||
# Create new user
|
||||
user = User(
|
||||
username=username,
|
||||
email=email,
|
||||
first_name=first_name,
|
||||
last_name=last_name,
|
||||
role='user' # First user can be manually promoted to admin
|
||||
)
|
||||
user.set_password(password)
|
||||
|
||||
try:
|
||||
await user.save()
|
||||
flash_message('Registrazione completata! Ora puoi effettuare il login.', 'success')
|
||||
return redirect(url_for('auth.login'))
|
||||
except Exception as e:
|
||||
flash_message('Errore durante la registrazione. Riprova.', 'error')
|
||||
return await render_template('auth/register.html')
|
||||
|
||||
@auth_bp.route('/logout')
|
||||
async def logout():
|
||||
"""Logout user"""
|
||||
logout_user()
|
||||
flash_message('Logout effettuato con successo', 'success')
|
||||
return redirect(url_for('home.index'))
|
||||
|
||||
@auth_bp.route('/profile')
|
||||
async def profile():
|
||||
"""User profile page"""
|
||||
user = await get_current_user()
|
||||
if not user:
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
return await render_template('auth/profile.html', user=user)
|
||||
218
routes/dashboard.py
Normal file
218
routes/dashboard.py
Normal file
@@ -0,0 +1,218 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Dashboard Routes (Admin)
|
||||
|
||||
from quart import Blueprint, request, render_template, redirect, url_for, jsonify
|
||||
from models.user import User
|
||||
from models.project import Project
|
||||
from models.category import Category
|
||||
from utils.auth import admin_required, get_current_user
|
||||
from utils.helpers import flash_message, generate_slug, calculate_pagination
|
||||
from utils.validators import validate_project_data
|
||||
|
||||
dashboard_bp = Blueprint('dashboard', __name__, url_prefix='/dashboard')
|
||||
|
||||
@dashboard_bp.route('/')
|
||||
@admin_required
|
||||
async def index():
|
||||
"""Dashboard home"""
|
||||
current_user = await get_current_user()
|
||||
|
||||
# Get statistics
|
||||
stats = {
|
||||
'total_users': await User.count(),
|
||||
'total_projects': await Project.count(published_only=False),
|
||||
'published_projects': await Project.count(published_only=True),
|
||||
'featured_projects': len(await Project.get_featured())
|
||||
}
|
||||
|
||||
# Get recent projects
|
||||
recent_projects = await Project.get_all(published_only=False, limit=5)
|
||||
|
||||
return await render_template('dashboard/index.html',
|
||||
user=current_user,
|
||||
stats=stats,
|
||||
recent_projects=recent_projects)
|
||||
|
||||
@dashboard_bp.route('/projects')
|
||||
@admin_required
|
||||
async def projects():
|
||||
"""Projects management"""
|
||||
page = int(request.args.get('page', 1))
|
||||
per_page = 10
|
||||
|
||||
# Get projects with pagination
|
||||
projects = await Project.get_all(published_only=False, limit=per_page, offset=(page-1)*per_page)
|
||||
total_projects = await Project.count(published_only=False)
|
||||
|
||||
pagination = calculate_pagination(total_projects, page, per_page)
|
||||
|
||||
return await render_template('dashboard/projects.html',
|
||||
projects=projects,
|
||||
pagination=pagination)
|
||||
|
||||
@dashboard_bp.route('/projects/new', methods=['GET', 'POST'])
|
||||
@admin_required
|
||||
async def new_project():
|
||||
"""Create new project"""
|
||||
if request.method == 'GET':
|
||||
categories = await Category.get_all()
|
||||
return await render_template('dashboard/project_form.html',
|
||||
project=None,
|
||||
categories=categories,
|
||||
action='create')
|
||||
|
||||
form_data = await request.form
|
||||
data = {
|
||||
'title': form_data.get('title', '').strip(),
|
||||
'description': form_data.get('description', '').strip(),
|
||||
'content': form_data.get('content', '').strip(),
|
||||
'github_url': form_data.get('github_url', '').strip(),
|
||||
'demo_url': form_data.get('demo_url', '').strip(),
|
||||
'image_url': form_data.get('image_url', '').strip(),
|
||||
'technologies': form_data.getlist('technologies'),
|
||||
'category_id': int(form_data.get('category_id')) if form_data.get('category_id') else None,
|
||||
'is_featured': bool(form_data.get('is_featured')),
|
||||
'is_published': bool(form_data.get('is_published'))
|
||||
}
|
||||
|
||||
# Validate data
|
||||
is_valid, errors = validate_project_data(data)
|
||||
|
||||
if not is_valid:
|
||||
for field, error in errors.items():
|
||||
flash_message(error, 'error')
|
||||
categories = await Category.get_all()
|
||||
return await render_template('dashboard/project_form.html',
|
||||
project=data,
|
||||
categories=categories,
|
||||
action='create')
|
||||
|
||||
# Create project
|
||||
current_user = await get_current_user()
|
||||
project = Project(
|
||||
title=data['title'],
|
||||
slug=generate_slug(data['title']),
|
||||
description=data['description'],
|
||||
content=data['content'],
|
||||
github_url=data['github_url'],
|
||||
demo_url=data['demo_url'],
|
||||
image_url=data['image_url'],
|
||||
technologies=data['technologies'],
|
||||
category_id=data['category_id'],
|
||||
is_featured=data['is_featured'],
|
||||
is_published=data['is_published'],
|
||||
created_by=current_user.id
|
||||
)
|
||||
|
||||
try:
|
||||
await project.save()
|
||||
flash_message('Progetto creato con successo!', 'success')
|
||||
return redirect(url_for('dashboard.projects'))
|
||||
except Exception as e:
|
||||
flash_message('Errore durante la creazione del progetto', 'error')
|
||||
categories = await Category.get_all()
|
||||
return await render_template('dashboard/project_form.html',
|
||||
project=data,
|
||||
categories=categories,
|
||||
action='create')
|
||||
|
||||
@dashboard_bp.route('/projects/<int:project_id>/edit', methods=['GET', 'POST'])
|
||||
@admin_required
|
||||
async def edit_project(project_id):
|
||||
"""Edit project"""
|
||||
project = await Project.find_by_id(project_id)
|
||||
if not project:
|
||||
flash_message('Progetto non trovato', 'error')
|
||||
return redirect(url_for('dashboard.projects'))
|
||||
|
||||
if request.method == 'GET':
|
||||
categories = await Category.get_all()
|
||||
return await render_template('dashboard/project_form.html',
|
||||
project=project,
|
||||
categories=categories,
|
||||
action='edit')
|
||||
|
||||
form_data = await request.form
|
||||
data = {
|
||||
'title': form_data.get('title', '').strip(),
|
||||
'description': form_data.get('description', '').strip(),
|
||||
'content': form_data.get('content', '').strip(),
|
||||
'github_url': form_data.get('github_url', '').strip(),
|
||||
'demo_url': form_data.get('demo_url', '').strip(),
|
||||
'image_url': form_data.get('image_url', '').strip(),
|
||||
'technologies': form_data.getlist('technologies'),
|
||||
'category_id': int(form_data.get('category_id')) if form_data.get('category_id') else None,
|
||||
'is_featured': bool(form_data.get('is_featured')),
|
||||
'is_published': bool(form_data.get('is_published'))
|
||||
}
|
||||
|
||||
# Validate data
|
||||
is_valid, errors = validate_project_data(data)
|
||||
|
||||
if not is_valid:
|
||||
for field, error in errors.items():
|
||||
flash_message(error, 'error')
|
||||
categories = await Category.get_all()
|
||||
return await render_template('dashboard/project_form.html',
|
||||
project=project,
|
||||
categories=categories,
|
||||
action='edit')
|
||||
|
||||
# Update project
|
||||
project.title = data['title']
|
||||
project.slug = generate_slug(data['title'])
|
||||
project.description = data['description']
|
||||
project.content = data['content']
|
||||
project.github_url = data['github_url']
|
||||
project.demo_url = data['demo_url']
|
||||
project.image_url = data['image_url']
|
||||
project.technologies = data['technologies']
|
||||
project.category_id = data['category_id']
|
||||
project.is_featured = data['is_featured']
|
||||
project.is_published = data['is_published']
|
||||
|
||||
try:
|
||||
await project.save()
|
||||
flash_message('Progetto aggiornato con successo!', 'success')
|
||||
return redirect(url_for('dashboard.projects'))
|
||||
except Exception as e:
|
||||
flash_message('Errore durante l\'aggiornamento del progetto', 'error')
|
||||
categories = await Category.get_all()
|
||||
return await render_template('dashboard/project_form.html',
|
||||
project=project,
|
||||
categories=categories,
|
||||
action='edit')
|
||||
|
||||
@dashboard_bp.route('/projects/<int:project_id>/delete', methods=['POST'])
|
||||
@admin_required
|
||||
async def delete_project(project_id):
|
||||
"""Delete project"""
|
||||
project = await Project.find_by_id(project_id)
|
||||
if not project:
|
||||
return jsonify({'error': 'Progetto non trovato'}), 404
|
||||
|
||||
try:
|
||||
await project.delete()
|
||||
flash_message('Progetto eliminato con successo!', 'success')
|
||||
return redirect(url_for('dashboard.projects'))
|
||||
except Exception as e:
|
||||
flash_message('Errore durante l\'eliminazione del progetto', 'error')
|
||||
return redirect(url_for('dashboard.projects'))
|
||||
|
||||
@dashboard_bp.route('/users')
|
||||
@admin_required
|
||||
async def users():
|
||||
"""Users management"""
|
||||
page = int(request.args.get('page', 1))
|
||||
per_page = 10
|
||||
|
||||
users = await User.get_all(limit=per_page, offset=(page-1)*per_page)
|
||||
total_users = await User.count()
|
||||
|
||||
pagination = calculate_pagination(total_users, page, per_page)
|
||||
|
||||
return await render_template('dashboard/users.html',
|
||||
users=users,
|
||||
pagination=pagination)
|
||||
Reference in New Issue
Block a user