From 29d2da094c3c5059c952dc219f9d1de3e3ff96f5 Mon Sep 17 00:00:00 2001 From: Sergei Maertens Date: Sat, 28 Jan 2017 13:39:54 +0100 Subject: [PATCH 1/7] Add docs to test matrix --- .travis.yml | 2 ++ docs/conf.py | 3 ++- tox.ini | 17 ++++++++++++++++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9b6b25d..b12879f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -50,3 +50,5 @@ env: - TOXENV=pypy-django19 - TOXENV=pypy-django110 - TOXENV=pypy-djangolatest + + - TOXENV=docs diff --git a/docs/conf.py b/docs/conf.py index 7b8f1c4..7e7d28b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -99,6 +99,7 @@ # If true, keep warnings as "system message" paragraphs in the built documents. #keep_warnings = False +suppress_warnings = ['image.nonlocal_uri'] # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False @@ -137,7 +138,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +# html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied diff --git a/tox.ini b/tox.ini index 3543600..0e89c19 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,10 @@ [tox] -envlist = py{26,27}-django{13,14,15,16},py27-django{17,18,19,110,latest},py{33,34,py}-django{15,16,17,18,latest},py{34,35,py}-django{18,19,110,latest} +envlist = + py{26,27}-django{13,14,15,16}, + py27-django{17,18,19,110,latest}, + py{33,34,py}-django{15,16,17,18,latest}, + py{34,35,py}-django{18,19,110,latest}, + docs skip_missing_interpreters = true [testenv] @@ -17,3 +22,13 @@ deps= coverage coveralls commands=coverage run --rcfile={toxinidir}/.coveragerc {toxinidir}/setup.py test + +[testenv:docs] +basepython=python +changedir=docs +skipsdist=true +deps= + sphinx + sphinx_rtd_theme +commands= + sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html From bd6db97aafa4834dab673b94f84a00251db1a4e5 Mon Sep 17 00:00:00 2001 From: Sergei Maertens Date: Sat, 28 Jan 2017 13:52:54 +0100 Subject: [PATCH 2/7] Refs #34 -- reduce verbosity Per https://github.com/bigjason/django-choices/issues/34\#issuecomment-275801964, there is no actual need to specify the validator generated on the DjangoChoices clases. This documentation update reflects this. --- docs/choices.rst | 13 +++++++------ docs/index.rst | 16 ++++++++-------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/docs/choices.rst b/docs/choices.rst index f15f91a..02f62d6 100644 --- a/docs/choices.rst +++ b/docs/choices.rst @@ -169,12 +169,13 @@ Returns a dictionary with a mapping from value to label: validator +++++++++ -Returns a validator that can be used in your model field. This validator checks -that the value passed to the field is indeed a value specified in your choices -class. +.. note:: + At least since Django 1.3, there is model and form-level validation of the + choices. Unless you have a reason to explicitly specify/override the validator, + you can skip specifying this validator. -.. note:: - This validator had issues in Django 1.7 and up with the new migrations. - validators have to be deconstructible. This was fixed in the 1.4 release. +Returns a validator that can be used in your model field. This validator checks +that the value passed to the field is indeed a value specified in your choices +class. diff --git a/docs/index.rst b/docs/index.rst index 311df83..e324976 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -60,12 +60,12 @@ To use it, you write a choices class, and use it in your model fields: .. code-block:: python - from djchoices import DjangoChoices, ChoiceItem + from djchoices import ChoiceItem, DjangoChoices class Book(models.Model): - class BookTypes(DjangoChoices): + class BookType(DjangoChoices): short_story = ChoiceItem('short', 'Short story') novel = ChoiceItem('novel', 'Novel') non_fiction = ChoiceItem('non_fiction', 'Non fiction') @@ -73,26 +73,26 @@ To use it, you write a choices class, and use it in your model fields: author = models.ForeignKey('Author') book_type = models.CharField( - max_length=20, choices=BookTypes.choices, - default=BookTypes.novel, validators=[BookTypes.validator] + max_length=20, choices=BookType.choices, + default=BookType.novel ) -You can use this in other places like this: +You can then use the availe choices in other modules, e.g.: .. code-block:: python from .models import Book - Person.objects.create(author=my_author, type=Book.BookTypes.short_story) -The `DjangoChoices` classes can be located anywhere you want, +The ``DjangoChoices`` classes can be located anywhere you want, for example you can put them outside of the model declaration if you have a 'common' set of choices for different models. Any place is valid though, -you can group them all together in `choices.py` if you want. +you can group them all together in ``choices.py`` if you want. + License ------- From f03474c4cffab1ff7f3f62f9d257728ce1f6396b Mon Sep 17 00:00:00 2001 From: Sergei Maertens Date: Sat, 28 Jan 2017 14:06:59 +0100 Subject: [PATCH 3/7] Added Django 1.11 alpha to test matrix --- .travis.yml | 4 ++++ tox.ini | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b12879f..29c98c0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,6 +22,7 @@ env: - TOXENV=py27-django18 - TOXENV=py27-django19 - TOXENV=py27-django110 + - TOXENV=py27-django111 - TOXENV=py27-djangolatest - TOXENV=py33-django15 @@ -36,11 +37,13 @@ env: - TOXENV=py34-django18 - TOXENV=py34-django19 - TOXENV=py34-django110 + - TOXENV=py34-django111 - TOXENV=py34-djangolatest - TOXENV=py35-django18 - TOXENV=py35-django19 - TOXENV=py35-django110 + - TOXENV=py35-django111 - TOXENV=py35-djangolatest - TOXENV=pypy-django15 @@ -49,6 +52,7 @@ env: - TOXENV=pypy-django18 - TOXENV=pypy-django19 - TOXENV=pypy-django110 + - TOXENV=pypy-django111 - TOXENV=pypy-djangolatest - TOXENV=docs diff --git a/tox.ini b/tox.ini index 0e89c19..46e2ca5 100644 --- a/tox.ini +++ b/tox.ini @@ -1,9 +1,9 @@ [tox] envlist = py{26,27}-django{13,14,15,16}, - py27-django{17,18,19,110,latest}, + py27-django{17,18,19,110,111,latest}, py{33,34,py}-django{15,16,17,18,latest}, - py{34,35,py}-django{18,19,110,latest}, + py{34,35,py}-django{18,19,110,111,latest}, docs skip_missing_interpreters = true @@ -17,6 +17,7 @@ deps= django18: Django>=1.8,<1.9 django19: Django>=1.9,<1.10 django110: Django>=1.10,<1.11 + django111: Django>=1.11a1,<2.0 py26: unittest2 django13,django14: six coverage From b17078aec6e2b288939256038a63ba243d818fd7 Mon Sep 17 00:00:00 2001 From: Sergei Maertens Date: Mon, 13 Feb 2017 17:34:37 +0100 Subject: [PATCH 4/7] Drop python-old and Django-old support --- .travis.yml | 20 -------------------- README.rst | 28 ++++++++++++---------------- djchoices/__init__.py | 2 ++ djchoices/choices.py | 9 ++++++--- djchoices/compat.py | 21 --------------------- djchoices/tests/test_choices.py | 10 ++-------- djchoices/tests/test_private_api.py | 8 ++------ djchoices/tests/utils.py | 5 ----- docs/choices.rst | 6 +++--- docs/index.rst | 8 +++++--- tox.ini | 13 ++----------- 11 files changed, 34 insertions(+), 96 deletions(-) delete mode 100644 djchoices/compat.py delete mode 100644 djchoices/tests/utils.py diff --git a/.travis.yml b/.travis.yml index 29c98c0..8e9d30e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,31 +9,14 @@ after_success: - coveralls env: - - TOXENV=py26-django13 - - TOXENV=py26-django14 - - TOXENV=py26-django15 - - TOXENV=py26-django16 - - - TOXENV=py27-django13 - - TOXENV=py27-django14 - - TOXENV=py27-django15 - - TOXENV=py27-django16 - - TOXENV=py27-django17 - TOXENV=py27-django18 - TOXENV=py27-django19 - TOXENV=py27-django110 - TOXENV=py27-django111 - TOXENV=py27-djangolatest - - TOXENV=py33-django15 - - TOXENV=py33-django16 - - TOXENV=py33-django17 - TOXENV=py33-django18 - - TOXENV=py33-djangolatest - - TOXENV=py34-django15 - - TOXENV=py34-django16 - - TOXENV=py34-django17 - TOXENV=py34-django18 - TOXENV=py34-django19 - TOXENV=py34-django110 @@ -46,9 +29,6 @@ env: - TOXENV=py35-django111 - TOXENV=py35-djangolatest - - TOXENV=pypy-django15 - - TOXENV=pypy-django16 - - TOXENV=pypy-django17 - TOXENV=pypy-django18 - TOXENV=pypy-django19 - TOXENV=pypy-django110 diff --git a/README.rst b/README.rst index 27677ae..fa568bf 100644 --- a/README.rst +++ b/README.rst @@ -9,7 +9,7 @@ Django-Choices :target: https://coveralls.io/github/bigjason/django-choices?branch=master .. image:: https://readthedocs.org/projects/django-choices/badge/?version=latest - :target: http://django-choices.readthedocs.org/en/latest/ + :target: http://django-choices.readthedocs.io/en/latest/ :alt: Documentation Status .. image:: https://img.shields.io/pypi/v/django-choices.svg @@ -19,7 +19,7 @@ Order and sanity for django model choices. ------------------------------------------------------ Django choices provides a declarative way of using the choices_ option on django_ -fields. +fields. Read the full `Documentation`_ on ReadTheDocs. ------------ Installation @@ -62,31 +62,26 @@ With this:: class Person(models.Model): # Choices class PersonType(DjangoChoices): - Customer = ChoiceItem("C") - Employee = ChoiceItem("E") - Groundhog = ChoiceItem("G") + customer = ChoiceItem("C") + employee = ChoiceItem("E") + groundhog = ChoiceItem("G") # Fields name = models.CharField(max_length=32) - type = models.CharField(max_length=1, - choices=PersonType.choices, - validators=[PersonType.validator]) + type = models.CharField(max_length=1, choices=PersonType.choices) You can use this elsewhere like this:: # Other code - Person.create(name="Phil", type=Person.PersonType.Groundhog) + Person.create(name="Phil", type=Person.PersonType.groundhog) -You can use without value, and the label will be used as value:: +You can use them without value, and the label will be used as value:: class Sample(DjangoChoices): - OptionA = ChoiceItem() - OptionB = ChoiceItem() + option_a = ChoiceItem() + option_b = ChoiceItem() - print(Sample.OptionA) # "OptionA" - -The `DjangoChoices` classes can be located anywhere you want. If I have a lot of -declarations I will sometimes place them in a `const.py` or `choices.py`. + print(Sample.option_a) # "option_a" ------- License @@ -103,3 +98,4 @@ The source code can be found on github_. .. _django: http://www.djangoproject.com/ .. _github: https://github.com/bigjason/django-choices .. _PyPi: http://pypi.python.org/pypi/django-choices/ +.. _docs: http://django-choices.readthedocs.io/en/latest/ diff --git a/djchoices/__init__.py b/djchoices/__init__.py index d94be7a..346e28a 100644 --- a/djchoices/__init__.py +++ b/djchoices/__init__.py @@ -1,3 +1,5 @@ +from __future__ import absolute_import, unicode_literals + from pkg_resources import get_distribution from djchoices.choices import ChoiceItem, DjangoChoices, C diff --git a/djchoices/choices.py b/djchoices/choices.py index 012573d..3cbf0f6 100644 --- a/djchoices/choices.py +++ b/djchoices/choices.py @@ -1,8 +1,11 @@ +from __future__ import absolute_import, unicode_literals + import re +from collections import OrderedDict from django.core.exceptions import ValidationError - -from .compat import deconstructible, OrderedDict, six +from django.utils import six +from django.utils.deconstruct import deconstructible __all__ = ["ChoiceItem", "DjangoChoices", "C"] @@ -55,7 +58,7 @@ def __init__(self, value=None, label=None, order=None): self.order = ChoiceItem.order # Shorter convenience alias. -C = ChoiceItem +C = ChoiceItem # noqa class DjangoChoicesMeta(type): diff --git a/djchoices/compat.py b/djchoices/compat.py deleted file mode 100644 index 81f7916..0000000 --- a/djchoices/compat.py +++ /dev/null @@ -1,21 +0,0 @@ -""" -Module to handle different Django/Python version libraries. -""" -try: - from collections import OrderedDict -except ImportError: # Py2.6, fall back to Django's implementation - from django.utils.datastructures import SortedDict as OrderedDict # pragma: no cover - - -try: - from django.utils import six -except ImportError: - import six # pragma: no cover - - -try: - from django.utils.deconstruct import deconstructible -except ImportError: - # just return a noop decorator - def deconstructible(cls): - return cls diff --git a/djchoices/tests/test_choices.py b/djchoices/tests/test_choices.py index 05b1a39..5baa9f4 100644 --- a/djchoices/tests/test_choices.py +++ b/djchoices/tests/test_choices.py @@ -1,12 +1,7 @@ -try: - import unittest2 as unittest -except ImportError: - import unittest +import unittest from djchoices import DjangoChoices, C, ChoiceItem -from .utils import has_new_migrations - class NumericTestClass(DjangoChoices): Item_0 = C(0) @@ -131,7 +126,7 @@ def test_validation_error_message(self): message = ("Select a valid choice. 4 is not " "one of the available choices.") - self.assertRaisesRegexp(ValidationError, message, + self.assertRaisesRegexp(ValidationError, message, NumericTestClass.validator, 4) def test_subclass1_validator(self): @@ -165,7 +160,6 @@ def test_empty_value_class(self): self.assertEqual(choices[1][0], "Option2") self.assertEqual(choices[2][0], "Option3") - @unittest.skipUnless(*has_new_migrations()) def test_deconstructible_validator(self): deconstructed = NumericTestClass.validator.deconstruct() self.assertEqual(deconstructed, ( diff --git a/djchoices/tests/test_private_api.py b/djchoices/tests/test_private_api.py index 3a135ed..1bd4030 100644 --- a/djchoices/tests/test_private_api.py +++ b/djchoices/tests/test_private_api.py @@ -1,9 +1,6 @@ -try: - import unittest2 as unittest -except ImportError: - import unittest +import unittest -from djchoices import DjangoChoices, C, ChoiceItem +from djchoices import DjangoChoices, C, ChoiceItem # noqa class PrivateAPITests(unittest.TestCase): @@ -23,4 +20,3 @@ class Choices(DjangoChoices): keys = Choices.labels.keys() self.assertEqual(set(keys), set(['a', 'b'])) - diff --git a/djchoices/tests/utils.py b/djchoices/tests/utils.py deleted file mode 100644 index 92873c6..0000000 --- a/djchoices/tests/utils.py +++ /dev/null @@ -1,5 +0,0 @@ -import django - - -def has_new_migrations(): - return (django.VERSION[:2] >= (1, 7), "Test requires the Django migrations introduced in Django 1.7") diff --git a/docs/choices.rst b/docs/choices.rst index 02f62d6..a6d59a0 100644 --- a/docs/choices.rst +++ b/docs/choices.rst @@ -1,7 +1,7 @@ Choice items ============ -The `ChoiceItem` class is what drives the choices. Each instance +The ``ChoiceItem`` class is what drives the choices. Each instance corresponds to a possible choice for your field. @@ -113,8 +113,8 @@ value as well, and it will be determined from the label. ) -`DjangoChoices` class attributes --------------------------------- +``DjangoChoices`` class attributes +---------------------------------- The choices class itself has a few useful attributes. Most notably `choices`, which returns the choices as a tuple. diff --git a/docs/index.rst b/docs/index.rst index e324976..b7b5c28 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -39,9 +39,11 @@ Requirements ------------ Django choices is fairly simple, so most Python and Django -versions should work. Django choices is tested against Python -2.6, 2.7, 3.3, 3.4, 3.5 and PyPy. Django 1.3 until 1.9 (including) -are officially supported (= tested in Travis). +versions should work. It is tested against Python 2.7, 3.3, 3.4, 3.5 and PyPy. +Django 1.8 until and including 1.11 alpha are supported (and tested in Travis). + +If you need to support older Python or Django versions, you should stick with +version ``1.4.4``. Backwards compatibility is dropped from 1.5 onwards. Quick-start diff --git a/tox.ini b/tox.ini index 46e2ca5..a877bd2 100644 --- a/tox.ini +++ b/tox.ini @@ -1,25 +1,16 @@ [tox] envlist = - py{26,27}-django{13,14,15,16}, - py27-django{17,18,19,110,111,latest}, - py{33,34,py}-django{15,16,17,18,latest}, - py{34,35,py}-django{18,19,110,111,latest}, + py33-django18, + py{27,34,35,py}-django{18,19,110,111,latest}, docs skip_missing_interpreters = true [testenv] deps= - django13: Django>=1.3,<1.4 - django14: Django>=1.4,<1.5 - django15: Django>=1.5,<1.6 - django16: Django>=1.6,<1.7 - django17: Django>=1.7,<1.8 django18: Django>=1.8,<1.9 django19: Django>=1.9,<1.10 django110: Django>=1.10,<1.11 django111: Django>=1.11a1,<2.0 - py26: unittest2 - django13,django14: six coverage coveralls commands=coverage run --rcfile={toxinidir}/.coveragerc {toxinidir}/setup.py test From 6c04b7fa409787bf4f6837b74ed68464a2f39dcd Mon Sep 17 00:00:00 2001 From: Sergei Maertens Date: Mon, 13 Feb 2017 17:42:17 +0100 Subject: [PATCH 5/7] update changelog --- Changelog.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Changelog.rst b/Changelog.rst index 9e2777a..ec045b0 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -2,6 +2,20 @@ Changelog ========= +1.5.0 +----- + +* Dropped support for old Python/Django versions. + +.. warning:: + Dropped support for Python versions < 2.7 and 3.3, and Django < 1.8. If you + need explicit support for these versions, you should stick to version 1.4.4. + +1.4.4 +----- + +* Bugfix for better IPython support (125d523e1c94e4edb344e3bb3ea1eab6f7d073ed) + 1.4.3 ----- From ec2e7abab6b9cbeb1152b0bdcf7f2386685e3bfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric=20Araujo?= Date: Mon, 13 Feb 2017 14:30:58 -0500 Subject: [PATCH 6/7] Save fields order for the public values attribute --- djchoices/choices.py | 2 +- djchoices/tests/test_choices.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/djchoices/choices.py b/djchoices/choices.py index 9a5ef84..7867615 100644 --- a/djchoices/choices.py +++ b/djchoices/choices.py @@ -72,7 +72,7 @@ class DjangoChoicesMeta(type): def __new__(cls, name, bases, attrs): fields = {} labels = Labels() - values = {} + values = OrderedDict() choices = [] # Get all the fields from parent classes. diff --git a/djchoices/tests/test_choices.py b/djchoices/tests/test_choices.py index a32e4d7..892b7b5 100644 --- a/djchoices/tests/test_choices.py +++ b/djchoices/tests/test_choices.py @@ -67,6 +67,9 @@ def test_class_values(self): self.assertEqual(SubClass1.values[SubClass1.Item_4], "Item 4") self.assertEqual(SubClass1.values[SubClass1.Item_5], "Item 5") + def test_class_values_order(self): + self.assertEqual(list(StringTestClass.values), ["", "O", "T", "H"]) + def test_numeric_class_order(self): choices = NumericTestClass.choices self.assertEqual(choices[0][0], 0) From efe00c02456c3f841e40412a6d30b0237162cd2c Mon Sep 17 00:00:00 2001 From: Sergei Maertens Date: Mon, 13 Feb 2017 21:00:33 +0100 Subject: [PATCH 7/7] Enable Python 3.5 --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 8e9d30e..2a49260 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,6 @@ language: python +python: + - "3.5" sudo: false install: