Django-tables2: Doppelte Tabellenzeilen nach der Sortierung nach Spaltenüberschrift

Erstellt am 3. Aug. 2016  ·  32Kommentare  ·  Quelle: jieter/django-tables2

Ich habe ein Problem, bei dem einige Zeilen anstelle anderer Zeilen mehrmals gerendert werden. Die fragliche Tabelle wurde in früheren Versionen von django und django-tables2 perfekt gerendert. Ich habe beide gleichzeitig auf die neueste Version aktualisiert (django 1.10, django-tables2 1.2.4), dann trat dieses Problem auf.

Sehen Sie sich dieses Bild hier für eine Demonstration an: http://imgur.com/a/pPRT8

Wenn ich eine Tabelle lade, ohne eine Sortierreihenfolge nach Spalte auszuwählen, sehe ich dieses Problem nicht. Sobald ich auf eine Spaltenüberschrift klicke, um die Sortierreihenfolge zu ändern, werden die Zeilen dupliziert.

Bisher habe ich festgestellt, dass die an das BoundRows-Objekt übergebenen Daten korrekt sind und keine doppelten Elemente enthalten. Beim Durchlaufen der gebundenen Zeilen werden jedoch doppelte Einträge zurückgegeben. Es sieht also so aus, als würde etwas das Datenobjekt ändern, während die Iteration stattfindet.

Dieses Problem tritt bei Tabellen mit einer Länge von ~150 auf, und ich konnte dieses Problem (noch) nicht mit einem kleineren Testdatensatz reproduzieren.

Dieses Snippet zeigt den Testcode, den ich verwendet habe, um die Ursache des Problems einzugrenzen.

--- a/django_tables2/rows.py
+++ b/django_tables2/rows.py
@@ -8,6 +8,7 @@ from django.utils import six
 from .columns.linkcolumn import BaseLinkColumn
 from .utils import A, AttributeDict, call_with_appropriate, computed_values

+import sys

 class BoundRow(object):
     """
@@ -187,9 +188,17 @@ class BoundRows(object):
     def __init__(self, data, table):
         self.data = data
         self.table = table
+        for d in data:
+            print >>sys.stderr, d
+
+        print >>sys.stderr, "end data"
+

     def __iter__(self):
         for record in self.data:
+            print >>sys.stderr, "__iter__", record
             yield BoundRow(record, table=self.table)
bug

Hilfreichster Kommentar

wohoo, endlich behoben!

Ja, ich werde innerhalb einer Stunde freigeben.

Alle 32 Kommentare

Das Kristallisieren des Abfragesatzes über list() verhindert das Auftreten des Problems:

+++ b/django_tables2/rows.py
@@ -8,6 +8,7 @@ from django.utils import six
 from .columns.linkcolumn import BaseLinkColumn
 from .utils import A, AttributeDict, call_with_appropriate, computed_values

+import sys

 class BoundRow(object):
     """
@@ -187,9 +193,26 @@ class BoundRows(object):
     def __init__(self, data, table):
         self.data = data
         self.table = table


     def __iter__(self):
+        print >>sys.stderr, type(list(self.data.data))

Vielen Dank für die Meldung, das Casting in eine Liste ist als Lösung nicht akzeptabel, da dies bei größeren Datensätzen nicht funktioniert.

Können Sie versuchen, Ihre django-tables2-Version herunterzustufen, um sicherzustellen, dass die Aktualisierung Ihrer Django-Version kein Problem darstellt?

Ja definitiv keine Lösung - aber es kann auf die Ursache hinweisen. Der andere Grund, warum das Casting in eine Liste keine geeignete Lösung ist, besteht darin, dass es sich nicht immer um ein Abfrageset zu handeln scheint, das die Daten enthält. In diesem Fall existiert self.data.data nicht.

@wtfrank , ich

Ich glaube, ich habe das gleiche Problem: In meiner Tabelle fehlt eine Zeile und eine andere ist ein Duplikat... Bei mir begann das mit Version 1.2.2.
Ein Downgrade auf 1.2.1 "behebt" das Problem bei mir... Gibt es irgendwelche Informationen, die ich bereitstellen kann, um die Suche zu erleichtern?

Ein minimaler Testfall wäre wirklich schön zu haben. Wir können dann git bisect ausführen, um den fehlerhaften Commit zu finden.

Ich prüfe gerade einen Testfall, aber seltsamerweise kann ich das Problem in meiner Testumgebung nicht reproduzieren ...

Lokal (mit ./manage runserver mit sqlite) ist alles in Ordnung, während in der Produktion (mit uwsgi und mysql) das Problem von Dupes besteht... (beide Umgebungen laufen auf gleichen Versionen)

Das Abfrageset enthält keine Duplikate.... Aber zu dem Zeitpunkt, zu dem die table.html gerendert wird, fehlt dem Abfrageset ein Objekt und ein anderes ist doppelt vorhanden...

Ich melde mich hoffentlich bald mit einem Testfall zurück :)

Das Problem tritt auch in Version 1.2.6 auf. Ich denke, es ist etw mit #329 oder #330, aber bis jetzt kann ich keinen Weg finden, es zu beheben.

Diese gelten nur für 1.2.6, daher erwarte ich nicht, dass sie die Quelle dieses Fehlers sind.

Tut mir leid, ich kann keinen reproduzierbaren Fall liefern, aber ich habe gerade dieses Problem (oder zumindest glaube ich, dass es dieses Problem ist) und kann sagen, dass es sicherlich zwischen 1.2.1 und 1.2.2 aufgetreten ist .

Wenn ich auf 1.2.1 downgrade, sieht alles gut aus. Wenn ich auf 1.2.2 oder höher aktualisiere (ich habe natürlich mit der neuesten Version 1.2.6 angefangen), verliere ich ständig die erste Zeile (von drei, die ich in der DB habe) und bekomme stattdessen eine Kopie einer anderen. ZB wenn ich auf 1.2.1 habe:

| Datum | Treffer | ... |
| --- | --- | --- |
| 2016-10-08 | 123 | ... |
| 2016-10-07 | 321 | ... |
| 2016-10-06 | 0 | ... |

Auf 1.2.2-1.2.6 bekomme ich stattdessen konsequent dies:

| Datum | Treffer | ... |
| --- | --- | --- |
| 2016-10-06 | 0 | ... |
| 2016-10-07 | 321 | ... |
| 2016-10-06 | 0 | ... |

Ich verwende Django 1.9.9 auf Python 2.7.12, das Projekt wurde mit pydanny/cookiecutter-django gestartet (obwohl danach stark modifiziert), und mein Code sieht so aus:

class DailySummaryTable(tables.Table):
    class Meta:
        model = DailySummary
        attrs = {"class": "paleblue"}
        empty_text = _("No stats yet")
        fields = ("date", "hits", ...long boring list...)
        # order_by = ("-date",)
        # orderable = False

Meta Modells enthält nur verbose_name{,_plural} und die Ansicht ist ein einfaches DetailView , das wie folgt aussieht:

class SourceDetailView(LoginRequiredMixin, DetailView):
    model = Source
    ...
    def get_context_data(self, **kwargs):
        ctx = super(...)
        ctx["stats"] = DailySummaryTable(DailySummary.objects.filter(source=self.object))
        return ctx

Und es gibt drei Zeilen in DB (PostgreSQL 9.6), die alle die gleichen source_id (ich habe nur eine), sodass sie alle mit der Abfrage übereinstimmen. Seltsamerweise scheint das Problem zu verschwinden, wenn ich .filter(source=...) durch .all() ersetze.

Das ist alles, was ich herausfinden konnte. Ich hoffe es hilft. Ich schätze, ich bleibe erstmal bei 1.2.1 :)

Vielen Dank, dass Sie Ihre Wanderungen gezeigt haben, ich werde mich später mit diesem Problem befassen.

Ich bin gerade auf das gleiche Problem gestoßen, das Kristallisieren des Abfragesatzes über list() ist auch auf unserer Seite keine praktikable Lösung.

@op-alex-reid können Sie versuchen, Ihren Anwendungsfall in einen minimal reproduzierbaren Testfall zu verwandeln?

Ich habe das gleiche Problem (Django==1.9, django-tables==1.2.3) mit folgendem Code:

class UserPlayerTable(tables.Table):
    actions = tables.LinkColumn('player:my_players_detail', args=[A('slug')],
                                text=_('View / Edit'),
                                verbose_name=_('View / Edit'), empty_values=())
    embed = tables.LinkColumn('player:my_players_embed', args=[A('slug')],
                                text=_('View / Embed now'),
                                verbose_name=_('View / Embed now'), empty_values=())

    class Meta:
        template = 'tables/table.html'
        model = Player
        fields = ('created', 'slug', 'actions', 'embed')
        attrs = {"class": "changeset"}
        order_by = ['-created']
        orderable = False

und

UserPlayerTable(Player.objects.filter(user=context['impersonate_user']))

Interessanterweise tritt das Problem nicht auf, wenn ich orderable = True setze.

In meinem Fall ist das Kristallisieren des Abfragesatzes über list() _ist_ eine Option (und vorerst der Fix), da es nur maximal 5 Zeilen in der Tabelle gibt.

Danke für das Beispiel!

Ich habe versucht, dies mit diesem Code zu reproduzieren:

class Player(models.Model):
    person = models.ForeignKey(Person)
    created = models.DateTimeField(auto_now_add=True)
    score = models.PositiveIntegerField()

def test_issue_361(per_page=5):

    bob = Person.objects.create(first_name='Bob', last_name='Builder')
    eve = Person.objects.create(first_name='Eve', last_name='Dropper')

    for i in range(10):
        Player.objects.create(person=bob, score=randint(0, 200))
        Player.objects.create(person=eve, score=randint(200, 400))
        Player.objects.create(person=bob, score=5)
        Player.objects.create(person=eve, score=5)

    class UserPlayerTable(tables.Table):
        class Meta:
            model = Player
            fields = ('person.name', 'score',)
            order_by = ['-score']
            orderable = False

    queryset = Player.objects.filter(score=5)

    table = UserPlayerTable(queryset)
    RequestConfig(request, paginate={'per_page': per_page}).configure(table)
    html = table.as_html(request)

    # count the number of rows, subtract one for the header
    assert (html.count('<tr') - 1) == per_page

aber das führt nicht zu duplizierten Zeilen...

Ich bin heute auf das gleiche Problem gestoßen. Ich verwende django-tables2==1.2.9 in einem Django==1.10-Projekt, in dem ich auch django-filters==1.0.1 verwende.

Interessanterweise tritt die Verdoppelung der Zeilen in meiner Tabelle nur auf, wenn

  1. es gibt viele Einträge zum Anzeigen
    (Wenn ich also die Paginierung mit 25 Datensätzen pro Seite aktiviere, ist alles in Ordnung, aber wenn ich den vollständigen Datensatz zeige, erhalte ich mehrere identische Einträge)

  2. die Tabelle ist nach einer Spalte sortiert, die Nicht-Strings enthält
    (alles ist in Ordnung, wenn ich nach einer Spalte sortiere, die z. B. Namen enthält, aber das Sortieren nach Spalten mit Datumsangaben, Ints oder Bools führt zu Problemen)

Ein Downgrade auf 1.2.1 behebt das Problem, aber das ist nicht wirklich eine Option.
Außerdem scheinen die Dupes statt und nicht zusätzlich zu den echten Einträgen angezeigt zu werden, da die Anzahl {{filter.qs.count}} so ist, wie sie sein sollte.

Ich hoffe, das hilft ein wenig, dem Problem auf den Grund zu gehen, denn ansonsten macht mir die Arbeit mit django-tables2 sehr viel Spaß.

Danke für deine ganze Arbeit!

@n0ctua danke!

Kannst du dir die genauen ausgeführten Abfragen ansehen und sie vielleicht hier teilen?

Ich hatte gerade auch dieses Problem und verstehe, warum es bei mir passiert, also dachte ich, ich würde es posten, da ich vermute, dass andere es aus dem gleichen Grund haben. _(Obwohl ich es nicht mit anderen Versionen von django-tables2 ausprobiert habe, könnte ich mich also irren, da ich nicht verstehe, warum dies dies verhindern würde... Obwohl... ich tatsächlich darüber nachdenke, würde ich vermuten, dass dies liegt daran, dass in älteren Versionen nicht paginierte Tabellen eine einzelne Abfrage gegen die Datenbank ausgeführt haben, anstatt eine Abfrage für jede Zeile – mehr Details dazu unten ...)_

Das liegt daran, dass _SQL order by auf nicht-eindeutigen Feldern nicht deterministisch ist_. Wenn Sie also mit top n kombiniert werden, erhalten Sie immer wieder dieselben obersten n Zeilen. (Außerdem generiert aus irgendeinem Grund, wenn ich ein Table ohne Paginierung erstelle, eine Abfrage für _jede_ Zeile. IE top 1 ).

Ich bin mir nicht sicher, was hier die beste Lösung ist? Ich denke, der einzige Weg, um zu garantieren, dass dies funktioniert, besteht darin, das letzte order by immer auf einem eindeutigen Feld zu haben. Idealerweise der Primärschlüssel? Könnte es sich auch lohnen, der Docs-Warnung diesbezüglich einige Anmerkungen hinzuzufügen?

Als separates Problem, aber eines, das die Häufigkeit der Vorkommnisse wirklich verringert (und Abfragen für nicht paginierte Tabellen beschleunigt), besteht darin, dass anstelle einer top 1 Abfrage für jede Zeile in der Tabelle nur eine einzelne ausgeführt würde Abfrage, die alle Ergebnisse abruft.

@intiocean diese Anzahl von Abfragen für nicht paginierte Tabellen ist ziemlich seltsam,

def test_single_query_for_non_paginated_table(settings):
    '''
    A non-paginated table should not generate a query for each row, but only
    one query to count the rows and one to fetch the rows.
    '''
    from django.db import connection
    settings.DEBUG = True

    for i in range(10):
        Person.objects.create(first_name='Bob %d' % randint(0, 200), last_name='Builder')

    num_queries_before = len(connection.queries)

    class PersonTable(tables.Table):
        class Meta:
            model = Person
            fields = ('first_name', 'last_name')

    table = PersonTable(Person.objects.all())
    request = build_request('/')
    RequestConfig(request, paginate=False).configure(table)
    table.as_html(request)

    # print '\n'.join(q['sql'] for q in connection.queries)
    assert len(connection.queries) - num_queries_before == 2

Ich habe erfolglos versucht, einen Test für den paginierten Fall zu erstellen, der fehlschlägt. Ich verstehe nicht warum, aber im paginierten Fall scheint alles normal zu funktionieren ... Ich dachte, ich würde Zeilen erhalten, die auf mehreren Seiten dupliziert erscheinen, wenn ich nach einem nicht eindeutigen Feld sortiere.

@intiocean Ich glaube, ich habe es mit 10f5e968bc10552be0ad02ee566513b5d274d5c5 behoben

@jieter Großartig! Ich kann verstehen, warum dies das Problem mit nicht paginierten Tabellen lösen würde, denke aber immer noch, dass die Sortierung nach einer nicht eindeutigen Spalte, wenn die Daten auf verschiedene Seiten verteilt sind, dazu führen kann, dass dieselbe Zeile auf mehreren Seiten angezeigt wird, aufgrund der top n ... limit x offset y

Was denken Sie?

Ähnlich wie: http://stackoverflow.com/questions/31736700/duplicate-elements-in-django-paginate-after-order-by-call

@wtfrank , @intiocean @n0ctua @op-alex- reid @fliphess Ich habe gerade einige Commits an den Master geschoben, die dies beheben könnten. Können Sie überprüfen?

@intiocean Wenn dies die

Ich kann bestätigen, dass dies nicht paginierte Tabellen @jieter behebt. Dies verbessert auch die Leistung für nicht paginierte Tabellen, da es jetzt nur noch eine Abfrage statt einer für jede Zeile gibt.

Prüfen:
Mit 962f502 und Sortierung nach Spalte 'Notizen' -> Duplizierung
image

Mit f853078 und Sortierung nach der Spalte 'Notizen' -> keine Duplizierung 🎉
image

Hast du eine Chance @jieter zu veröffentlichen?

wohoo, endlich behoben!

Ja, ich werde innerhalb einer Stunde freigeben.

veröffentlicht 1.4.0

Super, danke @jieter!

@jieter und @intiocean vielen Dank für die Behebung des

Hallo, ich habe dieses Problem gerade in Version 1.21.2 und Django==2.0.6 erlebt. Dank des Kommentars von intiocean wurde das Problem behoben, indem die zusätzliche Sortierung nach 'pk' festgelegt wurde.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen