Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add back compatibility #99

Merged
merged 10 commits into from
Oct 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ dist
.coverage*
.env
example/example.db
.tox

64 changes: 59 additions & 5 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,79 @@ python:
- "3.4"
- "3.5"
- "3.6"
- "3.7"
env:
- DJANGO_VERSION='Django>=1.4,<1.5'
- DJANGO_VERSION='Django>=1.5,<1.6'
- DJANGO_VERSION='Django>=1.6,<1.7'
- DJANGO_VERSION='Django>=1.7,<1.8'
- DJANGO_VERSION='Django>=1.8,<1.9'
- DJANGO_VERSION='Django>=1.9,<1.10'
- DJANGO_VERSION='Django>=1.10,<1.11'
- DJANGO_VERSION='Django>=1.11,<2.0'
- DJANGO_VERSION='Django>=2.0,<2.1'
- DJANGO_VERSION='Django>=2.1,<2.2'
- DJANGO_VERSION='https://github.com/django/django/archive/master.tar.gz'
matrix:
exclude:
- python: "2.7"
env: DJANGO_VERSION='Django>=2.0,<2.1'
env: DJANGO_VERSION='https://github.com/django/django/archive/master.tar.gz'
- python: "2.7"
env: DJANGO_VERSION='Django>=2.1,<2.2'
- python: "2.7"
env: DJANGO_VERSION='https://github.com/django/django/archive/master.tar.gz'
env: DJANGO_VERSION='Django>=2.0,<2.1'
- python: "2.7"
env: DJANGO_VERSION='Django>=1.6,<1.7'
- python: "3.4"
env: DJANGO_VERSION='Django>=1.4,<1.5'
- python: "3.4"
env: DJANGO_VERSION='Django>=1.5,<1.6'
- python: "3.4"
env: DJANGO_VERSION='Django>=1.6,<1.7'
- python: "3.4"
env: DJANGO_VERSION='Django>=2.1,<2.2'
- python: "2.7"
- python: "3.4"
env: DJANGO_VERSION='https://github.com/django/django/archive/master.tar.gz'
- python: "3.5"
env: DJANGO_VERSION='Django>=1.4,<1.5'
- python: "3.5"
env: DJANGO_VERSION='Django>=1.5,<1.6'
- python: "3.5"
env: DJANGO_VERSION='Django>=1.6,<1.7'
- python: "3.5"
env: DJANGO_VERSION='Django>=1.7,<1.8'
- python: "3.5"
env: DJANGO_VERSION='Django>=2.1,<2.2'
- python: "3.5"
env: DJANGO_VERSION='https://github.com/django/django/archive/master.tar.gz'
allow_failures:
- env: DJANGO_VERSION='https://github.com/django/django/archive/master.tar.gz'
- python: "3.6"
env: DJANGO_VERSION='Django>=1.4,<1.5'
- python: "3.6"
env: DJANGO_VERSION='Django>=1.5,<1.6'
- python: "3.6"
env: DJANGO_VERSION='Django>=1.6,<1.7'
- python: "3.6"
env: DJANGO_VERSION='Django>=1.7,<1.8'
- python: "3.6"
env: DJANGO_VERSION='Django>=1.8,<1.9'
- python: "3.6"
env: DJANGO_VERSION='Django>=1.9,<1.10'
- python: "3.6"
env: DJANGO_VERSION='Django>=1.10,<1.11'
- python: "3.7"
env: DJANGO_VERSION='Django>=1.4,<1.5'
- python: "3.7"
env: DJANGO_VERSION='Django>=1.5,<1.6'
- python: "3.7"
env: DJANGO_VERSION='Django>=1.6,<1.7'
- python: "3.7"
env: DJANGO_VERSION='Django>=1.7,<1.8'
- python: "3.7"
env: DJANGO_VERSION='Django>=1.8,<1.9'
- python: "3.7"
env: DJANGO_VERSION='Django>=1.9,<1.10'
- python: "3.7"
env: DJANGO_VERSION='Django>=1.10,<1.11'

install:
- pip install -q $DJANGO_VERSION
Expand Down
13 changes: 11 additions & 2 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
0.1.9 (2019-10-02)
------------------

* Added support for Django 2
* Added support for Python 3.6
* Drop support for Python (2.6, 3.3)
* Thanks to:
* `hirokinko <https://github.com/hirokinko>`_

0.1.6 (2017-05-10)
------------------

Expand Down Expand Up @@ -64,7 +73,7 @@

* Test/example project
* Now works if the first composant of the list of tuple is an integer
* Now max_length is not required, the Multiselect field calculate it automatically.
* Now max_length is not required, the Multiselect field calculate it automatically.
* The max_choices attr can be a attr in the model field
* Refactor the code
* Spanish translations
Expand All @@ -88,4 +97,4 @@
0.0.1 (2012-09-27)
------------------

* Initial version from the next `snippet <http://djangosnippets.org/snippets/1200/>`_
* Initial version from the next `snippet <http://djangosnippets.org/snippets/1200/>`_
16 changes: 8 additions & 8 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ This egg is inspired by this `snippet <http://djangosnippets.org/snippets/1200/>

Supported Python versions: 2.7, 3.4+

Supported Django versions: 1.11-2.0+
Supported Django versions: 1.4-2.0+

Installation
============
Expand All @@ -35,25 +35,25 @@ Configure your models.py
.. code-block:: python

from multiselectfield import MultiSelectField

# ...

MY_CHOICES = (('item_key1', 'Item title 1.1'),
('item_key2', 'Item title 1.2'),
('item_key3', 'Item title 1.3'),
('item_key4', 'Item title 1.4'),
('item_key5', 'Item title 1.5'))

MY_CHOICES2 = ((1, 'Item title 2.1'),
(2, 'Item title 2.2'),
(3, 'Item title 2.3'),
(4, 'Item title 2.4'),
(5, 'Item title 2.5'))

class MyModel(models.Model):

# .....

my_field = MultiSelectField(choices=MY_CHOICES)
my_field2 = MultiSelectField(choices=MY_CHOICES2,
max_choices=3,
Expand Down Expand Up @@ -103,7 +103,7 @@ Django REST Framework comes with a ``MultipleChoiceField`` that works perfectly
.. code-block:: python

from rest_framework import fields, serializers

from myapp.models import MY_CHOICES, MY_CHOICES2

class MyModelSerializer(serializers.HyperlinkedModelSerializer):
Expand Down
29 changes: 24 additions & 5 deletions example/app/test_msf.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,12 @@
u = str


def get_field(model, name):
return model._meta.get_field(name)
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):
Expand Down Expand Up @@ -73,8 +77,12 @@ def test_values_list(self):
# 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+.
self.assertListEqual(tag_list_list, [['sex', 'work', 'happy']])
self.assertListEqual(categories_list_list, [['1', '3', '5']])
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']])

def test_form(self):
form_class = modelform_factory(Book, fields=('title', 'tags', 'categories'))
Expand Down Expand Up @@ -131,17 +139,28 @@ def test_named_groups_form(self):
self.assertEqual(len(form_class.base_fields), 1)
form = form_class(initial={'published_in': ['BC', 'AK']})

expected_html = u("""<p><label>Province or State:</label> <ul id="id_published_in"><li>Canada - Provinces<ul id="id_published_in_0"><li><label for="id_published_in_0_0"><input id="id_published_in_0_0" name="published_in" type="checkbox" value="AB" /> Alberta</label></li>\n"""
expected_html = u("""<p><label for="id_published_in_0">Province or State:</label> <ul id="id_published_in"><li>Canada - Provinces<ul id="id_published_in_0"><li><label for="id_published_in_0_0"><input id="id_published_in_0_0" name="published_in" type="checkbox" value="AB" /> Alberta</label></li>\n"""
"""<li><label for="id_published_in_0_1"><input checked="checked" id="id_published_in_0_1" name="published_in" type="checkbox" value="BC" /> British Columbia</label></li></ul></li>\n"""
"""<li>USA - States<ul id="id_published_in_1"><li><label for="id_published_in_1_0"><input checked="checked" id="id_published_in_1_0" name="published_in" type="checkbox" value="AK" /> Alaska</label></li>\n"""
"""<li><label for="id_published_in_1_1"><input id="id_published_in_1_1" name="published_in" type="checkbox" value="AL" /> Alabama</label></li>\n"""
"""<li><label for="id_published_in_1_2"><input id="id_published_in_1_2" name="published_in" type="checkbox" value="AZ" /> Arizona</label></li></ul></li></ul></p>""")

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)


Expand Down
9 changes: 6 additions & 3 deletions example/app/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@
# You should have received a copy of the GNU Lesser General Public License
# along with this software. If not, see <http://www.gnu.org/licenses/>.


from django import VERSION
from django.conf import settings
from django.contrib.auth import login
from django.contrib.auth import get_user_model

from django.urls import reverse
from django.http import HttpResponseRedirect

if VERSION >= (2, 0):
from django.urls import reverse
else:
from django.core.urlresolvers import reverse


def app_index(request):
user = get_user_model().objects.get(username='admin')
Expand Down
2 changes: 1 addition & 1 deletion example/example/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@
'django.contrib.messages',
'django.contrib.staticfiles',
# Uncomment the next line to enable the admin:
'django.contrib.admin',
# 'django.contrib.admin',
# Uncomment the next line to enable admin documentation:
# 'django.contrib.admindocs',

Expand Down
61 changes: 41 additions & 20 deletions example/example/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,54 @@
# You should have received a copy of the GNU Lesser General Public License
# along with this software. If not, see <http://www.gnu.org/licenses/>.

from django import VERSION
from django.conf import settings
from django.views.static import serve


try:
from django.conf.urls import include, url
except ImportError:
from django.urls import include, url
from django.contrib import admin
from django.views.static import serve

# 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 < (1, 4):
from django.conf.urls.defaults import include, patterns, url
else:
from django.urls import include, url

admin.autodiscover()

js_info_dict = {
'packages': ('django.conf',),
}

urlpatterns = [
url(r'^', include('app.urls')),
url(r'^admin/', admin.site.urls),
]

urlpatterns += [
url(
r'^%s(?P<path>.*)$' % settings.MEDIA_URL[1:],
serve,
{
'document_root': settings.MEDIA_ROOT,
'show_indexes': True,
},
),
]
if VERSION < (1, 11):
urlpatterns = patterns(
'',
url(r'^', include('app.urls')),
)
urlpatterns += patterns(
'',
url(r'^%s(?P<path>.*)$' % settings.MEDIA_URL[1:]),
)
else:
urlpatterns = [
url(r'^', include('app.urls')),
url(
r'^%s(?P<path>.*)$' % settings.MEDIA_URL[1:],
serve,
{
'document_root': settings.MEDIA_ROOT,
'show_indexes': True,
},
),
]
7 changes: 6 additions & 1 deletion example/run_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
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
Expand All @@ -29,6 +30,10 @@
else:
os.environ[ENVIRONMENT_VARIABLE] = sys.argv[1]

application = get_wsgi_application()
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()

management.call_command('test', 'app')
11 changes: 8 additions & 3 deletions multiselectfield/db/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@

from django.db import models
from django.utils.text import capfirst
from django.utils.encoding import python_2_unicode_compatible
from django.core import exceptions

from ..forms.fields import MultiSelectFormField, MinChoicesValidator, MaxChoicesValidator
Expand All @@ -47,7 +46,6 @@ def wrapper(cls):
return wrapper


@python_2_unicode_compatible
class MSFList(list):

def __init__(self, choices, *args, **kwargs):
Expand All @@ -58,6 +56,10 @@ 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)


class MultiSelectField(models.CharField):
""" Choice values can not contain commas. """
Expand Down Expand Up @@ -102,7 +104,10 @@ def get_choices_selected(self, arr_choices):
return choices_selected

def value_to_string(self, obj):
value = super(MultiSelectField, self).value_from_object(obj)
try:
value = self._get_val_from_obj(obj)
except AttributeError:
value = super(MultiSelectField, self).value_from_object(obj)
return self.get_prep_value(value)

def validate(self, value, model_instance):
Expand Down
Loading