Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Titlise column verbose_name when derived from model #382

Merged
merged 4 commits into from
Sep 9, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions django_tables2/columns/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from itertools import islice

from django.utils import six
from django.utils.safestring import SafeData

from django_tables2.templatetags.django_tables2 import title
from django_tables2.utils import (Accessor, AttributeDict, OrderBy,
Expand Down Expand Up @@ -223,7 +224,7 @@ def from_field(cls, field):
verbose_name = field.get_related_field().verbose_name
else:
verbose_name = getattr(field, 'verbose_name', field.name)
return cls(verbose_name=verbose_name)
return cls(verbose_name=title(verbose_name))


@six.python_2_unicode_compatible
Expand Down Expand Up @@ -437,8 +438,17 @@ def orderable(self):
@property
def verbose_name(self):
"""
Return the verbose name for this column, or fallback to the titlised
column name.
Return the verbose name for this column.

In order of preference, this will return:
1) The column's explicitly defined `verbose_name`
2) The titlised model's `verbose_name` (if applicable)
3) Fallback to the titlised column name.

Any `verbose_name` that was not passed explicitly in the column
definition is returned titlised in keeping with the Django convention
of `verbose_name` being defined in lowercase and uppercased/titlised
as needed by the application.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add backticks around variable/property names here? i.e.: verbose_names -> verbose_names


If the table is using queryset data, then use the corresponding model
field's `~.db.Field.verbose_name`. If it's traversing a relationship,
Expand All @@ -452,7 +462,7 @@ def verbose_name(self):

# This is our reasonable fallback, should the next section not result
# in anything useful.
name = title(self.name.replace('_', ' '))
name = self.name.replace('_', ' ')

# Try to use a model field's verbose_name
if hasattr(self.table.data, 'queryset') and hasattr(self.table.data.queryset, 'model'):
Expand All @@ -463,7 +473,12 @@ def verbose_name(self):
name = field.field.verbose_name
else:
name = getattr(field, 'verbose_name', field.name)
return name

# If verbose_name was mark_safe()'d, return intact to keep safety
if isinstance(name, SafeData):
return name

return title(name)

@property
def visible(self):
Expand Down
5 changes: 3 additions & 2 deletions django_tables2/columns/booleancolumn.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from django.utils import six
from django.utils.html import escape, format_html

from django_tables2.templatetags.django_tables2 import title
from django_tables2.utils import AttributeDict

from .base import Column, library
Expand Down Expand Up @@ -58,6 +59,6 @@ def render(self, value, record, bound_column):
@classmethod
def from_field(cls, field):
if isinstance(field, models.BooleanField):
return cls(verbose_name=field.verbose_name, null=False)
return cls(verbose_name=title(field.verbose_name), null=False)
if isinstance(field, models.NullBooleanField):
return cls(verbose_name=field.verbose_name, null=True)
return cls(verbose_name=title(field.verbose_name), null=True)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can move the title-call to the beginning of this method and keep it in a local variable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just tried this and it failed a test. Unfortunately I don't think we simplify by extracting the title call. Do you want the title call assigned to a local variable before constructing the column class in these or was this note just for the booleancolumn since it had two calls and could potentially be simplified?

    @classmethod
    def from_field(cls, field):
        verbose_name = title(field.verbose_name)
        if isinstance(field, models.BooleanField):
            return cls(verbose_name=verbose_name, null=False)
        if isinstance(field, models.NullBooleanField):
            return cls(verbose_name=verbose_name, null=True)
___________________________________________________________________________ test_foreign_key ___________________________________________________________________________

    def test_foreign_key():
>       class PersonTable(tables.Table):

tests/test_models.py:367: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
django_tables2/tables.py:197: in __new__
    extra[field_name] = columns.library.column_for_field(field)
django_tables2/columns/base.py:40: in column_for_field
    column = candidate.from_field(field)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

cls = <class 'django_tables2.columns.booleancolumn.BooleanColumn'>, field = <django.contrib.contenttypes.fields.GenericForeignKey object at 0x7f13a8b9aa90>

    @classmethod
    def from_field(cls, field):
>       verbose_name = title(field.verbose_name)
E       AttributeError: 'GenericForeignKey' object has no attribute 'verbose_name'

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, it indeed makes sense to not move it up, let's keep it as is.

Thanks for trying anyway!

4 changes: 3 additions & 1 deletion django_tables2/columns/datecolumn.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

from django.db import models

from django_tables2.templatetags.django_tables2 import title

from .base import library
from .templatecolumn import TemplateColumn

Expand All @@ -27,4 +29,4 @@ def __init__(self, format=None, short=True, *args, **kwargs):
@classmethod
def from_field(cls, field):
if isinstance(field, models.DateField):
return cls(verbose_name=field.verbose_name)
return cls(verbose_name=title(field.verbose_name))
4 changes: 3 additions & 1 deletion django_tables2/columns/datetimecolumn.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

from django.db import models

from django_tables2.templatetags.django_tables2 import title

from .base import library
from .templatecolumn import TemplateColumn

Expand All @@ -27,4 +29,4 @@ def __init__(self, format=None, short=True, *args, **kwargs):
@classmethod
def from_field(cls, field):
if isinstance(field, models.DateTimeField):
return cls(verbose_name=field.verbose_name)
return cls(verbose_name=title(field.verbose_name))
4 changes: 3 additions & 1 deletion django_tables2/columns/emailcolumn.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

from django.db import models

from django_tables2.templatetags.django_tables2 import title

from .base import library
from .linkcolumn import BaseLinkColumn

Expand Down Expand Up @@ -43,4 +45,4 @@ def render(self, record, value):
@classmethod
def from_field(cls, field):
if isinstance(field, models.EmailField):
return cls(verbose_name=field.verbose_name)
return cls(verbose_name=title(field.verbose_name))
3 changes: 2 additions & 1 deletion django_tables2/columns/filecolumn.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from django.db import models
from django.utils.html import format_html

from django_tables2.templatetags.django_tables2 import title
from django_tables2.utils import AttributeDict

from .base import library
Expand Down Expand Up @@ -84,4 +85,4 @@ def render(self, record, value):
@classmethod
def from_field(cls, field):
if isinstance(field, models.FileField):
return cls(verbose_name=field.verbose_name)
return cls(verbose_name=title(field.verbose_name))
4 changes: 3 additions & 1 deletion django_tables2/columns/timecolumn.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from django.conf import settings
from django.db import models

from django_tables2.templatetags.django_tables2 import title

from .base import library
from .templatecolumn import TemplateColumn

Expand All @@ -28,4 +30,4 @@ def __init__(self, format=None, *args, **kwargs):
@classmethod
def from_field(cls, field):
if isinstance(field, models.TimeField):
return cls(verbose_name=field.verbose_name)
return cls(verbose_name=title(field.verbose_name))
4 changes: 3 additions & 1 deletion django_tables2/columns/urlcolumn.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

from django.db import models

from django_tables2.templatetags.django_tables2 import title

from .base import library
from .linkcolumn import BaseLinkColumn

Expand Down Expand Up @@ -32,4 +34,4 @@ def render(self, record, value):
@classmethod
def from_field(cls, field):
if isinstance(field, models.URLField):
return cls(verbose_name=field.verbose_name)
return cls(verbose_name=title(field.verbose_name))
6 changes: 6 additions & 0 deletions tests/app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ class Person(models.Model):
safe = models.CharField(
max_length=200, blank=True, verbose_name=mark_safe("<b>Safe</b>"))

website = models.URLField(
max_length=200, null=True, blank=True,
verbose_name="web site")

birthdate = models.DateField(null=True)

content_type = models.ForeignKey(ContentType, null=True, blank=True)
object_id = models.PositiveIntegerField(null=True, blank=True)
foreign_key = GenericForeignKey()
Expand Down
38 changes: 23 additions & 15 deletions tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ class PersonTable(tables.Table):
fn1 = tables.Column(accessor='first_name')
fn2 = tables.Column(accessor='first_name.upper')
fn3 = tables.Column(accessor='last_name', verbose_name='OVERRIDE')
fn4 = tables.Column(accessor='last_name', verbose_name='override')
last_name = tables.Column()
ln1 = tables.Column(accessor='last_name')
ln2 = tables.Column(accessor='last_name.upper')
Expand All @@ -116,35 +117,42 @@ class PersonTable(tables.Table):
# that we should expect that the two columns that use the ``last_name``
# field should both use the model's ``last_name`` field's ``verbose_name``,
# however both fields that use the ``first_name`` field should just use a
# capitalized version of the column name as the column header.
# titlised version of the column name as the column header.
table = PersonTable(Person.objects.all())
# Should be generated (capitalized column name)
assert 'first name' == table.columns['first_name'].verbose_name
assert 'first name' == table.columns['fn1'].verbose_name
assert 'first name' == table.columns['fn2'].verbose_name
assert 'First Name' == table.columns['first_name'].verbose_name
assert 'First Name' == table.columns['fn1'].verbose_name
assert 'First Name' == table.columns['fn2'].verbose_name
assert 'OVERRIDE' == table.columns['fn3'].verbose_name
# Should use the model field's verbose_name
assert 'surname' == table.columns['last_name'].verbose_name
assert 'surname' == table.columns['ln1'].verbose_name
assert 'surname' == table.columns['ln2'].verbose_name
assert 'override' == table.columns['fn4'].verbose_name
# Should use the titlised model field's verbose_name
assert 'Surname' == table.columns['last_name'].verbose_name
assert 'Surname' == table.columns['ln1'].verbose_name
assert 'Surname' == table.columns['ln2'].verbose_name
assert 'OVERRIDE' == table.columns['ln3'].verbose_name
assert 'name' == table.columns['region'].verbose_name
assert 'name' == table.columns['r1'].verbose_name
assert 'name' == table.columns['r2'].verbose_name
assert 'Name' == table.columns['region'].verbose_name
assert 'Name' == table.columns['r1'].verbose_name
assert 'Name' == table.columns['r2'].verbose_name
assert 'OVERRIDE' == table.columns['r3'].verbose_name
assert "translation test" == table.columns["trans_test"].verbose_name
assert "translation test lazy" == table.columns["trans_test_lazy"].verbose_name
assert "Translation Test" == table.columns["trans_test"].verbose_name
assert "Translation Test Lazy" == table.columns["trans_test_lazy"].verbose_name

# -------------------------------------------------------------------------

# Now we'll try using a table with Meta.model
class PersonTable(tables.Table):
first_name = tables.Column(verbose_name="OVERRIDE")

class Meta:
model = Person

# Issue #16
table = PersonTable([])
assert "translation test" == table.columns["trans_test"].verbose_name
assert "translation test lazy" == table.columns["trans_test_lazy"].verbose_name
assert "Translation Test" == table.columns["trans_test"].verbose_name
assert "Translation Test Lazy" == table.columns["trans_test_lazy"].verbose_name
assert "Web Site" == table.columns["website"].verbose_name
assert "Birthdate" == table.columns["birthdate"].verbose_name
assert "OVERRIDE" == table.columns["first_name"].verbose_name


def test_data_verbose_name():
Expand Down
2 changes: 1 addition & 1 deletion tests/test_templatetags.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def test_render_table_supports_queryset():
'request': build_request('/')}))

root = parse(html)
assert [e.text for e in root.findall('.//thead/tr/th/a')] == ['ID', 'name', 'mayor']
assert [e.text for e in root.findall('.//thead/tr/th/a')] == ['ID', 'Name', 'Mayor']
td = [[td.text for td in tr.findall('td')] for tr in root.findall('.//tbody/tr')]
db = []
for region in Region.objects.all():
Expand Down