Estou tendo um problema em que algumas linhas são renderizadas várias vezes no lugar de outras linhas. A tabela em questão costumava renderizar perfeitamente bem em versões anteriores do django e django-tables2. Eu atualizei os dois ao mesmo tempo para a versão mais recente de cada um (django 1.10, django-tables2 1.2.4), então esse problema começou a acontecer.
Veja esta imagem aqui para uma demonstração: http://imgur.com/a/pPRT8
Quando carrego uma tabela sem escolher uma ordem de classificação pela coluna, não vejo esse problema. Assim que clico no cabeçalho de uma coluna para alterar a ordem de classificação, as linhas são duplicadas.
Até agora, identifiquei que os dados passados para o objeto BoundRows estão corretos, sem itens duplicados. No entanto, a iteração pelas linhas vinculadas retorna entradas duplicadas. Portanto, parece que algo pode estar mudando o objeto de dados enquanto a iteração está ocorrendo.
Esse problema está acontecendo em tabelas de comprimento ~ 150, e eu (ainda) não consegui reproduzir esse problema com um conjunto de dados de teste menor.
Este trecho mostra o código de teste que usei para restringir a causa do 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)
Cristalizar o queryset por meio de list () impede que o problema aconteça:
+++ 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))
Obrigado por relatar, lançar para a lista não é aceitável como uma solução, pois isso não funcionará com conjuntos de dados maiores.
Você pode tentar fazer o downgrade de sua versão do django-tables2 para ter certeza de que não é um problema com a atualização da sua versão do Django?
Sim, definitivamente não é uma solução - mas pode ser um indicativo da causa. O outro motivo pelo qual o cast para list não é uma solução adequada é porque nem sempre parece ser um queryset contendo os dados, caso em que self.data.data não existirá.
@wtfrank , entendo, mas ajudaria muito se você pudesse fornecer um caso de teste reproduzível. Você tentou fazer o downgrade do django-tables2 para a versão anterior que você usava?
Acho que estou tendo o mesmo problema: na minha tabela, uma linha está faltando e outra é uma duplicata ... Para mim, isso começou com a versão 1.2.2.
Fazer downgrade para 1.2.1 "fixxes" é o problema para mim ... Há alguma informação que eu possa fornecer para tornar a pesquisa mais fácil?
Seria muito bom ter um caso de teste mínimo. Podemos então executar git bisect para encontrar o commit inválido.
Estou analisando um caso de teste no momento, mas, estranhamente, não consigo reproduzir o problema no meu ambiente de teste ...
Localmente (usando ./manage runserver usando sqlite) está tudo bem, enquanto na produção (executando uwsgi e mysql) existe o problema de ingênuos ... (ambos os ambientes rodam em versões iguais)
O queryset não contém nenhum dupes .... Mas no momento em que o table.html é renderizado, o queryset está sem um objeto e outro está duplicado ...
Voltando para você em breve com um caso de teste, espero :)
O problema ocorre também na versão 1.2.6. Eu acho que é sth com # 329 ou # 330, mas até agora não consigo encontrar uma maneira de consertar.
Eles são aplicáveis apenas ao 1.2.6, então não espero que sejam a fonte desse bug.
Não posso fornecer um caso reproduzível adequado, mas acabei de ter esse problema (ou, pelo menos, acredito que seja esse o problema) e posso dizer que certamente aconteceu entre 1.2.1 e 1.2.2 .
Sempre que faço o downgrade para 1.2.1, tudo parece bem. Quando eu atualizo para 1.2.2 ou superior (eu comecei com o 1.2.6 mais recente, é claro), eu sempre perco a primeira linha (das três que tenho no banco de dados) e obtenho uma cópia de outra em vez dela. Por exemplo, quando em 1.2.1 eu tenho:
| data | acessos | ... |
| --- | --- | --- |
| 08/10/2016 | 123 ... |
| 07/10/2016 | 321 ... |
| 06-10-2016 | 0 | ... |
Em 1.2.2-1.2.6, recebo consistentemente em vez disso:
| data | acessos | ... |
| --- | --- | --- |
| 06-10-2016 | 0 | ... |
| 07/10/2016 | 321 ... |
| 06-10-2016 | 0 | ... |
Estou usando Django 1.9.9 no Python 2.7.12, o projeto foi iniciado usando pydanny / cookiecutter-django (embora muito modificado depois disso) e meu código se parece com este:
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
O Meta
tem apenas verbose_name{,_plural}
coisas e a visualização é um DetailView
simples que se parece com isto:
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
E há três linhas no banco de dados (PostgreSQL 9.6), todas tendo o mesmo source_id
(eu tenho apenas uma), então todas correspondem à consulta. Estranhamente, se eu substituir .filter(source=...)
por .all()
o problema parece desaparecer.
Isso é tudo que eu fui capaz de descobrir. Espero que ajude. Acho que vou ficar com 1.2.1 por enquanto :)
Obrigado por mostrar suas divagações, examinarei esse problema mais tarde.
Acabei de ter o mesmo problema, cristalizar o queryset via list () também não é uma solução viável da nossa parte.
@ op-alex-reid você pode tentar transformar seu caso de uso em um caso de teste reproduzível mínimo?
Estou tendo o mesmo problema (Django == 1.9, django-tables == 1.2.3) com o seguinte 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
e
UserPlayerTable(Player.objects.filter(user=context['impersonate_user']))
Curiosamente, quando defino orderable = True
, o problema não ocorre.
No meu caso, cristalizar o queryset via list () _é_ uma opção (e a correção por enquanto), uma vez que existem apenas no máximo 5 linhas na tabela.
Obrigado pelo exemplo!
Tentei reproduzir isso 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
mas isso não produz linhas duplicadas ...
Eu encontrei o mesmo problema hoje. Estou usando django-tables2 == 1.2.9 em um projeto Django == 1.10, onde também utilizo django-filters == 1.0.1.
Curiosamente, a duplicação das linhas na minha tabela só ocorre se
existem muitas entradas para mostrar
(então, se eu ativar a paginação com 25 registros por página, está tudo bem, mas se eu exibir o conjunto de dados completo, obtenho várias entradas idênticas)
a tabela é classificada por uma coluna que contém não strings
(está tudo bem se eu classificar por uma coluna que contém, por exemplo, nomes, mas classificar por colunas com datas, ints ou bools resulta em problemas)
O downgrade para 1.2.1 resolve o problema, mas isso não é realmente uma opção.
Além disso, os ingênuos parecem ser mostrados em vez de e não em adição às entradas reais, porque a contagem {{filter.qs.count}} é como deveria ser.
Espero que isso ajude um pouco a chegar ao fundo do problema, porque além disso, eu realmente gosto de trabalhar com django-tables2.
Obrigado por todo o seu trabalho!
@ n0ctua, obrigado!
Você pode dar uma olhada nas consultas exatas executadas e talvez compartilhá-las aqui?
Também acabei de ter esse problema e entendo por que está acontecendo comigo, então pensei em postar, pois suspeito que outros têm pelo mesmo motivo. _ (Embora eu não tenha tentado com outras versões de django-tables2, então posso estar errado, pois não entendo por que isso iria impedir que isso acontecesse ... Embora ... realmente pensando sobre isso, eu colocaria a hipótese de que isso é porque em versões anteriores as tabelas não paginadas executavam uma única consulta no banco de dados em vez de uma consulta para cada linha - mais detalhes sobre isso a seguir ...) _
É porque _SQL order by
em campos não únicos não é determinístico_ então, quando combinado por top n
você obtém as mesmas n linhas principais repetidamente. (Além disso, por alguma razão, quando eu crio um Table
sem paginação, ele gera uma consulta para _cada_ linha. IE top 1
).
Não tenho certeza de qual é a melhor solução aqui? Acho que a única maneira de garantir que isso funcionará é sempre ter o order by
final em um campo único. Idealmente, a chave primária? Além disso, pode valer a pena adicionar algumas notas aos documentos de aviso sobre isso?
Como um problema separado, mas que realmente reduzirá a frequência com que isso acontece (e acelerará as consultas para tabelas não paginadas), em vez de executar uma consulta top 1
para cada linha da tabela, seria apenas executar uma única consulta que busca todos os resultados.
@intiocean este número de consultas para tabelas não paginadas é bastante estranho, não sei por que isso acontece, e não deveria acontecer. Este caso de teste mostra o 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
Tentei, sem sucesso, criar um teste para o caso paginado que falha. Não entendo por que, mas tudo parece funcionar normalmente no caso paginado ... Pensei em obter linhas que aparecem como se duplicadas em várias páginas, se classificar por um campo não exclusivo.
@intiocean Acho que consertei com 10f5e968bc10552be0ad02ee566513b5d274d5c5
@jieter Ótimo! Posso ver por que isso resolveria o problema com tabelas não paginadas, mas ainda acho que ordenar por uma coluna não exclusiva quando os dados são espalhados por páginas diferentes pode resultar na mesma linha aparecendo em várias páginas devido ao top n ... limit x offset y
O que você acha?
Semelhante a: http://stackoverflow.com/questions/31736700/duplicate-elements-in-django-paginate-after-order-by-call
@wtfrank , @intiocean @ n0ctua @ op-alex-reid @fliphess Acabei de
@intiocean Se essa é a origem do problema, acho que só podemos corrigi-lo parcialmente documentando esse comportamento.
Posso confirmar que isso corrige tabelas não paginadas @jieter. Isso também melhora o desempenho de tabelas não paginadas, pois agora há apenas uma consulta em vez de uma para cada linha.
Verificar:
Com 962f502 e classificação pela coluna 'Notas' -> duplicação
Com f853078 e classificação pela coluna 'Notas' -> sem duplicação 🎉
Alguma chance de você fazer um lançamento @jieter?
wohoo, finalmente consertado!
Sim, vou soltar dentro de uma hora.
lançado 1.4.0
Incrível, obrigado @jieter!
@jieter e @intiocean muito obrigado por consertar isso! Funciona perfeitamente agora.
Olá, acabei de enfrentar esse problema na versão 1.21.2 e no Django == 2.0.6. Graças a intiocean
's comentário , fixa-lo com a criação de encomenda adicional por 'pk'.
Comentários muito úteis
wohoo, finalmente consertado!
Sim, vou soltar dentro de uma hora.