Skip to content

Commit

Permalink
Only ever apply capitalisation to the first character of any verbose …
Browse files Browse the repository at this point in the history
…name, or do nothing to it. fixes #491

Doing it this way conforms to the django recommendations:
https://docs.djangoproject.com/en/stable/topics/db/models/#verbose-field-names

> The convention is not to capitalize the first letter of the verbose_name.
> Django will automatically capitalize the first letter where it needs to.
  • Loading branch information
jieter committed Apr 10, 2018
1 parent 0bb23fa commit 72f6de1
Show file tree
Hide file tree
Showing 14 changed files with 76 additions and 100 deletions.
9 changes: 5 additions & 4 deletions django_tables2/columns/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
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, OrderByTuple, call_with_appropriate, computed_values
from django_tables2.utils import (Accessor, AttributeDict, OrderBy, OrderByTuple, call_with_appropriate,
computed_values, ucfirst)


class Library(object):
Expand Down Expand Up @@ -256,7 +256,8 @@ 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=title(verbose_name))

return cls(verbose_name=ucfirst(verbose_name))


@six.python_2_unicode_compatible
Expand Down Expand Up @@ -537,7 +538,7 @@ def verbose_name(self):
if isinstance(name, SafeData):
return name

return title(name)
return ucfirst(name)

@property
def visible(self):
Expand Down
7 changes: 3 additions & 4 deletions django_tables2/columns/booleancolumn.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +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 django_tables2.utils import AttributeDict, ucfirst

from .base import Column, library

Expand Down Expand Up @@ -70,8 +69,8 @@ def value(self, record, value, bound_column):
@classmethod
def from_field(cls, field):
if isinstance(field, models.NullBooleanField):
return cls(verbose_name=title(field.verbose_name), null=True)
return cls(verbose_name=ucfirst(field.verbose_name), null=True)

if isinstance(field, models.BooleanField):
null = getattr(field, 'null', False)
return cls(verbose_name=title(field.verbose_name), null=null)
return cls(verbose_name=ucfirst(field.verbose_name), null=null)
4 changes: 2 additions & 2 deletions django_tables2/columns/datecolumn.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from django.db import models

from django_tables2.templatetags.django_tables2 import title
from django_tables2.utils import ucfirst

from .base import library
from .templatecolumn import TemplateColumn
Expand All @@ -29,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=title(field.verbose_name))
return cls(verbose_name=ucfirst(field.verbose_name))
4 changes: 2 additions & 2 deletions django_tables2/columns/datetimecolumn.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from django.db import models

from django_tables2.templatetags.django_tables2 import title
from django_tables2.utils import ucfirst

from .base import library
from .templatecolumn import TemplateColumn
Expand All @@ -29,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=title(field.verbose_name))
return cls(verbose_name=ucfirst(field.verbose_name))
4 changes: 2 additions & 2 deletions django_tables2/columns/emailcolumn.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from django.db import models

from django_tables2.templatetags.django_tables2 import title
from django_tables2.utils import ucfirst

from .base import library
from .linkcolumn import BaseLinkColumn
Expand Down Expand Up @@ -45,4 +45,4 @@ def render(self, record, value):
@classmethod
def from_field(cls, field):
if isinstance(field, models.EmailField):
return cls(verbose_name=title(field.verbose_name))
return cls(verbose_name=ucfirst(field.verbose_name))
5 changes: 2 additions & 3 deletions django_tables2/columns/filecolumn.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +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 django_tables2.utils import AttributeDict, ucfirst

from .base import library
from .linkcolumn import BaseLinkColumn
Expand Down Expand Up @@ -85,4 +84,4 @@ def render(self, record, value):
@classmethod
def from_field(cls, field):
if isinstance(field, models.FileField):
return cls(verbose_name=title(field.verbose_name))
return cls(verbose_name=ucfirst(field.verbose_name))
5 changes: 2 additions & 3 deletions django_tables2/columns/jsoncolumn.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@

from django.utils.html import format_html

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

from .base import library
from .linkcolumn import BaseLinkColumn
Expand Down Expand Up @@ -58,4 +57,4 @@ def render(self, record, value):
def from_field(cls, field):
if POSTGRES_AVAILABLE:
if isinstance(field, JSONField) or isinstance(field, HStoreField):
return cls(verbose_name=title(field.verbose_name))
return cls(verbose_name=ucfirst(field.verbose_name))
4 changes: 2 additions & 2 deletions django_tables2/columns/manytomanycolumn.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from django.utils.encoding import force_text
from django.utils.html import conditional_escape, mark_safe

from django_tables2.templatetags.django_tables2 import title
from django_tables2.utils import ucfirst

from .base import Column, library

Expand Down Expand Up @@ -82,4 +82,4 @@ def render(self, value):
@classmethod
def from_field(cls, field):
if isinstance(field, models.ManyToManyField):
return cls(verbose_name=title(field.verbose_name))
return cls(verbose_name=ucfirst(field.verbose_name))
4 changes: 2 additions & 2 deletions django_tables2/columns/timecolumn.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from django.conf import settings
from django.db import models

from django_tables2.templatetags.django_tables2 import title
from django_tables2.utils import ucfirst

from .base import library
from .templatecolumn import TemplateColumn
Expand All @@ -30,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=title(field.verbose_name))
return cls(verbose_name=ucfirst(field.verbose_name))
4 changes: 2 additions & 2 deletions django_tables2/columns/urlcolumn.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from django.db import models

from django_tables2.templatetags.django_tables2 import title
from django_tables2.utils import ucfirst

from .base import library
from .linkcolumn import BaseLinkColumn
Expand Down Expand Up @@ -34,4 +34,4 @@ def render(self, record, value):
@classmethod
def from_field(cls, field):
if isinstance(field, models.URLField):
return cls(verbose_name=title(field.verbose_name))
return cls(verbose_name=ucfirst(field.verbose_name))
30 changes: 0 additions & 30 deletions django_tables2/templatetags/django_tables2.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.template import Node, TemplateSyntaxError
from django.template.defaultfilters import title as old_title
from django.template.defaultfilters import stringfilter
from django.template.loader import get_template, select_template
from django.templatetags.l10n import register as l10n_register
from django.utils import six
Expand Down Expand Up @@ -211,34 +209,6 @@ class Meta:
return RenderTableNode(table, template)


@register.filter
@stringfilter
def title(value):
'''
A slightly better title template filter.
Same as Django's builtin `~django.template.defaultfilters.title` filter,
but operates on individual words and leaves words unchanged if they already
have a capital letter or a digit. Actually Django's filter also skips
words with digits but only for latin letters (or at least not for
cyrillic ones).
'''
return ' '.join([
any([c.isupper() or c.isdigit() for c in w]) and w or old_title(w)
for w in value.split()
])


title.is_safe = True

try:
from django.utils.functional import keep_lazy_text
title = keep_lazy_text(title)
except ImportError:
# to keep backward (Django < 1.10) compatibility
from django.utils.functional import lazy
title = lazy(title, six.text_type)

register.filter('localize', l10n_register.filters['localize'])
register.filter('unlocalize', l10n_register.filters['unlocalize'])

Expand Down
11 changes: 11 additions & 0 deletions django_tables2/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,21 @@

from django.db import models
from django.db.models.fields import FieldDoesNotExist
from django.template.defaultfilters import stringfilter
from django.utils import six
from django.utils.functional import keep_lazy_text
from django.utils.html import format_html_join


@keep_lazy_text
@stringfilter
def ucfirst(s):
if not isinstance(s, six.string_types):
return ''
else:
return s[0].upper() + s[1:]


class Sequence(list):
'''
Represents a column sequence, e.g. ``('first_name', '...', 'last_name')``
Expand Down
38 changes: 19 additions & 19 deletions tests/test_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@
CSV_SEP = '\r\n'

EXPECTED_CSV = CSV_SEP.join(
('First Name,Surname', ) + tuple(','.join(name) for name in NAMES)
('First name,Surname', ) + tuple(','.join(name) for name in NAMES)
) + CSV_SEP

EXPECTED_JSON = list([
{'First Name': first_name, 'Surname': last_name}
{'First name': first_name, 'Surname': last_name}
for first_name, last_name in NAMES
])

Expand Down Expand Up @@ -65,7 +65,7 @@ def test_None_values(self):

exporter = TableExport('csv', table)
expected = (
'First Name,Last Name',
'First name,Last name',
'Yildiz,van der Kuil',
'Jan,'
)
Expand All @@ -76,13 +76,13 @@ def test_null_values(self):

class Table(tables.Table):
first_name = tables.Column()
last_name = tables.Column(verbose_name='Last Name')
last_name = tables.Column(verbose_name='Last name')
occupation = tables.Column(verbose_name='Occupation')

table = Table(Person.objects.all())
exporter = TableExport('csv', table)
expected = (
'First Name,Last Name,Occupation',
'First name,Last name,Occupation',
'Jan,Coen,'
)
self.assertEqual(exporter.export(), CSV_SEP.join(expected) + CSV_SEP)
Expand All @@ -96,14 +96,14 @@ def setUp(self):

def test_view_should_support_csv_export(self):
response, view = View.as_view()(build_request('/?_export=csv'))
assert response.getvalue().decode('utf8') == EXPECTED_CSV
self.assertEqual(response.getvalue().decode('utf8'), EXPECTED_CSV)

# should just render the normal table without the _export query
response, view = View.as_view()(build_request('/'))
html = response.render().rendered_content

assert 'Yildiz' in html
assert 'Lindy' not in html
self.assertIn('Yildiz', html)
self.assertNotIn('Lindy', html)

def test_should_raise_error_for_unsupported_file_type(self):
table = Table([])
Expand All @@ -113,7 +113,7 @@ def test_should_raise_error_for_unsupported_file_type(self):

def test_should_support_json_export(self):
response, view = View.as_view()(build_request('/?_export=json'))
assert json.loads(response.getvalue().decode('utf8')) == EXPECTED_JSON
self.assertEqual(json.loads(response.getvalue().decode('utf8')), EXPECTED_JSON)

def test_should_support_custom_trigger_param(self):
class View(DispatchHookMixin, ExportMixin, tables.SingleTableView):
Expand All @@ -122,7 +122,7 @@ class View(DispatchHookMixin, ExportMixin, tables.SingleTableView):
model = Person # required for ListView

response, view = View.as_view()(build_request('/?export_to=json'))
assert json.loads(response.getvalue().decode('utf8')) == EXPECTED_JSON
self.assertEqual(json.loads(response.getvalue().decode('utf8')), EXPECTED_JSON)

def test_should_support_custom_filename(self):
class View(DispatchHookMixin, ExportMixin, tables.SingleTableView):
Expand All @@ -131,7 +131,7 @@ class View(DispatchHookMixin, ExportMixin, tables.SingleTableView):
model = Person # required for ListView

response, view = View.as_view()(build_request('/?_export=json'))
assert response['Content-Disposition'] == 'attachment; filename="people.json"'
self.assertEqual(response['Content-Disposition'], 'attachment; filename="people.json"')

def test_function_view(self):
'''
Expand All @@ -151,14 +151,14 @@ def table_view(request):
})

response = table_view(build_request('/?_export=csv'))
assert response.getvalue().decode('utf8') == EXPECTED_CSV
self.assertEqual(response.getvalue().decode('utf8'), EXPECTED_CSV)

# must also support the normal html table.
response = table_view(build_request('/'))
html = response.content.decode('utf8')

assert 'Yildiz' in html
assert 'Lindy' not in html
self.assertIn('Yildiz', html)
self.assertNotIn('Lindy', html)


class OccupationTable(tables.Table):
Expand Down Expand Up @@ -187,9 +187,9 @@ def test_should_work_with_foreign_keys(self):
response, view = OccupationView.as_view()(build_request('/?_export=xls'))
data = response.content
# binary data, so not possible to compare to an exact expectation
assert data.find('Vlaanderen'.encode())
assert data.find('Ecoloog'.encode())
assert data.find('Timmerman'.encode())
self.assertTrue(data.find('Vlaanderen'.encode()))
self.assertTrue(data.find('Ecoloog'.encode()))
self.assertTrue(data.find('Timmerman'.encode()))

def test_should_work_with_foreign_key_fields(self):
class OccupationWithForeignKeyFieldsTable(tables.Table):
Expand All @@ -208,11 +208,11 @@ class View(DispatchHookMixin, ExportMixin, tables.SingleTableView):
data = response.getvalue().decode('utf8')

expected_csv = '\r\n'.join((
'Name,Boolean,Region,First Name',
'Name,Boolean,Region,First name',
'Timmerman,True,Vlaanderen,Richard',
'Ecoloog,False,Vlaanderen,Richard\r\n'
))
assert data == expected_csv
self.assertEqual(data, expected_csv)

def test_should_allow_exclude_columns(self):
class OccupationExcludingView(DispatchHookMixin, ExportMixin, tables.SingleTableView):
Expand Down
Loading

0 comments on commit 72f6de1

Please sign in to comment.