Skip to content

Commit 95513e5

Browse files
authored
Drop python 2.7 and django 1.11 support. (#666)
* Remove python 2.7 support * Remove ucfirst utility function in favour of django.utils.text.capfirst * Start compiling a changelog for 2.1.0 * Cleanup super() calls * Update tutorial to use django 2.0 url syntax and CBV's * Upgrade to xenial to include sqlite 3.8.3 * Do not install django-master on py35 * No need to inherit from object anymore * Use new url syntax in example/urls.py, some more cleanups * Drop py34 support * Update requirements and drop django 1.11
1 parent cf7bed5 commit 95513e5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+215
-449
lines changed

.travis.yml

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
dist: xenial
12
language: python
23
cache:
34
pip: true
@@ -8,21 +9,14 @@ addons:
89
- myspell-en-us
910

1011
install:
11-
- pip install tox python-coveralls
12+
- pip install tox coverage
1213

1314
script:
1415
- tox
1516

1617
matrix:
1718
include:
18-
- { python: 2.7, env: TOXENV=py27-1.11 }
19-
- { python: 3.4, env: TOXENV=py34-1.11 }
20-
- { python: 3.4, env: TOXENV=py34-2.0 }
21-
- { python: 3.5, env: TOXENV=py35-1.11 }
22-
- { python: 3.5, env: TOXENV=py35-2.0 }
2319
- { python: 3.5, env: TOXENV=py35-2.1 }
24-
- { python: 3.5, env: TOXENV=py35-master }
25-
- { python: 3.6, env: TOXENV=py36-2.0 }
2620
- { python: 3.6, env: TOXENV=py36-2.1 }
2721
- { python: 3.6, env: TOXENV=py36-master }
2822
- { python: 3.7-dev, env: TOXENV=py37-2.1 }
@@ -39,4 +33,7 @@ matrix:
3933
- env: TOXENV=py37-master
4034

4135
after_success:
42-
coveralls
36+
- pip combine --amend
37+
- pip report -m
38+
- pip install codecov
39+
- codecov

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Change log
22

3+
## 2.1.0 (not yet released)
4+
- Dropped support for python 2.7. Django==1.11 is still supported on python 3.
5+
- Removed `django_tables2.utils.ucfirst`, use `django.utils.text.capfirst` instead.
6+
7+
38
## 2.0.6 (2019-03-26)
49
- Add optional 'table' kwarg to `row_attrs` callables
510

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ has native support for pagination and sorting. It does for HTML tables what
1010

1111
- Available on pypi as [django-tables2](https://pypi.python.org/pypi/django-tables2)
1212
- Tested against currently supported versions of Django
13-
[and the python versions Django supports](https://docs.djangoproject.com/en/dev/faq/install/#what-python-version-can-i-use-with-django)
13+
[and supported python 3 versions Django supports](https://docs.djangoproject.com/en/dev/faq/install/#what-python-version-can-i-use-with-django) No python 2.7 support anymore
1414
(see [Travis CI](https://travis-ci.org/jieter/django-tables2)
1515
- [Documentation on readthedocs.org](https://django-tables2.readthedocs.io/en/latest/)
1616
- [Bug tracker](http://github.com/jieter/django-tables2/issues)

django_tables2/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
# coding: utf-8
21
from .tables import Table, TableBase, table_factory
32
from .columns import (
43
BooleanColumn,

django_tables2/columns/base.py

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,23 @@
1-
# coding: utf-8
2-
from __future__ import absolute_import, unicode_literals
3-
41
from collections import OrderedDict
52
from itertools import islice
63

74
from django.core.exceptions import ImproperlyConfigured
85
from django.urls import reverse
9-
from django.utils import six
106
from django.utils.html import format_html
117
from django.utils.safestring import SafeData
8+
from django.utils.text import capfirst
129

13-
from django_tables2.utils import (
10+
from ..utils import (
1411
Accessor,
1512
AttributeDict,
1613
OrderBy,
1714
OrderByTuple,
1815
call_with_appropriate,
1916
computed_values,
20-
ucfirst,
2117
)
2218

2319

24-
class Library(object):
20+
class Library:
2521
"""
2622
A collection of columns.
2723
"""
@@ -64,7 +60,7 @@ def column_for_field(self, field):
6460
library = Library()
6561

6662

67-
class LinkTransform(object):
63+
class LinkTransform:
6864
"""
6965
Object used to generate attributes for the `<a>`-tag to wrap the cell content in.
7066
"""
@@ -160,7 +156,7 @@ def __call__(self, content, **kwargs):
160156

161157

162158
@library.register
163-
class Column(object):
159+
class Column:
164160
"""
165161
Represents a single column of a table.
166162
@@ -275,7 +271,7 @@ def __init__(
275271
linkify=False,
276272
initial_sort_descending=False,
277273
):
278-
if not (accessor is None or isinstance(accessor, six.string_types) or callable(accessor)):
274+
if not (accessor is None or isinstance(accessor, str) or callable(accessor)):
279275
raise TypeError(
280276
"accessor must be a string or callable, not %s" % type(accessor).__name__
281277
)
@@ -289,7 +285,7 @@ def __init__(
289285
self.attrs = attrs or getattr(self, "attrs", {})
290286

291287
# massage order_by into an OrderByTuple or None
292-
order_by = (order_by,) if isinstance(order_by, six.string_types) else order_by
288+
order_by = (order_by,) if isinstance(order_by, str) else order_by
293289
self.order_by = OrderByTuple(order_by) if order_by is not None else None
294290
if empty_values is not None:
295291
self.empty_values = empty_values
@@ -424,11 +420,10 @@ def from_field(cls, field):
424420
else:
425421
verbose_name = getattr(field, "verbose_name", field.name)
426422

427-
return cls(verbose_name=ucfirst(verbose_name))
423+
return cls(verbose_name=capfirst(verbose_name))
428424

429425

430-
@six.python_2_unicode_compatible
431-
class BoundColumn(object):
426+
class BoundColumn:
432427
"""
433428
A *run-time* version of `.Column`. The difference between
434429
`.BoundColumn` and `.Column`, is that `.BoundColumn` objects include the
@@ -456,7 +451,7 @@ def __init__(self, table, column, name):
456451
self.current_value = None
457452

458453
def __str__(self):
459-
return six.text_type(self.header)
454+
return str(self.header)
460455

461456
@property
462457
def accessor(self):
@@ -707,7 +702,7 @@ def verbose_name(self):
707702
if isinstance(name, SafeData):
708703
return name
709704

710-
return ucfirst(name)
705+
return capfirst(name)
711706

712707
@property
713708
def visible(self):
@@ -724,7 +719,7 @@ def localize(self):
724719
return self.column.localize
725720

726721

727-
class BoundColumns(object):
722+
class BoundColumns:
728723
"""
729724
Container for spawning `.BoundColumn` objects.
730725
@@ -748,7 +743,7 @@ class BoundColumns(object):
748743
def __init__(self, table, base_columns):
749744
self._table = table
750745
self.columns = OrderedDict()
751-
for name, column in six.iteritems(base_columns):
746+
for name, column in base_columns.items():
752747
self.columns[name] = bc = BoundColumn(table, column, name)
753748
bc.render = getattr(table, "render_" + name, column.render)
754749
# How the value is defined: 1. value_<name> 2. render_<name> 3. column.value.
@@ -846,7 +841,7 @@ def __contains__(self, item):
846841
847842
*item* can either be a `~.BoundColumn` object, or the name of a column.
848843
"""
849-
if isinstance(item, six.string_types):
844+
if isinstance(item, str):
850845
return item in self.iternames()
851846
else:
852847
# let's assume we were given a column
@@ -875,7 +870,7 @@ def __getitem__(self, index):
875870
return next(islice(self.iterall(), index, index + 1))
876871
except StopIteration:
877872
raise IndexError
878-
elif isinstance(index, six.string_types):
873+
elif isinstance(index, str):
879874
for column in self.iterall():
880875
if column.name == index:
881876
return column

django_tables2/columns/booleancolumn.py

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
1-
# coding: utf-8
2-
from __future__ import absolute_import, unicode_literals
3-
41
from django.db import models
5-
from django.utils import six
62
from django.utils.html import escape, format_html
3+
from django.utils.text import capfirst
74

8-
from django_tables2.utils import AttributeDict, ucfirst
9-
5+
from ..utils import AttributeDict
106
from .base import Column, library
117

128

@@ -30,10 +26,10 @@ class BooleanColumn(Column):
3026
"""
3127

3228
def __init__(self, null=False, yesno="✔,✘", **kwargs):
33-
self.yesno = yesno.split(",") if isinstance(yesno, six.string_types) else tuple(yesno)
29+
self.yesno = yesno.split(",") if isinstance(yesno, str) else tuple(yesno)
3430
if not null:
3531
kwargs["empty_values"] = ()
36-
super(BooleanColumn, self).__init__(**kwargs)
32+
super().__init__(**kwargs)
3733

3834
def _get_bool_value(self, record, value, bound_column):
3935
# If record is a model, we need to check if it has choices defined.
@@ -51,7 +47,7 @@ def _get_bool_value(self, record, value, bound_column):
5147
def render(self, value, record, bound_column):
5248
value = self._get_bool_value(record, value, bound_column)
5349
text = self.yesno[int(not value)]
54-
attrs = {"class": six.text_type(value).lower()}
50+
attrs = {"class": str(value).lower()}
5551
attrs.update(self.attrs.get("span", {}))
5652

5753
return format_html("<span {}>{}</span>", AttributeDict(attrs).as_html(), escape(text))
@@ -66,8 +62,8 @@ def value(self, record, value, bound_column):
6662
@classmethod
6763
def from_field(cls, field):
6864
if isinstance(field, models.NullBooleanField):
69-
return cls(verbose_name=ucfirst(field.verbose_name), null=True)
65+
return cls(verbose_name=capfirst(field.verbose_name), null=True)
7066

7167
if isinstance(field, models.BooleanField):
7268
null = getattr(field, "null", False)
73-
return cls(verbose_name=ucfirst(field.verbose_name), null=null)
69+
return cls(verbose_name=capfirst(field.verbose_name), null=null)

django_tables2/columns/checkboxcolumn.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
# coding: utf-8
2-
from __future__ import absolute_import, unicode_literals
3-
41
from django.utils.safestring import mark_safe
52

63
from django_tables2.utils import Accessor, AttributeDict
@@ -51,7 +48,7 @@ def __init__(self, attrs=None, checked=None, **extra):
5148
self.checked = checked
5249
kwargs = {"orderable": False, "attrs": attrs}
5350
kwargs.update(extra)
54-
super(CheckBoxColumn, self).__init__(**kwargs)
51+
super().__init__(**kwargs)
5552

5653
@property
5754
def header(self):

django_tables2/columns/datecolumn.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
1-
# coding: utf-8
2-
from __future__ import absolute_import, unicode_literals
3-
41
from django.db import models
5-
6-
from django_tables2.utils import ucfirst
2+
from django.utils.text import capfirst
73

84
from .base import library
95
from .templatecolumn import TemplateColumn
@@ -25,9 +21,9 @@ def __init__(self, format=None, short=True, *args, **kwargs):
2521
if format is None:
2622
format = "SHORT_DATE_FORMAT" if short else "DATE_FORMAT"
2723
template = '{{ value|date:"%s"|default:default }}' % format
28-
super(DateColumn, self).__init__(template_code=template, *args, **kwargs)
24+
super().__init__(template_code=template, *args, **kwargs)
2925

3026
@classmethod
3127
def from_field(cls, field):
3228
if isinstance(field, models.DateField):
33-
return cls(verbose_name=ucfirst(field.verbose_name))
29+
return cls(verbose_name=capfirst(field.verbose_name))

django_tables2/columns/datetimecolumn.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
1-
# coding: utf-8
2-
from __future__ import absolute_import, unicode_literals
3-
41
from django.db import models
5-
6-
from django_tables2.utils import ucfirst
2+
from django.utils.text import capfirst
73

84
from .base import library
95
from .templatecolumn import TemplateColumn
@@ -25,9 +21,9 @@ def __init__(self, format=None, short=True, *args, **kwargs):
2521
if format is None:
2622
format = "SHORT_DATETIME_FORMAT" if short else "DATETIME_FORMAT"
2723
template = '{{ value|date:"%s"|default:default }}' % format
28-
super(DateTimeColumn, self).__init__(template_code=template, *args, **kwargs)
24+
super().__init__(template_code=template, *args, **kwargs)
2925

3026
@classmethod
3127
def from_field(cls, field):
3228
if isinstance(field, models.DateTimeField):
33-
return cls(verbose_name=ucfirst(field.verbose_name))
29+
return cls(verbose_name=capfirst(field.verbose_name))

django_tables2/columns/emailcolumn.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
1-
# coding: utf-8
2-
from __future__ import absolute_import, unicode_literals
3-
41
from django.db import models
5-
6-
from django_tables2.utils import ucfirst
2+
from django.utils.text import capfirst
73

84
from .base import library
95
from .linkcolumn import BaseLinkColumn
@@ -42,4 +38,4 @@ def get_url(self, value):
4238
@classmethod
4339
def from_field(cls, field):
4440
if isinstance(field, models.EmailField):
45-
return cls(verbose_name=ucfirst(field.verbose_name))
41+
return cls(verbose_name=capfirst(field.verbose_name))

django_tables2/columns/filecolumn.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
1-
# coding: utf-8
2-
from __future__ import absolute_import, unicode_literals
3-
41
import os
52

63
from django.db import models
74
from django.utils.html import format_html
5+
from django.utils.text import capfirst
86

9-
from django_tables2.utils import AttributeDict, ucfirst
10-
7+
from ..utils import AttributeDict
118
from .base import library
129
from .linkcolumn import BaseLinkColumn
1310

@@ -40,7 +37,7 @@ class FileColumn(BaseLinkColumn):
4037

4138
def __init__(self, verify_exists=True, **kwargs):
4239
self.verify_exists = verify_exists
43-
super(FileColumn, self).__init__(**kwargs)
40+
super().__init__(**kwargs)
4441

4542
def get_url(self, value, record):
4643
storage = getattr(value, "storage", None)
@@ -52,7 +49,7 @@ def get_url(self, value, record):
5249
def text_value(self, record, value):
5350
if self.text is None:
5451
return os.path.basename(value.name)
55-
return super(FileColumn, self).text_value(record, value)
52+
return super().text_value(record, value)
5653

5754
def render(self, record, value):
5855
attrs = AttributeDict(self.attrs.get("span", {}))
@@ -86,4 +83,4 @@ def render(self, record, value):
8683
@classmethod
8784
def from_field(cls, field):
8885
if isinstance(field, models.FileField):
89-
return cls(verbose_name=ucfirst(field.verbose_name))
86+
return cls(verbose_name=capfirst(field.verbose_name))

django_tables2/columns/jsoncolumn.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
1-
# coding: utf-8
2-
from __future__ import absolute_import, unicode_literals
3-
41
import json
52

63
from django.utils.html import format_html
4+
from django.utils.text import capfirst
75

8-
from django_tables2.utils import AttributeDict, ucfirst
9-
6+
from ..utils import AttributeDict
107
from .base import library
118
from .linkcolumn import BaseLinkColumn
129

@@ -48,7 +45,7 @@ def __init__(self, json_dumps_kwargs=None, **kwargs):
4845
json_dumps_kwargs if json_dumps_kwargs is not None else {"indent": 2}
4946
)
5047

51-
super(JSONColumn, self).__init__(**kwargs)
48+
super().__init__(**kwargs)
5249

5350
def render(self, record, value):
5451
return format_html(
@@ -61,4 +58,4 @@ def render(self, record, value):
6158
def from_field(cls, field):
6259
if POSTGRES_AVAILABLE:
6360
if isinstance(field, JSONField) or isinstance(field, HStoreField):
64-
return cls(verbose_name=ucfirst(field.verbose_name))
61+
return cls(verbose_name=capfirst(field.verbose_name))

0 commit comments

Comments
 (0)