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("""
- Canada - Provinces
\n"""
- """- USA - States
""")
+ 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'