Walter Mantovani
commited on
Commit
·
d20dd7e
1
Parent(s):
0e78cd8
nuova app caricata per test
Browse files- Dockerfile +1 -1
- _old_app/app.py +118 -0
- _old_app/requirements.txt +3 -0
- {static → _old_app/static}/script.js +0 -0
- {templates → _old_app/templates}/guestbook.html +0 -0
- {templates → _old_app/templates}/home.html +0 -0
- {templates → _old_app/templates}/includes/flash.html +0 -0
- _old_app/templates/login.html +26 -0
- {templates → _old_app/templates}/signup.html +0 -0
- app.py +168 -87
- avvio_server_flask.html +0 -0
- avvio_server_flask.ipynb +67 -0
- models.py +114 -0
- populate_db.py +64 -0
- requirements.txt +1 -1
- settings.py +5 -0
- static/data/esempio_res_repliche.json +92 -0
- static/imgs/prodotti/prodotto1.jpg +0 -0
- static/imgs/prodotti/prodotto2.jpg +0 -0
- static/imgs/prodotti/prodotto3.jpg +0 -0
- static/imgs/shortcut-icon.png +0 -0
- static/scripts/load_lotti.js +63 -0
- static/scripts/load_prenotazioni.js +71 -0
- static/styles/style.css +0 -0
- templates/_layout_base.html +90 -0
- templates/index.html +22 -0
- templates/login.html +20 -23
- templates/prenotazione.html +92 -0
- templates/prenotazioni.html +17 -0
Dockerfile
CHANGED
@@ -30,7 +30,7 @@ COPY --chown=user . $HOME/app
|
|
30 |
# Espone la porta 5000
|
31 |
EXPOSE 5000
|
32 |
|
33 |
-
# CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
|
34 |
CMD ["gunicorn", "-b", "0.0.0.0:7860", "app:app"]
|
35 |
# CMD ["flask", "run", "--host=0.0.0.0"]
|
36 |
# CMD ["python", "-m", "flask", "run", "--host=0.0.0.0"]
|
|
|
|
30 |
# Espone la porta 5000
|
31 |
EXPOSE 5000
|
32 |
|
|
|
33 |
CMD ["gunicorn", "-b", "0.0.0.0:7860", "app:app"]
|
34 |
# CMD ["flask", "run", "--host=0.0.0.0"]
|
35 |
# CMD ["python", "-m", "flask", "run", "--host=0.0.0.0"]
|
36 |
+
# CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
|
_old_app/app.py
ADDED
@@ -0,0 +1,118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from flask import Flask, render_template, request, session, redirect, flash, url_for, jsonify
|
3 |
+
from flask_sqlalchemy import SQLAlchemy
|
4 |
+
from markupsafe import escape
|
5 |
+
|
6 |
+
BASE_DIR_PATH = os.path.abspath(os.path.dirname(__file__))
|
7 |
+
DATABASE_PATH = os.path.join(BASE_DIR_PATH, 'db.sqlite3')
|
8 |
+
|
9 |
+
app = Flask(__name__)
|
10 |
+
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///'+DATABASE_PATH
|
11 |
+
app.config['SECRET_KEY'] = 'mysecretkey'
|
12 |
+
db = SQLAlchemy(app)
|
13 |
+
|
14 |
+
class Utente(db.Model):
|
15 |
+
__tablename__ = 'utente'
|
16 |
+
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
17 |
+
nickname = db.Column(db.String(80), unique=True, nullable=False)
|
18 |
+
username = db.Column(db.String(80), unique=True, nullable=False)
|
19 |
+
password = db.Column(db.String(30), nullable=False)
|
20 |
+
# messaggi = db.relationship('Messaggio', backref=db.backref('utente', lazy='dynamic'))
|
21 |
+
# --- OR ---
|
22 |
+
messaggi = db.relationship('Messaggio', back_populates='utente', lazy='dynamic')
|
23 |
+
|
24 |
+
class Messaggio(db.Model):
|
25 |
+
__tablename__ = 'messaggio'
|
26 |
+
id = db.Column(db.Integer, primary_key=True)
|
27 |
+
user_id = db.Column(db.Integer, db.ForeignKey('utente.id'), nullable=False)
|
28 |
+
messaggio = db.Column(db.Text, nullable=False)
|
29 |
+
timestamp = db.Column(db.DateTime, default=db.func.now(), nullable=False)
|
30 |
+
# utente = db.relationship('Utente', backref=db.backref('messaggi', lazy='dynamic'))
|
31 |
+
# --- OR ---
|
32 |
+
utente = db.relationship('Utente', back_populates='messaggi')
|
33 |
+
|
34 |
+
|
35 |
+
@app.route('/')
|
36 |
+
def home():
|
37 |
+
if 'user_id' in session:
|
38 |
+
utente = db.session.query(Utente).get(session['user_id'])
|
39 |
+
return render_template('home.html', user=utente)
|
40 |
+
return render_template('home.html')
|
41 |
+
|
42 |
+
|
43 |
+
@app.route('/guestbook')
|
44 |
+
def guestbook():
|
45 |
+
if 'user_id' not in session:
|
46 |
+
return redirect(url_for('login'))
|
47 |
+
return render_template('guestbook.html')
|
48 |
+
|
49 |
+
@app.route('/api/guestbook', methods=['GET', 'POST'])
|
50 |
+
def api_guestbook():
|
51 |
+
if 'user_id' not in session:
|
52 |
+
return jsonify({'error': 'Accesso non autorizzato.'}), 401
|
53 |
+
|
54 |
+
if request.method == 'POST':
|
55 |
+
messaggio = request.json.get('messaggio')
|
56 |
+
if not messaggio:
|
57 |
+
return jsonify({'error': 'Il messaggio non può essere vuoto!'}), 400
|
58 |
+
new_message = Messaggio(user_id=session['user_id'], messaggio=escape(messaggio))
|
59 |
+
db.session.add(new_message)
|
60 |
+
db.session.commit()
|
61 |
+
return jsonify({'success': True}), 201
|
62 |
+
|
63 |
+
messages = Messaggio.query.order_by(Messaggio.timestamp.desc()).all()
|
64 |
+
response = [
|
65 |
+
{'nickname': message.utente.nickname, 'messaggio': message.messaggio}
|
66 |
+
for message in messages
|
67 |
+
]
|
68 |
+
return jsonify(response), 200
|
69 |
+
|
70 |
+
|
71 |
+
|
72 |
+
@app.route('/signup', methods=['GET', 'POST'])
|
73 |
+
def signup():
|
74 |
+
if request.method == 'POST':
|
75 |
+
nickname = request.form.get('nickname')
|
76 |
+
username = request.form.get('username')
|
77 |
+
password = request.form.get('password')
|
78 |
+
if not nickname or not username or not password:
|
79 |
+
flash('Tutti i campi sono obbligatori!')
|
80 |
+
return redirect(url_for('signup'))
|
81 |
+
if Utente.query.filter_by(username=username).first() or Utente.query.filter_by(nickname=nickname).first():
|
82 |
+
flash("Il nickname o l'username sono già in uso!")
|
83 |
+
return redirect(url_for('signup'))
|
84 |
+
new_user = Utente(nickname=nickname, username=username, password=password)
|
85 |
+
db.session.add(new_user)
|
86 |
+
db.session.commit()
|
87 |
+
flash('Registrazione effettuata con successo!')
|
88 |
+
return redirect(url_for('login'))
|
89 |
+
return render_template('signup.html')
|
90 |
+
|
91 |
+
|
92 |
+
@app.route('/login', methods=['GET', 'POST'])
|
93 |
+
def login():
|
94 |
+
if request.method == 'POST':
|
95 |
+
username = request.form['username']
|
96 |
+
password = request.form['password']
|
97 |
+
utente = Utente.query.filter_by(username=username, password=password).first()
|
98 |
+
if utente:
|
99 |
+
session['user_id'] = utente.id
|
100 |
+
flash('Login riuscito!')
|
101 |
+
return redirect(url_for('guestbook'))
|
102 |
+
else:
|
103 |
+
flash('Credenziali non valide!')
|
104 |
+
return redirect(url_for('login'))
|
105 |
+
return render_template('login.html')
|
106 |
+
|
107 |
+
@app.route('/logout')
|
108 |
+
def logout():
|
109 |
+
session.pop('user_id', None)
|
110 |
+
flash('Logout effettuato con successo!')
|
111 |
+
return redirect(url_for('home'))
|
112 |
+
|
113 |
+
with app.app_context():
|
114 |
+
db.create_all()
|
115 |
+
|
116 |
+
if __name__ == '__main__':
|
117 |
+
|
118 |
+
app.run(host="0.0.0.0", port=7860)
|
_old_app/requirements.txt
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
Flask
|
2 |
+
Flask-SQLAlchemy
|
3 |
+
gunicorn
|
{static → _old_app/static}/script.js
RENAMED
File without changes
|
{templates → _old_app/templates}/guestbook.html
RENAMED
File without changes
|
{templates → _old_app/templates}/home.html
RENAMED
File without changes
|
{templates → _old_app/templates}/includes/flash.html
RENAMED
File without changes
|
_old_app/templates/login.html
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!doctype html>
|
2 |
+
<html lang="en">
|
3 |
+
|
4 |
+
<head>
|
5 |
+
<meta charset="UTF-8">
|
6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
7 |
+
<title>Login</title>
|
8 |
+
</head>
|
9 |
+
|
10 |
+
<body>
|
11 |
+
<h2>Login</h2>
|
12 |
+
|
13 |
+
<form method="POST" action="/login">
|
14 |
+
<label for="username">Username:</label>
|
15 |
+
<input type="text" id="username" name="username" required><br>
|
16 |
+
<label for="password">Password:</label>
|
17 |
+
<input type="password" id="password" name="password" required><br>
|
18 |
+
<button type="submit">Login</button>
|
19 |
+
</form>
|
20 |
+
|
21 |
+
<!-- Mostra i messaggi flash -->
|
22 |
+
{% include 'includes/flash.html' %}
|
23 |
+
|
24 |
+
</body>
|
25 |
+
|
26 |
+
</html>
|
{templates → _old_app/templates}/signup.html
RENAMED
File without changes
|
app.py
CHANGED
@@ -1,118 +1,199 @@
|
|
1 |
-
import
|
2 |
-
from
|
3 |
-
from
|
4 |
-
from
|
5 |
-
|
6 |
-
BASE_DIR_PATH = os.path.abspath(os.path.dirname(__file__))
|
7 |
-
DATABASE_PATH = os.path.join(BASE_DIR_PATH, 'db.sqlite3')
|
8 |
|
9 |
app = Flask(__name__)
|
10 |
-
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///'+DATABASE_PATH
|
11 |
-
app.config['SECRET_KEY'] = 'mysecretkey'
|
12 |
-
db = SQLAlchemy(app)
|
13 |
-
|
14 |
-
class Utente(db.Model):
|
15 |
-
__tablename__ = 'utente'
|
16 |
-
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
17 |
-
nickname = db.Column(db.String(80), unique=True, nullable=False)
|
18 |
-
username = db.Column(db.String(80), unique=True, nullable=False)
|
19 |
-
password = db.Column(db.String(30), nullable=False)
|
20 |
-
# messaggi = db.relationship('Messaggio', backref=db.backref('utente', lazy='dynamic'))
|
21 |
-
# --- OR ---
|
22 |
-
messaggi = db.relationship('Messaggio', back_populates='utente', lazy='dynamic')
|
23 |
-
|
24 |
-
class Messaggio(db.Model):
|
25 |
-
__tablename__ = 'messaggio'
|
26 |
-
id = db.Column(db.Integer, primary_key=True)
|
27 |
-
user_id = db.Column(db.Integer, db.ForeignKey('utente.id'), nullable=False)
|
28 |
-
messaggio = db.Column(db.Text, nullable=False)
|
29 |
-
timestamp = db.Column(db.DateTime, default=db.func.now(), nullable=False)
|
30 |
-
# utente = db.relationship('Utente', backref=db.backref('messaggi', lazy='dynamic'))
|
31 |
-
# --- OR ---
|
32 |
-
utente = db.relationship('Utente', back_populates='messaggi')
|
33 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
34 |
|
35 |
@app.route('/')
|
36 |
def home():
|
37 |
-
|
38 |
-
utente = db.session.query(Utente).get(session['user_id'])
|
39 |
-
return render_template('home.html', user=utente)
|
40 |
-
return render_template('home.html')
|
41 |
-
|
42 |
|
43 |
-
@app.route('/
|
44 |
-
def
|
45 |
if 'user_id' not in session:
|
46 |
return redirect(url_for('login'))
|
47 |
-
return render_template('
|
48 |
|
49 |
-
@app.route('/api/
|
50 |
-
def
|
51 |
-
|
52 |
-
|
|
|
|
|
|
|
|
|
|
|
53 |
|
54 |
-
if request.method == 'POST':
|
55 |
-
messaggio = request.json.get('messaggio')
|
56 |
-
if not messaggio:
|
57 |
-
return jsonify({'error': 'Il messaggio non può essere vuoto!'}), 400
|
58 |
-
new_message = Messaggio(user_id=session['user_id'], messaggio=escape(messaggio))
|
59 |
-
db.session.add(new_message)
|
60 |
-
db.session.commit()
|
61 |
-
return jsonify({'success': True}), 201
|
62 |
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
69 |
|
|
|
70 |
|
|
|
|
|
|
|
71 |
|
72 |
-
@app.route('/signup', methods=['GET', 'POST'])
|
73 |
-
def signup():
|
74 |
if request.method == 'POST':
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
90 |
|
91 |
|
92 |
@app.route('/login', methods=['GET', 'POST'])
|
93 |
def login():
|
94 |
if request.method == 'POST':
|
95 |
-
|
96 |
password = request.form['password']
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
|
|
102 |
else:
|
103 |
-
flash('
|
104 |
-
|
105 |
return render_template('login.html')
|
106 |
|
|
|
107 |
@app.route('/logout')
|
108 |
def logout():
|
109 |
session.pop('user_id', None)
|
110 |
-
|
111 |
-
return redirect(url_for('home'))
|
112 |
|
113 |
-
with app.app_context():
|
114 |
-
db.create_all()
|
115 |
|
116 |
if __name__ == '__main__':
|
117 |
-
|
118 |
-
|
|
|
|
1 |
+
from flask import Flask, render_template, jsonify, request, redirect, url_for, session, flash
|
2 |
+
from models import User, Lotto, Prenotazione, db
|
3 |
+
from populate_db import init_db
|
4 |
+
from settings import DATABASE_PATH
|
|
|
|
|
|
|
5 |
|
6 |
app = Flask(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7 |
|
8 |
+
app.config.update(
|
9 |
+
SECRET_KEY='my_very_secret_key123',
|
10 |
+
SQLALCHEMY_DATABASE_URI='sqlite:///'+DATABASE_PATH,
|
11 |
+
# SQLALCHEMY_TRACK_MODIFICATIONS=False,
|
12 |
+
# DEBUG=True
|
13 |
+
)
|
14 |
+
|
15 |
+
db.init_app(app)
|
16 |
|
17 |
@app.route('/')
|
18 |
def home():
|
19 |
+
return render_template('index.html')
|
|
|
|
|
|
|
|
|
20 |
|
21 |
+
@app.route('/prenotazioni')
|
22 |
+
def prenotazioni():
|
23 |
if 'user_id' not in session:
|
24 |
return redirect(url_for('login'))
|
25 |
+
return render_template('prenotazioni.html')
|
26 |
|
27 |
+
@app.route('/api/dati_lotti')
|
28 |
+
def get_dati_lotti():
|
29 |
+
# Esegui la query per ottenere tutti i lotti in ordine di data
|
30 |
+
lotti = Lotto.query.order_by(Lotto.data_consegna).all()
|
31 |
+
lotti_data = []
|
32 |
+
for lotto in lotti: # Model objects
|
33 |
+
lotti_data.append(lotto.to_dict())
|
34 |
+
print(lotti_data)
|
35 |
+
return jsonify(lotti_data)
|
36 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
37 |
|
38 |
+
@app.route('/api/dati_prenotazioni')
|
39 |
+
def get_dati_prenotazioni():
|
40 |
+
if 'user_id' not in session:
|
41 |
+
return redirect(url_for('login'))
|
42 |
+
# Esegui la query per ottenere tutte le prenotazioni dell'utente loggato
|
43 |
+
# in ordine di data del lotto
|
44 |
+
# prenotazioni_user = Prenotazione.query.filter(Prenotazione.user_id == session['user_id'])
|
45 |
+
# prenotazioni = prenotazioni_user.join(Lotto).order_by(Lotto.data_consegna).all()
|
46 |
+
|
47 |
+
# Oppure, sfruttando la relazione tra Utente e Prenotazione con lazy='dynamic':
|
48 |
+
utente = db.session.get(User, session['user_id'])
|
49 |
+
prenotazioni = utente.prenotazioni.join(Lotto).order_by(Lotto.data_consegna).all()
|
50 |
+
|
51 |
+
prenotazioni_data = []
|
52 |
+
for prenot in prenotazioni:
|
53 |
+
# prenotazione_data = {
|
54 |
+
# 'prenotazione_id': prenot.id,
|
55 |
+
# 'qta': prenot.qta,
|
56 |
+
# 'prezzoTotale': prenot.get_prezzo_totale_str(),
|
57 |
+
# 'lotto': {
|
58 |
+
# 'lotto_id': prenot.lotto.id,
|
59 |
+
# 'dataConsegna': prenot.lotto.get_date(),
|
60 |
+
# 'qtaUnitaMisura': prenot.lotto.qta_unita_misura,
|
61 |
+
# 'qtaLotto': prenot.lotto.qta_lotto,
|
62 |
+
# 'qtaDisponibile': prenot.lotto.get_qta_disponibile(),
|
63 |
+
# 'prezzoUnitario': prenot.lotto.get_prezzo_str(),
|
64 |
+
# 'sospeso': prenot.lotto.sospeso,
|
65 |
+
# 'prodotto': {
|
66 |
+
# 'prodotto_id': prenot.lotto.prodotto.id,
|
67 |
+
# 'nome': prenot.lotto.prodotto.nome,
|
68 |
+
# 'produttore': {
|
69 |
+
# 'produttore_id': prenot.lotto.prodotto.produttore.id,
|
70 |
+
# 'nome': prenot.lotto.prodotto.produttore.nome,
|
71 |
+
# 'descrizione': prenot.lotto.prodotto.produttore.descrizione,
|
72 |
+
# 'indirizzo': prenot.lotto.prodotto.produttore.indirizzo,
|
73 |
+
# 'telefono': prenot.lotto.prodotto.produttore.telefono,
|
74 |
+
# 'email': prenot.lotto.prodotto.produttore.email,
|
75 |
+
# }
|
76 |
+
# }
|
77 |
+
# }
|
78 |
+
# }
|
79 |
+
# prenotazioni_data.append(prenotazione_data)
|
80 |
+
prenotazioni_data.append(prenot.to_dict())
|
81 |
+
|
82 |
+
return jsonify(prenotazioni_data)
|
83 |
+
|
84 |
+
|
85 |
+
@app.route('/prenotazione/<int:lotto_id>', methods=['GET', 'POST'])
|
86 |
+
def prenotazione(lotto_id):
|
87 |
+
if 'user_id' not in session:
|
88 |
+
return redirect(url_for('login'))
|
89 |
|
90 |
+
lotto = db.session.get(Lotto, lotto_id)
|
91 |
|
92 |
+
# Cerca se esiste già una prenotazione per il lotto e l'utente loggato
|
93 |
+
prenotazione_esistente = Prenotazione.query.filter_by(
|
94 |
+
lotto_id=lotto_id, user_id=session['user_id']).first()
|
95 |
|
|
|
|
|
96 |
if request.method == 'POST':
|
97 |
+
azione = request.form.get('azione')
|
98 |
+
quantita = int(request.form.get('quantita'), 0)
|
99 |
+
qta_disponibile = lotto.get_qta_disponibile()
|
100 |
+
|
101 |
+
if quantita <= 0:
|
102 |
+
flash('Quantità non valida. Inserire un numero maggiore di 0.', 'danger')
|
103 |
+
return render_template('prenotazione.html', azione=azione, lotto=lotto, quantita=1,
|
104 |
+
prenotazione_esistente=prenotazione_esistente)
|
105 |
+
|
106 |
+
if azione == 'aggiornaPrenotazione':
|
107 |
+
if not prenotazione_esistente:
|
108 |
+
raise ValueError('Prenotazione non esistente.')
|
109 |
+
if quantita > qta_disponibile + prenotazione_esistente.qta:
|
110 |
+
flash('La quantità richiesta supera quella disponibile.', 'danger')
|
111 |
+
return render_template('prenotazione.html', azione=azione, lotto=lotto, quantita=quantita,
|
112 |
+
prenotazione_esistente=prenotazione_esistente)
|
113 |
+
|
114 |
+
prenotazione_esistente.qta = quantita
|
115 |
+
db.session.commit()
|
116 |
+
flash(f'Prenotazione di "{lotto.prodotto.nome}" aggiornata a {quantita} {lotto.qta_unita_misura}.', 'success')
|
117 |
+
|
118 |
+
elif azione == 'nuovaPrenotazione':
|
119 |
+
if quantita > qta_disponibile:
|
120 |
+
flash('La quantità richiesta supera quella disponibile.', 'danger')
|
121 |
+
return render_template('prenotazione.html', azione=azione, lotto=lotto, quantita=quantita,
|
122 |
+
prenotazione_esistente=prenotazione_esistente)
|
123 |
+
|
124 |
+
if prenotazione_esistente:
|
125 |
+
prenotazione_esistente.qta += quantita
|
126 |
+
db.session.commit()
|
127 |
+
flash(f'Aggiunti {quantita} {lotto.qta_unita_misura} alla prenotazione preesistente di "{lotto.prodotto.nome}".', 'success')
|
128 |
+
else:
|
129 |
+
prenotazione = Prenotazione(
|
130 |
+
user_id=session['user_id'],
|
131 |
+
lotto_id=lotto_id,
|
132 |
+
qta=quantita
|
133 |
+
)
|
134 |
+
db.session.add(prenotazione)
|
135 |
+
db.session.commit()
|
136 |
+
flash(f'Prenotazione di {quantita} {lotto.qta_unita_misura} di "{lotto.prodotto.nome}" effettuata con successo!', 'success')
|
137 |
+
|
138 |
+
elif azione == 'eliminaPrenotazione':
|
139 |
+
if not prenotazione_esistente:
|
140 |
+
raise ValueError('Prenotazione non esistente.')
|
141 |
+
db.session.delete(prenotazione_esistente)
|
142 |
+
db.session.commit()
|
143 |
+
flash('Prenotazione eliminata con successo.', 'warning')
|
144 |
+
|
145 |
+
else:
|
146 |
+
raise ValueError('Azione non implementata.')
|
147 |
+
|
148 |
+
return redirect(url_for('prenotazioni'))
|
149 |
+
|
150 |
+
else: # GET request
|
151 |
+
# Legge il parametro GET 'action' dalla URL
|
152 |
+
azione = request.args.get('azione')
|
153 |
+
if azione == 'aggiornaPrenotazione':
|
154 |
+
if prenotazione_esistente:
|
155 |
+
quantita = prenotazione_esistente.qta
|
156 |
+
else:
|
157 |
+
raise ValueError('Prenotazione non esistente.')
|
158 |
+
elif azione == 'nuovaPrenotazione':
|
159 |
+
quantita = 1
|
160 |
+
# if prenotazione_esistente:
|
161 |
+
# azione = 'aggiungiPrenotazione'
|
162 |
+
elif azione == 'eliminaPrenotazione':
|
163 |
+
...
|
164 |
+
raise ValueError('Azione non implementata con GET.')
|
165 |
+
else:
|
166 |
+
raise ValueError('Azione non valida.')
|
167 |
+
|
168 |
+
return render_template('prenotazione.html',
|
169 |
+
azione=azione, lotto=lotto, quantita=quantita,
|
170 |
+
prenotazione_esistente=prenotazione_esistente)
|
171 |
|
172 |
|
173 |
@app.route('/login', methods=['GET', 'POST'])
|
174 |
def login():
|
175 |
if request.method == 'POST':
|
176 |
+
email = request.form['email']
|
177 |
password = request.form['password']
|
178 |
+
user = User.query.filter_by(email=email).first()
|
179 |
+
|
180 |
+
if user and user.password == password:
|
181 |
+
session['user_id'] = user.id
|
182 |
+
flash(f'Login riuscito. Benvenuto {user.nome}!', 'success')
|
183 |
+
return redirect(url_for('prenotazioni'))
|
184 |
else:
|
185 |
+
flash('Login non riuscito. Controlla email e password.', 'danger')
|
186 |
+
|
187 |
return render_template('login.html')
|
188 |
|
189 |
+
|
190 |
@app.route('/logout')
|
191 |
def logout():
|
192 |
session.pop('user_id', None)
|
193 |
+
return redirect(url_for('login'))
|
|
|
194 |
|
|
|
|
|
195 |
|
196 |
if __name__ == '__main__':
|
197 |
+
with app.app_context():
|
198 |
+
init_db()
|
199 |
+
app.run(debug=True)
|
avvio_server_flask.html
ADDED
The diff for this file is too large to render.
See raw diff
|
|
avvio_server_flask.ipynb
ADDED
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"cells": [
|
3 |
+
{
|
4 |
+
"cell_type": "markdown",
|
5 |
+
"metadata": {},
|
6 |
+
"source": [
|
7 |
+
"## 1. Installazione di Python\n",
|
8 |
+
"\n",
|
9 |
+
"1. Vai alla pagina di download ufficiale di Python per Windows: [Python Releases for Windows](https://www.python.org/downloads/windows/).\n",
|
10 |
+
"\n",
|
11 |
+
"2. Trova la versione di Python che desideri utilizzare (ad esempio, **Python 3.12**).\n",
|
12 |
+
"\n",
|
13 |
+
"3. Fai clic sul link corrispondente al tuo sistema (64-bit o 32-bit) per scaricare il file eseguibile dell'installer di Python.\n",
|
14 |
+
"\n",
|
15 |
+
"Una volta scaricato, esegui l'installer e segui le istruzioni della procedura guidata mantenendo le impostazioni predefinite."
|
16 |
+
]
|
17 |
+
},
|
18 |
+
{
|
19 |
+
"cell_type": "markdown",
|
20 |
+
"metadata": {},
|
21 |
+
"source": [
|
22 |
+
"## 2. Installazione di Flask e Flask-SQLAlchemy\n",
|
23 |
+
"\n",
|
24 |
+
"1. Apri il terminale o il prompt dei comandi e digita il seguente comando per installare Flask:\n",
|
25 |
+
"\n",
|
26 |
+
"```cmd\n",
|
27 |
+
"> py -m pip install Flask\n",
|
28 |
+
"> py -m pip install Flask-SQLAlchemy\n",
|
29 |
+
"```\n",
|
30 |
+
"\n",
|
31 |
+
"Questo installerà Flask e Flask-SQLAlchemy nel tuo ambiente Python."
|
32 |
+
]
|
33 |
+
},
|
34 |
+
{
|
35 |
+
"cell_type": "markdown",
|
36 |
+
"metadata": {},
|
37 |
+
"source": [
|
38 |
+
"## 3. Avvio dell'applicazione\n",
|
39 |
+
"\n",
|
40 |
+
"### Con Run di VS Code\n",
|
41 |
+
"\n",
|
42 |
+
"1. Apri il file `app.py` dall'IDE.\n",
|
43 |
+
"\n",
|
44 |
+
"2. Esegui il file `app.py` con il comando \"Run\" dell'IDE.\n",
|
45 |
+
"\n",
|
46 |
+
"### Da terminale\n",
|
47 |
+
"\n",
|
48 |
+
"1. Apri il terminale o il prompt dei comandi e naviga nella directory in cui si trova il file `app.py`.\n",
|
49 |
+
"\n",
|
50 |
+
"2. Digita il seguente comando per avviare l'applicazione Flask:\n",
|
51 |
+
" ```\n",
|
52 |
+
" > py app.py\n",
|
53 |
+
" ```\n",
|
54 |
+
" Questo avvierà l'applicazione e ti mostrerà l'URL locale (di solito `http://127.0.0.1:5000/`).\n",
|
55 |
+
"\n",
|
56 |
+
"3. Apri il tuo browser e visita l'URL indicato. Dovresti vedere l'applicazione Flask in esecuzione."
|
57 |
+
]
|
58 |
+
}
|
59 |
+
],
|
60 |
+
"metadata": {
|
61 |
+
"language_info": {
|
62 |
+
"name": "python"
|
63 |
+
}
|
64 |
+
},
|
65 |
+
"nbformat": 4,
|
66 |
+
"nbformat_minor": 2
|
67 |
+
}
|
models.py
ADDED
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import locale
|
2 |
+
from flask_sqlalchemy import SQLAlchemy
|
3 |
+
from sqlalchemy_serializer import SerializerMixin
|
4 |
+
|
5 |
+
locale.setlocale(locale.LC_TIME, 'it_IT')
|
6 |
+
|
7 |
+
db = SQLAlchemy()
|
8 |
+
|
9 |
+
class User(db.Model, SerializerMixin):
|
10 |
+
__tablename__ = 'users'
|
11 |
+
id = db.mapped_column(db.Integer(), primary_key=True)
|
12 |
+
cognome = db.mapped_column(db.String(50), nullable=False)
|
13 |
+
nome = db.mapped_column(db.String(50), nullable=False)
|
14 |
+
telefono = db.mapped_column(db.String(20), nullable=False)
|
15 |
+
email = db.mapped_column(db.String(50), unique=True, nullable=False)
|
16 |
+
password = db.mapped_column(db.String(30), nullable=False)
|
17 |
+
# -- RELATIONSHIPS --
|
18 |
+
prenotazioni = db.relationship('Prenotazione', back_populates='user', lazy='dynamic')
|
19 |
+
|
20 |
+
serialize_rules = ('-prenotazioni.user',)
|
21 |
+
|
22 |
+
|
23 |
+
class Produttore(db.Model, SerializerMixin):
|
24 |
+
__tablename__ = 'produttori'
|
25 |
+
id = db.mapped_column(db.Integer(), primary_key=True)
|
26 |
+
nome = db.mapped_column(db.String(100), nullable=False)
|
27 |
+
descrizione = db.mapped_column(db.Text(), nullable=False)
|
28 |
+
indirizzo = db.mapped_column(db.Text(), nullable=False)
|
29 |
+
telefono = db.mapped_column(db.String(20), nullable=False)
|
30 |
+
email = db.mapped_column(db.String(50), nullable=False)
|
31 |
+
# -- RELATIONSHIPS --
|
32 |
+
prodotti = db.relationship('Prodotto', back_populates='produttore')
|
33 |
+
|
34 |
+
serialize_rules = ('-prodotti.produttore',)
|
35 |
+
|
36 |
+
|
37 |
+
class Prodotto(db.Model, SerializerMixin):
|
38 |
+
__tablename__ = 'prodotti'
|
39 |
+
id = db.mapped_column(db.Integer(), primary_key=True)
|
40 |
+
produttore_id = db.mapped_column(db.Integer(), db.ForeignKey('produttori.id'), nullable=False)
|
41 |
+
nome = db.mapped_column(db.String(50), nullable=False)
|
42 |
+
# -- RELATIONSHIPS --
|
43 |
+
produttore = db.relationship('Produttore', back_populates='prodotti')
|
44 |
+
lotti = db.relationship('Lotto', back_populates='prodotto')
|
45 |
+
|
46 |
+
serialize_rules = ('-produttore.prodotti', '-lotti.prodotto')
|
47 |
+
|
48 |
+
|
49 |
+
class Lotto(db.Model, SerializerMixin):
|
50 |
+
__tablename__ = 'lotti'
|
51 |
+
id = db.mapped_column(db.Integer(), primary_key=True)
|
52 |
+
prodotto_id = db.mapped_column(db.Integer(), db.ForeignKey('prodotti.id'), nullable=False)
|
53 |
+
data_consegna = db.mapped_column(db.Date(), nullable=False)
|
54 |
+
qta_unita_misura = db.mapped_column(db.String(10), nullable=False)
|
55 |
+
qta_lotto = db.mapped_column(db.Integer(), nullable=False)
|
56 |
+
prezzo_unitario = db.mapped_column(db.Float(), nullable=False)
|
57 |
+
sospeso = db.mapped_column(db.Boolean(), default=False)
|
58 |
+
# -- RELATIONSHIPS --
|
59 |
+
prodotto = db.relationship('Prodotto', back_populates='lotti')
|
60 |
+
prenotazioni = db.relationship('Prenotazione', back_populates='lotto')
|
61 |
+
|
62 |
+
serialize_rules = ('-prodotto.lotti', '-prenotazioni.lotto', 'get_qta_disponibile', 'get_date', 'get_prezzo_str')
|
63 |
+
|
64 |
+
def get_qta_disponibile(self):
|
65 |
+
qta_prenotata = sum(prenotazione.qta for prenotazione in self.prenotazioni)
|
66 |
+
return self.qta_lotto - qta_prenotata
|
67 |
+
|
68 |
+
def get_date(self):
|
69 |
+
return self.data_consegna.strftime('%A %d/%m/%Y')
|
70 |
+
|
71 |
+
def get_prezzo_str(self):
|
72 |
+
return f'{self.prezzo_unitario:.2f} €/{self.qta_unita_misura}'
|
73 |
+
|
74 |
+
class Prenotazione(db.Model, SerializerMixin):
|
75 |
+
__tablename__ = 'prenotazioni'
|
76 |
+
id = db.mapped_column(db.Integer(), primary_key=True)
|
77 |
+
user_id = db.mapped_column(db.Integer(), db.ForeignKey('users.id'), nullable=False)
|
78 |
+
lotto_id = db.mapped_column(db.Integer(), db.ForeignKey('lotti.id'), nullable=False)
|
79 |
+
qta = db.mapped_column(db.Integer, nullable=False)
|
80 |
+
# -- RELATIONSHIPS --
|
81 |
+
user = db.relationship('User', back_populates='prenotazioni')
|
82 |
+
lotto = db.relationship('Lotto', back_populates='prenotazioni') # , order_by='Lotto.data_consegna')
|
83 |
+
|
84 |
+
serialize_rules = ('-user.prenotazioni', '-lotto.prenotazioni', 'get_prezzo_totale_str')
|
85 |
+
|
86 |
+
|
87 |
+
__table_args__ = (
|
88 |
+
db.UniqueConstraint('user_id', 'lotto_id', name='user_id_lotto_id_uniq'),
|
89 |
+
)
|
90 |
+
|
91 |
+
@db.validates('qta')
|
92 |
+
def validate_qta(self, key, qta):
|
93 |
+
if qta <= 0:
|
94 |
+
raise ValueError('La quantità deve essere maggiore di zero.')
|
95 |
+
|
96 |
+
# Per usare questo, va rivisto lo script di inizializzazione del DB
|
97 |
+
# altre_prenotazioni = Prenotazione.query.filter(
|
98 |
+
# Prenotazione.lotto_id == self.lotto_id,
|
99 |
+
# Prenotazione.id != self.id
|
100 |
+
# ).all()
|
101 |
+
# qta_gia_prenotata = sum(prenotazione.qta for prenotazione in altre_prenotazioni)
|
102 |
+
# lotto = db.session.query(Lotto).get(self.lotto_id)
|
103 |
+
# if qta_gia_prenotata + qta > lotto.qta_lotto:
|
104 |
+
# raise ValueError('Quantità non disponibile!')
|
105 |
+
|
106 |
+
# lotto = db.session.query(Lotto).get(self.lotto_id)
|
107 |
+
# if lotto.sospeso:
|
108 |
+
# raise ValueError('Il lotto è sospeso!')
|
109 |
+
|
110 |
+
return qta
|
111 |
+
|
112 |
+
def get_prezzo_totale_str(self):
|
113 |
+
prezzo = self.qta * self.lotto.prezzo_unitario
|
114 |
+
return f'€ {prezzo:.2f}'
|
populate_db.py
ADDED
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from datetime import datetime
|
2 |
+
from models import db, Produttore, Prodotto, Lotto, User, Prenotazione
|
3 |
+
|
4 |
+
def init_db():
|
5 |
+
|
6 |
+
db.create_all()
|
7 |
+
|
8 |
+
if not User.query.first():
|
9 |
+
|
10 |
+
# Inserisci dati iniziali nelle tabelle
|
11 |
+
produttori = [
|
12 |
+
Produttore(nome='Cascina della Civetta', descrizione='Produttore di olio e frutta secca', indirizzo='Via Cavour 12, Ugento (LE)', telefono='0172 123456', email='[email protected]'),
|
13 |
+
Produttore(nome='Universo Bio', descrizione='Prodotti biologici', indirizzo='Via Garibaldi 2, Melle', telefono='011 9876543', email='[email protected]'),
|
14 |
+
Produttore(nome='Fattoria del Sole', descrizione='Prodotti tipici', indirizzo='Via Roma 1, Cuneo', telefono='0171 987654', email='[email protected]'),
|
15 |
+
Produttore(nome='Azienda Agricola La Quiete', descrizione='Frutta e verdura', indirizzo='Via Torino 3, Alba', telefono='0173 987654', email='[email protected]'),
|
16 |
+
]
|
17 |
+
|
18 |
+
prodotti = [
|
19 |
+
Prodotto(produttore_id=1, nome='Olio extravergine di oliva Bio'),
|
20 |
+
Prodotto(produttore_id=1, nome='Mele Golden'),
|
21 |
+
Prodotto(produttore_id=2, nome='Farina di grano tenero'),
|
22 |
+
Prodotto(produttore_id=2, nome='Miele di acacia Bio'),
|
23 |
+
Prodotto(produttore_id=3, nome='Riso Originario integrale'),
|
24 |
+
Prodotto(produttore_id=3, nome='Formaggio Toma'),
|
25 |
+
Prodotto(produttore_id=4, nome='Zucchine novelle'),
|
26 |
+
]
|
27 |
+
|
28 |
+
lotti = [
|
29 |
+
Lotto(prodotto_id=1, data_consegna=datetime.strptime('2024-06-27', '%Y-%m-%d'), qta_unita_misura='L', qta_lotto=100, prezzo_unitario=8.50, sospeso=False),
|
30 |
+
Lotto(prodotto_id=1, data_consegna=datetime.strptime('2024-07-29', '%Y-%m-%d'), qta_unita_misura='L', qta_lotto=500, prezzo_unitario=8.50, sospeso=False),
|
31 |
+
Lotto(prodotto_id=2, data_consegna=datetime.strptime('2024-07-28', '%Y-%m-%d'), qta_unita_misura='Kg', qta_lotto=200, prezzo_unitario=2.50, sospeso=True),
|
32 |
+
Lotto(prodotto_id=2, data_consegna=datetime.strptime('2024-07-30', '%Y-%m-%d'), qta_unita_misura='Kg', qta_lotto=100, prezzo_unitario=2.50, sospeso=False),
|
33 |
+
Lotto(prodotto_id=3, data_consegna=datetime.strptime('2024-07-27', '%Y-%m-%d'), qta_unita_misura='Kg', qta_lotto=100, prezzo_unitario=2.00, sospeso=False),
|
34 |
+
Lotto(prodotto_id=3, data_consegna=datetime.strptime('2024-07-29', '%Y-%m-%d'), qta_unita_misura='Kg', qta_lotto=50, prezzo_unitario=2.00, sospeso=False),
|
35 |
+
Lotto(prodotto_id=4, data_consegna=datetime.strptime('2024-07-28', '%Y-%m-%d'), qta_unita_misura='Kg', qta_lotto=200, prezzo_unitario=5.00, sospeso=True),
|
36 |
+
Lotto(prodotto_id=4, data_consegna=datetime.strptime('2024-07-30', '%Y-%m-%d'), qta_unita_misura='Kg', qta_lotto=100, prezzo_unitario=5.00, sospeso=False),
|
37 |
+
Lotto(prodotto_id=5, data_consegna=datetime.strptime('2024-07-27', '%Y-%m-%d'), qta_unita_misura='Kg', qta_lotto=100, prezzo_unitario=3.00, sospeso=False),
|
38 |
+
]
|
39 |
+
|
40 |
+
users = [
|
41 |
+
User(cognome='Rossi', nome='Paolo', telefono= '3304953849', email='[email protected]', password='pwd123'),
|
42 |
+
User(cognome='Bianchi', nome='John', telefono= '3245903845', email='[email protected]', password='pwd321'),
|
43 |
+
User(cognome='Verdi', nome='Giuseppe', telefono='3456748567', email='[email protected]', password='pwd456'),
|
44 |
+
User(cognome='Neri', nome='Francesca', telefono='3834565646', email='[email protected]', password='pwd654'),
|
45 |
+
User(cognome='Bruni', nome='Carla', telefono= '3347866223', email='[email protected]', password='pwd789'),
|
46 |
+
]
|
47 |
+
|
48 |
+
prenotazioni = [
|
49 |
+
Prenotazione(user_id=1, lotto_id=1, qta=2),
|
50 |
+
Prenotazione(user_id=2, lotto_id=2, qta=1),
|
51 |
+
Prenotazione(user_id=3, lotto_id=3, qta=3),
|
52 |
+
Prenotazione(user_id=4, lotto_id=4, qta=2),
|
53 |
+
Prenotazione(user_id=5, lotto_id=5, qta=1),
|
54 |
+
Prenotazione(user_id=1, lotto_id=6, qta=2),
|
55 |
+
Prenotazione(user_id=2, lotto_id=7, qta=1),
|
56 |
+
Prenotazione(user_id=3, lotto_id=8, qta=3),
|
57 |
+
Prenotazione(user_id=4, lotto_id=9, qta=2),
|
58 |
+
]
|
59 |
+
|
60 |
+
db.session.bulk_save_objects(produttori + prodotti + lotti + users + prenotazioni)
|
61 |
+
db.session.commit()
|
62 |
+
|
63 |
+
if __name__ == '__main__':
|
64 |
+
init_db()
|
requirements.txt
CHANGED
@@ -1,3 +1,3 @@
|
|
1 |
Flask
|
2 |
Flask-SQLAlchemy
|
3 |
-
|
|
|
1 |
Flask
|
2 |
Flask-SQLAlchemy
|
3 |
+
sqlalchemy-serializer
|
settings.py
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
|
3 |
+
BASE_DIR_PATH = os.path.abspath(os.path.dirname(__file__))
|
4 |
+
|
5 |
+
DATABASE_PATH = os.path.join(BASE_DIR_PATH, 'db.sqlite3')
|
static/data/esempio_res_repliche.json
ADDED
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
[
|
2 |
+
{
|
3 |
+
"CodReplica": 1,
|
4 |
+
"Evento": {
|
5 |
+
"CodEvento": 1,
|
6 |
+
"NomeEvento": "Concerto di Chopin",
|
7 |
+
"Locale": {
|
8 |
+
"CodLocale": 1,
|
9 |
+
"Nome": "Music Lounge",
|
10 |
+
"Luogo": "Ponte3-Piano2",
|
11 |
+
"Posti": 150
|
12 |
+
}
|
13 |
+
},
|
14 |
+
"DataEOra": "27-07-2020-20:30:00",
|
15 |
+
"Annullato": false
|
16 |
+
},
|
17 |
+
{
|
18 |
+
"CodReplica": 2,
|
19 |
+
"Evento": {
|
20 |
+
"CodEvento": 1,
|
21 |
+
"NomeEvento": "Concerto di Chopin",
|
22 |
+
"Locale": {
|
23 |
+
"CodLocale": 1,
|
24 |
+
"Nome": "Music Lounge",
|
25 |
+
"Luogo": "Ponte3-Piano2",
|
26 |
+
"Posti": 150
|
27 |
+
}
|
28 |
+
},
|
29 |
+
"DataEOra": "28-07-2020-20:30:00",
|
30 |
+
"Annullato": false
|
31 |
+
},
|
32 |
+
{
|
33 |
+
"CodReplica": 3,
|
34 |
+
"Evento": {
|
35 |
+
"CodEvento": 1,
|
36 |
+
"NomeEvento": "Concerto di Chopin",
|
37 |
+
"Locale": {
|
38 |
+
"CodLocale": 1,
|
39 |
+
"Nome": "Music Lounge",
|
40 |
+
"Luogo": "Ponte3-Piano2",
|
41 |
+
"Posti": 150
|
42 |
+
}
|
43 |
+
},
|
44 |
+
"DataEOra": "29-07-2020-20:30:00",
|
45 |
+
"Annullato": false
|
46 |
+
},
|
47 |
+
{
|
48 |
+
"CodReplica": 4,
|
49 |
+
"Evento": {
|
50 |
+
"CodEvento": 2,
|
51 |
+
"NomeEvento": "Beethoven sul mare",
|
52 |
+
"Locale": {
|
53 |
+
"CodLocale": 1,
|
54 |
+
"Nome": "Music Lounge",
|
55 |
+
"Luogo": "Ponte3-Piano2",
|
56 |
+
"Posti": 150
|
57 |
+
}
|
58 |
+
},
|
59 |
+
"DataEOra": "01-08-2020-20:30:00",
|
60 |
+
"Annullato": false
|
61 |
+
},
|
62 |
+
{
|
63 |
+
"CodReplica": 5,
|
64 |
+
"Evento": {
|
65 |
+
"CodEvento": 3,
|
66 |
+
"NomeEvento": "Il ventaglio - Goldoni",
|
67 |
+
"Locale": {
|
68 |
+
"CodLocale": 2,
|
69 |
+
"Nome": "Teatro",
|
70 |
+
"Luogo": "Ponte8-Piano3",
|
71 |
+
"Posti": 350
|
72 |
+
}
|
73 |
+
},
|
74 |
+
"DataEOra": "30-07-2020-21:00:00",
|
75 |
+
"Annullato": false
|
76 |
+
},
|
77 |
+
{
|
78 |
+
"CodReplica": 6,
|
79 |
+
"Evento": {
|
80 |
+
"CodEvento": 4,
|
81 |
+
"NomeEvento": "Pool Brunch Party",
|
82 |
+
"Locale": {
|
83 |
+
"CodLocale": 4,
|
84 |
+
"Nome": "Piscina",
|
85 |
+
"Luogo": "Ponte5-Piano9",
|
86 |
+
"Posti": 150
|
87 |
+
}
|
88 |
+
},
|
89 |
+
"DataEOra": "27-07-2020-21:00:00",
|
90 |
+
"Annullato": true
|
91 |
+
}
|
92 |
+
]
|
static/imgs/prodotti/prodotto1.jpg
ADDED
static/imgs/prodotti/prodotto2.jpg
ADDED
static/imgs/prodotti/prodotto3.jpg
ADDED
static/imgs/shortcut-icon.png
ADDED
static/scripts/load_lotti.js
ADDED
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// elementi pagina
|
2 |
+
const rowLotti = document.querySelector('#row-lotti');
|
3 |
+
|
4 |
+
onLoad();
|
5 |
+
|
6 |
+
function onLoad() {
|
7 |
+
|
8 |
+
// eseguo la chiamata per ottenere i Lotti
|
9 |
+
// select dei Lotti con in join Prodotti e join con Produttori
|
10 |
+
|
11 |
+
const urlLotti = '/api/dati_lotti';
|
12 |
+
|
13 |
+
fetch(urlLotti).then(res => res.json()).then(data => {
|
14 |
+
console.log(data);
|
15 |
+
|
16 |
+
for(lotto of data) {
|
17 |
+
|
18 |
+
let renderPrenota = '';
|
19 |
+
if (lotto.sospeso) {
|
20 |
+
renderPrenota = '<a href="#" class="btn btn-danger disabled w-100">Sospeso</a>';
|
21 |
+
} else if (lotto.get_qta_disponibile == 0) {
|
22 |
+
renderPrenota = '<a href="#" class="btn btn-danger disabled w-100">Esaurito</a>';
|
23 |
+
} else {
|
24 |
+
renderPrenota = `<a href="/prenotazione/${lotto.id}?azione=nuovaPrenotazione" class="btn btn-primary w-100">Prenota</a>`;
|
25 |
+
}
|
26 |
+
|
27 |
+
rowLotti.innerHTML += `
|
28 |
+
<div class="col-lg-3 mb-3">
|
29 |
+
<div class="card mb-3 w-100 h-100">
|
30 |
+
<div class="card-header">
|
31 |
+
<h4 class="card-title">
|
32 |
+
${lotto.prodotto.nome}
|
33 |
+
</h4>
|
34 |
+
<div class="text-end"><small>(cod. lotto ${lotto.id})</small></div>
|
35 |
+
</div>
|
36 |
+
<div class="card-body d-flex flex-column">
|
37 |
+
<p class="card-text">
|
38 |
+
<small>Disponibile da:</small> <b>${lotto.get_date}</b>
|
39 |
+
</p>
|
40 |
+
<p class="card-text">
|
41 |
+
<small>Prodotto da:</small> <b>${lotto.prodotto.produttore.nome}</b>
|
42 |
+
</p>
|
43 |
+
<p class="card-text">
|
44 |
+
<small>Prezzo:</small> <b>${lotto.get_prezzo_str}</b>
|
45 |
+
</p>
|
46 |
+
<p class="card-text">
|
47 |
+
<small>Q.tà totale lotto:</small> <b>${lotto.qta_lotto}</b>
|
48 |
+
</p>
|
49 |
+
<p class="card-text">
|
50 |
+
<small>Q.tà disponibile:</small> <b>${lotto.get_qta_disponibile}</b>
|
51 |
+
</p>
|
52 |
+
<div class="mt-auto">
|
53 |
+
${renderPrenota}
|
54 |
+
</div>
|
55 |
+
</div>
|
56 |
+
</div>
|
57 |
+
</div>
|
58 |
+
`;
|
59 |
+
}
|
60 |
+
|
61 |
+
});
|
62 |
+
|
63 |
+
}
|
static/scripts/load_prenotazioni.js
ADDED
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// elementi pagina
|
2 |
+
const rowPrenotazioni = document.querySelector('#row-prenotazioni');
|
3 |
+
|
4 |
+
onLoad();
|
5 |
+
|
6 |
+
function onLoad() {
|
7 |
+
|
8 |
+
// eseguo la chiamata per ottenere le prenotazioni
|
9 |
+
// select delle Prenotazioni con in join Lotti, join con Prodotti e join con Produttori
|
10 |
+
|
11 |
+
const urlPrenotazioni = '/api/dati_prenotazioni';
|
12 |
+
|
13 |
+
fetch(urlPrenotazioni).then(res => res.json()).then(data => {
|
14 |
+
console.log(data);
|
15 |
+
|
16 |
+
if (data.length == 0) {
|
17 |
+
rowPrenotazioni.innerHTML = `<p>Non hai ancora effettuato prenotazioni, <a href="/">scopri i prodotti disponibili</a>!</p>`;
|
18 |
+
}
|
19 |
+
|
20 |
+
for(prenot of data) {
|
21 |
+
|
22 |
+
let renderModifica = '';
|
23 |
+
if(!prenot.lotto.sospeso) {
|
24 |
+
renderModifica = `<a href="/prenotazione/${prenot.lotto.id}?azione=aggiornaPrenotazione" class="btn btn-primary w-100">Modifica prenotazione</a>`;
|
25 |
+
} else {
|
26 |
+
renderModifica = '<a href="#" class="btn btn-danger disabled">Annullato</a>';
|
27 |
+
}
|
28 |
+
|
29 |
+
rowPrenotazioni.innerHTML += `
|
30 |
+
<div class="col-lg-3 mb-3">
|
31 |
+
<div class="card mb-3 w-100 h-100">
|
32 |
+
<div class="card-header">
|
33 |
+
<h4 class="card-title">
|
34 |
+
${prenot.lotto.prodotto.nome}
|
35 |
+
</h4>
|
36 |
+
<div class="text-end"><small>(cod. lotto ${prenot.lotto.id})</small></div>
|
37 |
+
</div>
|
38 |
+
<div class="card-body d-flex flex-column">
|
39 |
+
<p class="card-text">
|
40 |
+
<small>Disponibile da:</small> <b>${prenot.lotto.get_date}</b>
|
41 |
+
</p>
|
42 |
+
<p class="card-text">
|
43 |
+
<small>Prodotto da:</small> <b>${prenot.lotto.prodotto.produttore.nome}</b>
|
44 |
+
</p>
|
45 |
+
<p class="card-text">
|
46 |
+
<small>Prezzo:</small> <b>${prenot.lotto.prezzo_unitario}</b>
|
47 |
+
</p>
|
48 |
+
<p class="card-text">
|
49 |
+
<small>Q.tà totale lotto:</small> <b>${prenot.lotto.qta_lotto}</b>
|
50 |
+
</p>
|
51 |
+
<p class="card-text">
|
52 |
+
<small>Q.tà disponibile:</small> <b>${prenot.lotto.get_qta_disponibile}</b>
|
53 |
+
</p>
|
54 |
+
<div class="mt-auto alert alert-primary">
|
55 |
+
<p class="card-text">
|
56 |
+
<small>Q.tà prenotata:</small> <b>${prenot.qta} ${prenot.lotto.qta_unita_misura}</b>
|
57 |
+
</p>
|
58 |
+
<p class="card-text">
|
59 |
+
<small>Prezzo totale:</small> <b>${prenot.get_prezzo_totale_str}</b>
|
60 |
+
</p>
|
61 |
+
${renderModifica}
|
62 |
+
</div>
|
63 |
+
</div>
|
64 |
+
</div>
|
65 |
+
</div>
|
66 |
+
`;
|
67 |
+
}
|
68 |
+
|
69 |
+
});
|
70 |
+
|
71 |
+
}
|
static/styles/style.css
ADDED
File without changes
|
templates/_layout_base.html
ADDED
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html class="h-100" lang="it">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8">
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
+
|
7 |
+
<!-- TITLE -->
|
8 |
+
<title>{% block title %}- BioSquadra GAS{% endblock %}</title>
|
9 |
+
|
10 |
+
<link rel="shortcut icon" href="{{ url_for('static', filename='imgs/shortcut-icon.png') }}">
|
11 |
+
|
12 |
+
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
|
13 |
+
<link rel="stylesheet" href="{{ url_for('static', filename='styles/style.css') }}">
|
14 |
+
</head>
|
15 |
+
|
16 |
+
<body class="d-flex flex-column h-100">
|
17 |
+
|
18 |
+
<header>
|
19 |
+
<nav class="navbar navbar-expand-sm bg-dark navbar-dark">
|
20 |
+
<div class="container-fluid">
|
21 |
+
<!-- area logo/brand -->
|
22 |
+
<a class="navbar-brand" href="{{ url_for('home') }}">
|
23 |
+
<img src="/static/imgs/shortcut-icon.png" alt="Logo" width="30" height="30"
|
24 |
+
class="d-inline-block align-text-top">
|
25 |
+
BioSquadra
|
26 |
+
</a>
|
27 |
+
<!-- hamburger menu per responsive su mobile -->
|
28 |
+
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#collapsibleNavbar">
|
29 |
+
<span class="navbar-toggler-icon"></span>
|
30 |
+
</button>
|
31 |
+
<!-- voci di menù -->
|
32 |
+
<div class="collapse navbar-collapse" id="collapsibleNavbar">
|
33 |
+
<ul class="navbar-nav">
|
34 |
+
<li class="nav-item">
|
35 |
+
<a class="nav-link" href="{{ url_for('home') }}">Lotti disponibili</a>
|
36 |
+
</li>
|
37 |
+
{% if session.user_id %}
|
38 |
+
<li class="nav-item">
|
39 |
+
<a class="nav-link" href="{{ url_for('prenotazioni') }}">Prenotazioni</a>
|
40 |
+
</li>
|
41 |
+
<li class="nav-item">
|
42 |
+
<a class="nav-link" href="{{ url_for('logout') }}">Logout</a>
|
43 |
+
</li>
|
44 |
+
{% else %}
|
45 |
+
<li class="nav-item">
|
46 |
+
<a class="nav-link" href="{{ url_for('login') }}">Login</a>
|
47 |
+
</li>
|
48 |
+
{% endif %}
|
49 |
+
</ul>
|
50 |
+
</div>
|
51 |
+
</div>
|
52 |
+
</nav>
|
53 |
+
</header>
|
54 |
+
|
55 |
+
<main class="flex-shrink-0">
|
56 |
+
<section class="container mt-4">
|
57 |
+
<!-- H1 -->
|
58 |
+
<h1 class="mb-3">{% block h1 %} {% endblock %}</h1>
|
59 |
+
|
60 |
+
<!-- FLASH MESSAGES -->
|
61 |
+
{% with messages = get_flashed_messages(with_categories=true) %}
|
62 |
+
{% if messages %}
|
63 |
+
{% for category, message in messages %}
|
64 |
+
<div class="alert alert-{{ category }}" role="alert">
|
65 |
+
{{ message }}
|
66 |
+
</div>
|
67 |
+
{% endfor %}
|
68 |
+
{% endif %}
|
69 |
+
{% endwith %}
|
70 |
+
|
71 |
+
<!-- CONTENT -->
|
72 |
+
{% block content %} {% endblock %}
|
73 |
+
</section>
|
74 |
+
</main>
|
75 |
+
|
76 |
+
<footer class="bg-dark text-white text-center py-3 mt-auto">
|
77 |
+
<div class="container">
|
78 |
+
<p>© 2024 BioSquadra GAS. Alcuni diritti riservati.</p>
|
79 |
+
<p>
|
80 |
+
via Roma 100, Qualche Città, 12345, XY<br>
|
81 |
+
Phone: (+39) 123.4567.890 | Email: [email protected]
|
82 |
+
</p>
|
83 |
+
</div>
|
84 |
+
</footer>
|
85 |
+
|
86 |
+
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
|
87 |
+
{% block scripts %} {% endblock %}
|
88 |
+
|
89 |
+
</body>
|
90 |
+
</html>
|
templates/index.html
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{% extends "_layout_base.html" %}
|
2 |
+
|
3 |
+
{% block title %}Prodotti disponibili {{ super() }}{% endblock %}
|
4 |
+
|
5 |
+
{% block h1 %}Benvenuto nella piattaforma di prenotazione prodotti{% endblock %}
|
6 |
+
|
7 |
+
{% block content %}
|
8 |
+
|
9 |
+
{% if not session.user_id %}
|
10 |
+
<!-- Avviso inserito se l'utente non è loggato -->
|
11 |
+
<p>Per prenotare i prodotti, <a href="{{ url_for('login') }}">accedi</a>.</p>
|
12 |
+
{% endif %}
|
13 |
+
|
14 |
+
<div class="row" id="row-lotti">
|
15 |
+
<!-- riga popolata dinamicamente con una colonna BS contenente una card per ogni lotto -->
|
16 |
+
</div>
|
17 |
+
|
18 |
+
{% endblock %}
|
19 |
+
|
20 |
+
{% block scripts %}
|
21 |
+
<script src="{{ url_for('static', filename='scripts/load_lotti.js') }}"></script>
|
22 |
+
{% endblock %}
|
templates/login.html
CHANGED
@@ -1,26 +1,23 @@
|
|
1 |
-
|
2 |
-
<html lang="en">
|
3 |
|
4 |
-
|
5 |
-
<meta charset="UTF-8">
|
6 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
7 |
-
<title>Login</title>
|
8 |
-
</head>
|
9 |
|
10 |
-
|
11 |
-
<h2>Login</h2>
|
12 |
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
<
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
</
|
25 |
-
|
26 |
-
</
|
|
|
|
|
|
|
|
1 |
+
{% extends "_layout_base.html" %}
|
|
|
2 |
|
3 |
+
{% block title %}Login {{ super() }}{% endblock %}
|
|
|
|
|
|
|
|
|
4 |
|
5 |
+
{% block h1 %}Login{% endblock %}
|
|
|
6 |
|
7 |
+
{% block content %}
|
8 |
+
<div class="row justify-content-center">
|
9 |
+
<div class="col-md-6">
|
10 |
+
<form method="POST">
|
11 |
+
<div class="form-group mb-3">
|
12 |
+
<label for="email" class="form-label">Email</label>
|
13 |
+
<input type="email" class="form-control" id="email" name="email" required>
|
14 |
+
</div>
|
15 |
+
<div class="form-group mb-3">
|
16 |
+
<label for="password" class="form-label">Password</label>
|
17 |
+
<input type="password" class="form-control" id="password" name="password" required>
|
18 |
+
</div>
|
19 |
+
<button type="submit" class="btn btn-primary">Login</button>
|
20 |
+
</form>
|
21 |
+
</div>
|
22 |
+
</div>
|
23 |
+
{% endblock %}
|
templates/prenotazione.html
ADDED
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{% extends "_layout_base.html" %}
|
2 |
+
|
3 |
+
{% block title %}Prenotazione {{ super() }}{% endblock %}
|
4 |
+
|
5 |
+
{% block h1 %}
|
6 |
+
{% if prenotazione_esistente %}
|
7 |
+
{% if azione == 'nuovaPrenotazione' %}
|
8 |
+
Aggiungi alla tua prenotazione
|
9 |
+
{% elif azione == 'aggiornaPrenotazione' %}
|
10 |
+
Aggiorna la tua prenotazione
|
11 |
+
{% endif %}
|
12 |
+
{% else %}
|
13 |
+
Nuova prenotazione
|
14 |
+
{% endif %}
|
15 |
+
|
16 |
+
{% endblock %}
|
17 |
+
|
18 |
+
{% block content %}
|
19 |
+
<div class="row mb-4 justify-content-center">
|
20 |
+
<!-- lotto infos -->
|
21 |
+
<div class="col-md-6">
|
22 |
+
<div class="card">
|
23 |
+
<div class="card-header">
|
24 |
+
<h4 class="card-title">
|
25 |
+
{{ lotto.prodotto.nome }}
|
26 |
+
</h4>
|
27 |
+
<div class="text-end"><small>(cod. lotto {{ lotto.id }})</small></div>
|
28 |
+
</div>
|
29 |
+
<div class="card-body">
|
30 |
+
<p class="card-text">
|
31 |
+
<small>Disponibile da:</small> <b>{{ lotto.get_date() }}</b>
|
32 |
+
</p>
|
33 |
+
<p class="card-text">
|
34 |
+
<small>Prodotto da:</small> <b>{{ lotto.prodotto.produttore.nome }}</b>
|
35 |
+
</p>
|
36 |
+
<p class="card-text">
|
37 |
+
<small>Q.tà totale lotto:</small> <b>{{ lotto.qta_lotto }} {{ lotto.qta_unita_misura }}</b>
|
38 |
+
</p>
|
39 |
+
<p class="card-text">
|
40 |
+
<small>Q.tà disponibile:</small> <b>{{ lotto.get_qta_disponibile() }} {{ lotto.qta_unita_misura }}</b>
|
41 |
+
</p>
|
42 |
+
<p class="card-text">
|
43 |
+
<small>Prezzo:</small> <b>{{ lotto.get_prezzo_str() }}</b>
|
44 |
+
</p>
|
45 |
+
{% if prenotazione_esistente %}
|
46 |
+
<div class="row alert alert-primary text-center">
|
47 |
+
<div class="col card-text">
|
48 |
+
<small>Q.tà già prenotata:</small> <b>{{ prenotazione_esistente.qta }} {{ lotto.qta_unita_misura }}</b>
|
49 |
+
</div>
|
50 |
+
<div class="col card-text">
|
51 |
+
<small>Prezzo totale:</small> <b>{{ prenotazione_esistente.get_prezzo_totale_str() }}</b>
|
52 |
+
</div>
|
53 |
+
</div>
|
54 |
+
{% endif %}
|
55 |
+
</div>
|
56 |
+
<!-- Form per la creazione o la modifica di una prenotazione -->
|
57 |
+
<div class="card-footer">
|
58 |
+
<form method="POST">
|
59 |
+
<!-- <input type="hidden" name="lotto_id" value="{{ lotto.id }}"> -->
|
60 |
+
{% if azione == 'nuovaPrenotazione' %}
|
61 |
+
<div class="form-group mb-3">
|
62 |
+
<label for="quantita" class="form-label">Quantità</label>
|
63 |
+
<input type="number" class="form-control" id="quantita" name="quantita"
|
64 |
+
required min="1" max="{{ lotto.get_qta_disponibile() }}"
|
65 |
+
value="{{ quantita }}">
|
66 |
+
</div>
|
67 |
+
<button type="submit" class="btn btn-primary w-100" name="azione" value="nuovaPrenotazione">
|
68 |
+
{% if prenotazione_esistente %} Aggiungi quantità {% else %} Prenota quantità {% endif %}
|
69 |
+
</button>
|
70 |
+
{% elif azione == 'aggiornaPrenotazione' %}
|
71 |
+
<div class="form-group mb-3">
|
72 |
+
<label for="quantita" class="form-label">Quantità</label>
|
73 |
+
<input type="number" class="form-control" id="quantita" name="quantita"
|
74 |
+
required min="0" max="{{ lotto.get_qta_disponibile() + prenotazione_esistente.qta}}"
|
75 |
+
value="{{ quantita }}">
|
76 |
+
</div>
|
77 |
+
<button type="submit" class="btn btn-primary" name="azione" value="aggiornaPrenotazione">Modifica quantità</button>
|
78 |
+
<button type="submit" class="btn btn-danger float-end" name="azione" value="eliminaPrenotazione"
|
79 |
+
onclick="return confirm('Sei sicuro di voler eliminare la prenotazione?')">
|
80 |
+
Elimina prenotazione
|
81 |
+
</button>
|
82 |
+
{% endif %}
|
83 |
+
</form>
|
84 |
+
</div>
|
85 |
+
</div>
|
86 |
+
</div>
|
87 |
+
</div>
|
88 |
+
{% endblock %}
|
89 |
+
|
90 |
+
{% block scripts %}
|
91 |
+
<script src="{{ url_for('static', filename='scripts/load_prenotazione.js') }}"></script>
|
92 |
+
{% endblock %}
|
templates/prenotazioni.html
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{% extends "_layout_base.html" %}
|
2 |
+
|
3 |
+
{% block title %}Prenotazioni {{ super() }}{% endblock %}
|
4 |
+
|
5 |
+
{% block h1 %}Le tue prenotazioni{% endblock %}
|
6 |
+
|
7 |
+
{% block content %}
|
8 |
+
|
9 |
+
<div class="row" id="row-prenotazioni">
|
10 |
+
<!-- riga popolata dinamicamente con una colonna BS contenente una card per ogni prenotazione -->
|
11 |
+
</div>
|
12 |
+
|
13 |
+
{% endblock %}
|
14 |
+
|
15 |
+
{% block scripts %}
|
16 |
+
<script src="{{ url_for('static', filename='scripts/load_prenotazioni.js') }}"></script>
|
17 |
+
{% endblock %}
|