Django-tables2: Дублированные строки таблицы после сортировки по заголовку столбца

Созданный на 3 авг. 2016  ·  32Комментарии  ·  Источник: jieter/django-tables2

У меня возникла проблема, когда некоторые строки отображаются несколько раз вместо других строк. Данная таблица отлично отображалась в более ранних версиях django и django-tables2. Я обновил их обоих одновременно до последней версии каждого (django 1.10, django-tables2 1.2.4), после чего эта проблема начала возникать.

См. Это изображение здесь для демонстрации: http://imgur.com/a/pPRT8

Когда я загружаю таблицу, не выбирая порядок сортировки по столбцу, я не вижу этой проблемы. Как только я нажимаю заголовок столбца, чтобы изменить порядок сортировки, строки дублируются.

Пока что я определил, что данные, переданные в объект BoundRows, верны, без повторяющихся элементов. Однако повторение связанных строк возвращает повторяющиеся записи. Таким образом, похоже, что что-то может изменять объект данных во время итерации.

Эта проблема возникает в таблицах длиной ~ 150, и я (пока) не смог воспроизвести эту проблему с меньшим набором тестовых данных.

Этот фрагмент показывает тестовый код, который я использовал, чтобы сузить причину проблемы.

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

Самый полезный комментарий

wohoo, наконец-то исправлено!

Да, отпущу в течении часа.

Все 32 Комментарий

Кристаллизация набора запросов через list () предотвращает возникновение проблемы:

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

Спасибо за отчет, приведение к списку неприемлемо в качестве решения, поскольку оно не будет работать с большими наборами данных.

Можете ли вы попробовать понизить версию вашей django-tables2, чтобы убедиться, что это не проблема с обновлением вашей версии Django?

Да, определенно не решение, но это может указывать на причину. Другая причина, по которой приведение к списку не является подходящим решением, заключается в том, что это не всегда кажется набором запросов, в любом случае хранящим данные, и в этом случае self.data.data не существует.

@wtfrank , я понимаю, но было бы очень

Я думаю, что столкнулся с той же проблемой: в моей таблице одна строка отсутствует, а другая дублируется ... Для меня это началось с версии 1.2.2.
Переход на 1.2.1 "решает" проблему для меня .... Могу ли я предоставить какую-либо информацию, чтобы облегчить поиск?

Было бы неплохо иметь минимальный тестовый пример. Затем мы можем запустить git bisect, чтобы найти плохую фиксацию.

В данный момент я изучаю тестовый пример, но, как ни странно, я не могу воспроизвести проблему в своей тестовой среде ...

Локально (с использованием ./manage runserver с использованием sqlite) все в порядке, в то время как в производственной среде (с использованием uwsgi и mysql) существует проблема дублирования ... (обе среды работают на равных версиях)

В наборе запросов нет дубликатов .... Но во время рендеринга table.html в наборе запросов отсутствует один объект, а другой дублируется ...

Надеюсь, скоро вернусь к вам с тестовым примером :)

Проблема возникает также в версии 1.2.6. Я думаю, что это что-то с # 329 или # 330, но пока я не могу найти способ исправить это.

Они применимы только к 1.2.6, поэтому я не ожидаю, что они станут источником этой ошибки.

Извините, я не могу предоставить надлежащий воспроизводимый случай, но у меня только что возникла эта проблема (или, по крайней мере, я считаю, что это проблема) и могу сказать, что это определенно произошло между 1.2.1 и 1.2.2 .

Всякий раз, когда я переключаюсь на 1.2.1, все выглядит нормально. Когда я обновляюсь до 1.2.2 или выше (я начал с последней версии 1.2.6, конечно), я постоянно теряю первую строку (из трех, которые есть в БД) и вместо нее получаю копию другой. Например, когда на 1.2.1 у меня есть:

| дата | хиты | ... |
| --- | --- | --- |
| 2016-10-08 | 123 | ... |
| 2016-10-07 | 321 | ... |
| 2016-10-06 | 0 | ... |

На 1.2.2-1.2.6 я постоянно получаю это:

| дата | хиты | ... |
| --- | --- | --- |
| 2016-10-06 | 0 | ... |
| 2016-10-07 | 321 | ... |
| 2016-10-06 | 0 | ... |

Я использую Django 1.9.9 на Python 2.7.12, проект был запущен с использованием pydanny / cookiecutter-django (хотя после этого сильно изменился), и мой код выглядит так:

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 модели содержит только verbose_name{,_plural} , а просмотр представляет собой простой DetailView который выглядит так:

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

И в БД есть три строки (PostgreSQL 9.6), все с одинаковыми source_id (у меня только одна), поэтому все они соответствуют запросу. Как ни странно, если я заменю .filter(source=...) на .all() проблема, похоже, исчезнет.

Это все, что мне удалось выяснить. Надеюсь, это поможет. Думаю, я пока буду придерживаться 1.2.1 :)

Спасибо, что показали свои странствия, я займусь этим вопросом позже.

Я только что столкнулся с той же проблемой, кристаллизация набора запросов через list () также не является жизнеспособным решением с нашей стороны.

@ op-alex-reid можете ли вы попытаться превратить свой вариант использования в минимально воспроизводимый тестовый пример?

У меня такая же проблема (Django == 1.9, django-tables == 1.2.3) со следующим кодом:

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

а также

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

Интересно, что когда я устанавливаю orderable = True , проблема не возникает.

В моем случае кристаллизация набора запросов с помощью list () _ является вариантом (и исправлением на данный момент), поскольку в таблице не более 5 строк.

Спасибо за пример!

Я попытался воспроизвести это с помощью этого кода:

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

но это не дает повторяющихся строк ...

Сегодня я столкнулся с той же проблемой. Я использую django-tables2 == 1.2.9 в проекте Django == 1.10, где я также использую django-filters == 1.0.1.

Интересно, что удвоение строк в моей таблице происходит только в том случае, если

  1. есть много записей для отображения
    (поэтому, если я включу разбиение на страницы с 25 записями на страницу, все в порядке, но если я покажу полный набор данных, я получу несколько идентичных записей)

  2. таблица отсортирована по столбцу, который не содержит строк
    (все в порядке, если я сортирую по столбцу, который содержит, например, имена, но сортировка по столбцам с датами, целыми числами или логическими значениями приводит к проблемам)

Переход на 1.2.1 решает проблему, но на самом деле это не вариант.
Кроме того, кажется, что дубликаты отображаются вместо реальных записей, а не в дополнение к ним , потому что счетчик {{filter.qs.count}} такой, каким должен быть.

Я надеюсь, что это немного поможет разобраться в проблеме, потому что в остальном мне очень нравится работать с django-tables2.

Спасибо за вашу работу!

@ n0ctua спасибо!

Можете ли вы посмотреть на точные выполненные запросы и, возможно, поделиться ими здесь?

У меня тоже была эта проблема, и я понимаю, почему это происходит со мной, поэтому решил опубликовать, поскольку подозреваю, что у других есть это по той же причине. _ (Хотя я не пробовал с другими версиями django-tables2, поэтому могу ошибаться, так как не понимаю, почему это остановило бы это ... Хотя ... на самом деле думая об этом, я бы предположил, что это потому что в более старых версиях таблицы без разбивки на страницы выполняли один запрос к базе данных вместо запроса для каждой строки - подробнее об этом ниже ...) _

Это потому, что _SQL order by для неуникальных полей не является детерминированным_, поэтому при объединении с помощью top n вы снова и снова получаете одни и те же первые n строк. (Кроме того, по какой-то причине, когда я создаю Table без разбивки на страницы, он генерирует запрос для _every_ row. IE top 1 ).

Я не уверен, какое здесь лучшее решение? Я думаю, что единственный способ гарантировать, что это сработает, - всегда иметь последний order by в уникальном поле. В идеале первичный ключ? Кроме того, может быть, стоит добавить несколько примечаний к предупреждению об этом в документации?

В качестве отдельной проблемы, но та, которая действительно уменьшит частоту этого (и ускорит запросы для таблиц без разбивки на страницы), является то, если вместо выполнения запроса top 1 для каждой строки в таблице будет выполняться просто один запрос, который получает все результаты.

@intiocean такое количество запросов для таблиц без

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

Я безуспешно пытался создать тест для разбитого на страницы случая, который терпит неудачу. Я не понимаю, почему, но в случае с разбивкой на страницы все работает нормально ... Я думал, что получу строки, которые выглядят как дублированные на нескольких страницах при сортировке по неуникальному полю.

@intiocean Кажется, я исправил это с помощью 10f5e968bc10552be0ad02ee566513b5d274d5c5

@jieter Отлично! Я понимаю, почему это решило бы проблему с таблицами без разбивки на страницы, но все же считаю, что упорядочение по неуникальному столбцу, когда данные распределены по разным страницам, может привести к тому, что одна и та же строка появится на нескольких страницах из-за top n ... limit x offset y

Что вы думаете?

Похоже на: http://stackoverflow.com/questions/31736700/duplicate-elements-in-django-paginate-after-order-by-call

@wtfrank , @intiocean @ n0ctua @ op-alex-reid @fliphess Я только что отправил в коммитов, которые могут это исправить. Вы можете проверить?

@intiocean Если это источник проблемы, я думаю, что мы сможем решить эту проблему только частично, задокументировав такое поведение.

Я могу подтвердить, что это исправляет не разбитые на страницы таблицы @jieter. Это также улучшает производительность для таблиц без разбивки на страницы, поскольку теперь существует только один запрос вместо одного для каждой строки.

Проверять:
С 962f502 и сортировкой по столбцу «Примечания» -> дублирование
image

С f853078 и сортировкой по столбцу «Примечания» -> без дублирования 🎉
image

Есть ли шанс сделать релиз @jieter?

wohoo, наконец-то исправлено!

Да, отпущу в течении часа.

выпущен 1.4.0

Отлично, спасибо @jieter!

@jieter и @intiocean большое вам спасибо за исправление! Теперь работает отлично.

Привет, я только что столкнулся с этой проблемой в версии 1.21.2 и Django == 2.0.6. Благодаря комментарию intiocean исправил с установкой дополнительного упорядочивания по 'pk'.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги