diff --git a/.travis.yml b/.travis.yml index fd5626c..1f86da9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: python -python: 3.5 +python: 3.8 sudo: false notifications: @@ -13,8 +13,8 @@ cache: - $HOME/.cache/pip env: - - TOX_ENV=py27-dj111 - - TOX_ENV=py35-dj111 + - TOX_ENV=py38-dj22 + - TOX_ENV=py38-dj30 script: - tox -e $TOX_ENV @@ -26,7 +26,7 @@ deploy: secure: nyHO+Ouaq/64fhdwtvvbK5jE+GiLFyah3YC8ibUP4QuOGQcl6CSOLTVhVqmvoCbJQBAQi84KefitnaPK/Jvpbpp3jBKsou5cApoarlTCCqKME9B0P5bQYgYoHBo1xxB0RFpjnbyOYPLYEBKo9aHqh0/R0GUPY8lUALZ7lfp0C4FR8Ogugbm5DRwLh/1V4mBYb/jZOrN2VkFlXwmVDrYWdPAA5Bz9rviu0aXLg7UOyNQIf6qywnPBCpZEHl6eGBG8DdMSniV+mNq9L10ISU9DoaVj7jnrEPFvKxnyFl/a4KeCGvP4kH90/gHuUTU6loet4clGpnXa5/n+5Zq0t5WfOr/3NL1lntCbQmqVniqffPW4nz99rMQ36iLl2Xoz9UTwTEyN7AdkmIIHKVox9hmVfM/npoVwyQEBoY6XnysY9s8yrnqWF3cEzoQzCqOxnt6pF/4aO7aUkfA27W5vwCYDJziGAPT0h62lVDJJvNUfis2mAk5kYqFvJZduqzUcDgivCsHN37wShk//rRAZcrjCbaiOvrLKJsS8A8L6NrDainN+U46GWnerZXr1P3kxw8luwMXKpZXS8MN50Ju2mj/T3YNK66K6EBTY9xT1r5ZXEy7TSJ1rEamPJEpRSddyTRoCxgI7EFrY+zl2AeU4ClBzudPqmmnWWKXsv0vwk/omtFU= on: tags: true - condition: $TOX_ENV = py35-dj111 + condition: $TOX_ENV = py38-dj30 distributions: "bdist_wheel" before_deploy: - 'rm -fr build htmlcov dist .eggs .tox' diff --git a/CHANGELOG b/CHANGELOG index cc27842..53935d0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,11 @@ Change history for django-templateselector ------------------------------------------ +1.0.0 +^^^^^^ +* |FIX| Remove support for end-of-life Django and Python versions +* |NEW| Add support for Django 3.0 + 0.2.5 ^^^^^^ * |FIX| Added ``:focus`` visual styles so that keyboard navigation through a form diff --git a/demo_project.py b/demo_project.py index 43b1667..841567c 100644 --- a/demo_project.py +++ b/demo_project.py @@ -1,6 +1,5 @@ #! /usr/bin/env python # -*- coding: utf-8 -*- -from __future__ import absolute_import import os import sys sys.dont_write_bytecode = True @@ -8,15 +7,7 @@ try: from django.conf import settings except ImportError: - MISSING_DEPENDENCIES.append("Django\>=1.11") -try: - from os import scandir -except ImportError: - try: - from scandir import scandir - except ImportError: - MISSING_DEPENDENCIES.append("scandir\>=1.5") - + MISSING_DEPENDENCIES.append("Django\>=2.0") if MISSING_DEPENDENCIES: deps = " ".join(MISSING_DEPENDENCIES) diff --git a/setup.cfg b/setup.cfg index 7d64eda..aab02aa 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,4 +1,4 @@ -[pytest] +[tool:pytest] norecursedirs=.* *.egg .svn _build src bin lib local include testpaths=templateselector/tests python_files=test_*.py diff --git a/setup.py b/setup.py index a58911b..1d8ddfa 100644 --- a/setup.py +++ b/setup.py @@ -2,26 +2,11 @@ # -*- coding: utf-8 -*- import sys import os -from setuptools import setup, __version__ as setuptools_version +from setuptools import setup from setuptools.command.test import test as TestCommand -INSTALL_REQUIRES = [] -EXTRA_REQUIRES = {} -scandir = "scandir>=1.5" -if int(setuptools_version.split(".", 1)[0]) < 18: - assert "bdist_wheel" not in sys.argv, "setuptools 18 required for wheels." - # For legacy setuptools + sdist. - if sys.version_info[0] == 2: - INSTALL_REQUIRES.append(scandir) -else: - EXTRA_REQUIRES[":python_version<'3'"] = [scandir] - -if sys.version_info[0] == 2: - # get the Py3K compatible `encoding=` for opening files. - from io import open HERE = os.path.abspath(os.path.dirname(__file__)) - class PyTest(TestCommand): def initialize_options(self): TestCommand.initialize_options(self) @@ -62,7 +47,7 @@ def make_readme(root_path): setup( name="django-templateselector", - version="0.2.5", + version="1.0.0", author="Keryn Knight", author_email="django-templateselector@kerynknight.com", maintainer="Keryn Knight", @@ -74,9 +59,8 @@ def make_readme(root_path): ], include_package_data=True, install_requires=[ - "Django>=1.8", - ] + INSTALL_REQUIRES, - extras_require=EXTRA_REQUIRES, + "Django>=2.0", + ], tests_require=[ "pytest>=2.6", "pytest-django>=2.8.0", @@ -93,11 +77,9 @@ def make_readme(root_path): "Intended Audience :: Developers", "License :: OSI Approved :: {}".format(LICENSE), "Natural Language :: English", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.5", "Framework :: Django", - "Framework :: Django :: 1.11", + "Framework :: Django :: 2", + "Framework :: Django :: 3", ], ) diff --git a/templateselector/__init__.py b/templateselector/__init__.py index eac5412..7386458 100644 --- a/templateselector/__init__.py +++ b/templateselector/__init__.py @@ -1,11 +1,9 @@ # -*- coding: utf-8 -*- -from __future__ import absolute_import -from __future__ import unicode_literals -__version_info__ = '0.2.5' -__version__ = '0.2.5' -version = '0.2.5' -VERSION = '0.2.5' +__version_info__ = '1.0.0' +__version__ = '1.0.0' +version = '1.0.0' +VERSION = '1.0.0' def get_version(): return version # pragma: no cover diff --git a/templateselector/admin.py b/templateselector/admin.py index 7a81d70..a542ac3 100644 --- a/templateselector/admin.py +++ b/templateselector/admin.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals, absolute_import from django.contrib.admin import AllValuesFieldListFilter, FieldListFilter -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ _ALL = _('All') diff --git a/templateselector/fields.py b/templateselector/fields.py index 6cc3b73..8ad3a4a 100644 --- a/templateselector/fields.py +++ b/templateselector/fields.py @@ -1,10 +1,8 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals, absolute_import from operator import itemgetter from django.conf import settings from django.contrib import admin from django.contrib.staticfiles import finders -from django.utils import six from django.utils.deconstruct import deconstructible from django.utils.module_loading import import_string from django.utils.text import capfirst @@ -19,8 +17,7 @@ from django.template import TemplateDoesNotExist, engines from django.template.loader import get_template from django.utils.encoding import force_text -from django.utils.functional import curry -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import gettext_lazy as _ __all__ = ['TemplateField', 'TemplateChoiceField'] @@ -78,7 +75,7 @@ def __init__(self, match='^.*$', display_name='templateselector.fields.nice_disp template_exists_validator = TemplateExistsValidator(self.match) self.validators.append(template_exists_validator) - if isinstance(display_name, six.text_type) and '.' in display_name: + if isinstance(display_name, str) and '.' in display_name: display_name = import_string(display_name) if not callable(display_name): raise ImproperlyConfigured(_("display_name= argument must be a callable which takes a single string")) @@ -140,20 +137,17 @@ def check(self, **kwargs): def contribute_to_class(self, cls, name, **kwargs): super(TemplateField, self).contribute_to_class(cls, name, **kwargs) - display = curry(self.__get_FIELD_template_display, field=self) - display.short_description = self.verbose_name - display.admin_order_field = name - setattr(cls, 'get_%s_display' % self.name, display) - template_instance = curry(self.__get_FIELD_template_instance, field=self) - setattr(cls, 'get_%s_instance' % self.name, template_instance) - - def __get_FIELD_template_display(self, cls, field): - value = getattr(cls, field.attname) - return self.display_name(value) - - def __get_FIELD_template_instance(self, cls, field): - value = getattr(cls, field.attname) - return get_template(value) + field = self + def __get_FIELD_template_display(self): + value = getattr(self, field.attname) + return field.display_name(value) + def __get_FIELD_template_instance(self): + value = getattr(self, field.attname) + return get_template(value) + __get_FIELD_template_display.short_description = self.verbose_name + __get_FIELD_template_display.admin_order_field = name + setattr(cls, 'get_%s_display' % self.name, __get_FIELD_template_display) + setattr(cls, 'get_%s_instance' % self.name, __get_FIELD_template_instance) class TemplateChoiceField(TypedChoiceField): @@ -166,7 +160,7 @@ def __init__(self, match='^.*$', display_name='templateselector.fields.nice_disp max_length = None if 'max_length' in kwargs: max_length = kwargs.pop('max_length') - if isinstance(display_name, six.text_type) and '.' in display_name: + if isinstance(display_name, str) and '.' in display_name: display_name = import_string(display_name) if not callable(display_name): raise ImproperlyConfigured(_("display_name= argument must be a callable which takes a single string")) diff --git a/templateselector/handlers.py b/templateselector/handlers.py index 41ed47b..a828618 100644 --- a/templateselector/handlers.py +++ b/templateselector/handlers.py @@ -1,21 +1,6 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals, absolute_import from django.template.loaders import app_directories, filesystem, cached -try: - from os import scandir -except ImportError: # pragma: no cover - try: - from scandir import scandir - except ImportError as e: - import sys - from django.utils import six - message = ("You're probably using Python 2.x, so you'll need to " - "install the backport: `pip install scandir\>=1.5`") - flattened = " ".join(e.args) + "\n" + message - e.args = (flattened,) - e.message = flattened - six.reraise(*sys.exc_info()) - +from os import scandir __all__ = ['get_results_from_registry'] diff --git a/templateselector/tests/__init__.py b/templateselector/tests/__init__.py index d98c976..5ee00d4 100644 --- a/templateselector/tests/__init__.py +++ b/templateselector/tests/__init__.py @@ -1,3 +1,2 @@ # -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals __all__ = [] diff --git a/templateselector/tests/admin.py b/templateselector/tests/admin.py index 4d73011..e750beb 100644 --- a/templateselector/tests/admin.py +++ b/templateselector/tests/admin.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals, absolute_import - from django.contrib import admin from templateselector.tests.models import MyModel diff --git a/templateselector/tests/models.py b/templateselector/tests/models.py index a6d6ec9..d956b0d 100644 --- a/templateselector/tests/models.py +++ b/templateselector/tests/models.py @@ -1,11 +1,8 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals, absolute_import from django.db.models import Model from django.utils.encoding import force_text -from django.utils.six import python_2_unicode_compatible from templateselector.fields import TemplateField -@python_2_unicode_compatible class MyModel(Model): f = TemplateField(match="^admin/.+\.html$", verbose_name="test 'f'") diff --git a/templateselector/tests/test_form_field.py b/templateselector/tests/test_form_field.py index b1178aa..94293b5 100644 --- a/templateselector/tests/test_form_field.py +++ b/templateselector/tests/test_form_field.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -from __future__ import unicode_literals, absolute_import - from unittest import skipIf from uuid import UUID @@ -23,21 +21,21 @@ def modelcls(): def test_field_warns_if_providing_coerce_callable(): with pytest.raises(ImproperlyConfigured): - TemplateChoiceField(coerce=int, match="^.*$") + TemplateChoiceField(coerce=int, match=r"^.*$") def test_field_warns_if_providing_dumb_display_name(): with pytest.raises(ImproperlyConfigured): - TemplateChoiceField(display_name='goose', match="^.*$") + TemplateChoiceField(display_name='goose', match=r"^.*$") def test_field_warns_if_missing_caret_at_start(): with pytest.raises(ImproperlyConfigured): - TemplateChoiceField(match=".*$") + TemplateChoiceField(match=r".*$") def test_field_warns_if_missing_dollar_at_end(): with pytest.raises(ImproperlyConfigured): - TemplateChoiceField(match="^.*") + TemplateChoiceField(match=r"^.*") def test_model_field_yields_correct_formfield(modelcls): @@ -48,7 +46,7 @@ def test_model_field_yields_correct_formfield(modelcls): @override_settings(TEMPLATESELECTOR_DISPLAY_NAMES={'admin/500.html': '500'}) def test_choices(): - x = TemplateChoiceField(match="^admin/[0-9]+.html$") + x = TemplateChoiceField(match=r"^admin/[0-9]+.html$") assert set(x.choices) == {('admin/404.html', '404'), ('admin/500.html', '500')} @@ -58,14 +56,14 @@ def test_choices_using_custom_setting_mapping(): 'admin/500.html': 'Server Error', } with override_settings(TEMPLATESELECTOR_DISPLAY_NAMES=s): - x = TemplateChoiceField(match="^admin/[0-9]+.html$") + x = TemplateChoiceField(match=r"^admin/[0-9]+.html$") assert set(x.choices) == set(s.items()) def test_choices_using_custom_namer(): def namer(data): return str(UUID('F'*32)) - x = TemplateChoiceField(match="^admin/[0-9]+.html$", display_name=namer) + x = TemplateChoiceField(match=r"^admin/[0-9]+.html$", display_name=namer) assert set(x.choices) == {('admin/404.html', 'ffffffff-ffff-ffff-ffff-ffffffffffff'), ('admin/500.html', 'ffffffff-ffff-ffff-ffff-ffffffffffff')} @@ -74,7 +72,7 @@ def namer(data): def form_cls(): class MyForm(Form): a = IntegerField() - b = TemplateChoiceField(match="^admin/[0-9]+.html$") + b = TemplateChoiceField(match=r"^admin/[0-9]+.html$") return MyForm @@ -98,15 +96,13 @@ def test_admin_default_formfield(rf, modelcls): assert isinstance(widget, AdminTemplateSelector) -class WidgetTestCase(SimpleTestCase): - @skipIf(django.VERSION[0:2] < (1, 11), "won't render properly in Django pre-template-widgets") - def test_html_output(self): - form = form_cls()(data={'a': '1', 'b': 'admin/404.html'}) - STATIC_URL = "/TESTOUTPUT/" - with override_settings(STATIC_URL=STATIC_URL, TEMPLATESELECTOR_DISPLAY_NAMES={'admin/500.html': '500'}): - rendered = force_text(form['b']) +def test_html_output(form_cls): + form = form_cls(data={'a': '1', 'b': 'admin/404.html'}) + STATIC_URL = "/TESTOUTPUT/" + with override_settings(STATIC_URL=STATIC_URL, TEMPLATESELECTOR_DISPLAY_NAMES={'admin/500.html': '500'}): + rendered = force_text(form['b']) - self.assertHTMLEqual(rendered, """ + SimpleTestCase().assertHTMLEqual(rendered, """