📝 Modulo 5: Form e Input Utente

Impara a raccogliere, validare e processare dati dagli utenti

📚

MODULO
5 di 8

⏱️

TEMPO
45 minuti

🎯

LIVELLO
Intermedio

🎯 Perché i Form Sono Importanti?

I form HTML sono il ponte tra l’utente e la tua applicazione. Permettono di:

📝

Raccogliere Dati
Nome, email, password, preferenze

🔐

Autenticazione
Login e registrazione utenti

💬

Interazione
Commenti, feedback, contatti

🛒

Transazioni
E-commerce, pagamenti, ordini

💡 Ciclo di Vita di un Form

Un form in Flask segue questo percorso:

  1. GET Request: L’utente visita la pagina con il form vuoto
  2. Compilazione: L’utente riempie i campi e clicca “Invia”
  3. POST Request: I dati vengono inviati al server
  4. Validazione: Flask controlla che i dati siano corretti
  5. Processing: I dati vengono elaborati e salvati
  6. Response: Mostra un risultato o reindirizza

🚀 Il Tuo Primo Form in Flask

Iniziamo con un semplice form di contatto.

1
Crea il template del form

📄 templates/contatto.html
<!DOCTYPE html>
<html lang="it">
<head>
    <meta charset="UTF-8">
    <title>Contattaci</title>
</head>
<body>
    <h1>Contattaci</h1>
    
    <form method="POST" action="{{ url_for('contatto') }}">
        <div>
            <label for="nome">Nome:</label>
            <input type="text" id="nome" name="nome" required>
        </div>
        
        <div>
            <label for="email">Email:</label>
            <input type="email" id="email" name="email" required>
        </div>
        
        <div>
            <label for="messaggio">Messaggio:</label>
            <textarea id="messaggio" name="messaggio" rows="4" required></textarea>
        </div>
        
        <button type="submit">Invia</button>
    </form>
</body>
</html>

2
Crea la route in Flask

📄 app.py
from flask import Flask, render_template, request

app = Flask(__name__)

# Route per mostrare il form (GET)
@app.route('/contatto', methods=['GET'])
def mostra_form():
    return render_template('contatto.html')

# Route per processare il form (POST)
@app.route('/contatto', methods=['POST'])
def processa_form():
    # Ottieni i dati dal form
    nome = request.form.get('nome')
    email = request.form.get('email')
    messaggio = request.form.get('messaggio')
    
    # Qui potresti salvare nel database o inviare email
    print(f"Nuovo messaggio da {nome} ({email}): {messaggio}")
    
    return f"Grazie {nome}! Il tuo messaggio è stato ricevuto."

if __name__ == '__main__':
    app.run(debug=True)

🔍 Spiegazione del Codice

  • methods=['GET', 'POST']: Specifica che la route accetta entrambi i metodi
  • request.form.get('nome'): Recupera il valore del campo “nome” dal form
  • action="{{ url_for('contatto') }}": Invia il form alla stessa route
  • required: Attributo HTML5 per validazione lato client

📡 Metodi HTTP: GET vs POST

Comprendere la differenza tra GET e POST è fondamentale per i form.

Metodo Scopo Dati Sicurezza Quando Usare
GET Ottenere dati Nell’URL (visibili) ⚠️ Bassa Ricerca, filtri, paginazione
POST Inviare dati Nel corpo (nascosti) ✅ Alta Login, registrazione, pagamenti

🎯 Best Practice

SEMPRE usare POST per:

  • Password e dati sensibili
  • Operazioni che modificano dati (CRUD)
  • File upload
  • Transazioni finanziarie

Usare GET per:

  • Filtrare risultati di ricerca
  • Paginazione
  • Condividere link con parametri

🎨 Form Avanzato con Tutti i Campi

Esempio di Form Completo











📄 templates/registrazione.html
<form method="POST" action="{{ url_for('registrazione') }}">
    <!-- Campi di testo -->
    <input type="text" name="nome_completo">
    <input type="email" name="email">
    <input type="tel" name="telefono">
    <input type="date" name="data_nascita">
    
    <!-- Select -->
    <select name="genere">
        <option value="maschio">Maschio</option>
        <option value="femmina">Femmina</option>
    </select>
    
    <!-- Checkbox multiple -->
    <input type="checkbox" name="interessi" value="tecnologia">
    <input type="checkbox" name="interessi" value="sport">
    
    <!-- Radio buttons -->
    <input type="radio" name="newsletter" value="si">
    <input type="radio" name="newsletter" value="no">
    
    <!-- Textarea -->
    <textarea name="messaggio"></textarea>
    
    <!-- File upload -->
    <input type="file" name="foto_profilo">
    
    <button type="submit">Registrati</button>
</form>

🔐 Validazione dei Dati

La validazione è CRUCIALE per la sicurezza e l’integrità dei dati.

1
Validazione Lato Client (HTML5)

Immediata ma facile da bypassare:

<!-- HTML5 Validation -->
<input type="text" required>
<input type="email" required>
<input type="number" min="1" max="100">
<input type="password" minlength="8">
<input type="text" pattern="[A-Za-z]{3,}">

2
Validazione Lato Server (Flask)

Obbligatoria per sicurezza:

📄 app.py – Validazione Base
from flask import Flask, request, render_template, flash, redirect, url_for

app = Flask(__name__)
app.secret_key = 'chiave-segreta'  # Necessaria per i flash messages

@app.route('/registrazione', methods=['GET', 'POST'])
def registrazione():
    if request.method == 'POST':
        # Recupera i dati
        nome = request.form.get('nome', '').strip()
        email = request.form.get('email', '').strip()
        password = request.form.get('password', '')
        
        # Lista errori
        errori = []
        
        # Validazione Nome
        if not nome:
            errori.append('Il nome è obbligatorio')
        elif len(nome) < 2:
            errori.append('Il nome deve essere di almeno 2 caratteri')
        
        # Validazione Email
        if not email:
            errori.append('L\'email è obbligatoria')
        elif '@' not in email or '.' not in email:
            errori.append('Email non valida')
        
        # Validazione Password
        if not password:
            errori.append('La password è obbligatoria')
        elif len(password) < 8:
            errori.append('La password deve essere di almeno 8 caratteri')
        
        # Se ci sono errori, mostra form con errori
        if errori:
            for errore in errori:
                flash(errore, 'error')
            return render_template('registrazione.html')
        
        # Se tutto ok, procedi
        flash('Registrazione completata con successo!', 'success')
        return redirect(url_for('home'))
    
    # Se è una GET, mostra il form vuoto
    return render_template('registrazione.html')

3
Template con Flash Messages

📄 templates/registrazione.html - Parte flash
<!DOCTYPE html>
<html lang="it">
<head>
    <meta charset="UTF-8">
    <title>Registrazione</title>
</head>
<body>
    <!-- Mostra messaggi flash -->
    {% with messages = get_flashed_messages(with_categories=true) %}
        {% if messages %}
            {% for category, message in messages %}
                <div class="flash {{ category }}">
                    {{ message }}
                </div>
            {% endfor %}
        {% endif %}
    {% endwith %}
    
    <form method="POST">
        <!-- form fields here -->
    </form>
</body>
</html>

💬 Flash Messages - Feedback per l'Utente

I flash messages sono il modo migliore per comunicare con l'utente dopo un'azione.

✅ Successo! Il tuo account è stato creato con successo.
❌ Errore! L'email inserita è già in uso.
📄 app.py - Esempi Flash
from flask import flash

# Messaggi di successo
flash('Login effettuato con successo!', 'success')
flash('Prodotto aggiunto al carrello.', 'info')
flash('Email inviata con successo.', 'success')

# Messaggi di errore
flash('Password errata.', 'error')
flash('Email già registrata.', 'warning')
flash('Compila tutti i campi obbligatori.', 'error')

# Messaggi informativi
flash('Controlla la tua email per confermare.', 'info')
flash('La sessione scadrà tra 5 minuti.', 'warning')

📄 templates/base.html - Gestione Flash
<!DOCTYPE html>
<html lang="it">
<head>
    <meta charset="UTF-8">
    <title>{% block title %}Sito Flask{% endblock %}</title>
    <style>
        .flash {
            padding: 15px;
            margin: 10px 0;
            border-radius: 5px;
            border-left: 5px solid;
        }
        
        .flash.success {
            background-color: #d1fae5;
            border-color: #10b981;
            color: #065f46;
        }
        
        .flash.error {
            background-color: #fee2e2;
            border-color: #ef4444;
            color: #991b1b;
        }
        
        .flash.warning {
            background-color: #fef3c7;
            border-color: #f59e0b;
            color: #92400e;
        }
        
        .flash.info {
            background-color: #dbeafe;
            border-color: #3b82f6;
            color: #1e40af;
        }
    </style>
</head>
<body>
    <!-- Flash Messages Container -->
    <div class="flash-container">
        {% with messages = get_flashed_messages(with_categories=true) %}
            {% if messages %}
                {% for category, message in messages %}
                    <div class="flash {{ category }}">
                        {{ message }}
                    </div>
                {% endfor %}
            {% endif %}
        {% endwith %}
    </div>
    
    {% block content %}{% endblock %}
</body>
</html>

🛡️ Validazione Sicura con Flask-WTF

Per una validazione robusta e sicura, Flask-WTF è la soluzione professionale:

1
Installazione e Configurazione

# Installa Flask-WTF
pip install flask-wtf

📄 app.py
from flask import Flask, render_template, redirect, url_for, flash, session
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, EmailField
from wtforms.validators import DataRequired, Email, Length, EqualTo, ValidationError
from flask_wtf.csrf import CSRFProtect
import re

app = Flask(__name__)
app.secret_key = 'tua-chiave-segreta-molto-lunga'

# Attiva protezione CSRF
csrf = CSRFProtect(app)

# Validatore personalizzato per sicurezza password
def password_complexity(form, field):
    password = field.data
    if not re.search(r'[A-Z]', password):
        raise ValidationError('La password deve contenere almeno una lettera maiuscola')
    if not re.search(r'[a-z]', password):
        raise ValidationError('La password deve contenere almeno una lettera minuscola')
    if not re.search(r'\d', password):
        raise ValidationError('La password deve contenere almeno un numero')
    if not re.search(r'[!@#$%^&*(),.?":{}|<>]', password):
        raise ValidationError('La password deve contenere almeno un carattere speciale')

2
Definizione del Form con Validatori

📄 app.py - Continua
# Form di Registrazione Sicuro
class RegistrazioneSicuraForm(FlaskForm):
    username = StringField('Username', validators=[
        DataRequired(message='L\'username è obbligatorio'),
        Length(min=3, max=20, message='L\'username deve essere tra 3 e 20 caratteri'),
    ])
    
    email = EmailField('Email', validators=[
        DataRequired(message='L\'email è obbligatoria'),
        Email(message='Inserisci un\'email valida'),
    ])
    
    password = PasswordField('Password', validators=[
        DataRequired(message='La password è obbligatoria'),
        Length(min=8, message='La password deve essere di almeno 8 caratteri'),
        password_complexity  # Validatore personalizzato
    ])
    
    conferma_password = PasswordField('Conferma Password', validators=[
        DataRequired(message='Devi confermare la password'),
        EqualTo('password', message='Le password non coincidono')
    ])
    
    accetta_termini = BooleanField('Accetto i termini e condizioni', validators=[
        DataRequired(message='Devi accettare i termini e condizioni')
    ])
    
    submit = SubmitField('Registrati')

# Form di Login
class LoginForm(FlaskForm):
    username = StringField('Username', validators=[
        DataRequired(message='Inserisci il tuo username')
    ])
    
    password = PasswordField('Password', validators=[
        DataRequired(message='Inserisci la tua password')
    ])
    
    remember_me = BooleanField('Ricordami')
    submit = SubmitField('Accedi')

3
Route per Gestire Login/Registrazione Sicura

📄 app.py - Route Protette
from functools import wraps

# Decorator per proteggere le route
def login_required(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if 'user_id' not in session:
            flash('Devi fare il login per accedere a questa pagina', 'warning')
            return redirect(url_for('login'))
        return f(*args, **kwargs)
    return decorated_function

# Route di registrazione
@app.route('/registrazione-sicura', methods=['GET', 'POST'])
def registrazione_sicura():
    form = RegistrazioneSicuraForm()
    
    if form.validate_on_submit():
        # Qui salveresti l'utente nel database
        # Per ora simuliamo con la sessione
        
        # Crittografa la password prima di salvarla (in produzione!)
        # from werkzeug.security import generate_password_hash
        # hashed_password = generate_password_hash(form.password.data)
        
        # Salva in sessione (temporaneo)
        session['user_id'] = 1
        session['username'] = form.username.data
        session['email'] = form.email.data
        session['logged_in'] = True
        
        flash('🎉 Registrazione completata con successo!', 'success')
        return redirect(url_for('dashboard'))
    
    return render_template('registrazione_sicura.html', form=form)

# Route di login
@app.route('/login-sicuro', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    
    if form.validate_on_submit():
        # Qui verificheresti le credenziali dal database
        # Per ora simuliamo
        if form.username.data == 'admin' and form.password.data == 'Password123!':
            session['user_id'] = 1
            session['username'] = form.username.data
            session['logged_in'] = True
            
            if form.remember_me.data:
                # Imposta cookie permanente
                session.permanent = True
                app.permanent_session_lifetime = timedelta(days=30)
            
            flash('✅ Login effettuato con successo!', 'success')
            
            # Reindirizza alla pagina richiesta o alla dashboard
            next_page = request.args.get('next')
            return redirect(next_page or url_for('dashboard'))
        else:
            flash('❌ Credenziali non valide', 'error')
    
    return render_template('login_sicuro.html', form=form)

# Route protetta
@app.route('/dashboard')
@login_required  # ⭐⭐⭐ SOLO UTENTI LOGGATI ⭐⭐⭐
def dashboard():
    return render_template('dashboard.html', username=session['username'])

# Logout
@app.route('/logout')
def logout():
    session.clear()
    flash('👋 Logout effettuato', 'info')
    return redirect(url_for('home'))

4
Template per Form Sicuro

📄 templates/registrazione_sicura.html
<!DOCTYPE html>
<html lang="it">
<head>
    <meta charset="UTF-8">
    <title>Registrazione Sicura</title>
    <style>
        .form-container {
            max-width: 600px;
            margin: 50px auto;
            padding: 30px;
            background: #f8fafc;
            border-radius: 10px;
            box-shadow: 0 4px 15px rgba(0,0,0,0.1);
        }
        
        .form-title {
            color: #7c3aed;
            text-align: center;
            margin-bottom: 30px;
        }
        
        .form-group {
            margin-bottom: 20px;
        }
        
        .form-group label {
            display: block;
            margin-bottom: 8px;
            font-weight: 600;
            color: #374151;
        }
        
        .form-control {
            width: 100%;
            padding: 12px;
            border: 2px solid #e5e7eb;
            border-radius: 6px;
            font-size: 16px;
            transition: all 0.3s;
        }
        
        .form-control:focus {
            outline: none;
            border-color: #8b5cf6;
            box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.1);
        }
        
        .form-control.is-invalid {
            border-color: #ef4444;
        }
        
        .invalid-feedback {
            color: #ef4444;
            font-size: 14px;
            margin-top: 5px;
            display: block;
        }
        
        .password-requirements {
            background: #f0f9ff;
            border-left: 4px solid #0ea5e9;
            padding: 15px;
            margin: 15px 0;
            border-radius: 4px;
        }
        
        .password-requirements ul {
            margin: 0;
            padding-left: 20px;
        }
        
        .password-requirements li {
            margin-bottom: 5px;
        }
        
        .btn-primary {
            width: 100%;
            background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%);
            color: white;
            padding: 14px;
            border: none;
            border-radius: 6px;
            font-size: 16px;
            font-weight: 600;
            cursor: pointer;
            transition: transform 0.2s;
        }
        
        .btn-primary:hover {
            transform: translateY(-2px);
        }
        
        .login-link {
            text-align: center;
            margin-top: 20px;
            color: #6b7280;
        }
        
        .login-link a {
            color: #7c3aed;
            text-decoration: none;
            font-weight: 600;
        }
    </style>
</head>
<body>
    <div class="form-container">
        <h1 class="form-title">🔐 Registrazione Sicura</h1>
        
        <!-- Flash Messages -->
        {% with messages = get_flashed_messages(with_categories=true) %}
            {% if messages %}
                {% for category, message in messages %}
                    <div class="alert alert-{{ category }}">
                        {{ message }}
                    </div>
                {% endfor %}
            {% endif %}
        {% endwith %}
        
        <form method="POST" action="{{ url_for('registrazione_sicura') }}">
            {{ form.hidden_tag() }} <!-- CSRF Token -->
            
            <div class="form-group">
                {{ form.username.label }}
                {{ form.username(class="form-control") }}
                {% if form.username.errors %}
                    {% for error in form.username.errors %}
                        <div class="invalid-feedback">{{ error }}</div>
                    {% endfor %}
                {% endif %}
            </div>
            
            <div class="form-group">
                {{ form.email.label }}
                {{ form.email(class="form-control") }}
                {% if form.email.errors %}
                    {% for error in form.email.errors %}
                        <div class="invalid-feedback">{{ error }}</div>
                    {% endfor %}
                {% endif %}
            </div>
            
            <div class="form-group">
                {{ form.password.label }}
                {{ form.password(class="form-control") }}
                
                <div class="password-requirements">
                    <strong>Requisiti password:</strong>
                    <ul>
                        <li>Almeno 8 caratteri</li>
                        <li>Almeno una lettera maiuscola</li>
                        <li>Almeno una lettera minuscola</li>
                        <li>Almeno un numero</li>
                        <li>Almeno un carattere speciale</li>
                    </ul>
                </div>
                
                {% if form.password.errors %}
                    {% for error in form.password.errors %}
                        <div class="invalid-feedback">{{ error }}</div>
                    {% endfor %}
                {% endif %}
            </div>
            
            <div class="form-group">
                {{ form.conferma_password.label }}
                {{ form.conferma_password(class="form-control") }}
                {% if form.conferma_password.errors %}
                    {% for error in form.conferma_password.errors %}
                        <div class="invalid-feedback">{{ error }}</div>
                    {% endfor %}
                {% endif %}
            </div>
            
            <div class="form-group">
                {{ form.accetta_termini(class="form-check-input") }}
                {{ form.accetta_termini.label }}
                {% if form.accetta_termini.errors %}
                    {% for error in form.accetta_termini.errors %}
                        <div class="invalid-feedback">{{ error }}</div>
                    {% endfor %}
                {% endif %}
            </div>
            
            {{ form.submit(class="btn-primary") }}
        </form>
        
        <div class="login-link">
            Già registrato? <a href="{{ url_for('login') }}">Accedi qui</a>
        </div>
    </div>
</body>
</html>

🔒 Best Practice di Sicurezza

✅ Hash Password

Sempre usare hashing (bcrypt, werkzeug)

from werkzeug.security import generate_password_hash, check_password_hash

# Hash della password
hashed = generate_password_hash(password)

# Verifica
check_password_hash(hashed, password)

✅ Rate Limiting

Limita tentativi di login

# Installa Flask-Limiter
pip install Flask-Limiter

# Configura
limiter = Limiter(app)
@app.route('/login')
@limiter.limit("5 per minute")
def login():
    ...

✅ Session Management

Sessione sicura con timeout

app.config.update(
    SESSION_COOKIE_SECURE=True,
    SESSION_COOKIE_HTTPONLY=True,
    PERMANENT_SESSION_LIFETIME=1800  # 30 min
)

👁️ Anteprima Form Sicuro

🛡️ Sistema di Login Sicuro

✅ Funzionalità Implementate:

CSRF Protection

Validazione Server

Password Complesse

Login Required

Session Security

Rate Limiting

🔐 Codice di Esempio:
@app.route('/dashboard')
@login_required # ⭐ Solo loggati ⭐
def dashboard():
    return render_template('dashboard.html')

📁 Upload di File

Flask permette di gestire facilmente l'upload di file.

1
Configura Flask per gli upload

📄 app.py - Configurazione
from flask import Flask, request, render_template
import os
from werkzeug.utils import secure_filename

app = Flask(__name__)

# Configurazione upload
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024  # 16MB max
app.config['UPLOAD_FOLDER'] = 'static/uploads'
app.config['ALLOWED_EXTENSIONS'] = {'png', 'jpg', 'jpeg', 'gif', 'pdf', 'txt'}

# Controlla estensioni permesse
def allowed_file(filename):
    return '.' in filename and \
           filename.rsplit('.', 1)[1].lower() in app.config['ALLOWED_EXTENSIONS']

2
Route per l'upload

@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        # Controlla se il file è presente
        if 'file' not in request.files:
            flash('Nessun file selezionato', 'error')
            return redirect(request.url)
        
        file = request.files['file']
        
        # Se l'utente non seleziona file
        if file.filename == '':
            flash('Nessun file selezionato', 'error')
            return redirect(request.url)
        
        # Controlla estensione e salva
        if file and allowed_file(file.filename):
            filename = secure_filename(file.filename)
            file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
            flash('File caricato con successo!', 'success')
            return redirect(url_for('upload_file'))
        else:
            flash('Tipo di file non consentito', 'error')
    
    return render_template('upload.html')

3
Template per l'upload

📄 templates/upload.html
<form method="POST" enctype="multipart/form-data">
    <div class="form-group">
        <label for="file">Seleziona file:</label>
        <input type="file" id="file" name="file" required>
        <small>Formati consentiti: PNG, JPG, GIF, PDF, TXT (max 16MB)</small>
    </div>
    
    <div class="form-group">
        <label for="descrizione">Descrizione:</label>
        <input type="text" id="descrizione" name="descrizione">
    </div>
    
    <button type="submit" class="btn">Carica File</button>
</form>

⚠️ IMPORTANTE: Sicurezza File Upload

  • SEMPRE usare secure_filename() per prevenire path traversal
  • Limitare le dimensioni dei file con MAX_CONTENT_LENGTH
  • Validare le estensioni dei file
  • Non salvare file nella root dell'applicazione
  • Considerare il salvataggio su servizi esterni (AWS S3, Cloudinary)

🛡️ Protezione CSRF

CSRF (Cross-Site Request Forgery) è un attacco che forza un utente ad eseguire azioni non volute.

❌ Senza CSRF Protection

Un malintenzionato potrebbe creare un form che, quando visitato da un utente loggato, esegue azioni al posto suo (trasferimenti, cambi password, ecc.).

1
Installa Flask-WTF

# Installa la libreria
pip install flask-wtf

2
Configura Flask-WTF

📄 app.py - Configurazione
from flask import Flask, render_template
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Email, Length
from flask_wtf.csrf import CSRFProtect

app = Flask(__name__)
app.config['SECRET_KEY'] = 'chiave-molto-segreta-e-complessa'

# Abilita CSRF Protection
csrf = CSRFProtect(app)

# Definisci il form
class RegistrazioneForm(FlaskForm):
    nome = StringField('Nome', validators=[DataRequired()])
    email = StringField('Email', validators=[DataRequired(), Email()])
    password = PasswordField('Password', validators=[
        DataRequired(),
        Length(min=8, message='La password deve essere di almeno 8 caratteri')
    ])
    submit = SubmitField('Registrati')

3
Usa il form nel template

📄 templates/registrazione_wtf.html
<form method="POST">
    {{ form.hidden_tag() }}  <!-- CSRF Token -->
    
    <div class="form-group">
        {{ form.nome.label }}
        {{ form.nome(class="form-control") }}
        {% if form.nome.errors %}
            <div class="error">
                {% for error in form.nome.errors %}
                    {{ error }}
                {% endfor %}
            </div>
        {% endif %}
    </div>
    
    <div class="form-group">
        {{ form.email.label }}
        {{ form.email(class="form-control") }}
        {% if form.email.errors %}
            <div class="error">
                {% for error in form.email.errors %}
                    {{ error }}
                {% endfor %}
            </div>
        {% endif %}
    </div>
    
    {{ form.submit(class="btn btn-primary") }}
</form>

💪 Esercizi Pratici

Esercizio 1
Facile

📝 Form di Contatto Completo

Crea un form di contatto con:

  • Validazione lato client (HTML5)
  • Validazione lato server (Flask)
  • Flash messages per successo/errore
  • Protezione CSRF con Flask-WTF
  • Stile CSS professionale

Esercizio 2
Medio

🛒 Carrello della Spesa

Crea un sistema di carrello della spesa con:

  1. Form per aggiungere prodotti (nome, quantità, prezzo)
  2. Validazione: quantità > 0, prezzo positivo
  3. Calcolo automatico del totale
  4. Possibilità di rimuovere prodotti
  5. Salvataggio in sessione Flask
  6. Checkout form con dati di pagamento (simulato)

Esercizio 3
Difficile

🏆 Sistema di Quiz

Crea un sistema di quiz con:

  1. Form dinamico che genera domande da un database (simulato)
  2. Timer per ogni domanda (JavaScript)
  3. Validazione in tempo reale
  4. Calcolo del punteggio finale
  5. Sistema di ranking
  6. Export risultati in CSV
  7. Upload di immagini per domande

✅ Modulo 5 Completato!

Ora sai gestire l'input degli utenti in modo sicuro e professionale. Pronto per salvare i dati in modo permanente?

MODULO 4

ORA SEI QUI
📝 Modulo 5
Form e Input

🎯 Cosa Aspettarti nel Prossimo Modulo

🗄️

SQLite con Flask

Database leggero integrato

🔧

SQLAlchemy ORM

Gestione database con Python

💾

CRUD Completo

Create, Read, Update, Delete

Torna in alto