Django-tables2: Filas de tabla duplicadas después de ordenar por encabezado de columna

Creado en 3 ago. 2016  ·  32Comentarios  ·  Fuente: jieter/django-tables2

Estoy experimentando un problema en el que algunas filas se representan varias veces en lugar de otras filas. La tabla en cuestión solía renderizarse perfectamente bien en versiones anteriores de django y django-tables2. Actualicé ambos al mismo tiempo a la última versión de cada uno (django 1.10, django-tables2 1.2.4) y luego este problema comenzó a ocurrir.

Vea esta imagen aquí para una demostración: http://imgur.com/a/pPRT8

Cuando cargo una tabla sin elegir un orden de clasificación por columna, no veo este problema. Una vez que hago clic en el encabezado de una columna para cambiar el orden de clasificación, las filas se duplican.

Hasta ahora he identificado que los datos pasados ​​al objeto BoundRows son correctos, sin elementos duplicados. Sin embargo, la iteración a través de las filas enlazadas devuelve entradas duplicadas. Entonces, parece que algo puede estar cambiando el objeto de datos mientras se lleva a cabo la iteración.

Este problema está sucediendo en tablas de ~ 150 de longitud, y (todavía) no he podido reproducir este problema con un conjunto de datos de prueba más pequeño.

Este fragmento muestra el código de prueba que he usado para delimitar la causa del problema.

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

Comentario más útil

wohoo, finalmente arreglado!

Sí, lo liberaré en una hora.

Todos 32 comentarios

Cristalizar el conjunto de consultas a través de list () detiene el problema:

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

Gracias por informar, la conversión a la lista no es aceptable como solución, ya que no funcionará con conjuntos de datos más grandes.

¿Puede intentar degradar su versión de django-tables2 para asegurarse de que no sea un problema con la actualización de su versión de Django?

Sí, definitivamente no es una solución, pero puede ser indicativo de la causa. La otra razón por la que la conversión a la lista no es una solución adecuada es porque no siempre parece ser un conjunto de consultas que contiene los datos de todos modos, en cuyo caso self.data.data no existirá.

@wtfrank , ya veo, pero realmente ayudaría si pudiera proporcionar un caso de prueba reproducible. ¿Intentó degradar django-tables2 a la versión anterior que utilizó?

Creo que me encuentro con el mismo problema: en mi tabla, falta una fila y otra es un duplicado ... Para mí, esto comenzó con la versión 1.2.2.
Cambiar a 1.2.1 "soluciona" el problema para mí ... ¿Hay alguna información que pueda proporcionar para facilitar la búsqueda?

Sería realmente bueno tener un caso de prueba mínimo. Luego podemos ejecutar git bisect para encontrar el compromiso incorrecto.

Estoy investigando un caso de prueba en este momento, pero curiosamente no puedo reproducir el problema en mi entorno de prueba ...

Localmente (usando ./manage runserver usando sqlite) todo está bien, mientras que en producción (ejecutando uwsgi y mysql) existe el problema de los dupes ... (ambos entornos se ejecutan en versiones iguales)

El conjunto de consultas no contiene ningún duplicado ... Pero en el momento en que se representa table.html, al conjunto de consultas le falta un objeto y otro está duplicado ...

Esperamos que nos comuniquemos pronto con un caso de prueba :)

El problema también ocurre en la versión 1.2.6. Creo que es algo con # 329 o # 330, pero hasta ahora no puedo encontrar la manera de solucionarlo.

Esos solo son aplicables a 1.2.6, por lo que no espero que sean la fuente de este error.

Lo siento, no puedo proporcionar un caso reproducible adecuado, pero acabo de tener este problema (o, al menos, creo que es este problema) y puedo decir que ciertamente sucedió entre 1.2.1 y 1.2.2 .

Siempre que cambio a 1.2.1, todo se ve bien. Cuando actualizo a 1.2.2 o superior (comencé con la última 1.2.6, por supuesto), constantemente pierdo la primera fila (de las tres que tengo en DB) y obtengo una copia de otra en su lugar. Por ejemplo, cuando en 1.2.1 tengo:

| fecha | aciertos | ... |
| --- | --- | --- |
| 2016-10-08 | 123 | ... |
| 2016-10-07 | 321 | ... |
| 2016-10-06 | 0 | ... |

En 1.2.2-1.2.6 siempre obtengo esto en su lugar:

| fecha | aciertos | ... |
| --- | --- | --- |
| 2016-10-06 | 0 | ... |
| 2016-10-07 | 321 | ... |
| 2016-10-06 | 0 | ... |

Estoy usando Django 1.9.9 en Python 2.7.12, el proyecto se inició usando pydanny / cookiecutter-django (aunque muy modificado después de eso), y mi código se ve así:

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

El modelo Meta solo tiene verbose_name{,_plural} cosas, y la vista es un DetailView simple que se ve así:

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

Y hay tres filas en DB (PostgreSQL 9.6), todas con el mismo source_id (solo tengo una), por lo que todas coinciden con la consulta. Curiosamente, si reemplazo .filter(source=...) con .all() el problema parece desaparecer.

Eso es todo lo que pude averiguar. Espero eso ayude. Supongo que me quedaré con 1.2.1 por ahora :)

Gracias por mostrar sus andanzas, analizaré este problema más adelante.

Me acabo de encontrar con el mismo problema, cristalizar el conjunto de consultas a través de list () tampoco es una solución viable de nuestro lado.

@ op-alex-reid ¿Puede intentar convertir su caso de uso en un caso de prueba reproducible mínimo?

Tengo el mismo problema (Django == 1.9, django-tables == 1.2.3) con el siguiente código:

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

y

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

Curiosamente, cuando configuro orderable = True , el problema no ocurre.

En mi caso, cristalizar el conjunto de consultas a través de list () _es_ es una opción (y la solución por ahora), ya que solo hay como máximo 5 filas en la tabla.

¡Gracias por el ejemplo!

Intenté reproducir esto usando este código:

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

pero esto no produce filas duplicadas ...

Hoy encontré el mismo problema. Estoy usando django-tables2 == 1.2.9 en un proyecto Django == 1.10, donde también utilizo django-filters == 1.0.1.

Curiosamente, la duplicación de filas en mi tabla solo ocurre si

  1. hay muchas entradas para mostrar
    (entonces, si habilito la paginación con 25 registros por página, todo está bien, pero si muestro el conjunto de datos completo, obtengo varias entradas idénticas)

  2. la tabla está ordenada por una columna que no contiene cadenas
    (todo está bien si ordeno por una columna que contiene, por ejemplo, nombres, pero ordenar por columnas con fechas, ints o bools da como resultado problemas)

La degradación a 1.2.1 resuelve el problema, pero esa no es realmente una opción.
Además, los duplicados parecen mostrarse en lugar de las entradas reales y no además de ellas , porque el recuento {{filter.qs.count}} es como debería ser.

Espero que esto ayude un poco a llegar al fondo del problema, porque aparte de eso, realmente disfruto trabajando con django-tables2.

¡Gracias por todo tu trabajo!

@ n0ctua gracias!

¿Puedes echar un vistazo a las consultas exactas ejecutadas y quizás compartirlas aquí?

También acabo de tener este problema y entiendo por qué me está sucediendo, así que pensé en publicarlo, ya que sospecho que otros lo tienen por la misma razón. _ (Aunque, no lo he probado con otras versiones de django-tables2, así que podría estar equivocado ya que no entiendo por qué eso evitaría que esto suceda ... Aunque ... pensando en ello, supongo que esto se debe a que en versiones anteriores, las tablas no paginadas ejecutaban una sola consulta contra la base de datos en lugar de una consulta para cada fila; más detalles sobre esto a continuación ...) _

Esto se debe a que _SQL order by en campos no únicos no es determinista_, por lo que cuando se combina con top n obtiene las mismas n filas una y otra vez. (Además, por alguna razón, cuando creo un Table sin paginación, genera una consulta para _ cada_ fila. IE top 1 ).

¿No estoy seguro de cuál es la mejor solución aquí? Creo que la única forma de garantizar que esto funcione es siempre tener el order by final en un campo único. ¿Idealmente la clave principal? Además, ¿podría valer la pena agregar algunas notas a los documentos que advierten sobre esto?

Como problema separado, pero que realmente reducirá la frecuencia con la que esto sucede (y acelerará las consultas para tablas no paginadas) es si, en lugar de ejecutar una consulta top 1 para cada fila de la tabla, se estaría ejecutando una única consulta que recupera todos los resultados.

@intiocean esta cantidad de consultas para tablas no paginadas es bastante extraño, no estoy seguro de por qué sucede esto y no debería suceder. Este caso de prueba muestra el problema:

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

He intentado, sin éxito, crear una prueba para el caso paginado que falla. No entiendo por qué, pero todo parece funcionar normalmente en el caso paginado ... Pensé que obtendría filas que parecen duplicadas en varias páginas si las ordenara por un campo no único.

@intiocean Creo que lo arreglé con 10f5e968bc10552be0ad02ee566513b5d274d5c5

@jieter ¡Genial! Puedo ver por qué eso resolvería el problema con las tablas no paginadas, pero aún creo que ordenar por una columna no única cuando los datos se distribuyen en diferentes páginas podría dar como resultado que la misma fila aparezca en varias páginas debido al top n ... limit x offset y

¿Qué piensas?

Similar a: http://stackoverflow.com/questions/31736700/duplicate-elements-in-django-paginate-after-order-by-call

@wtfrank , @intiocean @ n0ctua @ op-alex-reid @fliphess Acabo de

@intiocean Si esa es la fuente de este problema, creo que solo podemos solucionar este problema parcialmente documentando este comportamiento.

Puedo confirmar que esto corrige tablas no paginadas @jieter. Esto también mejora el rendimiento de las tablas no paginadas, ya que ahora solo hay una consulta en lugar de una para cada fila.

Cheque:
Con 962f502 y ordenando por la columna 'Notas' -> duplicación
image

Con f853078 y ordenando por la columna 'Notas' -> sin duplicación 🎉
image

¿Alguna posibilidad de que puedas hacer un lanzamiento @jieter?

wohoo, finalmente arreglado!

Sí, lo liberaré en una hora.

lanzado 1.4.0

¡Impresionante, gracias @jieter!

@jieter y @intiocean ¡ muchas gracias a ambos por arreglar esto! Funciona perfectamente ahora.

Hola, acabo de experimentar este problema en la versión 1.21.2 y Django == 2.0.6. Gracias al comentario de intiocean , lo solucioné configurando un pedido adicional por 'pk'.

¿Fue útil esta página
0 / 5 - 0 calificaciones