Django-tables2: ์—ด ๋จธ๋ฆฌ๊ธ€๋กœ ์ •๋ ฌํ•œ ํ›„ ์ค‘๋ณต๋œ ํ…Œ์ด๋ธ” ํ–‰

์— ๋งŒ๋“  2016๋…„ 08์›” 03์ผ  ยท  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)

๊ฐ€์žฅ ์œ ์šฉํ•œ ๋Œ“๊ธ€

์šฐ์™€ ๋“œ๋””์–ด ๊ณ ์ณ์กŒ๋‹ค!

๋„ค, 1์‹œ๊ฐ„ ์ด๋‚ด์— ํ’€์–ด๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค.

๋ชจ๋“  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 ๋ฒ„์ „ ์—…๋ฐ์ดํŠธ์— ๋ฌธ์ œ๊ฐ€ ์—†๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด django-tables2 ๋ฒ„์ „์„ ๋‹ค์šด๊ทธ๋ ˆ์ด๋“œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ?

์˜ˆ, ํ™•์‹คํžˆ ํ•ด๊ฒฐ์ฑ…์€ ์•„๋‹ˆ์ง€๋งŒ ์›์ธ์„ ๋‚˜ํƒ€๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ชฉ๋ก์œผ๋กœ ์บ์ŠคํŒ…ํ•˜๋Š” ๊ฒƒ์ด ์ ์ ˆํ•œ ์†”๋ฃจ์…˜์ด ์•„๋‹Œ ๋‹ค๋ฅธ ์ด์œ ๋Š” ๊ทธ๊ฒƒ์ด ํ•ญ์ƒ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด์œ ํ•˜๋Š” ์ฟผ๋ฆฌ ์„ธํŠธ์ธ ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ด์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ์—๋Š” self.data.data๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

@wtfrank ,

๋‚˜๋Š” ๊ฐ™์€ ๋ฌธ์ œ์— ์ง๋ฉดํ•˜๊ณ  ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. ๋‚ด ํ…Œ์ด๋ธ”์—์„œ ํ•˜๋‚˜์˜ ํ–‰์ด ๋ˆ„๋ฝ๋˜์—ˆ๊ณ  ๋‹ค๋ฅธ ํ•˜๋‚˜๋Š” ์ค‘๋ณต๋˜์—ˆ์Šต๋‹ˆ๋‹ค... ์ €์—๊ฒŒ ์ด๊ฒƒ์€ ๋ฒ„์ „ 1.2.2๋ถ€ํ„ฐ ์‹œ์ž‘๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
1.2.1 "fixxes"๋กœ ๋‹ค์šด๊ทธ๋ ˆ์ด๋“œํ•˜๋ฉด ๋ฌธ์ œ๊ฐ€ ํ•ด๊ฒฐ๋ฉ๋‹ˆ๋‹ค. ๊ฒ€์ƒ‰์„ ๋” ์‰ฝ๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๋Š” ์ •๋ณด๊ฐ€ ์žˆ์Šต๋‹ˆ๊นŒ?

์ตœ์†Œํ•œ์˜ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๊ฐ€ ์žˆ์œผ๋ฉด ์ •๋ง ์ข‹์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ git bisect๋ฅผ ์‹คํ–‰ํ•˜์—ฌ ์ž˜๋ชป๋œ ์ปค๋ฐ‹์„ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ˜„์žฌ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ์กฐ์‚ฌํ•˜๊ณ  ์žˆ์ง€๋งŒ ์ด์ƒํ•˜๊ฒŒ๋„ ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์—์„œ ๋ฌธ์ œ๋ฅผ ์žฌํ˜„ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค...

๋กœ์ปฌ(sqlite๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ./manage runserver ์‚ฌ์šฉ)์€ ๋ชจ๋‘ ๋ฌธ์ œ๊ฐ€ ์—†์ง€๋งŒ ํ”„๋กœ๋•์…˜(uwsgi ๋ฐ mysql ์‹คํ–‰)์—์„œ๋Š” ์†์ž„์ˆ˜ ๋ฌธ์ œ๊ฐ€ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค...(๋‘ ํ™˜๊ฒฝ ๋ชจ๋‘ ๋™์ผํ•œ ๋ฒ„์ „์—์„œ ์‹คํ–‰๋จ)

์ฟผ๋ฆฌ ์„ธํŠธ์—๋Š” ์†์ž„์ˆ˜๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.... ๊ทธ๋Ÿฌ๋‚˜ table.html์ด ๋ Œ๋”๋ง๋  ๋•Œ ์ฟผ๋ฆฌ ์„ธํŠธ์—๋Š” ํ•˜๋‚˜์˜ ๊ฐœ์ฒด๊ฐ€ ๋ˆ„๋ฝ๋˜๊ณ  ๋‹ค๋ฅธ ํ•˜๋‚˜๋Š” ์ค‘๋ณต๋ฉ๋‹ˆ๋‹ค...

๊ณง ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋กœ ๋‹ค์‹œ ๋Œ์•„์˜ค๊ฒ ์Šต๋‹ˆ๋‹ค :)

์ด ๋ฌธ์ œ๋Š” 1.2.6 ๋ฒ„์ „์—์„œ๋„ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. # 329 ๋˜๋Š” # 330์œผ๋กœ sth๋ผ๊ณ  ์ƒ๊ฐํ•˜์ง€๋งŒ ์ง€๊ธˆ๊นŒ์ง€ ๊ณ ์น  ๋ฐฉ๋ฒ•์„ ์ฐพ์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๊ฒƒ๋“ค์€ 1.2.6์—๋งŒ ์ ์šฉ ๊ฐ€๋Šฅํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด ๋ฒ„๊ทธ์˜ ์›์ธ์ด ๋  ๊ฒƒ์ด๋ผ๊ณ ๋Š” ์ƒ๊ฐํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค. ์žฌํ˜„ ๊ฐ€๋Šฅํ•œ ์ ์ ˆํ•œ ์‚ฌ๋ก€๋ฅผ ์ œ๊ณตํ•  ์ˆ˜ ์—†์ง€๋งŒ ๋ฐฉ๊ธˆ ์ด ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์œผ๋ฉฐ(๋˜๋Š” ์ ์–ด๋„ ์ด ๋ฌธ์ œ๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค) 1.2.1๊ณผ 1.2.2 ์‚ฌ์ด์— ํ™•์‹คํžˆ ๋ฐœ์ƒํ–ˆ์Œ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. .

1.2.1๋กœ ๋‹ค์šด๊ทธ๋ ˆ์ด๋“œํ•  ๋•Œ๋งˆ๋‹ค ๋ชจ๋“  ๊ฒƒ์ด ๊ดœ์ฐฎ์•„ ๋ณด์ž…๋‹ˆ๋‹ค. 1.2.2 ์ด์ƒ์œผ๋กœ ์—…๊ทธ๋ ˆ์ด๋“œํ•  ๋•Œ(๋ฌผ๋ก  ์ตœ์‹  1.2.6์œผ๋กœ ์‹œ์ž‘ํ–ˆ์Šต๋‹ˆ๋‹ค), ์ผ๊ด€๋˜๊ฒŒ ์ฒซ ๋ฒˆ์งธ ํ–‰(DB์— ์žˆ๋Š” 3๊ฐœ ์ค‘)์„ ์žƒ๊ณ  ๊ทธ ๋Œ€์‹  ๋‹ค๋ฅธ ํ–‰์˜ ์‚ฌ๋ณธ์„ ์–ป์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, 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 | ... |

์ €๋Š” Python 2.7.12์—์„œ Django 1.9.9๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์œผ๋ฉฐ ํ”„๋กœ์ ํŠธ๋Š” 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

๊ทธ๋ฆฌ๊ณ  DB(PostgreSQL 9.6)์—๋Š” ๋ชจ๋‘ ๋™์ผํ•œ source_id (ํ•˜๋‚˜๋งŒ ์žˆ์Œ)๋ฅผ ๊ฐ–๋Š” 3๊ฐœ์˜ ํ–‰์ด ์žˆ์œผ๋ฏ€๋กœ ๋ชจ๋‘ ์ฟผ๋ฆฌ์™€ ์ผ์น˜ํ•ฉ๋‹ˆ๋‹ค. ์ด์ƒํ•˜๊ฒŒ๋„ .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() ๋ฅผ ํ†ตํ•ด ์ฟผ๋ฆฌ ์„ธํŠธ๋ฅผ ๊ฒฐ์ •ํ™”ํ•˜๋Š” ๊ฒƒ์€ _is_ ์˜ต์…˜(์ง€๊ธˆ์€ ์ˆ˜์ • ์‚ฌํ•ญ)์ž…๋‹ˆ๋‹ค. ํ…Œ์ด๋ธ”์— ์ตœ๋Œ€ 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==1.10 ํ”„๋กœ์ ํŠธ์—์„œ django-tables2==1.2.9๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ django-filters==1.0.1๋„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

ํฅ๋ฏธ๋กญ๊ฒŒ๋„ ๋‚ด ํ…Œ์ด๋ธ”์˜ ํ–‰์ด ๋‘ ๋ฐฐ๋กœ ๋Š˜์–ด๋‚˜๋Š” ๊ฒƒ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒฝ์šฐ์—๋งŒ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

  1. ํ‘œ์‹œํ•  ํ•ญ๋ชฉ์ด ๋งŽ์ด ์žˆ์Šต๋‹ˆ๋‹ค.
    (๋”ฐ๋ผ์„œ ํŽ˜์ด์ง€๋‹น 25๊ฐœ์˜ ๋ ˆ์ฝ”๋“œ๋กœ ํŽ˜์ด์ง€ ๋งค๊น€์„ ํ™œ์„ฑํ™”ํ•˜๋ฉด ๋ชจ๋“  ๊ฒƒ์ด ์ •์ƒ์ด์ง€๋งŒ ์ „์ฒด ๋ฐ์ดํ„ฐ ์„ธํŠธ๋ฅผ ํ‘œ์‹œํ•˜๋ฉด ๋™์ผํ•œ ํ•ญ๋ชฉ์ด ์—ฌ๋Ÿฌ ๊ฐœ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค.)

  2. ํ…Œ์ด๋ธ”์ด ๋ฌธ์ž์—ด์ด ์•„๋‹Œ ์—ด์„ ๊ธฐ์ค€์œผ๋กœ ์ •๋ ฌ๋ฉ๋‹ˆ๋‹ค.
    (์˜ˆ๋ฅผ ๋“ค์–ด ์ด๋ฆ„์ด ํฌํ•จ๋œ ์—ด์„ ๊ธฐ์ค€์œผ๋กœ ์ •๋ ฌํ•˜๋ฉด ๋ชจ๋“  ๊ฒƒ์ด ์ข‹์ง€๋งŒ ๋‚ ์งœ, int ๋˜๋Š” bool์ด ์žˆ๋Š” ์—ด์„ ๊ธฐ์ค€์œผ๋กœ ์ •๋ ฌํ•˜๋ฉด ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค)

1.2.1๋กœ ๋‹ค์šด๊ทธ๋ ˆ์ด๋“œํ•˜๋ฉด ๋ฌธ์ œ๊ฐ€ ํ•ด๊ฒฐ๋˜์ง€๋งŒ ์‹ค์ œ๋กœ๋Š” ์˜ต์…˜์ด ์•„๋‹™๋‹ˆ๋‹ค.
๊ฒŒ๋‹ค๊ฐ€ {{filter.qs.count}} ์นด์šดํŠธ๊ฐ€ ์žˆ์–ด์•ผ ํ•˜๋Š” ๋Œ€๋กœ์ด๊ธฐ ๋•Œ๋ฌธ์— ์†์ž„์ˆ˜๋Š” ์‹ค์ œ ํ•ญ๋ชฉ ๋Œ€์‹  ํ‘œ์‹œ ๋˜๊ณ  ์ถ”๊ฐ€๋˜์ง€ ์•Š๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

django-tables2๋กœ ์ž‘์—…ํ•˜๋Š” ๊ฒƒ์„ ์ฆ๊ธด๋‹ค๋Š” ์  ์™ธ์—๋Š” ์ด๊ฒƒ์ด ๋ฌธ์ œ์˜ ํ•ต์‹ฌ์„ ํŒŒ์•…ํ•˜๋Š” ๋ฐ ์กฐ๊ธˆ์ด๋‚˜๋งˆ ๋„์›€์ด ๋˜๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค.

๋ชจ๋“  ์ž‘์—…์— ๊ฐ์‚ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค!

@n0ctua ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค!

์‹คํ–‰๋œ ์ •ํ™•ํ•œ ์ฟผ๋ฆฌ๋ฅผ ๋ณด๊ณ  ์—ฌ๊ธฐ์—์„œ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ?

์ €๋„ ๋ฐฉ๊ธˆ ์ด ๋ฌธ์ œ๋ฅผ ๊ฒช์—ˆ๊ณ  ์™œ ์ €์—๊ฒŒ ์ด๋Ÿฐ ์ผ์ด ์ผ์–ด๋‚˜๋Š”์ง€ ์ดํ•ดํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค๋„ ๊ฐ™์€ ์ด์œ ๋กœ ๋ฌธ์ œ๊ฐ€ ์žˆ๋Š” ๊ฒƒ ๊ฐ™์•„ ๊ฒŒ์‹œํ•  ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค. _(ํ•˜์ง€๋งŒ, ๋‚˜๋Š” django-tables2์˜ ๋‹ค๋ฅธ ๋ฒ„์ „์œผ๋กœ ์‹œ๋„ํ•˜์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด๊ฒƒ์ด ์™œ ์ด๋Ÿฐ ์ผ์ด ์ผ์–ด๋‚˜์ง€ ์•Š๋Š”์ง€ ์ดํ•ดํ•˜์ง€ ๋ชปํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ‹€๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค... ๋น„๋ก... ์‹ค์ œ๋กœ ๊ทธ๊ฒƒ์— ๋Œ€ํ•ด ์ƒ๊ฐํ•ด๋ณด๋ฉด ๋‚˜๋Š” ์ด๊ฒƒ์ด ์ด์ „ ๋ฒ„์ „์—์„œ๋Š” ํŽ˜์ด์ง€๊ฐ€ ๋งค๊ฒจ์ง€์ง€ ์•Š์€ ํ…Œ์ด๋ธ”์ด ๊ฐ ํ–‰์— ๋Œ€ํ•œ ์ฟผ๋ฆฌ ๋Œ€์‹  DB์— ๋Œ€ํ•ด ๋‹จ์ผ ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ ์•„๋ž˜์—์„œ...)_

๊ณ ์œ ํ•˜์ง€ ์•Š์€ ํ•„๋“œ์— ๋Œ€ํ•œ _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๋ฅผ ํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐํšŒ๊ฐ€ ์žˆ์Šต๋‹ˆ๊นŒ?

์šฐ์™€ ๋“œ๋””์–ด ๊ณ ์ณ์กŒ๋‹ค!

๋„ค, 1์‹œ๊ฐ„ ์ด๋‚ด์— ํ’€์–ด๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค.

1.4.0 ์ถœ์‹œ

๊ต‰์žฅํ•ฉ๋‹ˆ๋‹ค, ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค @jieter!

@jieter ์™€ @intiocean ๋ชจ๋‘ ์ด ๋ฌธ์ œ ๋ฅผ ํ•ด๊ฒฐํ•ด ์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค! ์ด์ œ ์™„๋ฒฝํ•˜๊ฒŒ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.

์•ˆ๋…•ํ•˜์„ธ์š”, ๋ฐฉ๊ธˆ 1.21.2 ๋ฒ„์ „ ๋ฐ Django==2.0.6์—์„œ ์ด ๋ฌธ์ œ๋ฅผ ๊ฒฝํ—˜ํ–ˆ์Šต๋‹ˆ๋‹ค. intiocean ์˜ comment ๋•๋ถ„์— 'pk'๋กœ ์ถ”๊ฐ€ ์ˆœ์„œ๋ฅผ ์„ค์ •ํ•˜์—ฌ ์ˆ˜์ •ํ–ˆ์Šต๋‹ˆ๋‹ค.

์ด ํŽ˜์ด์ง€๊ฐ€ ๋„์›€์ด ๋˜์—ˆ๋‚˜์š”?
0 / 5 - 0 ๋“ฑ๊ธ‰