Add test for .values_list()
This commit is contained in:
parent
67e47d24a8
commit
d997a7a422
42
README.rst
42
README.rst
|
@ -13,9 +13,9 @@ django-multiselectfield
|
|||
.. image:: https://pypip.in/d/django-multiselectfield/badge.png
|
||||
:target: https://pypi.python.org/pypi/django-multiselectfield
|
||||
|
||||
A new model and form field. With this you can get a multiple select from a choices
|
||||
A new model field and form field. With this you can get a multiple select from a choices. Stores to the database as a CharField of comma-separated values.
|
||||
|
||||
This egg is inspired by this `snippet <http://djangosnippets.org/snippets/1200/>`_
|
||||
This egg is inspired by this `snippet <http://djangosnippets.org/snippets/1200/>`_.
|
||||
|
||||
Installation
|
||||
============
|
||||
|
@ -24,28 +24,28 @@ Installation
|
|||
In 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,
|
||||
|
@ -57,7 +57,7 @@ In your settings.py
|
|||
|
||||
Only you need it, if you want the translation of django-multiselectfield
|
||||
|
||||
::
|
||||
.. code-block:: python
|
||||
|
||||
INSTALLED_APPS = (
|
||||
'django.contrib.auth',
|
||||
|
@ -72,13 +72,25 @@ Only you need it, if you want the translation of django-multiselectfield
|
|||
)
|
||||
|
||||
|
||||
Known Bugs and Limitations
|
||||
==========================
|
||||
|
||||
Only in Django 1.6 and 1.7, due to `Django bug #9619 <https://code.djangoproject.com/ticket/9619>`_, passing a MultiSelectField to ``values()`` or ``values_list()`` will return the database representation of the field (a string of comma-separated values). The workaround is to manually call ``.split(',')`` on the result.
|
||||
|
||||
The Django bug was introduced in Django 1.6 and is fixed in Django 1.8 and onward, so ``values()`` and ``values_list()`` return a vanilla Python list of values for Django <= 1.5 and Django >= 1.8.
|
||||
|
||||
See `issue #40 <https://github.com/goinnn/django-multiselectfield/issues/40>`_ for discussion about this bug.
|
||||
|
||||
|
||||
Development
|
||||
===========
|
||||
|
||||
You can get the last bleeding edge version of django-multiselectfield by doing a clone
|
||||
of its git repository::
|
||||
of its git repository:
|
||||
|
||||
git clone https://github.com/goinnn/django-multiselectfield
|
||||
.. code-block:: bash
|
||||
|
||||
git clone https://github.com/goinnn/django-multiselectfield
|
||||
|
||||
|
||||
Example project
|
||||
|
@ -87,7 +99,7 @@ Example project
|
|||
In the source tree, you will find a directory called `example <https://github.com/goinnn/django-multiselectfield/tree/master/example/>`_. It contains
|
||||
a readily setup project that uses django-multiselectfield. You can run it as usual:
|
||||
|
||||
::
|
||||
.. code-block:: bash
|
||||
|
||||
python manage.py syncdb --noinput
|
||||
python manage.py loaddata app_data
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
"fields": {
|
||||
"title": "My book 1",
|
||||
"tags": "sex,work,happy",
|
||||
"categories": "1,3,5"
|
||||
"categories": "1,3,5",
|
||||
"published_in": "BC,AL,AK"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9 on 2016-09-22 16:56
|
||||
# Generated by Django 1.9 on 2016-09-22 20:34
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
@ -20,7 +20,8 @@ class Migration(migrations.Migration):
|
|||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=200)),
|
||||
('categories', multiselectfield.db.fields.MultiSelectField(choices=[(1, 'Handbooks and manuals by discipline'), (2, 'Business books'), (3, 'Books of literary criticism'), (4, 'Books about literary theory'), (5, 'Books about literature')], default=1, max_length=9)),
|
||||
('tags', multiselectfield.db.fields.MultiSelectField(blank=True, choices=[('sex', 'Sex'), ('work', 'Work'), ('happy', 'Happy'), ('food', 'Food'), ('field', 'Field'), ('boring', 'Boring'), ('interesting', 'Interesting'), ('huge', 'huge'), ('nice', 'Nice')], max_length=54, null=True)),
|
||||
('tags', multiselectfield.db.fields.MultiSelectField(blank=True, choices=[('sex', 'Sex'), ('work', 'Work'), ('happy', 'Happy'), ('food', 'Food'), ('field', 'Field'), ('boring', 'Boring'), ('interesting', 'Interesting'), ('huge', 'Huge'), ('nice', 'Nice')], max_length=54, null=True)),
|
||||
('published_in', multiselectfield.db.fields.MultiSelectField(choices=[('Canada - Provinces', (('AB', 'Alberta'), ('BC', 'British Columbia'))), ('USA - States', (('AK', 'Alaska'), ('AL', 'Alabama'), ('AZ', 'Arizona')))], max_length=2, verbose_name='Province or State')),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
# along with this software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from multiselectfield import MultiSelectField
|
||||
|
||||
|
@ -27,15 +28,31 @@ CATEGORY_CHOICES = (
|
|||
)
|
||||
|
||||
TAGS_CHOICES = (
|
||||
('sex', 'Sex'),
|
||||
('work', 'Work'),
|
||||
('happy', 'Happy'),
|
||||
('food', 'Food'),
|
||||
('field', 'Field'),
|
||||
('boring', 'Boring'),
|
||||
('interesting', 'Interesting'),
|
||||
('huge', 'huge'),
|
||||
('nice', 'Nice'),
|
||||
('sex', _('Sex')),
|
||||
('work', _('Work')),
|
||||
('happy', _('Happy')),
|
||||
('food', _('Food')),
|
||||
('field', _('Field')),
|
||||
('boring', _('Boring')),
|
||||
('interesting', _('Interesting')),
|
||||
('huge', _('Huge')),
|
||||
('nice', _('Nice')),
|
||||
)
|
||||
|
||||
PROVINCES = (
|
||||
('AB', _("Alberta")),
|
||||
('BC', _("British Columbia")),
|
||||
)
|
||||
|
||||
STATES = (
|
||||
('AK', _("Alaska")),
|
||||
('AL', _("Alabama")),
|
||||
('AZ', _("Arizona")),
|
||||
)
|
||||
|
||||
PROVINCES_AND_STATES = (
|
||||
(_("Canada - Provinces"), PROVINCES),
|
||||
(_("USA - States"), STATES),
|
||||
)
|
||||
|
||||
|
||||
|
@ -47,6 +64,7 @@ class Book(models.Model):
|
|||
default=1)
|
||||
tags = MultiSelectField(choices=TAGS_CHOICES,
|
||||
null=True, blank=True)
|
||||
published_in = MultiSelectField(_("Province or State"), max_length=2, choices=PROVINCES_AND_STATES)
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
# 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/>.
|
||||
|
||||
import sys
|
||||
|
||||
from django import VERSION
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.forms.models import modelform_factory
|
||||
|
@ -24,6 +26,12 @@ from multiselectfield.utils import get_max_length
|
|||
from .models import Book
|
||||
|
||||
|
||||
if sys.version_info < (3,):
|
||||
u = unicode
|
||||
else:
|
||||
u = str
|
||||
|
||||
|
||||
if VERSION < (1, 9):
|
||||
def get_field(model, name):
|
||||
return model._meta.get_field_by_name(name)[0]
|
||||
|
@ -36,12 +44,47 @@ class MultiSelectTestCase(TestCase):
|
|||
|
||||
fixtures = ['app_data.json']
|
||||
|
||||
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
|
||||
|
||||
# Compare characters individually
|
||||
for i, chars in enumerate(zip(left, right)):
|
||||
self.assertEqual(chars[0], chars[1], msg=_msg % (i, chars[0], chars[1]))
|
||||
|
||||
def test_filter(self):
|
||||
self.assertEqual(Book.objects.filter(tags__contains='sex').count(), 1)
|
||||
self.assertEqual(Book.objects.filter(tags__contains='boring').count(), 0)
|
||||
|
||||
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']])
|
||||
|
||||
def test_form(self):
|
||||
form_class = modelform_factory(Book, fields='__all__')
|
||||
form_class = modelform_factory(Book, fields=('title', 'tags', 'categories'))
|
||||
self.assertEqual(len(form_class.base_fields), 3)
|
||||
form = form_class({'title': 'new book',
|
||||
'categories': '1,2'})
|
||||
|
@ -87,10 +130,10 @@ class MultiSelectTestCase(TestCase):
|
|||
|
||||
class MultiSelectUtilsTestCase(TestCase):
|
||||
def test_get_max_length_max_length_is_not_none(self):
|
||||
self.assertEqual(5, get_max_length([], 5))
|
||||
self.assertEqual(get_max_length([], 5), 5)
|
||||
|
||||
def test_get_max_length_max_length_is_none_and_choices_is_empty(self):
|
||||
self.assertEqual(200, get_max_length([], None))
|
||||
self.assertEqual(get_max_length([], None), 200)
|
||||
|
||||
def test_get_max_length_max_length_is_none_and_choices_is_not_empty(self):
|
||||
choices = [
|
||||
|
@ -98,4 +141,4 @@ class MultiSelectUtilsTestCase(TestCase):
|
|||
('key2', 'value2'),
|
||||
('key3', 'value3'),
|
||||
]
|
||||
self.assertEqual(14, get_max_length(choices, None))
|
||||
self.assertEqual(get_max_length(choices, None), 14)
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
import sys
|
||||
|
||||
import django
|
||||
from django import VERSION
|
||||
|
||||
from django.db import models
|
||||
from django.utils.text import capfirst
|
||||
|
@ -26,7 +26,7 @@ from ..forms.fields import MultiSelectFormField, MaxChoicesValidator
|
|||
from ..utils import get_max_length
|
||||
from ..validators import MaxValueMultiFieldValidator
|
||||
|
||||
if sys.version_info[0] == 2:
|
||||
if sys.version_info < (3,):
|
||||
string_type = unicode
|
||||
else:
|
||||
string_type = str
|
||||
|
@ -78,7 +78,7 @@ class MultiSelectField(models.CharField):
|
|||
arr_choices = self.get_choices_selected(self.get_choices_default())
|
||||
for opt_select in value:
|
||||
if (opt_select not in arr_choices):
|
||||
if django.VERSION[0] == 1 and django.VERSION[1] >= 6:
|
||||
if VERSION >= (1, 6):
|
||||
raise exceptions.ValidationError(self.error_messages['invalid_choice'] % {"value": value})
|
||||
else:
|
||||
raise exceptions.ValidationError(self.error_messages['invalid_choice'] % value)
|
||||
|
@ -143,7 +143,7 @@ class MultiSelectField(models.CharField):
|
|||
setattr(cls, 'get_%s_list' % self.name, get_list)
|
||||
setattr(cls, 'get_%s_display' % self.name, get_display)
|
||||
|
||||
if django.VERSION < (1, 8):
|
||||
if VERSION < (1, 8):
|
||||
MultiSelectField = add_metaclass(models.SubfieldBase)(MultiSelectField)
|
||||
|
||||
try:
|
||||
|
|
6
tox.ini
6
tox.ini
|
@ -3,9 +3,11 @@ envlist = py26-dj14,py27-dj14,py26-dj15,py27-dj15,py26-dj16,py27-dj16,py33-dj16,
|
|||
|
||||
[testenv]
|
||||
usedevelop = True
|
||||
setenv =
|
||||
PYTHONPATH=.
|
||||
commands =
|
||||
PYTHONPATH=. python {envbindir}/coverage run -p example/run_tests.py
|
||||
PYTHONPATH=. python {envbindir}/coverage run -p example/run_tests.py example.settings_no_debug
|
||||
python {envbindir}/coverage run -p example/run_tests.py
|
||||
python {envbindir}/coverage run -p example/run_tests.py example.settings_no_debug
|
||||
install_command =
|
||||
pip install {opts} {packages}
|
||||
|
||||
|
|
Loading…
Reference in a new issue