5 Stimmen

Paging abhängig von der Gruppierung der Elemente in Django

Für eine in Django/Python implementierte Website haben wir die folgenden Anforderungen:

Auf einer Ansichtsseite werden 15 Nachrichten pro Webpage angezeigt. Wenn zwei oder mehr Nachrichten aus derselben Quelle in der Ansicht aufeinander folgen, sollten sie gruppiert werden.

Vielleicht ist das nicht ganz klar, aber mit dem folgenden Beispiel könnte es sein:

Ein Beispiel ist (diesmal mit 5 Meldungen auf einer Seite):

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

Dies sollte als angezeigt werden:

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

So wird auf jeder Seite eine feste Anzahl von Artikeln angezeigt, von denen einige umgruppiert wurden.

Wir fragen uns, wie wir eine Django- oder MySQL-Abfrage erstellen können, um diese Daten optimal und auf einfache Weise abzufragen. Beachten Sie, dass Paging verwendet wird und dass die Nachrichten nach Zeit sortiert sind.

PS: Ich glaube nicht, dass es aufgrund der Natur von SQL eine einfache Lösung für dieses Problem gibt, aber manchmal können komplexe Probleme einfach gelöst werden

3voto

David Berger Punkte 11589

Ich sehe keine gute Möglichkeit, das, was Sie vorhaben, direkt zu tun. Wenn Sie bereit sind, ein wenig De-Normalisierung zu akzeptieren, würde ich ein Pre-Save-Signal empfehlen, um Nachrichten als am Kopf zu markieren.

#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()

Dann wird Ihre Anfrage auf magische Weise einfach:

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

Um ganz ehrlich zu sein... der Signalhörer müsste etwas komplizierter sein als der, den ich geschrieben habe. Es gibt eine Reihe von Problemen mit verlorener Synchronisation und verlorener Aktualisierung, die meinem Ansatz innewohnen und deren Lösung von Ihrem Server abhängt (wenn es sich um einen Single-Prozessor mit mehreren Threads handelt, kann ein Python Lock Objekt sollte ausreichen, aber wenn es sich um ein Mehrprozesssystem handelt, müssen Sie Sperren auf der Grundlage von Dateien oder Datenbankobjekten implementieren). Außerdem müssen Sie sicherlich auch einen entsprechenden Listener für das Löschsignal schreiben.

Natürlich erfordert diese Lösung das Hinzufügen von Datenbankzugriffen, aber diese sind bei der Bearbeitung und nicht bei der Ansicht, was sich für Sie lohnen könnte. Andernfalls sollten Sie vielleicht einen gröberen Ansatz in Erwägung ziehen: Nehmen Sie 30 Beiträge, gehen Sie sie in der Ansicht in einer Schleife durch, streichen Sie die, die Sie nicht anzeigen wollen, und wenn Sie noch 15 übrig haben, zeigen Sie sie an, ansonsten wiederholen Sie den Vorgang. Definitiv ein schreckliches Worst-Case-Szenario, aber vielleicht kein schrecklicher Durchschnittsfall?

Wenn Sie eine Serverkonfiguration haben, die einen einzelnen Prozess mit mehreren Threads verwendet, sollte ein Lock oder RLock ausreichen. Hier ist eine mögliche Implementierung mit nicht reentrant lock:

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()

Auch hier ist ein entsprechendes Löschsignal wichtig.

EDIT: Viele oder die meisten Serverkonfigurationen (wie z.B. Apache) arbeiten im Prefork-Modus, d.h. es laufen mehrere Prozesse ab. Der obige Code ist in diesem Fall unbrauchbar. Siehe diese Seite für Ideen, wie man mit der Synchronisierung mit Forked-Prozessen beginnen kann.

1voto

Guðmundur H Punkte 10358

Ich habe dafür eine einfache, wenn auch nicht perfekte Lösung, die nur auf Vorlagen basiert. In der Vorlage können Sie die Datensätze neu gruppieren, indem Sie die regroup Template-Tag. Nach der Umgruppierung können Sie aufeinanderfolgende Datensätze aus derselben Quelle ausblenden:

{% 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 %}

Das wäre perfekt, wenn es nicht eine Sache gäbe: die Paginierung. Wenn Sie 15 Artikel pro Seite anzeigen wollen und auf einer Seite die ersten fünf aus einer Quelle, die nächsten fünf aus einer anderen und die letzten fünf aus einer anderen Quelle stammen, wären nur drei Artikel auf der Seite sichtbar.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X