Home > flask, python > Flask (parte 3): database

Flask (parte 3): database

30 Giugno 2015

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)

Categorie:flask, python Tag: ,
I commenti sono chiusi.