Add test for .values_list()

This commit is contained in:
Drew Hubl 2016-09-22 14:12:56 -06:00
parent 67e47d24a8
commit d997a7a422
7 changed files with 114 additions and 37 deletions

View file

@ -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

View file

@ -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"
}
},
{

View file

@ -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')),
],
),
]

View file

@ -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

View file

@ -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)

View file

@ -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:

View file

@ -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}