2009-03-12 4 views
5

Per un sito web realizzato in Django/Python abbiamo il seguente requisito:Paging seconda raggruppamento di oggetti in Django

In una pagina vista ci sono 15 messaggi al web paging indicati. Quando ci sono più due o più messaggi dalla stessa fonte, che si susseguono sulla vista, dovrebbero essere raggruppati insieme.

Forse non è chiaro, ma con le seguenti exemple potrebbe essere:

Un esempio è (con 5 messaggi su una pagina di questa volta):

Message1 Source1 
    Message2 Source2 
    Message3 Source2 
    Message4 Source1 
    Message5 Source3 
    ... 

Questo dovrebbe essere indicato come:

Message1 Source1 
Message2 Source2 (click here to 1 more message from Source2) 
Message4 Source1 
Message5 Source3 
Message6 Source2 

Quindi su ogni pagina viene mostrato un numero fisso di elementi sulla pagina, dove alcuni sono stati raggruppati.

Ci chiediamo come possiamo creare una query su Django o MySQL per interrogare questi dati in modo ottimale e semplice. Si noti che viene utilizzato il paging e che i messaggi sono ordinati per ora.

PS: Io non credo che ci sia una soluzione semplice per questo a causa della natura di SQL, ma a volte i problemi complessi può essere facilmente risolto

risposta

3

Non vedo alcun modo per fare ciò che stai cercando di fare direttamente. Se sei disposto ad accettare un po 'di de-normalizzazione, ti consiglierei un segnale di pre-salvataggio per contrassegnare i messaggi come se fossero alla testa.

#In your model 
head = models.BooleanField(default=True) 

#As a signal plugin: 
def check_head(sender, **kwargs): 
    message = kwargs['instance'] 
    if hasattr(message,'no_check_head') and message.no_check_head: 
     return 
    previous_message = Message.objects.filter(time__lt=message.time).order_by('-time')[0] 
    if message.source == previous_message.source: 
     message.head = False 
    next_message = Message.objects.filter(time__gt=message.time).order_by('time')[0] 
    if message.source == next_message.source: 
     next_message.head = False 
     next_message.no_check_head 
     next_message.save() 

Poi la query diventa magicamente semplice:

messages = Message.objects.filter(head=True).order_by('time')[0:15] 

per essere onesti ... l'ascoltatore segnale sarebbe dovuto essere un po 'più complicato di quello che ho scritto. Esistono numerosi problemi di sincronizzazione/perdita persi inerenti al mio approccio, le cui soluzioni variano a seconda del server (se è single-process, multi-threaded, quindi un oggetto python Lock dovrebbe farvi passare, ma se è multi-elaborato, quindi sarà davvero necessario implementare il blocco basato su file o oggetti di database). Inoltre, dovrai anche scrivere un listener del segnale di cancellazione corrispondente.

Ovviamente questa soluzione comporta l'aggiunta di alcuni hit del database, ma sono in modifica anziché in visualizzazione, il che potrebbe essere utile per voi. Altrimenti, prendi in considerazione un approccio più rozzo: prendi 30 piani, passa attraverso la vista, elimina quelli che non verranno visualizzati e, se ne hai 15, mostrali, altrimenti ripeti. Decisamente un pessimo scenario terribile, ma forse non un caso medio terribile?

Se si disponeva di una configurazione del server che utilizzava un singolo processo multi-thread, un lucchetto o una RLock dovrebbero fare il trucco.Ecco una possibile implementazione con blocco non rientro:

import thread 
lock = thread.allocate_lock() 
def check_head(sender, **kwargs): 
    # This check must come outside the safe zone 
    # Otherwise, your code will screech to a hault 
    message = kwargs['instance'] 
    if hasattr(message,'no_check_head') and message.no_check_head: 
     return 
    # define safe zone 
    lock.acquire() 
    # see code above 
    .... 
    lock.release() 

Anche in questo caso, un segnale di eliminazione corrispondente è fondamentale.

MODIFICA: molte o la maggior parte delle configurazioni del server (come Apache) eseguiranno il prefork, il che significa che ci sono diversi processi in corso. Il codice sopra sarà inutile in quel caso. Vedi this page per le idee su come iniziare a sincronizzare con i processi biforcati.

+0

Penso che questo sia l'approccio generale che dovresti prendere, sebbene tu possa sostituire gli hit del database memorizzando last_source in memcached; ci sarebbe ancora una potenziale condizione di competizione in condizioni di concorrenza pesante, ma se non dovesse essere perfetta al 100% ... –

+0

Come hai detto, non è l'opzione perfetta, ma continuo a pensare che sia la migliore. Grazie – Michael

+0

Inoltre ... Suppongo che mi sia sempre piaciuto usare i segnali ... ma sovrascrivere il metodo di salvataggio potrebbe fare la stessa cosa. Alcuni puristi stilisti sostengono che è "la cosa giusta". –

1

ho un semplice, anche se non perfetto, soluzione di modello-only per questo. Nel modello è possibile raggruppare i record utilizzando il tag modello regroup. Dopo il raggruppamento è possibile nascondere i record successivi dalla stessa fonte:

{% regroup records by source as grouped_records %} 
{% for group in grouped_records %} 
    {% for item in group.list %} 
    <li{% if not forloop.first %} style="display:none"{% endif %}> 
     {{ item.message }} {{ iterm.source }} 
     {% if forloop.first %} 
     {% ifnotequal group.list|length 1 %} 
      <a href="#" onclick="...">Show more from the same source...</a> 
     {% endifnotequal %}   
     {% endif %} 
    </li> 
    {% endfor %} 
{% endfor %} 

questo sarebbe perfetto se non fosse per una cosa: Impaginazione. Se intendi visualizzare 15 elementi per pagina e in una pagina i primi cinque provengono da una fonte, i successivi cinque da un'altra e gli ultimi cinque ancora un'altra, nella pagina ci saranno solo tre elementi visibili.