Walter Mantovani commited on
Commit
d20dd7e
·
1 Parent(s): 0e78cd8

nuova app caricata per test

Browse files
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 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)
 
 
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
- gunicorn
 
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>&copy; 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
- <!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>
 
 
 
 
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 %}