diff --git a/example/app/test_msf.py b/example/app/test_msf.py index 4f4da13..4f04d78 100644 --- a/example/app/test_msf.py +++ b/example/app/test_msf.py @@ -14,9 +14,6 @@ # You should have received a copy of the GNU Lesser General Public License # along with this software. If not, see . -import sys - -from django import VERSION from django.core.exceptions import ValidationError from django.forms.models import modelform_factory from django.test import TestCase @@ -25,36 +22,11 @@ from .models import Book, PROVINCES, STATES, PROVINCES_AND_STATES, ONE, TWO -if sys.version_info < (3,): - u = unicode # noqa: F821 -else: - u = str - - -if VERSION < (1, 9): - def get_field(model, name): - return model._meta.get_field_by_name(name)[0] -else: - def get_field(model, name): - return model._meta.get_field(name) - - class MultiSelectTestCase(TestCase): fixtures = ['app_data.json'] maxDiff = 4000 - def assertListEqual(self, left, right, msg=None): - if sys.version_info >= (3, 2): - # Added in Python 3.2 - self.assertCountEqual(left, right, msg=msg) - else: - # Manually check list equality - self.assertEqual(len(left), len(right), msg=msg) - for i, tag_list in enumerate(left): - for j, tag in enumerate(tag_list): - self.assertEqual(tag, right[i][j], msg=msg) - def assertStringEqual(self, left, right, msg=None): _msg = "Chars in position %%d differ: %%s != %%s. %s" % msg @@ -70,18 +42,10 @@ def test_values_list(self): tag_list_list = Book.objects.all().values_list('tags', flat=True) categories_list_list = Book.objects.all().values_list('categories', flat=True) - # Workaround for Django bug #9619 - # https://code.djangoproject.com/ticket/9619 - # For Django 1.6 and 1.7, calling values() or values_list() doesn't - # call Field.from_db_field, it simply returns a Python representation - # of the data in the database (which in our case is a string of - # comma-separated values). The bug was fixed in Django 1.8+. - if VERSION >= (1, 6) and VERSION < (1, 8): - self.assertStringEqual(tag_list_list, [u('sex,work,happy')]) - self.assertStringEqual(categories_list_list, [u('1,3,5')]) - else: - self.assertListEqual(tag_list_list, [['sex', 'work', 'happy']]) - self.assertListEqual(categories_list_list, [['1', '3', '5']]) + # assertCountEqual also ensures that the elements are the same (ignoring list order) + # https://docs.python.org/3.2/library/unittest.html#unittest.TestCase.assertCountEqual + self.assertCountEqual(tag_list_list, [['sex', 'work', 'happy']]) + self.assertCountEqual(categories_list_list, [['1', '3', '5']]) def test_form(self): form_class = modelform_factory(Book, fields=('title', 'tags', 'categories')) @@ -124,63 +88,102 @@ def test_object(self): def test_validate(self): book = Book.objects.get(id=1) - get_field(Book, 'tags').clean(['sex', 'work'], book) + Book._meta.get_field('tags').clean(['sex', 'work'], book) try: - get_field(Book, 'tags').clean(['sex1', 'work'], book) + Book._meta.get_field('tags').clean(['sex1', 'work'], book) raise AssertionError() except ValidationError: pass - get_field(Book, 'categories').clean(['1', '2', '3'], book) + Book._meta.get_field('categories').clean(['1', '2', '3'], book) try: - get_field(Book, 'categories').clean(['1', '2', '3', '4'], book) + Book._meta.get_field('categories').clean(['1', '2', '3', '4'], book) raise AssertionError() except ValidationError: pass try: - get_field(Book, 'categories').clean(['11', '12', '13'], book) + Book._meta.get_field('categories').clean(['11', '12', '13'], book) raise AssertionError() except ValidationError: pass def test_serializer(self): book = Book.objects.get(id=1) - self.assertEqual(get_field(Book, 'tags').value_to_string(book), 'sex,work,happy') - self.assertEqual(get_field(Book, 'categories').value_to_string(book), '1,3,5') + self.assertEqual(Book._meta.get_field('tags').value_to_string(book), 'sex,work,happy') + self.assertEqual(Book._meta.get_field('categories').value_to_string(book), '1,3,5') def test_flatchoices(self): - self.assertEqual(get_field(Book, 'published_in').flatchoices, list(PROVINCES + STATES)) + self.assertEqual(Book._meta.get_field('published_in').flatchoices, list(PROVINCES + STATES)) def test_named_groups(self): - self.assertEqual(get_field(Book, 'published_in').choices, PROVINCES_AND_STATES) + # We can't use a single self.assertEqual here, because model subchoices may be lists or tuples + # Iterate through the parent choices + for book_choices, province_or_state_choice in zip(Book._meta.get_field('published_in').choices, PROVINCES_AND_STATES): + parent_book_choice, *book_subchoices = book_choices + parent_pors_choice, *pors_subchoices = province_or_state_choice + # Check the parent keys + self.assertEqual(parent_book_choice, parent_pors_choice) + # Iterate through all of the subchoices + for book_subchoice, pors_subchoice in zip(book_subchoices, pors_subchoices): + # The model subchoices might be tuples, so make sure to convert both to lists + self.assertEqual(list(book_subchoice), list(pors_subchoice)) def test_named_groups_form(self): form_class = modelform_factory(Book, fields=('published_in',)) self.assertEqual(len(form_class.base_fields), 1) form = form_class(initial={'published_in': ['BC', 'AK']}) - expected_html = u("""

""") + expected_html = """ +

+ +

+
+ +
+ +
+
+ +
+
+
+ +
+ +
+
+ +
+
+ +
+
+
+

+ """ actual_html = form.as_p() - if (1, 11) <= VERSION: - # Django 1.11+ does not assign 'for' attributes on labels if they - # are group labels - expected_html = expected_html.replace('label for="id_published_in_0"', 'label') - - if VERSION < (1, 6): - # Django 1.6 renders the Python repr() for each group (eg: tuples - # with HTML entities), so we skip the test for that version - self.assertEqual(expected_html.replace('\n', ''), actual_html.replace('\n', '')) - - if VERSION >= (2, 0): - expected_html = expected_html.replace('input checked="checked"', 'input checked') - - if VERSION >= (1, 7): - self.assertHTMLEqual(expected_html, actual_html) self.assertHTMLEqual(expected_html, actual_html) diff --git a/example/app/urls.py b/example/app/urls.py index 556968d..b912d2e 100644 --- a/example/app/urls.py +++ b/example/app/urls.py @@ -14,35 +14,11 @@ # You should have received a copy of the GNU Lesser General Public License # along with this software. If not, see . -from django import VERSION -try: - from django.conf.urls import url - - # Compatibility for Django > 1.8 - def patterns(prefix, *args): - if VERSION < (1, 9): - from django.conf.urls import patterns as django_patterns - return django_patterns(prefix, *args) - elif prefix != '': - raise NotImplementedError("You need to update your URLConf for " - "Django 1.10, or tweak it to remove the " - "prefix parameter") - else: - return list(args) -except ImportError: # Django < 1.4 - if VERSION < (4, 0): - from django.conf.urls.defaults import patterns, url - else: - from django.urls import re_path as url +from django.urls import path from .views import app_index -if VERSION < (1, 11): - urlpatterns = patterns( - '', - url(r'^$', app_index, name='app_index'), - ) -else: - urlpatterns = [ - url(r'^$', app_index, name='app_index'), - ] + +urlpatterns = [ + path('', app_index, name='app_index'), +] diff --git a/example/example/settings.py b/example/example/settings.py index ff2e5d0..74290c4 100644 --- a/example/example/settings.py +++ b/example/example/settings.py @@ -18,8 +18,6 @@ import os from os import path -from django import VERSION - DEBUG = True BASE_DIR = path.dirname(path.abspath(__file__)) @@ -42,6 +40,8 @@ } } +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + # Hosts/domain names that are valid for this site; required if DEBUG is False # See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts ALLOWED_HOSTS = ['localhost'] @@ -121,57 +121,30 @@ # Python dotted path to the WSGI application used by Django's runserver. WSGI_APPLICATION = 'example.wsgi.application' -if VERSION < (1, 8): - TEMPLATE_DEBUG = DEBUG - - # List of callables that know how to import templates from various sources. - TEMPLATE_LOADERS = ( - 'django.template.loaders.filesystem.Loader', - 'django.template.loaders.app_directories.Loader', - # 'django.template.loaders.eggs.Loader', - ) - - TEMPLATE_DIRS = ( - # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". - # Always use forward slashes, even on Windows. - # Don't forget to use absolute paths, not relative paths. - ) - - TEMPLATE_CONTEXT_PROCESSORS = ( - 'django.contrib.auth.context_processors.auth', - 'django.core.context_processors.debug', - 'django.core.context_processors.i18n', - 'django.core.context_processors.media', - 'django.core.context_processors.request', - 'django.core.context_processors.tz', - 'django.core.context_processors.static', - 'django.contrib.messages.context_processors.messages', - ) -else: - TEMPLATES = [ - { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'OPTIONS': { - 'context_processors': [ - 'django.contrib.auth.context_processors.auth', - 'django.template.context_processors.debug', - 'django.template.context_processors.i18n', - 'django.template.context_processors.media', - 'django.template.context_processors.request', - 'django.template.context_processors.static', - 'django.template.context_processors.tz', - 'django.contrib.messages.context_processors.messages', - ], - 'debug': DEBUG, - 'loaders': [ - # List of callables that know how to import templates from - # various sources. - 'django.template.loaders.filesystem.Loader', - 'django.template.loaders.app_directories.Loader', - ] - }, - } - ] +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'OPTIONS': { + 'context_processors': [ + 'django.contrib.auth.context_processors.auth', + 'django.template.context_processors.debug', + 'django.template.context_processors.i18n', + 'django.template.context_processors.media', + 'django.template.context_processors.request', + 'django.template.context_processors.static', + 'django.template.context_processors.tz', + 'django.contrib.messages.context_processors.messages', + ], + 'debug': DEBUG, + 'loaders': [ + # List of callables that know how to import templates from + # various sources. + 'django.template.loaders.filesystem.Loader', + 'django.template.loaders.app_directories.Loader', + ] + }, + } +] INSTALLED_APPS = ( 'django.contrib.auth', @@ -227,13 +200,11 @@ } } -if VERSION >= (1, 4): - LOGGING['filters'] = { - 'require_debug_false': { - '()': 'django.utils.log.RequireDebugFalse', - }, - } - LOGGING['handlers']['mail_admins']['filters'] = ['require_debug_false'] +LOGGING['filters'] = { + 'require_debug_false': { + '()': 'django.utils.log.RequireDebugFalse', + }, +} +LOGGING['handlers']['mail_admins']['filters'] = ['require_debug_false'] -if VERSION >= (1, 6): - TEST_RUNNER = 'django.test.runner.DiscoverRunner' +TEST_RUNNER = 'django.test.runner.DiscoverRunner' diff --git a/example/run_tests.py b/example/run_tests.py index 42bf316..ec3f39a 100644 --- a/example/run_tests.py +++ b/example/run_tests.py @@ -19,7 +19,6 @@ import os import sys -import django from django.conf import ENVIRONMENT_VARIABLE from django.core import management from django.core.wsgi import get_wsgi_application @@ -30,10 +29,6 @@ else: os.environ[ENVIRONMENT_VARIABLE] = sys.argv[1] -if django.VERSION[0] == 1 and django.VERSION[1] >= 7: - from django.core.wsgi import get_wsgi_application as get_wsgi_application_v1 - application = get_wsgi_application_v1() -else: - application = get_wsgi_application() +application = get_wsgi_application() management.call_command('test', 'app') diff --git a/multiselectfield/db/fields.py b/multiselectfield/db/fields.py index 7d4eef6..7038f27 100644 --- a/multiselectfield/db/fields.py +++ b/multiselectfield/db/fields.py @@ -14,10 +14,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with this programe. If not, see . -import sys - from django import VERSION - from django.db import models from django.utils.text import capfirst from django.core import exceptions @@ -26,13 +23,6 @@ from ..utils import MSFList, get_max_length from ..validators import MaxValueMultiFieldValidator -if sys.version_info < (3,): - string_type = unicode # noqa: F821 -else: - string_type = str - -# Code from six egg https://bitbucket.org/gutworth/six/src/a3641cb211cc360848f1e2dd92e9ae6cd1de55dd/six.py?at=default - def add_metaclass(metaclass): """Class decorator for creating a class with a metaclass.""" @@ -46,6 +36,17 @@ def wrapper(cls): return wrapper +class MSFList(list): + + def __init__(self, choices, *args, **kwargs): + self.choices = choices + super(MSFList, self).__init__(*args, **kwargs) + + def __str__(msgl): + l = [msgl.choices.get(int(i)) if i.isdigit() else msgl.choices.get(i) for i in msgl] + return ', '.join([str(s) for s in l]) + + class MultiSelectField(models.CharField): """ Choice values can not contain commas. """ @@ -54,14 +55,17 @@ def __init__(self, *args, **kwargs): self.max_choices = kwargs.pop('max_choices', None) super(MultiSelectField, self).__init__(*args, **kwargs) self.max_length = get_max_length(self.choices, self.max_length) - self.validators[0] = MaxValueMultiFieldValidator(self.max_length) + self.validators.append(MaxValueMultiFieldValidator(self.max_length)) if self.min_choices is not None: self.validators.append(MinChoicesValidator(self.min_choices)) if self.max_choices is not None: self.validators.append(MaxChoicesValidator(self.max_choices)) def _get_flatchoices(self): - flat_choices = super(MultiSelectField, self)._get_flatchoices() + if VERSION >= (5,): + flat_choices = super(MultiSelectField, self).flatchoices + else: + flat_choices = super(MultiSelectField, self)._get_flatchoices() class MSFFlatchoices(list): # Used to trick django.contrib.admin.utils.display_for_field into @@ -82,10 +86,10 @@ def get_choices_selected(self, arr_choices): if named_groups: for choice_group_selected in arr_choices: for choice_selected in choice_group_selected[1]: - choices_selected.append(string_type(choice_selected[0])) + choices_selected.append(str(choice_selected[0])) else: for choice_selected in arr_choices: - choices_selected.append(string_type(choice_selected[0])) + choices_selected.append(str(choice_selected[0])) return choices_selected def value_to_string(self, obj): @@ -99,15 +103,12 @@ def validate(self, value, model_instance): arr_choices = self.get_choices_selected(self.get_choices_default()) for opt_select in value: if (opt_select not in arr_choices): - if VERSION >= (1, 6): - raise exceptions.ValidationError(self.error_messages['invalid_choice'] % {"value": value}) - else: - raise exceptions.ValidationError(self.error_messages['invalid_choice'] % value) + raise exceptions.ValidationError(self.error_messages['invalid_choice'] % {"value": value}) def get_default(self): default = super(MultiSelectField, self).get_default() if isinstance(default, int): - default = string_type(default) + default = str(default) return default def formfield(self, **kwargs): @@ -127,7 +128,7 @@ def get_prep_value(self, value): return '' if value is None else ",".join(map(str, value)) def get_db_prep_value(self, value, connection, prepared=False): - if not prepared and not isinstance(value, string_type): + if not prepared and not isinstance(value, str): value = self.get_prep_value(value) return value @@ -137,23 +138,17 @@ def to_python(self, value): if value: if isinstance(value, list): return value - elif isinstance(value, string_type): - value_list = map(lambda x: x.strip(), value.replace(u',', ',').split(',')) + elif isinstance(value, str): + value_list = map(lambda x: x.strip(), value.replace(',', ',').split(',')) return MSFList(choices, value_list) elif isinstance(value, (set, dict)): return MSFList(choices, list(value)) return MSFList(choices, []) - if VERSION < (2, ): - def from_db_value(self, value, expression, connection, context): - if value is None: - return value - return self.to_python(value) - else: - def from_db_value(self, value, expression, connection): - if value is None: - return value - return self.to_python(value) + def from_db_value(self, value, expression, connection): + if value is None: + return value + return self.to_python(value) def contribute_to_class(self, cls, name): super(MultiSelectField, self).contribute_to_class(cls, name) @@ -170,7 +165,7 @@ def get_list(obj): item_display = choicedict.get(int(value), value) except (ValueError, TypeError): item_display = value - display.append(string_type(item_display)) + display.append(str(item_display)) return display def get_display(obj): @@ -179,13 +174,3 @@ def get_display(obj): setattr(cls, 'get_%s_list' % self.name, get_list) setattr(cls, 'get_%s_display' % self.name, get_display) - - -if VERSION < (1, 8): - MultiSelectField = add_metaclass(models.SubfieldBase)(MultiSelectField) - -try: - from south.modelsinspector import add_introspection_rules - add_introspection_rules([], ['^multiselectfield\.db.fields\.MultiSelectField']) -except ImportError: - pass diff --git a/multiselectfield/utils.py b/multiselectfield/utils.py index 35d28c0..c08ff48 100644 --- a/multiselectfield/utils.py +++ b/multiselectfield/utils.py @@ -14,16 +14,6 @@ # You should have received a copy of the GNU Lesser General Public License # along with this programe. If not, see . -import sys - - -if sys.version_info[0] == 2: - string = basestring # noqa: F821 - string_type = unicode # noqa: F821 -else: - string = str - string_type = string - class MSFList(list): @@ -33,17 +23,13 @@ def __init__(self, choices, *args, **kwargs): def __str__(msgl): msg_list = [msgl.choices.get(int(i)) if i.isdigit() else msgl.choices.get(i) for i in msgl] - return u', '.join([string_type(s) for s in msg_list]) - - if sys.version_info < (3,): - def __unicode__(self, msgl): - return self.__str__(msgl) + return ', '.join([str(s) for s in msg_list]) def get_max_length(choices, max_length, default=200): if max_length is None: if choices: - return len(','.join([string_type(key) for key, label in choices])) + return len(','.join([str(key) for key, label in choices])) else: return default return max_length diff --git a/multiselectfield/validators.py b/multiselectfield/validators.py index b387680..9624288 100644 --- a/multiselectfield/validators.py +++ b/multiselectfield/validators.py @@ -27,10 +27,10 @@ def clean(self, x): class MinChoicesValidator(validators.MinLengthValidator): - message = _(u'You must select a minimum of %(limit_value)d choices.') + message = _('You must select a minimum of %(limit_value)d choices.') code = 'min_choices' class MaxChoicesValidator(validators.MaxLengthValidator): - message = _(u'You must select a maximum of %(limit_value)d choices.') + message = _('You must select a maximum of %(limit_value)d choices.') code = 'max_choices'