Merge pull request #52 from goinnn/min-choices

Min choices field option
This commit is contained in:
blag 2017-01-01 21:15:38 -07:00 committed by GitHub
commit 5d02ceb6ba
16 changed files with 124 additions and 74 deletions

View file

@ -7,52 +7,60 @@ python:
- "3.4"
- "3.5"
env:
- DJANGO=1.4.22
- DJANGO=1.5.12
- DJANGO=1.6.11
- DJANGO=1.7.11
- DJANGO=1.8.14
- DJANGO=1.9.9
- DJANGO=1.10.1
- 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='https://github.com/django/django/archive/master.tar.gz'
matrix:
exclude:
- python: "2.6"
env: DJANGO=1.7.11
env: DJANGO_VERSION='Django>=1.7,<1.8'
- python: "2.6"
env: DJANGO=1.8.14
env: DJANGO_VERSION='Django>=1.8,<1.9'
- python: "2.6"
env: DJANGO=1.9.9
env: DJANGO_VERSION='Django>=1.9,<1.10'
- python: "2.6"
env: DJANGO=1.10.1
env: DJANGO_VERSION='Django>=1.10,<1.11'
- python: "2.6"
env: DJANGO_VERSION='https://github.com/django/django/archive/master.tar.gz'
- python: "3.3"
env: DJANGO=1.4.22
env: DJANGO_VERSION='Django>=1.4,<1.5'
- python: "3.3"
env: DJANGO_VERSION='Django>=1.5,<1.6'
- python: "3.3"
env: DJANGO_VERSION='Django>=1.9,<1.10'
- python: "3.3"
env: DJANGO_VERSION='Django>=1.10,<1.11'
- python: "3.3"
env: DJANGO_VERSION='https://github.com/django/django/archive/master.tar.gz'
- python: "3.4"
env: DJANGO=1.4.22
- python: "3.5"
env: DJANGO=1.4.22
- python: "3.3"
env: DJANGO=1.5.12
env: DJANGO_VERSION='Django>=1.4,<1.5'
- python: "3.4"
env: DJANGO=1.5.12
- python: "3.5"
env: DJANGO=1.5.12
env: DJANGO_VERSION='Django>=1.5,<1.6'
- python: "3.4"
env: DJANGO=1.6.11
env: DJANGO_VERSION='Django>=1.6,<1.7'
- python: "3.5"
env: DJANGO=1.6.11
env: DJANGO_VERSION='Django>=1.4,<1.5'
- python: "3.5"
env: DJANGO=1.7.11
- python: "3.3"
env: DJANGO=1.9.9
- python: "3.3"
env: DJANGO=1.10.1
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'
allow_failures:
- env: DJANGO_VERSION='https://github.com/django/django/archive/master.tar.gz'
install:
- pip install -q Django==$DJANGO
- pip install tox coveralls
- pip install -q $DJANGO_VERSION
- pip install tox coveralls flake8
script:
- coverage erase
- if [[ $(python -c 'import sys; print("0" if sys.version_info < (2, 7) else "1")') == "1" ]]; then flake8 --ignore=E501; fi
- PYTHONPATH=. coverage run -p example/run_tests.py
- PYTHONPATH=. coverage run -p example/run_tests.py example.settings_no_debug
after_success:

View file

@ -22,4 +22,5 @@ from .models import Book
class BookAdmin(admin.ModelAdmin):
pass
admin.site.register(Book, BookAdmin)

View file

@ -28,15 +28,15 @@ CATEGORY_CHOICES = (
)
TAGS_CHOICES = (
('sex', _('Sex')),
('work', _('Work')),
('happy', _('Happy')),
('food', _('Food')),
('field', _('Field')),
('boring', _('Boring')),
('interesting', _('Interesting')),
('huge', _('Huge')),
('nice', _('Nice')),
('sex', _('Sex')), # noqa: E241
('work', _('Work')), # noqa: E241
('happy', _('Happy')), # noqa: E241
('food', _('Food')), # noqa: E241
('field', _('Field')), # noqa: E241
('boring', _('Boring')), # noqa: E241
('interesting', _('Interesting')), # noqa: E241
('huge', _('Huge')), # noqa: E241
('nice', _('Nice')), # noqa: E241
)
PROVINCES = (
@ -52,7 +52,7 @@ STATES = (
PROVINCES_AND_STATES = (
(_("Canada - Provinces"), PROVINCES),
(_("USA - States"), STATES),
(_("USA - States"), STATES), # noqa: E241
)
@ -60,7 +60,7 @@ class Book(models.Model):
title = models.CharField(max_length=200)
categories = MultiSelectField(choices=CATEGORY_CHOICES,
max_choices=3,
#default='1,5')
# default='1,5')
default=1)
tags = MultiSelectField(choices=TAGS_CHOICES,
null=True, blank=True)

View file

@ -27,7 +27,7 @@ from .models import Book, PROVINCES, STATES, PROVINCES_AND_STATES
if sys.version_info < (3,):
u = unicode
u = unicode # noqa: F821
else:
u = str
@ -129,7 +129,7 @@ class MultiSelectTestCase(TestCase):
self.assertEqual(get_field(Book, '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(get_field(Book, 'published_in').flatchoices, list(PROVINCES + STATES))
def test_named_groups(self):
self.assertEqual(get_field(Book, 'published_in').choices, PROVINCES_AND_STATES)

View file

@ -35,6 +35,7 @@ except ImportError: # Django < 1.4
from .views import app_index
urlpatterns = patterns('',
urlpatterns = patterns(
'',
url(r'^$', app_index, name='app_index'),
)

View file

@ -32,7 +32,7 @@ MANAGERS = ADMINS
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
'NAME': 'example.db', # Or path to database file if using sqlite3.
# The following settings are not used with sqlite3:
'USER': '',
@ -100,7 +100,7 @@ STATICFILES_DIRS = (
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
)
# Make this unique, and don't share it with anybody.
@ -128,7 +128,7 @@ if VERSION < (1, 8):
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
# 'django.template.loaders.eggs.Loader',
# 'django.template.loaders.eggs.Loader',
)
TEMPLATE_DIRS = (
@ -191,13 +191,13 @@ INSTALLED_APPS = (
# If formadmin is installed
from django.conf import ENVIRONMENT_VARIABLE
from django.conf import ENVIRONMENT_VARIABLE # noqa: E402
# I check it if formadmin is installed of this way because if I execute
# python manage.py runserver --settings=settings_no_debug
# I get an error
if os.environ[ENVIRONMENT_VARIABLE] == 'example.settings':
try:
import formadmin
import formadmin # noqa: F401
INSTALLED_APPS += ('formadmin',)
except ImportError:
pass

View file

@ -1,4 +1,4 @@
from example.settings import *
from example.settings import * # noqa: F401,F403
DEBUG = False
TEMPLATE_DEBUG = DEBUG

View file

@ -42,12 +42,14 @@ js_info_dict = {
'packages': ('django.conf',),
}
urlpatterns = patterns('',
urlpatterns = patterns(
'',
url(r'^', include('app.urls')),
url(r'^admin/', include(admin.site.urls)),
)
urlpatterns += patterns('',
urlpatterns += patterns(
'',
url(r'^%s(?P<path>.*)$' % settings.MEDIA_URL[1:],
serve,
{'document_root': settings.MEDIA_ROOT, 'show_indexes': True}),

View file

@ -15,16 +15,17 @@ framework.
"""
import os
# This application object is used by any WSGI server configured to use this
# file. This includes Django's development server, if the WSGI_APPLICATION
# setting points here.
from django.core.wsgi import get_wsgi_application
# We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks
# if running multiple sites in the same mod_wsgi process. To fix this, use
# mod_wsgi daemon mode with each site in its own daemon process, or use
# os.environ["DJANGO_SETTINGS_MODULE"] = "test_project.settings"
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings")
# This application object is used by any WSGI server configured to use this
# file. This includes Django's development server, if the WSGI_APPLICATION
# setting points here.
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
# Apply WSGI middleware here.

View file

@ -1,2 +1,2 @@
from multiselectfield.db.fields import MultiSelectField
from multiselectfield.forms.fields import MultiSelectFormField
from multiselectfield.db.fields import MultiSelectField # noqa: F401
from multiselectfield.forms.fields import MultiSelectFormField # noqa: F401

View file

@ -22,12 +22,12 @@ from django.db import models
from django.utils.text import capfirst
from django.core import exceptions
from ..forms.fields import MultiSelectFormField, MaxChoicesValidator
from ..forms.fields import MultiSelectFormField, MinChoicesValidator, MaxChoicesValidator
from ..utils import get_max_length
from ..validators import MaxValueMultiFieldValidator
if sys.version_info < (3,):
string_type = unicode
string_type = unicode # noqa: F821
else:
string_type = str
@ -50,10 +50,13 @@ class MultiSelectField(models.CharField):
""" Choice values can not contain commas. """
def __init__(self, *args, **kwargs):
self.min_choices = kwargs.pop('min_choices', None)
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)
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))
@ -145,6 +148,7 @@ class MultiSelectField(models.CharField):
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)

View file

@ -17,13 +17,14 @@
from django import forms
from ..utils import get_max_length
from ..validators import MaxValueMultiFieldValidator, MaxChoicesValidator
from ..validators import MaxValueMultiFieldValidator, MinChoicesValidator, MaxChoicesValidator
class MultiSelectFormField(forms.MultipleChoiceField):
widget = forms.CheckboxSelectMultiple
def __init__(self, *args, **kwargs):
self.min_choices = kwargs.pop('min_choices', None)
self.max_choices = kwargs.pop('max_choices', None)
self.max_length = kwargs.pop('max_length', None)
super(MultiSelectFormField, self).__init__(*args, **kwargs)
@ -31,3 +32,5 @@ class MultiSelectFormField(forms.MultipleChoiceField):
self.validators.append(MaxValueMultiFieldValidator(self.max_length))
if self.max_choices is not None:
self.validators.append(MaxChoicesValidator(self.max_choices))
if self.min_choices is not None:
self.validators.append(MinChoicesValidator(self.min_choices))

View file

@ -18,8 +18,8 @@ import sys
if sys.version_info[0] == 2:
string = basestring
string_type = unicode
string = basestring # noqa: F821
string_type = unicode # noqa: F821
else:
string = str
string_type = string

View file

@ -20,9 +20,16 @@ from django.utils.translation import ugettext_lazy as _
class MaxValueMultiFieldValidator(validators.MaxLengthValidator):
clean = lambda self, x: len(','.join(x))
code = 'max_multifield_value'
def clean(self, x):
return len(','.join(x))
class MinChoicesValidator(validators.MinLengthValidator):
message = _(u'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.')

View file

@ -23,6 +23,7 @@ from setuptools import setup, find_packages
def read(*rnames):
return open(os.path.join(os.path.dirname(__file__), *rnames)).read()
setup(
name="django-multiselectfield",
version="0.1.4",
@ -51,6 +52,7 @@ setup(
'django>=1.4',
'tox',
'coverage',
'flake8',
],
install_requires=[
'django>=1.4',

43
tox.ini
View file

@ -1,11 +1,12 @@
[tox]
envlist = py26-dj14,py27-dj14,py26-dj15,py27-dj15,py26-dj16,py27-dj16,py33-dj16,py27-dj17,py33-dj17,py34-dj17,py27-dj18,py33-dj18,py34-dj18,py35-dj18,py27-dj19,py34-dj19,py35-dj19,py27-dj110,py34-dj110,py35-dj110
envlist = py26-dj14,py26-dj15,py26-dj16,py27-dj14,py27-dj15,py27-dj16,py27-dj17,py27-dj18,py27-dj19,py27-dj110,py33-dj16,py33-dj17,py33-dj18,py34-dj17,py34-dj18,py34-dj19,py34-dj110,py35-dj18,py35-dj19,py35-dj110
[testenv]
usedevelop = True
setenv =
PYTHONPATH=.
commands =
python {envbindir}/flake8 --ignore=E501
python {envbindir}/coverage run -p example/run_tests.py
python {envbindir}/coverage run -p example/run_tests.py example.settings_no_debug
install_command =
@ -18,6 +19,7 @@ deps =
pillow==1.7.8
PyYAML==3.10
coveralls==0.3
flake8
[testenv:py26-dj15]
basepython = python2.6
@ -26,6 +28,7 @@ deps =
pillow==1.7.8
PyYAML==3.10
coveralls==0.3
flake8
[testenv:py26-dj16]
basepython = python2.6
@ -34,6 +37,7 @@ deps =
pillow==1.7.8
PyYAML==3.10
coveralls==0.3
flake8
[testenv:py27-dj14]
@ -43,6 +47,7 @@ deps =
pillow==1.7.8
PyYAML==3.10
coveralls==0.3
flake8
[testenv:py27-dj15]
basepython = python2.7
@ -51,6 +56,7 @@ deps =
pillow==1.7.8
PyYAML==3.10
coveralls==0.3
flake8
[testenv:py27-dj16]
basepython = python2.7
@ -59,6 +65,7 @@ deps =
pillow==1.7.8
PyYAML==3.10
coveralls==0.3
flake8
[testenv:py27-dj17]
basepython = python2.7
@ -67,30 +74,34 @@ deps =
pillow==1.7.8
PyYAML==3.10
coveralls==0.3
flake8
[testenv:py27-dj18]
basepython = python2.7
deps =
django==1.8.14
django==1.8.17
pillow==1.7.8
PyYAML==3.10
coveralls==0.3
flake8
[testenv:py27-dj19]
basepython = python2.7
deps =
django==1.9.9
django==1.9.12
pillow==1.7.8
PyYAML==3.10
coveralls==0.3
flake8
[testenv:py27-dj110]
basepython = python2.7
deps =
django==1.10.1
django==1.10.4
pillow==1.7.8
PyYAML==3.10
coveralls==0.3
flake8
[testenv:py33-dj16]
@ -100,6 +111,7 @@ deps =
pillow==2.1.0
PyYAML==3.10
coveralls==0.3
flake8
[testenv:py33-dj17]
basepython = python3.3
@ -108,14 +120,16 @@ deps =
pillow==2.1.0
PyYAML==3.10
coveralls==0.3
flake8
[testenv:py33-dj18]
basepython = python3.3
deps =
django==1.8.14
django==1.8.17
pillow==2.1.0
PyYAML==3.10
coveralls==0.3
flake8
[testenv:py34-dj17]
@ -125,52 +139,59 @@ deps =
pillow==2.1.0
PyYAML==3.10
coveralls==0.3
flake8
[testenv:py34-dj18]
basepython = python3.4
deps =
django==1.8.14
django==1.8.17
pillow==2.1.0
PyYAML==3.10
coveralls==0.3
flake8
[testenv:py34-dj19]
basepython = python3.4
deps =
django==1.9.9
django==1.9.12
pillow==2.1.0
PyYAML==3.10
coveralls==0.3
flake8
[testenv:py34-dj110]
basepython = python3.4
deps =
django==1.10.1
django==1.10.4
pillow==2.1.0
PyYAML==3.10
coveralls==0.3
flake8
[testenv:py35-dj18]
basepython = python3.5
deps =
django==1.8.14
django==1.8.17
pillow==2.1.0
PyYAML==3.10
coveralls==0.3
flake8
[testenv:py35-dj19]
basepython = python3.5
deps =
django==1.9.9
django==1.9.12
pillow==2.1.0
PyYAML==3.10
coveralls==0.3
flake8
[testenv:py35-dj110]
basepython = python3.5
deps =
django==1.10.1
django==1.10.4
pillow==2.1.0
PyYAML==3.10
coveralls==0.3
flake8