Flask (parte 3): database
Cominciamo a definire il database definendo gli oggetti che rappresenteranno le entità
in relazione tra loro.
Nel file fantamanager/app/models.py cominciamo con l’importare ciò di cui abbiamo bisogno:
from . import db from datetime import datetime
con la prima linea di codice torniamo indietro di una posizione (app) e trovando un
file __init__.py dove abbiamo definito db, lo importiamo
Ora definiamo degli oggetti molto scarni, con solo un id rappresentativo e la relazione
che lega una entità, all’altra.
class User(db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) teams = db.relationship('Team', backref='user') # M2M secondary tables leagues_teams = db.Table('leagues_teams', db.Column('league_id', db.Integer, db.ForeignKey('leagues.id')), db.Column('team_id', db.Integer, db.ForeignKey('teams.id'))) # M2M secondary Association object class LineupsPlayers(db.Model): __tablename__ = 'lineups_players' player_id = db.Column(db.Integer, db.ForeignKey('players.id'), primary_key=True) lineup_id = db.Column(db.Integer, db.ForeignKey('lineups.id'), primary_key=True) player = db.relationship('Player', backref=db.backref('form_ass', lazy='dynamic')) position = db.Column(db.Integer) class League(db.Model): __tablename__ = 'leagues' id = db.Column(db.Integer, primary_key=True) matches = db.relationship('Match', backref='league', lazy='dynamic') teams = db.relationship('Team', secondary=leagues_teams, backref=db.backref('all_leagues', lazy='dynamic'), lazy='dynamic') class Team(db.Model): __tablename__ = 'teams' id = db.Column(db.Integer, primary_key=True) leagues = db.relationship('League', secondary=leagues_teams, backref=db.backref('all_teams', lazy='dynamic'), lazy='dynamic') players = db.relationship('Player', backref='team', lazy='dynamic') lineups = db.relationship('Lineup', backref='team', lazy='dynamic') trades = db.relationship('Trade', backref='team', lazy='dynamic') user_id = db.Column(db.Integer, db.ForeignKey('users.id')) class Player(db.Model): __tablename__ = 'players' id = db.Column(db.Integer, primary_key=True) team_id = db.Column(db.Integer, db.ForeignKey('teams.id'),) evaluations = db.relationship('Evaluation', backref='player', lazy='dynamic') class Match (db.Model): __tablename__ = 'matches' id = db.Column(db.Integer, primary_key=True) league_id = db.Column(db.Integer, db.ForeignKey('leagues.id')) home_id = db.Column(db.Integer, db.ForeignKey('teams.id'),) visit_id = db.Column(db.Integer, db.ForeignKey('teams.id'),) home = db.relationship('Team', foreign_keys='Match.home_id', primaryjoin="Match.home_id==Team.id") visit = db.relationship('Team', foreign_keys='Match.visit_id', primaryjoin="Match.visit_id==Team.id") class Lineup (db.Model): __tablename__ = 'lineups' id = db.Column(db.Integer, primary_key=True) team_id = db.Column(db.Integer, db.ForeignKey('teams.id')) players = db.relationship('Player', secondary='lineups_players', backref=db.backref('lineups', lazy='dynamic')) def get_players_by_position(self): lu_players = LineupsPlayers.query.filter_by(lineup_id=self.id).order_by( LineupsPlayers.position).all() if lu_players: return [rec.player for rec in lu_players] class Evaluation(db.Model): __tablename__ = 'evaluations' id = db.Column(db.Integer, primary_key=True) player_id = db.Column(db.Integer, db.ForeignKey('players.id')) class Trade(db.Model): __tablename__ = 'trades' id = db.Column(db.Integer, primary_key=True) team_id = db.Column(db.Integer, db.ForeignKey('teams.id'),) player_id = db.Column(db.Integer, db.ForeignKey('players.id'),) player = db.relationship('Player', foreign_keys='Trade.player_id', primaryjoin="Trade.player_id==Player.id")
Sommariamente:
User è in relazione “uno a molti” con Team poichè come utente posso
partecipare con più squadre. mentre una squadra non può essere di più utenti.
League è in relazione “molti a molti” con Team, poichè si potrebbe, parallelamente
alla League, partecipare anche ad una coppa con la stessa squadra.
Team è in relazione “uno a molti” con Player, poichè un giocatore non può
far parte di più squadre. Qui si acuista un giocatore tramite asta.
E così via per le altre entità.
Cominciamo a creare il database poi, per gradi, aggiungeremo i campi che
ci servono grazie alle migrations.
Portiamoci al livello di fantamanager/manage.py e se abbiamo ancora la dir migrations,
rappresentativa della struttura di un progetto complesso, rimuoviamola.
Ora lanciamo il comando di inizializzazione del database:
python manage.py db init
(su ubuntu si può omettere il comando python, avendo utilizzato il shebang)
>python manage.py db init Creating directory ...\fantamanager\migrations ... done Creating directory ...\fantamanager\migrations\versions ... done ...
il db non è ancora stato creato, ma possiamo notare che la dir migration è stata creata.
Procediamo con il comando che genererà il primo script di migration:
python manage.py db migrate -m "first migration"
e il comando che rende la modifica permanente:
python manage.py db upgrade
>python manage.py db upgrade INFO [alembic.migration] Context impl SQLiteImpl. INFO [alembic.migration] Will assume non-transactional DDL.
Ora apriamo il file manage.py e rendiamo disponibili per la shell tutti gli
oggetti, in modo da non reimportarli ogni volta.
La funzione make_shell_context sarà:
def make_shell_context(): return dict(app=app, db=db, User=User, LineupsPlayers=LineupsPlayers, League=League, Team=Team, Player=Player, Match=Match, Lineup=Lineup, Evaluation=Evaluation, Trade=Trade)
Ora sistemiamo i nostri oggetti nel file models.py inquanto non
abbiamo inserito nessun campo, ma solo le relazioni tra gli oggetti stessi
es di User, League e Team migliorati:
#... class User(db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) teams = db.relationship('Team', backref='user') email = db.Column(db.String(64), unique=True, index=True) username = db.Column(db.String(64), unique=True, index=True) #... class League(db.Model): __tablename__ = 'leagues' id = db.Column(db.Integer, primary_key=True) matches = db.relationship('Match', backref='league', lazy='dynamic') teams = db.relationship('Team', secondary=leagues_teams, backref=db.backref('all_leagues', lazy='dynamic'), lazy='dynamic') name = db.Column(db.String(40), unique=True) budget = db.Column(db.Integer) max_trades = db.Column(db.Integer) max_goalkeepers = db.Column(db.Integer) max_defenders = db.Column(db.Integer) max_midfielders = db.Column(db.Integer) max_forwards = db.Column(db.Integer) rounds = db.Column(db.Integer) offset = db.Column(db.Integer) #... class Team(db.Model): __tablename__ = 'teams' id = db.Column(db.Integer, primary_key=True) leagues = db.relationship('League', secondary=leagues_teams, backref=db.backref('all_teams', lazy='dynamic'), lazy='dynamic') players = db.relationship('Player', backref='team', lazy='dynamic') lineups = db.relationship('Lineup', backref='team', lazy='dynamic') trades = db.relationship('Trade', backref='team', lazy='dynamic') user_id = db.Column(db.Integer, db.ForeignKey('users.id')) name = db.Column(db.String(50), unique=True) budget = db.Column(db.Integer()) max_trades = db.Column(db.Integer()) #...
creiamo la seconda migration con il comando:
python manage.py db migrate -m "user, team, league fields added"
>python manage.py db migrate -m "user, team, league fields added" INFO [alembic.migration] Context impl SQLiteImpl. INFO [alembic.migration] Will assume non-transactional DDL. INFO [alembic.autogenerate.compare] Detected added table 'users' INFO [alembic.autogenerate.compare] Detected added index 'ix_users_email' on '['email']' INFO [alembic.autogenerate.compare] Detected added index 'ix_users_username' on '['username']' INFO [alembic.autogenerate.compare] Detected added table 'leagues' INFO [alembic.autogenerate.compare] Detected added table 'matches' INFO [alembic.autogenerate.compare] Detected added table 'teams' INFO [alembic.autogenerate.compare] Detected added table 'trades' INFO [alembic.autogenerate.compare] Detected added table 'leagues_teams' INFO [alembic.autogenerate.compare] Detected added table 'players' INFO [alembic.autogenerate.compare] Detected added table 'lineups' INFO [alembic.autogenerate.compare] Detected added table 'lineups_players' INFO [alembic.autogenerate.compare] Detected added table 'evaluations' Generating C:\fantamanager\migrations\versions\3ebec3a59744_user_team_league_fields_added.py ... done
lo script di migration è generato, per renderlo effettivo facciamo l’upgrade:
python manage.py db upgrade
Nel caso si volesse tornare indietro, basterà al posto di “upgrade” utilizzare “downgrade”
>python manage.py db upgrade INFO [alembic.migration] Context impl SQLiteImpl. INFO [alembic.migration] Will assume non-transactional DDL. INFO [alembic.migration] Running upgrade -> 3ebec3a59744, user, team, league fields added
Ora possiamo entrare nella shell furba con il comando:
python manage.py shell
Creiamo un paio di Users senza salvarli su db:
>>> u1 = User(username="user_1", email="[email protected]") >>> u2 = User(username="user_2", email="[email protected]")
i dati senza il comando “add” e “commit” non sono salvati, infatti:
>>> User.query.all() []
se richiamiamo le istanze di User create, ci accorgiamo che non
sono proprio riconoscibili infatti:
>>> u1 <app.models.User object at 0x039A9E30> >>> u2 <app.models.User object at 0x039A9270>
Per renderle più amichevoli, dobbiamo utilizzare il metodo __repr__
>>> def myrepr(self): ... return "<User %r>" % self.username ... >>> User.__repr__ = myrepr >>> u1 <User 'user_1'> >>> u2 <User 'user_2'>
Ok, visto che ci piace, rendiamo effettiva questa modifica nel file models.py
aggiungendo il metodo __repr__ in ogni classe, come più ci piace.
Ora che abbiamo visto come lavorare sul database, modificandolo, focalizziamoci
sulle singole classi per introdurre anche le view e le template.
articolo successivo:
Flask (parte 4): views e templates
articoli precedenti:
Flask (parte 1): virtualenv
Flask (parte 2): struttura progetto complesso
link utili:
“Flask Web Development” di Miguel Grinberg, fondamentale per calarsi nel mondo di Flask.
Altri link fondamentali:
Flask sito ufficiale
il blog dell’autore (Flask mega tutorial)
Commenti recenti