Daniel Roseman's snippet, updated will all fixes mentioned in the comments of the first version + some other things to make it work under Django 1.4.
South, and dumpdata are working.
There's an ugly int(....) at the validate function in order to cast each value as an integer before comparing it to default choices : I needed this, but if you're storing strings values, just remove the int(......) wrapper.
Orginal readme
Usually you want to store multiple choices as a manytomany link to another table. Sometimes however it is useful to store them in the model itself. This field implements a model field and an accompanying formfield to store multiple choices as a comma-separated list of values, using the normal CHOICES attribute.
You'll need to set maxlength long enough to cope with the maximum number of choices, plus a comma for each.
The normal get_FOO_display() method returns a comma-delimited string of the expanded values of the selected choices.
The formfield takes an optional max_choices parameter to validate a maximum number of choices.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 | # New version of this snippet http://djangosnippets.org/snippets/1200/
# tested with Django 1.4
from django import forms
from django.db import models
from django.utils.text import capfirst
from django.core import exceptions
class MultiSelectFormField(forms.MultipleChoiceField):
widget = forms.CheckboxSelectMultiple
def __init__(self, *args, **kwargs):
self.max_choices = kwargs.pop('max_choices', 0)
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
class MultiSelectField(models.Field):
__metaclass__ = models.SubfieldBase
def get_internal_type(self):
return "CharField"
def get_choices_default(self):
return self.get_choices(include_blank=False)
def _get_FIELD_display(self, field):
value = getattr(self, field.attname)
choicedict = dict(field.choices)
def formfield(self, **kwargs):
# don't call super, as that overrides default widget if it has choices
defaults = {'required': not self.blank, 'label': capfirst(self.verbose_name),
'help_text': self.help_text, 'choices': self.choices}
if self.has_default():
defaults['initial'] = self.get_default()
defaults.update(kwargs)
return MultiSelectFormField(**defaults)
def get_prep_value(self, value):
return value
def get_db_prep_value(self, value, connection=None, prepared=False):
if isinstance(value, basestring):
return value
elif isinstance(value, list):
return ",".join(value)
def to_python(self, value):
if value is not None:
return value if isinstance(value, list) else value.split(',')
return ''
def contribute_to_class(self, cls, name):
super(MultiSelectField, self).contribute_to_class(cls, name)
if self.choices:
func = lambda self, fieldname = name, choicedict = dict(self.choices): ",".join([choicedict.get(value, value) for value in getattr(self, fieldname)])
setattr(cls, 'get_%s_display' % self.name, func)
def validate(self, value, model_instance):
arr_choices = self.get_choices_selected(self.get_choices_default())
for opt_select in value:
if (int(opt_select) not in arr_choices): # the int() here is for comparing with integer choices
raise exceptions.ValidationError(self.error_messages['invalid_choice'] % value)
return
def get_choices_selected(self, arr_choices=''):
if not arr_choices:
return False
list = []
for choice_selected in arr_choices:
list.append(choice_selected[0])
return list
def value_to_string(self, obj):
value = self._get_val_from_obj(obj)
return self.get_db_prep_value(value)
# needed for South compatibility
from south.modelsinspector import add_introspection_rules
add_introspection_rules([], ["^coop\.utils\.fields\.MultiSelectField"])
# Example Model
TYPES = (1, 'Product'),
(2, 'Service'),
(3, 'Skill'),
(4, 'Partnership')),
(5, 'Question'),
)
class Exchange(models.Model):
types = MultiSelectField(max_length=250, blank=True, choices=TYPES)
...
# Example Form
class ExchangeForm(forms.Form):
types = MultiSelectFormField(choices=TYPES)
...
|
More like this
- Template tag - list punctuation for a list of items by shapiromatron 10 months, 2 weeks ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 3 weeks ago
- Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
- Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
- Help text hyperlinks by sa2812 1 year, 7 months ago
Comments
Thanks for the snippet. Some thoughts:
-Inherit from CharField or else south won't properly inspect. -Also above, max_length missing error will be obscure -Remove checkbox widget as default
#
Thanks for the update. Suggestion: change the base from CharField to TextField and the delimiter from comma to newline (\n).
That way you can use commas in your values, don't have to care about max_length, and it's also more readable when looking at raw data.
(I allow for dynamic choices which get configured in a "Choice" model's TextField attribute - one line per choice - that goes together well and makes it easy to edit values through the admin)
#
Whenever I save a form with this field in it, it is in the form.changed_data list. How would I stop this from happening?
#
Can you help me? How i can store value of multiple select as "1;0;0;1;0", and not as now "1;4"?
#
Does this snippet work for 1.5, i'm currently having problems with the getattr line within _get_FIELD_display. any thoughts?
#
I created a package with this code, and I did some improvements:
https://github.com/goinnn/django-multiselectfield/blob/master/CHANGES.rst
#
Such a useful snippet - no need for m2m model just to store static data for your field choices. One improvement - use list comprehension for get_choices_selected, faster:
#
Please login first before commenting.