From 8c9c3a7682d83c211d3c6c7aa29646831bd47b11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Mart=C3=ADn?= Date: Tue, 26 Nov 2013 18:56:37 +0100 Subject: [PATCH] Now max_length is not required, the Multiselect field calculate it. And max_choices could be a param in the model --- example/example/app/models.py | 7 +++--- src/multiselectfield/db/fields.py | 24 ++++++++++++++++---- src/multiselectfield/forms/fields.py | 21 ++++++++--------- src/multiselectfield/utils.py | 34 ++++++++++++++++++++++++++++ src/multiselectfield/validators.py | 29 ++++++++++++++++++++++++ 5 files changed, 95 insertions(+), 20 deletions(-) create mode 100644 src/multiselectfield/utils.py create mode 100644 src/multiselectfield/validators.py diff --git a/example/example/app/models.py b/example/example/app/models.py index 628ffc5..f601d9f 100644 --- a/example/example/app/models.py +++ b/example/example/app/models.py @@ -41,9 +41,10 @@ TAGS_CHOICES = ( class Book(models.Model): title = models.CharField(max_length=200) - categories = MultiSelectField(choices=CATEGORY_CHOICES, max_length=10, - null=True, blank=True) - tags = MultiSelectField(choices=TAGS_CHOICES, max_length=10) + categories = MultiSelectField(choices=CATEGORY_CHOICES, + max_choices=3) + tags = MultiSelectField(choices=TAGS_CHOICES, + null=True, blank=True) def __str__(self): return self.title diff --git a/src/multiselectfield/db/fields.py b/src/multiselectfield/db/fields.py index d26420a..acc184c 100644 --- a/src/multiselectfield/db/fields.py +++ b/src/multiselectfield/db/fields.py @@ -21,7 +21,9 @@ from django.db import models from django.utils.text import capfirst from django.core import exceptions -from ..forms.fields import MultiSelectFormField +from ..forms.fields import MultiSelectFormField, MaxChoicesValidator +from ..utils import get_max_length +from ..validators import MaxValueMultiFieldValidator if sys.version_info[0] == 2: string = basestring @@ -48,11 +50,19 @@ def add_metaclass(metaclass): class MultiSelectField(models.CharField): """ Choice values can not contain commas. """ + 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) + if self.max_choices is not None: + self.validators.append(MaxChoicesValidator(self.max_choices)) + def get_choices_default(self): return self.get_choices(include_blank=False) - def get_choices_selected(self, arr_choices=''): - if not arr_choices: + def get_choices_selected(self, arr_choices=None): + if arr_choices is None: return False list = [] for choice_selected in arr_choices: @@ -75,8 +85,12 @@ class MultiSelectField(models.CharField): return def formfield(self, **kwargs): - defaults = {'required': not self.blank, 'label': capfirst(self.verbose_name), - 'help_text': self.help_text, 'choices': self.choices} + defaults = {'required': not self.blank, + 'label': capfirst(self.verbose_name), + 'help_text': self.help_text, + 'choices': self.choices, + 'max_length': self.max_length, + 'max_choices': self.max_choices} if self.has_default(): defaults['initial'] = self.get_default() defaults.update(kwargs) diff --git a/src/multiselectfield/forms/fields.py b/src/multiselectfield/forms/fields.py index 23e4491..db3fd1b 100644 --- a/src/multiselectfield/forms/fields.py +++ b/src/multiselectfield/forms/fields.py @@ -15,22 +15,19 @@ # along with this programe. If not, see . from django import forms -from django.contrib.humanize.templatetags.humanize import apnumber -from django.template.defaultfilters import pluralize + +from ..utils import get_max_length +from ..validators import MaxValueMultiFieldValidator, MaxChoicesValidator class MultiSelectFormField(forms.MultipleChoiceField): widget = forms.CheckboxSelectMultiple def __init__(self, *args, **kwargs): - self.max_choices = kwargs.pop('max_choices', 0) + self.max_choices = kwargs.pop('max_choices', None) + self.max_length = kwargs.pop('max_length', None) super(MultiSelectFormField, self).__init__(*args, **kwargs) - - def clean(self, value): - if not value and self.required: - raise forms.ValidationError(self.error_messages['required']) - if value and self.max_choices and len(value) > self.max_choices: - raise forms.ValidationError('You must select a maximum of %s choice%s.' - % (apnumber(self.max_choices), - pluralize(self.max_choices))) - return value + self.max_length = get_max_length(self.choices, self.max_length) + self.validators.append(MaxValueMultiFieldValidator(self.max_length)) + if self.max_choices is not None: + self.validators.append(MaxChoicesValidator(self.max_choices)) diff --git a/src/multiselectfield/utils.py b/src/multiselectfield/utils.py new file mode 100644 index 0000000..1f9e8c6 --- /dev/null +++ b/src/multiselectfield/utils.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2013 by Pablo Martín +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# 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 + string_type = unicode +else: + string = str + string_type = string + + +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])) + else: + return default + return max_length diff --git a/src/multiselectfield/validators.py b/src/multiselectfield/validators.py new file mode 100644 index 0000000..bd45332 --- /dev/null +++ b/src/multiselectfield/validators.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2013 by Pablo Martín +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this programe. If not, see . + + +from django.core import validators +from django.utils.translation import ugettext_lazy as _ + + +class MaxValueMultiFieldValidator(validators.MaxLengthValidator): + clean = lambda self, x: len(','.join(x)) + code = 'max_multifield_value' + + +class MaxChoicesValidator(validators.MaxLengthValidator): + message = _(u'You must select a maximum of %(limit_value)d choices.') + code = 'max_choices'