2013-03-31 12 views
7

Dire, abbiamo le seguenti relazioni:Come impaginare in Flask-SQLAlchemy per le query unite da db.session?

  • una persona può avere molti indirizzi email
  • un fornitore di servizi di posta elettronica può (ovviamente) servirà multipla indirizzo e-mail

così, è una relazione molti a molte relazioni Ho tre tabelle: email, provider e utenti. Le email hanno due ID stranieri per provider e utente.

Ora, data una persona specifica, voglio stampare tutti i provider di posta elettronica e l'indirizzo di posta elettronica che ospita per questa persona, se esiste. (Se la persona non ha un'e-mail in Gmail, voglio comunque che Gmail sia nel risultato. Credo che in questo caso sia necessario un join interno sinistro per risolvere questo problema.)

Ho capito come fare questo con il seguente sottoquery (di seguito il tutorial sqlalchemy):

email_subq = db.session.query(Emails).\ 
       filter(Emails.user_id==current_user.id).\ 
       subquery() 

provider_and_email = db.session.query(Provider, email_subq).\ 
       outerjoin(email_subq, Provider.emails).\ 
       all() 

questo funziona bene (restituisce un 4 parametri di (Provider, user_id, provider_id, email_address), tutte le informazioni che voglio), ma in seguito ho scoperto che questo è non utilizzando il Flask BaseQuery classe, in modo che pagination fornito da Flask-SQLAlchemy non funzioni. Apparentemente db.session.query() non è l'istanza di Query Flask-SQLAlchemy.

Ho provato a fare Emails.query.outerjoin[...] ma che restituisce solo colonne nella tabella e-mail anche se voglio entrambe le informazioni del provider e le e-mail.

La mia domanda: come posso fare la stessa cosa con Flask-SQLAlchemy in modo che non debba ri-implementare la paginazione che è già lì?


Credo che l'opzione più semplice a questo punto è quello di implementare la mia propria funzione paginate, ma mi piacerebbe sapere se c'è un altro corretta modo di fare questo.

risposta

0

Potrei sbagliarmi, ma penso che il tuo problema potrebbe essere il .all(). Usandolo, stai ricevendo un elenco, non un oggetto query.

Prova lasciando fuori, e passare la query al metodo impaginazione in questo modo (ho lasciato fuori tutti i dettagli subquery per chiarezza):

email_query = db.session.query(Emails).filter(**filters) 
email_query.paginate(page, per_page) 
+0

ho fatto sostituire il 'tutto()' 'con paginate()' e args è propria, così mi chiamo 'paginate' su un sqlalchemy.orm.query.Query object (verificato nel debugger della pagina di errori Flask). Immagino che questa non sia la fonte di errore qui. Ci scusiamo per non essere chiari nella domanda. –

+0

Dang, avevo sperato che fosse qualcosa di facile. Cosa succede se si utilizza l'alias della query Flask-SQLAlchemy? Qualcosa di simile: Email.query.filter (Emails.user_id == current_user.id) .outerjoin (ProviderEmail, ProviderEmail.provider_id == Email.id) –

+0

Produce una query SELECT con solo i campi in 'Email' inclusi, quindi nulla sul provider viene restituito; almeno è l'ultima volta che ci ho provato. –

0

Come si lanciati da init l'applicazione con SQLAlchemy?

Probabilmente la vostra connessione SQLAlchemy attuale non ha nulla a che fare con flask.ext.sqalchemy e si utilizza sqlalchemy originale

controllare questo tutorial e controllare le importazioni, che in realtà provengono da flask.ext.sqlalchemy

+0

Sono quasi sicuro di aver avviato Flask-SQLAlchemy nel modo corretto. Tutti i comandi di 'Modes.query' funzionano correttamente per me. E ho davvero seguito il tutorial di cui sopra. –

7

Non sono sicuro che questo finirà per essere la soluzione a lungo termine, e non affronta direttamente la mia preoccupazione di non usare il BaseQuery di Flask-SQLAlchemy, ma il modo più banale per realizzare cosa Voglio reimplementare la funzione paginata.

E, infatti, è abbastanza facile da usare l'originale routine di Flask-SQLAlchemy per fare questo:

def paginate(query, page, per_page=20, error_out=True): 
    if error_out and page < 1: 
     abort(404) 
    items = query.limit(per_page).offset((page - 1) * per_page).all() 
    if not items and page != 1 and error_out: 
     abort(404) 

    # No need to count if we're on the first page and there are fewer 
    # items than we expected. 
    if page == 1 and len(items) < per_page: 
     total = len(items) 
    else: 
     total = query.order_by(None).count() 

    return Pagination(query, page, per_page, total, items) 

Modificata dalla funzione paginate trovato attorno alla riga 376: https://github.com/mitsuhiko/flask-sqlalchemy/blob/master/flask_sqlalchemy.py

+0

Hai provato a vedere se la query che hai compilato potrebbe essere utilizzata per costruire un nuovo oggetto BaseQuery (simile a come stai creando un oggetto Paginate) permettendoti di riutilizzare la funzione di paginazione di BaseQuery? –

+0

Ah, sì. Suppongo che potrei, ma non l'ho ancora provato. Grazie per segnalarlo. –

2

Hey Ho trovato una soluzione rapida per questo qui è:

provider_and_email = Provider.query.with_entities(email_subq).\ 
      outerjoin(email_subq, Provider.emails).paginate(page, POST_PER_PAGE_LONG, False) 
3

la tua domanda è come usare Impaginazione di Flask-SQLAlchemy con SQLAlchemy regolare interrogazioni.

Da oggetto BaseQuery di Flask-SQLAlchemy non possiede un proprio stato, e deriva da Query di SQLAlchemy, ed è in realtà solo un contenitore per i metodi, si può usare questo hack:

from flask.ext.sqlalchemy import BaseQuery 
def paginate(sa_query, page, per_page=20, error_out=True): 
    sa_query.__class__ = BaseQuery 
    # We can now use BaseQuery methods like .paginate on our SA query 
    return sa_query.paginate(page, per_page, error_out) 

Per utilizzare:

@route(...) 
def provider_and_email_view(page): 
    provider_and_email = db.session.query(...) # any SQLAlchemy query 
    paginated_results = paginate(provider_and_email, page) 
    return render_template('...', paginated_results=paginated_results) 

* Modifica:

prega di fare attenzione a fare questo. È davvero solo un modo per evitare di copiare/incollare la funzione paginate, come si vede nell'altra risposta. Nota che BaseQuery non ha il metodo __init__. Vedi How dangerous is setting self.__class__ to something else?.

* Edit2:

Se BaseQuery avuto un __init__, si potrebbe costruire uno utilizzando l'oggetto query SA, piuttosto che l'hacking .__class__.

+1

Ricevo sa_query .__ class__ = BaseQuery TipoErrore: __class__ assegnazione: solo per tipi di heap ' –

3

Attualmente sto usando questo approccio:

query = BaseQuery([Provider, email_subq], db.session()) 

per creare la mia BaseQuery. db è l'istanza SqlAlchemy.

Update: come @afilbert suggests si può anche fare questo:

query = BaseQuery(provider_and_email.subquery(), db.session()) 
+1

Si scopre che è possibile anche prendere qualsiasi oggetto Query SqlAlchemy e solo chiamare il metodo .subquery().Quindi, usando l'esempio dell'OP: 'query_with_pagination = BaseQuery (provider_and_email.subquery(), db.session())' La tua risposta dovrebbe essere accettata, imo. – afilbert

+0

@afilbert Altri sono stati pubblicati nel '13, hanno aggiunto il mio perché mi piaceva di più. Grazie per il supporto morale però;) – Adversus

Problemi correlati