Warning: Creating default object from empty value in /membri/bancaldo/wp-content/plugins/paged-comments/paged-comments.php on line 31
bancaldo™ » sqlalchemy

Archivio

Archivio per la categoria ‘sqlalchemy’

Python: FantaStat 1.1 Light

15 dicembre 2011 Commenti chiusi

Si sta avvicinando il mercato di riparazione, quindi
ho creato un piccolo tool con Python, per tutti i FantaAllenatori,
che non vogliono arrivare impreparati all’asta invernale.

download:eseguibile winsorgenti python
Funziona come i fratelli maggiori FantaManager Full e
FantaManager Light, cioè si importano i file MCCxx.txt,
e si consultano le varie medie voto, gol total/bancaldo.wordpress.com/2011/08/05/lista-calciatori-gazzetta-2011-2012/”>file MCCxx.txt,
e si consultano le varie medie voto, gol totali, presenze,
di ogni singolo giocatore.

L’interfaccia è semplicissima (utilizzate le solite wx):

un bottone per importare il file MCC, un bottone per uscire,
una list-control per visualizzare i dati del database ed
un radiobox per visualizzare i calciatori, per ruolo.
Le colonne della list-control, ordinano in modo di, presenze,
di ogni singolo giocatore.

L’interfaccia è semplicissima (utilizzate le solite wx):

un bottone per importare il file MCC, un bottone per uscire,
una list-control per visualizzare i dati del database ed
un radiobox per visualizzare i calciatori, per ruolo.
Le colonne della list-control, ordinano in modo decrescente i
dati, eccetto nome e squadra (che francamente non incidono
sulla qualità della ricerca)

Il database è generato e gestito con Sqlite e SqlAlchemy.
Gli oggetti riferiti al db ed sqla (tabelle, campi ecc) sono definiti
in non-declarative perchè mi piace semplificare il porting su web con
Pylons (ho trovato difficoltà quando ho definito gli oggetti in
declarative)

Come sempre, ho cercato di utilizzare il pattern MVC,
spero riducendo al minimo gli errori di stile.

NOTA:Ricordarsi di posizionare le dirs /images e /giornate
dove si troverà l’eseguibile o i sorgenti.

Qui i codici (sicuramente migliorabili):

Model:

# -*- coding: utf-8 -*-#
# DataLight.py
'''MODEL Module for FantaStat Application:
Database engine: provided by Sqlite
Database O.R.M: provided by SqlAlchemy (no_declarative)
All data for fantapython manage are contained here.
All the O.R.M. operation on data are defined here as
Model-Class methods.
''' 

from sqlalchemy import create_engine, Column, Integer, Float
from sqlalchemy import ForeignKey, Table
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import aliased
from sqlalchemy.orm import sessionmaker
from sqlalchemy import orm, func, desc, asc
from sqlalchemy import MetaData
from sqlalchemy.types import Unicode

class Giocatore(object):
    '''class for sqla mapper'''
    pass

class Voto(object):
    '''class for sqla mapper'''
    pass

class Model(object): 
    '''Model class with common application data'''
    
    base = declarative_base()
    engine = create_engine('sqlite:///fantastat.db', echo = False)
    metadata = MetaData()
    
    giocatore_table = Table('giocatori', metadata,
        Column('id', Integer(), primary_key=True),
        Column('idgaz', Integer()),
        Column('nome', Unicode(255)),
        Column('squadra', Unicode(255)),
        Column('valore', Integer()),
        Column('ruolo', Unicode(255)),
    )
    
    voto_table = Table('voti', metadata,
        Column('id', Integer(), primary_key=True),
        Column('voto', Float()),
        Column('gg_', Integer()),
        Column('val', Integer()),
        Column('gol_fatti', Integer()),
        Column('pres', Integer()),
        Column('gol_sub', Integer()),
        Column('assist', Integer()),
        Column('ammo', Integer()),
        Column('esp', Integer()),
        Column('rig_tir', Integer()),
        Column('rig_ok', Integer()),
        Column('rig_no', Integer()),
        Column('voto_s', Float()),
    )
    
    # M2M relation
    giocatori_voti_table = Table('giocatori_voti', metadata,
        Column('giocatori_id', Integer, ForeignKey('giocatori.id')),
        Column('voti_id', Integer, ForeignKey('voti.id')),
    )

    # Map SQLAlchemy table definitions to python classes
    orm.mapper(Giocatore, giocatore_table, properties={
        'voti':orm.relation(Voto, secondary = giocatori_voti_table),})
    
    orm.mapper(Voto, voto_table, properties={
        'giocatori':orm.relation(Giocatore, secondary = giocatori_voti_table),})
    
    def __init__(self):
        
        Model.metadata.create_all(Model.engine)
        session = sessionmaker(bind = Model.engine)
        self.session = session()
        self.ruoli = ['portiere', 'difensore', 'ccampista', 'attaccante']
        self.value_state, self.val_right_state = 'asc', 'asc'
        self.name_state, self.name_right_state = 'asc', 'asc'
        self.player_to_change = None
        self.team_a, self.ruolo = '', ''
        self.cols = {0: Giocatore.nome, 1: Giocatore.squadra,
                     2: Voto.voto, 3: Voto.pres,
                     4: (Voto.gol_fatti + Voto.rig_ok),
                     5: Voto.gol_sub, 6: Voto.assist, 7: Voto.ammo,
                     8: Voto.esp, 9: Voto.rig_tir, 10: Voto.rig_no,
                     11: Giocatore.valore}
        
    def get_data_players(self, ruolo, item = Voto.voto):
        '''get the avg of vote and all total data by role'''
        query = self.session.query(Giocatore).filter(
                            Giocatore.ruolo == ruolo).subquery()
        adalias = aliased(Giocatore, query)

        query_p = self.session.query(adalias.nome, adalias.idgaz,
                          func.avg(Voto.voto),
                          func.sum(Voto.pres),
                          func.sum(Voto.gol_fatti+Voto.rig_ok),
                          func.sum(Voto.gol_sub),
                          func.sum(Voto.assist),
                          func.sum(Voto.ammo),
                          func.sum(Voto.esp),
                          func.sum(Voto.rig_tir),
                          func.sum(Voto.rig_no),
                          adalias.valore,
                          adalias.squadra).join( # < JOIN
                            adalias, Voto.giocatori).filter(Voto.pres > 0)
                            .group_by(adalias).order_by( # < GROUP & SORT
                                    asc(func.avg(item))).all()
        return query_p

    def get_players_sort_name(self, ruolo):
        '''Get all available goalkeeper'''
        if self.name_state == 'asc':
            query = self.session.query(Giocatore).filter(
                                        Giocatore.ruolo == '%s' % ruolo
                                        ).order_by(desc(Giocatore.idgaz)).all()
        else:
            query = self.session.query(Giocatore).filter(
                                          Giocatore.ruolo == '%s' % ruolo
                                          ).order_by((Giocatore.idgaz)).all()
        return query

    def get_inserted_days(self): # for checking days
        '''get inserted votes-days for import-votes checking'''
        ggs = [item[0] for item in self.session.query(func.distinct(Voto.gg_)).all()]
        return ggs

    def get_player_by_idgaz(self, idgaz):
        '''Get the player by idgaz'''
        player = self.session.query(Giocatore).filter(
                                    Giocatore.idgaz == idgaz).first()
        return player

    def do_players_exist(self): # check at the first time
        '''Return True if players are already imported'''
        if self.session.query(Giocatore).count() != 0:
            print "Gazzetta players present in database"
            return True
        else:
            print "Gazzetta players must be imported first!"
            return False 
        
    def import_new_player(self, idgaz, nome, squadra, valore, ruolo):
        '''Add new player on "giocatori" table'''
        player = Giocatore()
        player.idgaz = idgaz
        player.nome = nome
        player.squadra = squadra
        player.valore = valore
        player.ruolo = ruolo
        self.session.add(player)
        
    def upload_votes(self, item):
        '''Save the data-tuble of votes file into database'''
        print "UPLOAD", item
        rig_ok = int(item[10]) - int(item[11])
        voto = Voto()
        voto.voto = item[1]
        voto.gg_ = item[2]
        voto.val = item[3]
        voto.gol_fatti = item[4]
        voto.pres = item[5]
        voto.gol_sub = item[6]
        voto.assist = item[7]
        voto.ammo = item[8]
        voto.esp = item[9]
        voto.rig_tir = item[10]
        voto.rig_ok = rig_ok
        voto.rig_no = item[11]
        voto.voto_s = item[12]
        self.session.add(voto)
        #m2m append
        voto.giocatori.append(self.get_player_by_idgaz(item[0])) # pylint: disable=E1101,C0301
        return True

    def total_commit(self):
        '''external commit to use outside loop'''
        self.session.commit()

    def update_player_by_idgaz(self, idgaz, nome, squadra, valore, ruolo):
        '''Get the player by idgaz'''
        player = self.session.query(Giocatore).filter(
                                    Giocatore.idgaz == idgaz).first()
        player.idgaz = idgaz
        player.nome = nome
        player.squadra = squadra
        player.valore = int(valore)
        player.ruolo = ruolo
        
    def get_data_players_by_value(self, ruolo, jolly):
        '''generic query that uses subquery, alias and join function:
        return the sum of a Giocatore.value itering the votes, by idgaz'''
        query = self.session.query(Giocatore).filter(
                            Giocatore.ruolo == ruolo).subquery()
        adalias = aliased(Giocatore, query)
        query_p = self.session.query(adalias.nome, adalias.idgaz,
                          func.avg(Voto.voto),
                          func.sum(Voto.pres),
                          func.sum(Voto.gol_fatti+Voto.rig_ok),
                          func.sum(Voto.gol_sub),
                          func.sum(Voto.assist),
                          func.sum(Voto.ammo),
                          func.sum(Voto.esp),
                          func.sum(Voto.rig_tir),
                          func.sum(Voto.rig_no),
                          adalias.valore,
                          adalias.squadra).join( # < JOIN
                            adalias, Voto.giocatori).filter(Voto.pres > 0)
                            .group_by(adalias).order_by( # < GROUP & SORT
                                    asc(func.sum(jolly))).all()
        return query_p

    def get_data_players_by_valore(self, ruolo):
        '''generic query that uses subquery, alias and join function:
        return all total data player sorted by value'''
        query = self.session.query(Giocatore).filter(
                            Giocatore.ruolo == ruolo).subquery()
        adalias = aliased(Giocatore, query)
        query_p = self.session.query(adalias.nome, adalias.idgaz,
                          func.avg(Voto.voto),
                          func.sum(Voto.pres),
                          func.sum(Voto.gol_fatti+Voto.rig_ok),
                          func.sum(Voto.gol_sub),
                          func.sum(Voto.assist),
                          func.sum(Voto.ammo),
                          func.sum(Voto.esp),
                          func.sum(Voto.rig_tir),
                          func.sum(Voto.rig_no),
                          adalias.valore,
                          adalias.squadra).join( # < JOIN
                            adalias, Voto.giocatori).filter(Voto.pres > 0)
                            .group_by(adalias).order_by( # < GROUP & SORT
                                    asc(adalias.valore)).all()
        return query_p

def main():
    '''test starter'''
    mod = Model()


if __name__ == '__main__':
    main()

Qui il codice relativo al View:

# -*- coding: utf-8 -*-#
# ViewLight.py
'''VIEW Module for FantaPython Application:
Graphic library: provided by wx
All the frames and their children widgets,
are defined here.
Binding are defined under CONTROLLER Module.
Every Frame as a panel child and every widgets
have panel as first parent.
''' 


import wx
import os

from wx import html as wxhtml
from wx.lib.buttons import GenBitmapTextButton

class FrameVotiPlayers(wx.Frame):
    '''Frame for votes consulting'''
    def __init__(self, *args, **kwargs):
        ruoli = ['portiere', 'difensore', 'ccampista', 'attaccante']
        wx.Frame.__init__(self, *args, **kwargs)
        self.panel = wx.Panel(self, -1)
        self.panel.SetBackgroundColour('Pink')
        self.btn_import = GenBitmapTextButton(self.panel, wx.ID_NEW,
                                            wx.Bitmap('images\import_tr.png'),
                                            'Import players/votes'.rjust(30),
                                            size = (350, 40), pos = (5, 425))
        self.btn_exit = GenBitmapTextButton(self.panel, wx.ID_ANY,
                                            wx.Bitmap('images\quit.png'),
                                            'Exit'.rjust(30), size = (335, 40),
                                            pos = (355, 425))
        self.rboxruoli = wx.RadioBox(self.panel, wx.ID_ANY, "ruoli", (100, 100),
                                     wx.DefaultSize, ruoli, 4,
                                     wx.RA_SPECIFY_COLS)
        self.rboxruoli.Enable()
        self.listdati = wx.ListCtrl(self.panel, wx.NewId(), size = (690, 350),
                                  style = wx.LC_REPORT | wx.LC_HRULES |
                                  wx.LC_VRULES)
        self.listdati.Show(True)
        self.listdati.InsertColumn(0, "nome", wx.LIST_AUTOSIZE, 140)
        self.listdati.InsertColumn(1, "sq", wx.LIST_FORMAT_CENTER, 70)
        self.listdati.InsertColumn(2, "mv", wx.LIST_FORMAT_CENTER, 70)
        self.listdati.InsertColumn(3, "pr", wx.LIST_FORMAT_CENTER, 40)
        self.listdati.InsertColumn(4, "gf", wx.LIST_FORMAT_CENTER, 40)
        self.listdati.InsertColumn(5, "gs", wx.LIST_FORMAT_CENTER, 40)
        self.listdati.InsertColumn(6, "ass", wx.LIST_FORMAT_CENTER, 40)
        self.listdati.InsertColumn(7, "amm", wx.LIST_FORMAT_CENTER, 50)
        self.listdati.InsertColumn(8, "esp", wx.LIST_FORMAT_CENTER, 40)
        self.listdati.InsertColumn(9, "rt", wx.LIST_FORMAT_CENTER, 40)
        self.listdati.InsertColumn(10, "rs", wx.LIST_FORMAT_CENTER, 40)
        self.listdati.InsertColumn(11, "$", wx.LIST_FORMAT_CENTER, 35)

        # sizerize
        vbox = wx.BoxSizer(wx.VERTICAL)
        vbox.Add(self.rboxruoli, 0, wx.CENTER, 5)
        vbox.Add(wx.StaticLine(self.panel,), 0, wx.ALL|wx.EXPAND, 5)
        vbox.Add(self.listdati, 0, wx.ALL|wx.CENTER, 5)
        vbox.Add(wx.StaticLine(self.panel,), 0, wx.ALL|wx.EXPAND, 5)

        self.panel.SetSizer(vbox)
        self.Centre()
        self.Show()

class InfoMessage(wx.MessageDialog):
    '''Simple message Dialog'''
    def __init__(self, parent, message):
        wx.MessageDialog.__init__(self, parent, message, 'core info', wx.OK |
                                  wx.ICON_EXCLAMATION)
    def get_choice(self):
        '''get the state of the user choice'''
        if self.ShowModal() == wx.ID_OK:
            self.Destroy()

class ChoiceMessage(wx.MessageDialog):
    '''Simple choice message Dialog'''
    def __init__(self, parent, message):
        wx.MessageDialog.__init__(self, parent, message, 'Core question',
                                  wx.YES_NO | wx.ICON_QUESTION)
    def get_yes(self):
        '''get True if YES is clicked'''
        if self.ShowModal() == wx.ID_YES:
            return True
        else:
            self.Destroy()

class EntryDialog(wx.TextEntryDialog):            
    '''Simple Text Entry Dialog'''
    def __init__(self, parent, msg, value):
        wx.TextEntryDialog.__init__(self, parent, msg, 'Core request',
                                    defaultValue = value, style = wx.OK)
    def get_choice(self):
        '''get the state of the user choice'''
        if self.ShowModal() == wx.ID_OK:
            response = self.GetValue()
            self.Destroy()
            return response

class FileBrowser(wx.FileDialog):
    '''Class for file browser'''
    def __init__(self):
        self.fin = None
        wildcard = "File Gazzetta (*.txt)|*.txt|" 
            "Tutti i files (*.*)|*.*"
        wx.FileDialog.__init__(self, None, "scegli il file", os.getcwd(),
                               "", wildcard, wx.OPEN)
        if self.ShowModal() == wx.ID_OK:  
            print(self.GetPath())
            self.file = self.GetPath()
            self.fin = open(self.file, 'r')
        else:
            print "operazione apertura annullata"
            self.file = None
            self.Destroy()
        self.Destroy()
def main():
    '''app starter'''
    app = wx.PySimpleApp()
    FrameVotiPlayers(None, wx.ID_NEW, "FantaStat 1.1 light", size = (700, 500))
    app.MainLoop()

if __name__ == '__main__':
    main()

infine il codice relativo al COntroller:

# -*- coding: utf-8 -*-#
# ControllerLight.py
'''FantaStat v 1.1 Light
Controller Module for FantaStat Application:
Application pattern:
MODEL | VIEW | CONTROLLER
MODEL Module COntains all data for database querying
VIEW Module Contains all GUI for viewing all database data
CONTROLLER Module contains app logic.
All the binding method to frame widget are defined here''' 

import wx

from DataLight import Model
from ProgressBar import BarController
from ViewLight import FrameVotiPlayers, InfoMessage, ChoiceMessage, FileBrowser

class Controller(object):
    '''Controller class for MVC-like pattern'''
    def __init__(self):
        self.model = Model()
        self.list_voti = []
        self.ctrl = FrameVotiPlayers(None, wx.ID_NEW, "FantaStat 1.1 light",
                                     size = (700, 500))
        self.ctrl.btn_exit.Bind(wx.EVT_BUTTON, _frame_exit)
        self.ctrl.btn_import.Bind(wx.EVT_BUTTON, self.import_votes)
        self.query = self.model.get_data_players(u'portiere')
        self.fill_list(self.query)
        self.ctrl.rboxruoli.Bind(wx.EVT_RADIOBOX, self.on_roles)
        self.ctrl.listdati.Bind(wx.EVT_LIST_COL_CLICK, self.on_list_column)

    def upload_players(self, file_in):
        '''upload/upgrade players in db via model'''
        def get_flag_role(idg):
            """get the role, by idgaz"""
            if idg < 200:
                role = 'portiere'
            elif idg > 200 and idg < 500:
                role = 'difensore'
            elif idg > 500 and idg < 800:
                role = 'ccampista'
            else:
                role = 'attaccante'
            return role

        if file_in != None:
            record = len(file_in.readlines()) #progress bar param
            barc = BarController(record)
            file_in.seek(0)
            for line in file_in.readlines():
                val = line.split('|')
                try:
                    idr = int(val[0])
                except ValueError:
                    idr = int(val[0].replace('xefxbbxbf', ''))
                
                role = get_flag_role(idr)
                nomev = val[2].decode('utf-8').replace('"', '')
                squadrav = val[3].replace('"', '')
                play_in = self.model.get_player_by_idgaz(idr)
                if play_in == None:
                    print " <<<< assente giocatore con idgaz = ", idr
                    self.model.import_new_player(idr, nomev, squadrav,
                                                 int(val[27].rstrip()), role)
                else:
                    print " ++++ aggiorno giocatore con idgaz = ", idr
                    self.model.update_player_by_idgaz(idr, nomev,
                                        squadrav, int(val[27]), role)
                barc.update_progress_bar()
            self.model.session.commit()
            barc.destroy_progress_bar()
            print "core> dati inseriti con successo"
        else:
            print "core> Nessun dato da salvare"
    
    def import_votes(self, evt):
        '''Import Votes from Gazzetta.com'''
        parent = evt.GetEventObject().GetParent().GetParent()
        ggs = self.model.get_inserted_days()
        mes = "Inserire i voti nel database?"
        if ChoiceMessage(parent, mes).get_yes() == True:
            fbr = FileBrowser()
            file_in = fbr.fin
            path = fbr.file
            if file_in != None:
                choice = int(path.partition('MCC')[-1].partition('.txt')[0])
                if choice not in ggs:
                    try:
                        self.upload_players(file_in)
                    except ValueError:
                        print "core> Operazione annullata"
                    else:
                        self.list_voti = []
                        file_in.seek(0)
                        for line in file_in:
                            self.list_voti.append((int(line.split("|")[0]),
                                                   float(line.split("|")[7]),
                                                   int(line.split("|")[1]),
                                                   int((line.split("|")[27]
                                                        ).rstrip()),
                                                   int(line.split("|")[11]),
                                                   int(line.split("|")[6]),
                                                   int(line.split("|")[12]),
                                                   int(line.split("|")[15]),
                                                   int(line.split("|")[16]),
                                                   int(line.split("|")[17]),
                                                   int(line.split("|")[18]),
                                                   int(line.split("|")[21]),
                                                   float(line.split("|")[10])))
                        file_in.close()
                        print "core> pre RECORD"
                        record = len(self.list_voti)
                        if record > 0:
                            upbar = BarController(record)
                            for item in self.list_voti: 
                                self.model.upload_votes(item)
                                if True:
                                    upbar.update_progress_bar()
                            self.model.total_commit()
                            print "core> Dati Salvati con successo"
                            upbar.destroy_progress_bar()
                            role = self.ctrl.rboxruoli.GetStringSelection()
                            self.ctrl.listdati.DeleteAllItems()
                            self.fill_list(self.model.get_data_players(role))
                else:
                    mes = "errore file. Giornata gia' inserita!"
                    InfoMessage(parent, mes).get_choice()
        else:
            print "core> nessun dato da salvare"
    
    def fill_list(self, sqla_query):
        '''Fill the ListControl with stats data from db'''
        self.ctrl.listdati.DeleteAllItems()
        for player in sqla_query:
            nome = player[0]
            row = self.ctrl.listdati.InsertStringItem(0, nome)#ROW
            sq = player[12]
            self.ctrl.listdati.SetStringItem(row, 1, str(sq)) #1st C
            m_v = player[2]
            self.ctrl.listdati.SetStringItem(row, 2, str("%.3f" % m_v))
            pres = player[3]
            self.ctrl.listdati.SetStringItem(row, 3, str(pres)) 
            g_f = player[4]
            self.ctrl.listdati.SetStringItem(row, 4, str(g_f))
            g_s = player[5]
            self.ctrl.listdati.SetStringItem(row, 5, str(g_s))
            ass = player[6]
            self.ctrl.listdati.SetStringItem(row, 6, str(ass))
            ammo = player[7]
            self.ctrl.listdati.SetStringItem(row, 7, str(ammo))
            esp = player[8]
            self.ctrl.listdati.SetStringItem(row, 8, str(esp))
            r_t = player[9]
            self.ctrl.listdati.SetStringItem(row, 9, str(r_t))
            r_s = player[10]
            self.ctrl.listdati.SetStringItem(row, 10, str(r_s))
            val = player[11]
            self.ctrl.listdati.SetStringItem(row, 11, str(val))

    def on_list_column(self, evt):
        '''get the column header for sorting database data'''
        header = evt.GetColumn()
        ruolo = self.ctrl.rboxruoli.GetStringSelection()
        if 2 < header < 11:
            jolly = self.model.cols[header]
            self.ctrl.listdati.DeleteAllItems()
            self.fill_list(self.model.get_data_players_by_value(ruolo, jolly))
        elif header == 2:
            jolly = self.model.cols[header]
            self.ctrl.listdati.DeleteAllItems()
            self.fill_list(self.model.get_data_players(ruolo, jolly))
        elif header == 11:
            self.ctrl.listdati.DeleteAllItems()
            self.fill_list(self.model.get_data_players_by_valore(ruolo))
        
    def on_roles(self, evt):
        '''radio box "Ruolo" event handler'''
        role = evt.GetEventObject().GetStringSelection()
        players = self.model.get_data_players(role)
        self.ctrl.listdati.DeleteAllItems()
        self.fill_list(players)

def _frame_exit(evt):
    '''exit from frame'''
    frame = evt.GetEventObject().GetParent().GetParent()
    frame.Close()

def main():
    '''starter'''
    app = wx.PySimpleApp()
    Controller()
    app.MainLoop()
    
if __name__ == '__main__':
    main()

Ultimo oggetto estraneo al FantaStat, è la ControlBar delle importazioni:

# ProgressBar.py
'''Module containing a Progress Bar class'''

import wx

class Data(object):
    '''Data class for M-V-C pattern'''
    def __init__(self):
        self.count = 0
        self.max_v = 0
    def add_count (self):
        '''increase the progress bar count for showing its progress'''
        self.count += 1
    def get_max(self):
        '''Get the max value of the progress bar'''
        return self.max_v
    def get_count(self):
        '''Get the current count value of the progressbar in the loop'''
        return self.count
    def set_max(self, value):
        '''set the max value of the progress bar'''
        self.max_v = int(value)

class ProgressBar(wx.ProgressDialog):
    '''Progress Dialog constructor'''
    def __init__(self, max_v):
        wx.ProgressDialog.__init__(self, "", "Update...", max_v,
                                   style = wx.PD_AUTO_HIDE |
                                   wx.PD_ELAPSED_TIME |
                                   wx.PD_REMAINING_TIME)
    def progress(self, progress):
        '''update the progress bar during the loops'''
        wx.MicroSleep(1) #for short processes use wx.MilliSleep(1)
        self.Update(progress)
    def destroy(self):
        '''Distruttore della progress bar'''
        self.Destroy()

class BarController(object):
    '''Progress Bar Controller Constructor'''
    def __init__(self, value = 3):
        self.data = Data()
        self.data.set_max(value)
        self.max = self.data.get_max()
        self.count = self.data.get_count()
        self.progress_bar = ProgressBar(self.max)
    def update_progress_bar(self):
        '''Aggiorna la progressBar'''
        self.data.add_count()
        self.count = self.data.get_count()
        self.progress_bar.progress(self.count) # update progressbar
    def destroy_progress_bar(self):
        '''Destroy the wx.ProgressDialog instance'''
        self.progress_bar.destroy()

def main():
    '''app starter'''
    app = wx.PySimpleApp()
    bar_c = BarController(600)
    while bar_c.count < bar_c.max:
        bar_c.update_progress_bar()
    bar_c.destroy_progress_bar()
    app.MainLoop()
    
if __name__ == "__main__":
    main()

Nella versione 1.2 implementerò l’ordinamento decrescente per colonna E per media_voto,
in modo da ordinare i giocatori di pari valore (es presenze), mettendo prima quello
con la media voto maggiore.

…continua

Pylons: Fantamanager parte 3 – authorization e authentication con repoze.what

14 ottobre 2011 Commenti chiusi

<part_1<part_2
Le prime basi di pylons mi sono piaciute parecchio.
Ho testato i vari semplici esempi disponibili sulle guide e sulla documentazione
reperibile on-line, poi mi sono imbattuto sul problema dell’autenticazione e
delle autorizzazioni.
I metodi sono parecchi, ma ho scelto di appoggiarmi per questo compito
a Repoze.what (come suggerito dalla guida ufficiale).
Tale guida è mutuata da questa e, vista la scarsa documentazioni disponibile
in Italiano ho pensato di riassumere un po’ quello che ho provato e testato.
Per i programmatori più esperti, forse non ci sono stati problemi, per me, aspirante tale, invece sì.

Anticipo subito che i problemi più fastidiosi li ho avuti codificando i models di
Sqlalchemy, avvalendomi del declarative_base.
Non ho avuto modo di vedere repoze funzionante appieno, a causa di un errore riguardante il mapper di SQLA.

‘EagerLoader’ object has no attribute ‘mapper’
Per questa ragione ho dovuto ricodificare tutte le classi della mia applicazione:
Giocatore, Squadra ecc.

Comunque…

I componenti utili alla causa sono:

-Pylons v1.0
-repoze.what v1.0.9
-repoze.what-pylons v1.0
-repoze.what-quickstart v1.0.8

perchè tutto funzioni, sono necessarie le seguenti dipendenze:

-repoze.who
-repoze.who-friendlyform
-repoze.what
-repoze.what-pylons
-repoze.what-quickstart
-repoze.what-plugins-sql
-repoze.who.plugins.sa

Dovrebbero comunque installarsi tutte automaticamente via easy_install

easy_install repoze.what-pylons

easy_install repoze.what-quickstart

Ora riprendendo a mano il mio progetto di esempio, creo il file:
..FantaManagerfantamanagermodelauth.py
dove definisco i modelli utilizzati da repoze.what

from sqlalchemy import Table, ForeignKey, Column
from sqlalchemy.types import Unicode, UnicodeText, Integer, Date, CHAR
from sqlalchemy import orm
from fantamanager.model.meta import metadata
import os
from hashlib import sha1

group_table = Table('group', metadata,
    Column('id', Integer(), primary_key=True),
    Column('name', Unicode(255), unique=True, nullable=False),
)

permission_table = Table('permission', metadata,
    Column('id', Integer(), primary_key=True),
    Column('name', Unicode(255), unique=True, nullable=False),
)

user_table = Table('user', metadata,
    Column('id', Integer(), primary_key=True),
    Column('username', Unicode(255), unique=True, nullable=False),
    Column('email', Unicode(255), unique=True, nullable=False),
    Column('password', Unicode(80), nullable=False),
    Column('fullname', Unicode(255), nullable=False),
)

# many-to-many relationship between groups and permissions.
group_permission_table = Table('group_permission', metadata,
    Column('group_id', Integer, ForeignKey('group.id')),
    Column('permission_id', Integer, ForeignKey('permission.id')),
)

# many-to-many relationship between groups and users
user_group_table = Table('user_group', metadata,
    Column('user_id', Integer, ForeignKey('user.id')),
    Column('group_id', Integer, ForeignKey('group.id')),
)

class Group(object):
    pass

class Permission(object):
    pass

class User(object):

    def _set_password(self, password):
        &quot;&quot;&quot;Hash password on the fly.&quot;&quot;&quot;
        hashed_password = password

        if isinstance(password, unicode):
            password_8bit = password.encode('UTF-8')
        else:
            password_8bit = password

        salt = sha1()
        salt.update(os.urandom(60))
        hash = sha1()
        hash.update(password_8bit + salt.hexdigest())
        hashed_password = salt.hexdigest() + hash.hexdigest()

        # Make sure the hased password is an UTF-8 object at the end of the
        # process because SQLAlchemy _wants_ a unicode object for Unicode
        # fields
        if not isinstance(hashed_password, unicode):
            hashed_password = hashed_password.decode('UTF-8')

        self.password = hashed_password

    def _get_password(self):
        &quot;&quot;&quot;Return the password hashed&quot;&quot;&quot;
        return self.password

    def validate_password(self, password):
        &quot;&quot;&quot;
        Check the password against existing credentials.

        :param password: the password that was provided by the user to
            try and authenticate. This is the clear text version that we will
            need to match against the hashed one in the database.
        :type password: unicode object.
        :return: Whether the password is valid.
        :rtype: bool

        &quot;&quot;&quot;
        hashed_pass = sha1()
        hashed_pass.update(password + self.password[:40])
        return self.password[40:] == hashed_pass.hexdigest()

# Map SQLAlchemy table definitions to python classes
orm.mapper(Group, group_table, properties={
    'permissions':orm.relation(Permission, secondary=group_permission_table),
    'users':orm.relation(User, secondary=user_group_table),
})
orm.mapper(Permission, permission_table, properties={
    'groups':orm.relation(Group, secondary=group_permission_table),
})
orm.mapper(User, user_table, properties={
    'groups':orm.relation(Group, secondary=user_group_table),
})

il file ..FantaManagerfantamanagermodelmeta.py è così composto:

&quot;&quot;&quot;SQLAlchemy Metadata and Session object&quot;&quot;&quot;
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy import MetaData

__all__ = ['Base', 'Session']

Session = scoped_session(sessionmaker())

metadata = MetaData()

Ora importiamo Group, User, Permission nel file ..FantaManagerfantamanagermodel__init__.py

&quot;&quot;&quot;The application's model objects&quot;&quot;&quot;
import sqlalchemy as sa
from sqlalchemy import orm
from fantamanager.model import meta
from fantamanager.model.auth import User, Group, Permission

def init_model(engine):
    &quot;&quot;&quot;Call me before using any of the tables or classes in the model&quot;&quot;&quot;
    sm = orm.sessionmaker(autoflush=True, autocommit=False, bind=engine)
    meta.engine = engine
    meta.Session = orm.scoped_session(sm)

Una volta definite le class/table per le autorizzazioni/autenticazioni, dobbiamo settare
repoze.what in modo che le utilizzi. Per fare questo si utilizza la funzione
setup_sql_auth() all’interno della funzione add_auth(), nel file
..FantaManagerfantamanagerlibauth.py

from repoze.what.plugins.quickstart import setup_sql_auth
from fantamanager.model import meta
from fantamanager.model.auth import User, Group, Permission

def add_auth(app, config):
    &quot;&quot;&quot;
    Add authentication and authorization middleware to the ``app``.

    We're going to define post-login and post-logout pages
    to do some cool things.

    &quot;&quot;&quot;
    # we need to provide repoze.what with translations as described here:
    # http://what.repoze.org/docs/plugins/quickstart/
    return setup_sql_auth(app, User, Group, Permission, meta.Session,
                login_url='/account/login',
                post_login_url='/account/login',
                post_logout_url='/',
                login_handler='/account/login_handler',
                logout_handler='/account/logout',
                cookie_secret=config.get('cookie_secret'),
                translations={
                    'user_name': 'username',
                    'group_name': 'name',
                    'permission_name': 'name',
                })

Editiamo il file ..FantaManagerdevelopment.ini ed inseriamo il cookie_secret

[app:main]
use = egg:FantaManager
full_stack = true
static_files = true
sqlalchemy.url = sqlite:///%(here)s/FantaManager.sqlite
cache_dir = %(here)s/data
beaker.session.key = fantamanager
beaker.session.secret = somesecret
# set repoze cookie secret
cookie_secret = 'your-own-secret'

il paramtero cookie_secret, essendo config, un argomento passato alla precedente funzione add_auth(),
ci serve per generare i cookies.
Ora aggiungiamo il middleware (lo strato di cipolla Repoze).
Editiamo il file ..FantaManagerfantamanagerconfigmiddleware.py

&quot;&quot;&quot;Pylons middleware initialization&quot;&quot;&quot;
from beaker.middleware import SessionMiddleware
from paste.cascade import Cascade
from paste.registry import RegistryManager
from paste.urlparser import StaticURLParser
from paste.deploy.converters import asbool
from pylons.middleware import ErrorHandler, StatusCodeRedirect
from pylons.wsgiapp import PylonsApp
from routes.middleware import RoutesMiddleware

from fantamanager.config.environment import load_environment
from fantamanager.lib.auth import add_auth
# from repoze.who
from repoze.who.config import make_middleware_with_config as make_who_with_config

def make_app(global_conf, full_stack=True, static_files=True, **app_conf):
    ...
    # Configure the Pylons environment
    config = load_environment(global_conf, app_conf)

    # The Pylons WSGI app
    app = PylonsApp(config=config)

    # Routing/Session/Cache Middleware
    app = RoutesMiddleware(app, config['routes.map'], singleton=False)
    app = SessionMiddleware(app, config)

    # CUSTOM MIDDLEWARE HERE (filtered by error handling middlewares)
    # added for repoze.what auth
    app = add_auth(app, config)

    if asbool(full_stack):
        # Handle Python exceptions
        app = ErrorHandler(app, global_conf, **config['pylons.errorware'])

        # Display error documents for 401, 403, 404 status codes (and
        # 500 when debug is disabled)
        if asbool(config['debug']):
            app = StatusCodeRedirect(app)
        else:
            app = StatusCodeRedirect(app, [400, 401, 403, 404, 500])

    # Establish the Registry for this application
    app = RegistryManager(app)

    if asbool(static_files):
        # Serve static files
        static_app = StaticURLParser(config['pylons.paths']['static_files'])
        app = Cascade([static_app, app])
    app.config = config
    return app

il nostro strato di cipolla è rappresentato dalla riga di codice

app = add_auth(app, config)

subito sotto

    # CUSTOM MIDDLEWARE HERE

Aggiungiamo ora un Gruppo ‘admin’, un paio di utenti e i permessi di admin.
Inseriamo il codice necessario, all’interno del file
..FantaManagerfantamanagerwebsetup.py

&quot;&quot;&quot;Setup the FantaManager application&quot;&quot;&quot;
import logging

import pylons.test

from fantamanager import model
from fantamanager.model import meta
from fantamanager.config.environment import load_environment
from fantamanager.model.meta import Session
from fantamanager.model.auth import User, Group, Permission
from fantamanager.model.models import Giocatore, Squadra

log = logging.getLogger(__name__)


def setup_app(command, conf, vars):
    &quot;&quot;&quot;Place any commands to setup fantamanager here&quot;&quot;&quot;
    # Don't reload the app if it was loaded under the testing environment
    if not pylons.test.pylonsapp:
        load_environment(conf.global_conf, conf.local_conf)

    meta.metadata.bind = meta.engine
    # Create the tables if they don't already exist
    log.info(&quot;Creating tables&quot;)
    meta.metadata.drop_all(checkfirst=True, bind=Session.bind)
    meta.metadata.create_all(bind=Session.bind)
    # Now let's create users, group and permission

    # ADMIN group
    log.info(&quot;Adding initial users, groups and permissions...&quot;)
    log.info(&quot;Adding 'Admin' Group...&quot;)
    g = Group()
    g.name = u'admin'
    meta.Session.add(g)
    meta.Session.commit()
    log.info(&quot;+++ 'Admin' Group Done!&quot;)

    # ADMIN permission
    log.info(&quot;-&gt; Adding 'Admin' permission...&quot;)
    p = Permission()
    p.name = u'admin'
    p.groups.append(g)
    meta.Session.add(p)
    meta.Session.commit()
    log.info(&quot;+++ 'Admin' permission assigned to 'Admin' group!&quot;)

    # User ADMIN
    log.info(&quot;-&gt; Adding 'Admin' user...&quot;)
    u = User()
    u.username = u'admin'
    u.fullname = u'admin'
    u._set_password('admin')
    u.email = [email protected]'
    u.groups.append(g)
    meta.Session.add(u)
    meta.Session.commit()
    log.info(&quot;+++ 'Admin' user created!&quot;)
    log.info(&quot;+++ 'Admin' user assigned to 'Admin' group!&quot;)

    # User TEST
    log.info(&quot;-&gt; Adding 'Test' user...&quot;)
    u = User()
    u.username = u'test'
    u.fullname = u'test'
    u._set_password('test')
    u.email = [email protected]'
    meta.Session.add(u)
    meta.Session.commit()
    log.info(&quot;+++ 'Test' user created!&quot;)
    log.info(&quot;+++ Done!&quot;)

alternativamente utilizzare la shell di paster e creare gli oggetti interattivamente:

paster shell

Ora non resta che definire il controller e la action di Login:

paster controller account

Edito il controller appena creato (..FantaManagerfantamanagercontrollersaccount.py)

import logging

from pylons import request, response, session, tmpl_context as c, url
from pylons.controllers.util import abort, redirect

from fantamanager.lib.base import BaseController, render

from repoze.what.predicates import not_anonymous, has_permission
from repoze.what.plugins.pylonshq import ActionProtector
from pylons.controllers.util import redirect

log = logging.getLogger(__name__)

class AccountController(BaseController):

    def login(self):
        &quot;&quot;&quot;
        This is where the login form should be rendered.
        Without the login counter, we won't be able to tell if the user has
        tried to log in with wrong credentials
        &quot;&quot;&quot;
        identity = request.environ.get('repoze.who.identity')
        came_from = str(request.GET.get('came_from', '')) or 
                    url(controller='account', action='welcome')
        if identity:
            redirect(url(came_from))
        else:
            c.came_from = came_from
            c.login_counter = request.environ['repoze.who.logins'] + 1
            return render('/derived/account/login.html')

    @ActionProtector(not_anonymous())
    def welcome(self):
        &quot;&quot;&quot;
        Greet the user if she logged in successfully or redirect back
        to the login form otherwise(using ActionProtector decorator).
        &quot;&quot;&quot;
        identity = request.environ.get('repoze.who.identity')
        return 'Welcome back %s' % identity['repoze.who.userid']

    @ActionProtector(not_anonymous())
    def test_user_access(self):
        return 'You are inside user section'

    @ActionProtector(has_permission('admin'))
    def test_admin_access(self):
        return 'You are inside admin section'

…e la relativa template ..FantaManagerfantamanagertemplatesderivedaccountlogin.html

% if c.login_counter &gt; 1:
    Incorrect Username or Password
% endif
&lt;form action=&quot;${h.url(controller='account', action='login_handler'
,came_from=c.came_from, __logins=c.login_counter)}&quot; method=&quot;POST&quot;&gt;
&lt;label for=&quot;login&quot;&gt;Username:&lt;/label&gt;
&lt;input type=&quot;text&quot; id=&quot;login&quot; name=&quot;login&quot; /&gt;&lt;br /&gt;
&lt;label for=&quot;password&quot;&gt;Password:&lt;/label&gt;
&lt;input type=&quot;password&quot; id=&quot;password&quot; name=&quot;password&quot; /&gt;&lt;br /&gt;
&lt;input type=&quot;submit&quot; id=&quot;submit&quot; value=&quot;Submit&quot; /&gt;
&lt;/form&gt;

Prima di lanciare il server, ricodifico le class/table della mia applicazione
(precedentemente create utilizzando il declarative_base).
Per renderla breve utilizzo solo la classe Giocatore e Squadra:

# -*- coding: utf-8 -*-#
## ..modelmodels.py
'''model for FantaManager application powered by Pylons''' 

from fantamanager.model.meta import metadata
from sqlalchemy.types import Unicode
from sqlalchemy import create_engine, Column, Integer, String, Float
from sqlalchemy import ForeignKey, Table
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import backref, aliased
from sqlalchemy.orm import relation as relationship
from sqlalchemy.orm import sessionmaker
from sqlalchemy import orm, func, desc, asc, and_

#metadata = Base.metadata

giocatore_table = Table('giocatori', metadata,
    Column('id', Integer(), primary_key=True),
    Column('idgaz', Integer()),
    Column('nome', Unicode(255)),
    Column('squadra', Unicode(255)),
    Column('valore', Integer()),
    Column('ruolo', Unicode(255)),
)

squadra_table = Table('squadre', metadata,
    Column('id', Integer(), primary_key=True),
    Column('nome', Unicode(255)),
    Column('budget', Integer()),
    Column('cost', Integer()),
    Column('pts', Float()),
)
# M2M relation
squadre_giocatori_table = Table('squadre_giocatori', metadata,
    Column('squadre_id', Integer, ForeignKey('squadre.id')),
    Column('giocatori_id', Integer, ForeignKey('giocatori.id')),
)

class Giocatore(object):
    pass

class Squadra(object):
    pass

# Map SQLAlchemy table definitions to python classes
orm.mapper(Squadra, squadra_table, properties={
    'giocatori':orm.relation(Giocatore, secondary=squadre_giocatori_table),})
orm.mapper(Giocatore, giocatore_table, properties={
    'squadre':orm.relation(Squadra, secondary=squadre_giocatori_table),})

Lanciamo il setup dell’applicazione:

paster setup-app development.ini

e avviamo il server:

paster serve --reload development.ini

se visito la pagina:
http://127.0.0.1:5000/account/test_user_access
e inserisco username e password (‘test’, ‘test’)
vedo renderizzato correttamente: You are inside user sectionse visito la pagina
http://127.0.0.1:5000/account/test_admin_access
ottengo il FORBIDDEN default di pylons poichè mi vedo ancora assegnato il cookie
relativo al test-user

ATTENZIONE! svuotare la cache di firefox prima di testare la pagina di Admin!

se ri-visito la pagina:
http://127.0.0.1:5000/account/test_admin_access
e inserisco username e password (‘admin’, ‘admin’)
vedo renderizzato correttamente: You are inside ADMIN section
Tutto ok!

Ora se vogliamo decorare la azioni del mio controller personale (la mia app),
basta appunto anteporre il decorator desiderato, prima della action che vogliamo
proteggere, es:

import logging

from sqlalchemy import distinct

from pylons import request, response, session, tmpl_context as c, url
from pylons.controllers.util import abort, redirect
from fantamanager.lib.base import Session, BaseController, render
from fantamanager.model.models import Giocatore

from repoze.what.predicates import not_anonymous, has_permission
from repoze.what.plugins.pylonshq import ActionProtector

import fantamanager.lib.helpers as h

log = logging.getLogger(__name__)

class SerieaController(BaseController):
    def __before__(self):
        self.ateam_q = Session.query(distinct(Giocatore.squadra))

    @ActionProtector(not_anonymous())
    def index(self):
        return 'Test: &gt;&gt; Controller Serie_A &gt;&gt; Connection OK!'

    @ActionProtector(has_permission('admin'))
    def squadre(self):
        c.squadre = [team[0] for team in self.ateam_q.all()]
        return render('/seriea.mako')

se visito la pagina http://127.0.0.1:5000/seriea/squadre
devo fare il login: se utilizzo test mi becco il Forbidden
se svuoto la cache ed entro come admin, passo!

Questo è quanto.

Categorie:Fantacalcio, pylons, python, sqlalchemy Tag:

Fantalight e Fantagazzetta: update valutazioni calciatori

27 settembre 2011 Commenti chiusi

I file scaricati da Fantagazzetta, anche se con estensione .xls, sono in
realtà in formato html.
Per aggiornare le valutazioni dei calciatori ho scritto questo codice,
che permette l’update dei giocatori, direttamente sul database utilizzato da
Fantalight.
Ho utilizzato BeautifulSoup per il parsing del file html.

'''
Utilizzare con FantaGazzetta-Quotazioni.
Vengono aggiornate solo le valutazioni dei giocatori
pre-esistenti.
Anche se con estensione xls, il file in ingresso e'
un HTML, quindi per il parsing viene utilizzato
Beautifulsoup
'''
import wx, os
from BeautifulSoup import BeautifulSoup
from DataLight import *
from sqlalchemy import and_

class FileBrowser(wx.FileDialog):
    '''Class for file browser'''
    def __init__(self):
        self.fin = None
        wildcard = &quot;File Fantagazzetta (*.xls)|*.xls|&quot; 
            &quot;Tutti i files (*.*)|*.*&quot;
        wx.FileDialog.__init__(self, None, &quot;scegli il file&quot;, os.getcwd(),
                               &quot;&quot;, wildcard, wx.OPEN | wx.CHANGE_DIR)
        if self.ShowModal() == wx.ID_OK:  
            print(self.GetPath())
            self.file = self.GetPath()
            self.fin = open(self.file, 'r')
        else:
            print &quot;operazione apertura annullata&quot;
            self.Destroy()
        self.Destroy()

def parser():
    '''Parse the siracusa txt'''
    model = Model()
    f_b = FileBrowser()
    html = f_b.fin.read()
    soup = BeautifulSoup(''.join(html))
    soup.prettify()    
    table = soup.find('table')
    rows = table.findAll('tr')
    for tr in rows:
        columns = tr.findAll('td')
        values = []
        for td in columns:
            line = td.find(text = True)
            values.append(line)
        try: # scarto l'intestazione del file
            int(values[0])
            nome = values[2]
            sq = (values[3])[0:3]
            val = values[4]
            player = model.session.query(Giocatore).filter(and_(
                     Giocatore.nome == nome, Giocatore.squadra == sq)).one()
            player.valore = val
            model.session.commit()
            print player, &quot; ++++ value updated to %s&quot; % val
        except ValueError:
            pass #passo se il valore non e' un cod. giocatore

    f_b.fin.close()

def main():
    '''app starter'''
    app = wx.PySimpleApp()
    app.MainLoop()
    parser()

if __name__ == '__main__':
    main()

Eseguito nella stessa dir di fantalight, funziona egregiamente

&lt;Giocatore ('DI NATALE: attaccante')&gt;  ++++ value updated to 37
&lt;Giocatore ('CAVANI: attaccante')&gt;  ++++ value updated to 36
&lt;Giocatore ('IBRAHIMOVIC: attaccante')&gt;  ++++ value updated to 34
&lt;Giocatore ('PALACIO: attaccante')&gt;  ++++ value updated to 31
&lt;Giocatore ('TOTTI: attaccante')&gt;  ++++ value updated to 30
&lt;Giocatore ('PAZZINI: attaccante')&gt;  ++++ value updated to 29
&lt;Giocatore ('MATRI: attaccante')&gt;  ++++ value updated to 28
&lt;Giocatore ('FORLAN: attaccante')&gt;  ++++ value updated to 28
...

link utili:
fantalightbeautifulsoup

Categorie:FantaLega, python, sqlalchemy Tag:

Pylint: E1101 False Positive

22 giugno 2011 Commenti chiusi

Ho scritto un codice per capire bene come funzioni la
relazione many-to-many con l’orm di sqlalchemy (in declarative mode).

from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy import ForeignKey, Table
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import backref
from sqlalchemy.orm import relationship
from sqlalchemy.orm import sessionmaker


class Model(object):
    '''Model class with common application data'''
    base = declarative_base()
    engine = create_engine('sqlite:///test.db', echo = False)
    metadata = base.metadata
    # m2m relationship
    boms_codes = Table('boms_codes', metadata,
                               Column('codes_id', Integer,
                                      ForeignKey('codes.id')),
                               Column('boms_id', Integer,
                                      ForeignKey('boms.id')))
    def __init__(self):
        self.metadata = Model.metadata
        self.metadata.create_all(Model.engine)
        session = sessionmaker(bind = Model.engine)
        self.session = session()

class Code(Model.base):
    '''Code class for ORM Mapping'''
    __tablename__ = 'codes'
    id = Column(Integer, primary_key = True)
    code = Column(String)
    desc = Column(String)

    bom_id = Column(Integer, ForeignKey('boms.id'))
    def __init__(self, code, desc):
        self.code = code
        self.desc = desc
    def __repr__(self):
        return &quot;&lt;Code '%s: %s')&gt;&quot; % (self.code, self.desc)

class Bom(Model.base):
    '''Bill of material class for ORM Mapping'''
    __tablename__ = 'boms'
    id = Column(Integer, primary_key = True)
    code = Column(String)
    desc = Column(String)
    # m2m relationship
    code_id = relationship(&quot;Code&quot;, secondary = Model.boms_codes,
                                backref = backref('Bom'),
                                single_parent = False)
                                #cascade = 'all,delete-orphan')
    
    def __init__(self, code, desc):
        self.code = code
        self.desc = desc
    def __repr__(self):
        return &quot;&lt;B.O.M. '%s': %s&gt;&quot; % (self.code, self.desc)

def main():
    '''auto-test'''
    model = Model()
    cod_a = Code(code = '001', desc = 'code 001')
    cod_b = Code(code = '002', desc = 'code 002')
    model.session.add_all((cod_a, cod_b))
    model.session.commit()
    bom_a = Bom(code = '1001', desc = 'bom 1001')
    bom_b = Bom(code = '1002', desc = 'bom 1002')
    model.session.add_all((bom_a, bom_b))
    model.session.commit()
    bom_a.code_id.append(cod_a) # pylint: disable=E1101
    bom_a.code_id.append(cod_b)
    bom_b.code_id.append(cod_b) # pylint: disable=E1101
    model.session.commit()
    
if __name__ == '__main__':
    Model() 

Prima di postarlo, gli ho fatto fare un giro con pylint.

Sorpresa!
mi segnale 3 errori fiammanti qui:

    bom_a.code_id.append(cod_a)
    bom_a.code_id.append(cod_b)
    bom_b.code_id.append(cod_b)

googlando ho scoperto essere FALSI POSITIVI, nel senso che
il codice funziona benissimo, ma pylint, essendo uno “static checker”
vede nelle linee suddette questo tipo di errore:

ID:E1101 main: Instance of ‘RelationshipProperty’ has no ‘append’ member
ovvero non sa di avere a che fare con una lista e si “incarta”.

Per risolvere il problema ho navigato un po’:
c’è chi consiglia di disabilitare globalmente il tipo di errore E1101,
o da riga di comando o, con Eclipse, in questo modo:


Ma il metodo non mi piace, poichè mi sfuggerebbero gli altri errori “veri”.

Altra strada suggerita qui:

è quella di “esonerare” solo la linea di codice, dal controllo, commentandola
in questo modo:

    bom_a.code_id.append(cod_a) # pylint: disable=E1101
    bom_a.code_id.append(cod_b)
    bom_b.code_id.append(cod_b) # pylint: disable=E1101


come si nota, le righe che presentano il commento

# pylint: disable=Exxxx

vengono by-passate, quelle senza, segnalate correttamente.
In questo modo non è necessario bloccare a monte l’intera categoria di errori
(E1101), che, non essendo semplici Warnings, è meglio gestire con cautela.

Nel caso aggiungendo il commento, si dovesse sforare dagli 80 caratteri
per linea, aggiungere anche il bypass per il C0301

# pylint: disable=E1101,C0301
Categorie:Eclipse, PyDev, PyLint, python, sqlalchemy Tag:

Pylint: E1101 False Positive

22 giugno 2011 Commenti chiusi

Ho scritto un codice per capire bene come funzioni la
relazione many-to-many con l’orm di sqlalchemy (in declarative mode).

from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy import ForeignKey, Table
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import backref
from sqlalchemy.orm import relationship
from sqlalchemy.orm import sessionmaker


class Model(object):
    '''Model class with common application data'''
    base = declarative_base()
    engine = create_engine('sqlite:///test.db', echo = False)
    metadata = base.metadata
    # m2m relationship
    boms_codes = Table('boms_codes', metadata,
                               Column('codes_id', Integer,
                                      ForeignKey('codes.id')),
                               Column('boms_id', Integer,
                                      ForeignKey('boms.id')))
    def __init__(self):
        self.metadata = Model.metadata
        self.metadata.create_all(Model.engine)
        session = sessionmaker(bind = Model.engine)
        self.session = session()

class Code(Model.base):
    '''Code class for ORM Mapping'''
    __tablename__ = 'codes'
    id = Column(Integer, primary_key = True)
    code = Column(String)
    desc = Column(String)

    bom_id = Column(Integer, ForeignKey('boms.id'))
    def __init__(self, code, desc):
        self.code = code
        self.desc = desc
    def __repr__(self):
        return "<Code '%s: %s')>" % (self.code, self.desc)

class Bom(Model.base):
    '''Bill of material class for ORM Mapping'''
    __tablename__ = 'boms'
    id = Column(Integer, primary_key = True)
    code = Column(String)
    desc = Column(String)
    # m2m relationship
    code_id = relationship("Code", secondary = Model.boms_codes,
                                backref = backref('Bom'),
                                single_parent = False)
                                #cascade = 'all,delete-orphan')
    
    def __init__(self, code, desc):
        self.code = code
        self.desc = desc
    def __repr__(self):
        return "<B.O.M. '%s': %s>" % (self.code, self.desc)

def main():
    '''auto-test'''
    model = Model()
    cod_a = Code(code = '001', desc = 'code 001')
    cod_b = Code(code = '002', desc = 'code 002')
    model.session.add_all((cod_a, cod_b))
    model.session.commit()
    bom_a = Bom(code = '1001', desc = 'bom 1001')
    bom_b = Bom(code = '1002', desc = 'bom 1002')
    model.session.add_all((bom_a, bom_b))
    model.session.commit()
    bom_a.code_id.append(cod_a) # pylint: disable=E1101
    bom_a.code_id.append(cod_b)
    bom_b.code_id.append(cod_b) # pylint: disable=E1101
    model.session.commit()
    
if __name__ == '__main__':
    Model() 

Prima di postarlo, gli ho fatto fare un giro con pylint.

Sorpresa!
mi segnale 3 errori fiammanti qui:

    bom_a.code_id.append(cod_a)
    bom_a.code_id.append(cod_b)
    bom_b.code_id.append(cod_b)

googlando ho scoperto essere FALSI POSITIVI, nel senso che
il codice funziona benissimo, ma pylint, essendo uno “static checker”
vede nelle linee suddette questo tipo di errore:

ID:E1101 main: Instance of ‘RelationshipProperty’ has no ‘append’ member

ovvero non sa di avere a che fare con una lista e si “incarta”.

Per risolvere il problema ho navigato un po’:
c’è chi consiglia di disabilitare globalmente il tipo di errore E1101,
o da riga di comando o, con Eclipse, in questo modo:

Ma il metodo non mi piace, poichè mi sfuggerebbero gli altri errori “veri”.

Altra strada suggerita qui:

è quella di “esonerare” solo la linea di codice, dal controllo, commentandola
in questo modo:

    bom_a.code_id.append(cod_a) # pylint: disable=E1101
    bom_a.code_id.append(cod_b)
    bom_b.code_id.append(cod_b) # pylint: disable=E1101

come si nota, le righe che presentano il commento

# pylint: disable=Exxxx

vengono by-passate, quelle senza, segnalate correttamente.
In questo modo non è necessario bloccare a monte l’intera categoria di errori
(E1101), che, non essendo semplici Warnings, è meglio gestire con cautela.

Nel caso aggiungendo il commento, si dovesse sforare dagli 80 caratteri
per linea, aggiungere anche il bypass per il C0301

# pylint: disable=E1101,C0301
Categorie:Eclipse, PyDev, PyLint, python, sqlalchemy Tag:

Python: SQLAlchemy esempio m2m

9 giugno 2011 Commenti chiusi

Utilizzo SQLAlchemy ed il suo ORM per relazionare gli
oggetti Team, Player e Form.
Sono semplicemente gli oggetti rappresentanti una Squadra
di calcio (Team), i giocatori che formano la rosa (Player)
e le formazion che verranno schierate di volta in volta (Form).
Considerando di avere gli oggetti

Questo è lo “scheletro” per la relazione tra gli oggetti
Team, Player (one-to-many)
Team, Form (one-to-many)
Form, Player (many-to-many)

'''Data module for sql alchemy ORM'''

from sqlalchemy import create_engine, Column, Integer, String, Float, func
from sqlalchemy import ForeignKey, Table
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import backref
from sqlalchemy.orm import relation as relationship
from sqlalchemy.orm import sessionmaker


Base = declarative_base()
Engine = create_engine('sqlite:///provafanta.db', echo = False)
Metadata = Base.metadata
# m2m relationship
m2m_form_players = Table('m2m_form_players', Metadata,
                         Column('forms_id', Integer,
                                ForeignKey('forms.id')),
                         Column('players_id', Integer,
                                ForeignKey('players.id')))

class Player(Base):
    '''Player class for ORM Mapping'''
    __tablename__ = 'players'
    id = Column(Integer, primary_key = True)
    idgaz = Column(Integer)
    name = Column(String)

    team_id = Column(Integer, ForeignKey('teams.id'))
    form_id = Column(Integer, ForeignKey('forms.id'))
    
    def __init__(self, idgaz, name):
        self.idgaz = idgaz
        self.name = name
    def __repr__(self):
        return &quot;&lt;Player ('%s id: %s')&gt;&quot; % (self.name, self.idgaz)

class Team(Base):
    '''Team class for ORM Mapping. args: name'''
    __tablename__ = 'teams'
    id = Column(Integer, primary_key = True)
    name = Column(String)

    players = relationship(&quot;Player&quot;, backref = backref('Team'),
                           cascade = 'all,delete-orphan')
    forms = relationship(&quot;Form&quot;, backref = backref('Team'),
                         cascade = 'all,delete-orphan')
    
    def __init__(self, name):
        self.name = name
    def __repr__(self):
        return &quot;&lt;Team '%s'&gt;&quot; % (self.name)

class Form(Base):
    '''Daily-Team class for ORM Mapping'''
    __tablename__ = 'forms'
    id = Column(Integer, primary_key = True)
    desc = Column(String)

    team_id = Column(Integer, ForeignKey('teams.id'))
    # m2m relationship
    player_id = relationship(&quot;Player&quot;,
                             secondary = m2m_form_players,
                             backref = backref('Form'),
                             single_parent = True) 
    
    def __init__(self, desc):
        self.desc = desc
    def __repr__(self):
        return &quot;&lt;Formazione '%s'&gt;&quot; % (self.desc)

Metadata.create_all(Engine)
Session = sessionmaker(bind = Engine)
session = Session()

come da documentazione, per ottenere una relazione m2m, è
necessario appoggiarsi ad una tabella secondaria, per cui
definiamo una tabella che relazioni Form e Player:

m2m_form_players = Table('m2m_form_players', Metadata,
                         Column('forms_id', Integer,
                                ForeignKey('forms.id')),
                         Column('players_id', Integer,
                                ForeignKey('players.id')))

da richiamare all’interno della classe Form, puntando
all’id del Player che fa riferimento alla tabella seocndaria:

    player_id = relationship(&quot;Player&quot;,
                             secondary = m2m_form_players,
                             backref = backref('Form'),
                             single_parent = True,
                             cascade = 'all,delete-orphan') 

mentre nella classe Player, facciamo riferimento all’id
del Form come ForeignKey:

    form_id = Column(Integer, ForeignKey('forms.id'))

Per quello che riguarda la relazione one-to_many, tra
Team e Player e tra Team e Form, bastano semplicemente
i riferimenti alla ForeignKey nelle classi Player e
Form (relativamente a Team.id) e le relationship relative
nella classe Team

class Player(Base):
    ...
    team_id = Column(Integer, ForeignKey('teams.id'))
    ...
class Form(Base):
    ...
    team_id = Column(Integer, ForeignKey('teams.id'))
    ...
class Team(Base):
    ...
    players = relationship(&quot;Player&quot;, backref = backref('Team'),
                           cascade = 'all,delete-orphan')
    forms = relationship(&quot;Form&quot;, backref = backref('Team'),
                         cascade = 'all,delete-orphan')
    ...

Ho provato ad inserire quelche dato per verificare la correttezza
della struttura:

 
&gt;&gt;&gt; team = Team(name = 'A')
&gt;&gt;&gt; session.add(team)
&gt;&gt;&gt; session.commit()
&gt;&gt;&gt; team = session.query(Team).filter(Team.name == 'A').one()
&gt;&gt;&gt; pl = Player(name = 'CESAR', idgaz = 101)
&gt;&gt;&gt; pl
&lt;Player ('CESAR id: 101')&gt;
&gt;&gt;&gt; session.add(pl)
&gt;&gt;&gt; session.commit()
&gt;&gt;&gt; pl.team_id = team.id
&gt;&gt;&gt; t2 = Team(name='B')
&gt;&gt;&gt; session.add(t2)
&gt;&gt;&gt; session.commit()
&gt;&gt;&gt; pl1 = Player(name = 'MOTTA', idgaz = 201)
&gt;&gt;&gt; session.add(pl1)
&gt;&gt;&gt; pl1.team_id = team.id
...
&gt;&gt;&gt; player = Player(name = 'PAZZINI', idgaz = 805)
&gt;&gt;&gt; session.add(player)
&gt;&gt;&gt; session.commit()
&gt;&gt;&gt; team = session.query(Team).filter(Team.name == 'A').one()
&gt;&gt;&gt; player.team_id = team.id
&gt;&gt;&gt; team.players
[&lt;Player ('CESAR id: 101')&gt;, &lt;Player ('MOTTA id: 201')&gt;, &lt;Player ('MAICON id: 202')&gt;, &lt;Player ('PAZZINI id: 805')&gt;]

Ora inserisco la formazione per la giornata 1

&gt;&gt;&gt; form = Form(desc = 'A_1')
&gt;&gt;&gt; session.add(form)
&gt;&gt;&gt; form.team_id = team.id
&gt;&gt;&gt; session.commit()
&gt;&gt;&gt; form.Team
&lt;Team 'A'&gt;
&gt;&gt;&gt; team.players
[&lt;Player ('CESAR id: 101')&gt;, &lt;Player ('MOTTA id: 201')&gt;, &lt;Player ('MAICON id: 202')&gt;]
&gt;&gt;&gt; # aggiungo 2 player di team in formazione
&gt;&gt;&gt; form.player_id.append(pl)
&gt;&gt;&gt; form.player_id.append(pl1)
&gt;&gt;&gt; session.commit()

facciamo un controllo per vie traverse

&gt;&gt;&gt; f = session.query(Form).filter(Form.id == 1).one()
&gt;&gt;&gt; f
&lt;Formazione 'A_1'&gt;
&gt;&gt;&gt; f.player_id
[&lt;Player ('CESAR id: 101')&gt;, &lt;Player ('MOTTA id: 201')&gt;]
&gt;&gt;&gt; for player in f.player_id: print player.name

CESAR
MOTTA
&gt;&gt;&gt; ### aggiungo formazione
&gt;&gt;&gt; 
&gt;&gt;&gt; form = Form(desc = 'A_2')
&gt;&gt;&gt; session.add(form)
&gt;&gt;&gt; session.commit()
&gt;&gt;&gt; team = session.query(Team).filter(Team.name == 'A').one()
&gt;&gt;&gt; team.players
[&lt;Player ('CESAR id: 101')&gt;, &lt;Player ('MOTTA id: 201')&gt;, &lt;Player ('MAICON id: 202')&gt;, &lt;Player ('PAZZINI id: 805')&gt;]
&gt;&gt;&gt; team.players[1:]
[&lt;Player ('MOTTA id: 201')&gt;, &lt;Player ('MAICON id: 202')&gt;, &lt;Player ('PAZZINI id: 805')&gt;]
&gt;&gt;&gt; for player in team.players[1:]:
	form.player_id.append(player)

&gt;&gt;&gt; session.commit()

controllo che funzioni la relazione m2m per vie traverse

&gt;&gt;&gt; team
&lt;Team 'A'&gt;
&gt;&gt;&gt; team.forms
[&lt;Formazione 'A_1'&gt;]
&gt;&gt;&gt; form.team_id = team.id
&gt;&gt;&gt; session.commit()
&gt;&gt;&gt; team.forms
[&lt;Formazione 'A_1'&gt;, &lt;Formazione 'A_2'&gt;]
&gt;&gt;&gt; f1 = team.forms[0]
&gt;&gt;&gt; f1.player_id
[&lt;Player ('CESAR id: 101')&gt;, &lt;Player ('MOTTA id: 201')&gt;, &lt;Player ('PAZZINI id: 805')&gt;]
&gt;&gt;&gt; f2 = team.forms[1]
&gt;&gt;&gt; f2
&lt;Formazione 'A_2'&gt;
&gt;&gt;&gt; f2.player_id
[&lt;Player ('MOTTA id: 201')&gt;, &lt;Player ('MAICON id: 202')&gt;, &lt;Player ('PAZZINI id: 805')&gt;]
&gt;&gt;&gt; pazzo = f2.player_id[-1:][0]
&gt;&gt;&gt; pazzo
&lt;Player ('PAZZINI id: 805')&gt;
&gt;&gt;&gt; pazzo.Team
&lt;Team 'A'&gt;
&gt;&gt;&gt; pazzo.Form
[&lt;Formazione 'A_1'&gt;, &lt;Formazione 'A_2'&gt;]
&gt;&gt;&gt; pazzo.form_id
&gt;&gt;&gt; pazzo.Form
[&lt;Formazione 'A_1'&gt;, &lt;Formazione 'A_2'&gt;]

Categorie:python, sqlalchemy Tag:

Python: SQLAlchemy esempio m2m

9 giugno 2011 Commenti chiusi

Utilizzo SQLAlchemy ed il suo ORM per relazionare gli
oggetti Team, Player e Form.
Sono semplicemente gli oggetti rappresentanti una Squadra
di calcio (Team), i giocatori che formano la rosa (Player)
e le formazion che verranno schierate di volta in volta (Form).
Considerando di avere gli oggetti

Questo è lo “scheletro” per la relazione tra gli oggetti
Team, Player (one-to-many)
Team, Form (one-to-many)
Form, Player (many-to-many)

'''Data module for sql alchemy ORM'''

from sqlalchemy import create_engine, Column, Integer, String, Float, func
from sqlalchemy import ForeignKey, Table
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import backref
from sqlalchemy.orm import relation as relationship
from sqlalchemy.orm import sessionmaker


Base = declarative_base()
Engine = create_engine('sqlite:///provafanta.db', echo = False)
Metadata = Base.metadata
# m2m relationship
m2m_form_players = Table('m2m_form_players', Metadata,
                         Column('forms_id', Integer,
                                ForeignKey('forms.id')),
                         Column('players_id', Integer,
                                ForeignKey('players.id')))

class Player(Base):
    '''Player class for ORM Mapping'''
    __tablename__ = 'players'
    id = Column(Integer, primary_key = True)
    idgaz = Column(Integer)
    name = Column(String)

    team_id = Column(Integer, ForeignKey('teams.id'))
    form_id = Column(Integer, ForeignKey('forms.id'))
    
    def __init__(self, idgaz, name):
        self.idgaz = idgaz
        self.name = name
    def __repr__(self):
        return "<Player ('%s id: %s')>" % (self.name, self.idgaz)

class Team(Base):
    '''Team class for ORM Mapping. args: name'''
    __tablename__ = 'teams'
    id = Column(Integer, primary_key = True)
    name = Column(String)

    players = relationship("Player", backref = backref('Team'),
                           cascade = 'all,delete-orphan')
    forms = relationship("Form", backref = backref('Team'),
                         cascade = 'all,delete-orphan')
    
    def __init__(self, name):
        self.name = name
    def __repr__(self):
        return "<Team '%s'>" % (self.name)

class Form(Base):
    '''Daily-Team class for ORM Mapping'''
    __tablename__ = 'forms'
    id = Column(Integer, primary_key = True)
    desc = Column(String)

    team_id = Column(Integer, ForeignKey('teams.id'))
    # m2m relationship
    player_id = relationship("Player",
                             secondary = m2m_form_players,
                             backref = backref('Form'),
                             single_parent = True) 
    
    def __init__(self, desc):
        self.desc = desc
    def __repr__(self):
        return "<Formazione '%s'>" % (self.desc)

Metadata.create_all(Engine)
Session = sessionmaker(bind = Engine)
session = Session()

come da documentazione, per ottenere una relazione m2m, è
necessario appoggiarsi ad una tabella secondaria, per cui
definiamo una tabella che relazioni Form e Player:

m2m_form_players = Table('m2m_form_players', Metadata,
                         Column('forms_id', Integer,
                                ForeignKey('forms.id')),
                         Column('players_id', Integer,
                                ForeignKey('players.id')))

da richiamare all’interno della classe Form, puntando
all’id del Player che fa riferimento alla tabella seocndaria:

    player_id = relationship("Player",
                             secondary = m2m_form_players,
                             backref = backref('Form'),
                             single_parent = True,
                             cascade = 'all,delete-orphan') 

mentre nella classe Player, facciamo riferimento all’id
del Form come ForeignKey:

    form_id = Column(Integer, ForeignKey('forms.id'))

Per quello che riguarda la relazione one-to_many, tra
Team e Player e tra Team e Form, bastano semplicemente
i riferimenti alla ForeignKey nelle classi Player e
Form (relativamente a Team.id) e le relationship relative
nella classe Team

class Player(Base):
    ...
    team_id = Column(Integer, ForeignKey('teams.id'))
    ...
class Form(Base):
    ...
    team_id = Column(Integer, ForeignKey('teams.id'))
    ...
class Team(Base):
    ...
    players = relationship("Player", backref = backref('Team'),
                           cascade = 'all,delete-orphan')
    forms = relationship("Form", backref = backref('Team'),
                         cascade = 'all,delete-orphan')
    ...

Ho provato ad inserire quelche dato per verificare la correttezza
della struttura:

 
>>> team = Team(name = 'A')
>>> session.add(team)
>>> session.commit()
>>> team = session.query(Team).filter(Team.name == 'A').one()
>>> pl = Player(name = 'CESAR', idgaz = 101)
>>> pl
<Player ('CESAR id: 101')>
>>> session.add(pl)
>>> session.commit()
>>> pl.team_id = team.id
>>> t2 = Team(name='B')
>>> session.add(t2)
>>> session.commit()
>>> pl1 = Player(name = 'MOTTA', idgaz = 201)
>>> session.add(pl1)
>>> pl1.team_id = team.id
...
>>> player = Player(name = 'PAZZINI', idgaz = 805)
>>> session.add(player)
>>> session.commit()
>>> team = session.query(Team).filter(Team.name == 'A').one()
>>> player.team_id = team.id
>>> team.players
[<Player ('CESAR id: 101')>, <Player ('MOTTA id: 201')>, <Player ('MAICON id: 202')>, <Player ('PAZZINI id: 805')>]

Ora inserisco la formazione per la giornata 1

>>> form = Form(desc = 'A_1')
>>> session.add(form)
>>> form.team_id = team.id
>>> session.commit()
>>> form.Team
<Team 'A'>
>>> team.players
[<Player ('CESAR id: 101')>, <Player ('MOTTA id: 201')>, <Player ('MAICON id: 202')>]
>>> # aggiungo 2 player di team in formazione
>>> form.player_id.append(pl)
>>> form.player_id.append(pl1)
>>> session.commit()

facciamo un controllo per vie traverse

>>> f = session.query(Form).filter(Form.id == 1).one()
>>> f
<Formazione 'A_1'>
>>> f.player_id
[<Player ('CESAR id: 101')>, <Player ('MOTTA id: 201')>]
>>> for player in f.player_id: print player.name

CESAR
MOTTA
>>> ### aggiungo formazione
>>> 
>>> form = Form(desc = 'A_2')
>>> session.add(form)
>>> session.commit()
>>> team = session.query(Team).filter(Team.name == 'A').one()
>>> team.players
[<Player ('CESAR id: 101')>, <Player ('MOTTA id: 201')>, <Player ('MAICON id: 202')>, <Player ('PAZZINI id: 805')>]
>>> team.players[1:]
[<Player ('MOTTA id: 201')>, <Player ('MAICON id: 202')>, <Player ('PAZZINI id: 805')>]
>>> for player in team.players[1:]:
	form.player_id.append(player)

>>> session.commit()

controllo che funzioni la relazione m2m per vie traverse

>>> team
<Team 'A'>
>>> team.forms
[<Formazione 'A_1'>]
>>> form.team_id = team.id
>>> session.commit()
>>> team.forms
[<Formazione 'A_1'>, <Formazione 'A_2'>]
>>> f1 = team.forms[0]
>>> f1.player_id
[<Player ('CESAR id: 101')>, <Player ('MOTTA id: 201')>, <Player ('PAZZINI id: 805')>]
>>> f2 = team.forms[1]
>>> f2
<Formazione 'A_2'>
>>> f2.player_id
[<Player ('MOTTA id: 201')>, <Player ('MAICON id: 202')>, <Player ('PAZZINI id: 805')>]
>>> pazzo = f2.player_id[-1:][0]
>>> pazzo
<Player ('PAZZINI id: 805')>
>>> pazzo.Team
<Team 'A'>
>>> pazzo.Form
[<Formazione 'A_1'>, <Formazione 'A_2'>]
>>> pazzo.form_id
>>> pazzo.Form
[<Formazione 'A_1'>, <Formazione 'A_2'>]

Categorie:python, sqlalchemy Tag: