I'm looking for a solution to store a Python list as a comma-separated string in the database. After doing some research and concluding that Django does not come with something like this out of the box, I decided to write this myself.
This is the model field I've come up with:
class CommaSeparatedStringsField(models.CharField):
def from_db_value(self, value, *args):
if not value:
return []
return value.split(',')
def to_python(self, value):
if isinstance(value, list):
return value
return self.from_db_value(value)
def get_prep_value(self, value):
return ','.join(value)
def value_to_string(self, obj):
return self.get_prep_value(self.value_from_object(obj))
def formfield(self, **kwargs):
defaults = {'form_class': ListField}
defaults.update(kwargs)
return models.Field.formfield(self, **defaults)
Pretty simple stuff, and so far, no problems.
The form field and widget associated with this model field should be a multiple select box. Every element in the list is an option in the select field.
In case you're wondering why I want this this way, it's for Bootstrap Tags Input, a input widget for entering 'tags' (multiple values). This can work using a textbox but I'd rather represent this field in Python as a list.
Now there's MultipleChoiceField (which uses the MultipleSelect widget) but that has a pre-defined list of options. That's not what I want, in my case the options are the data, and I don't care about the selected option(s).
So I wrote a custom form field and widget that subclass the above-mentioned classes:
class ListWidget(SelectMultiple):
def render_options(self, options):
output = []
for option_value, option_label in options:
output.append(self.render_option([], option_value, option_label))
return '\n'.join(output)
class ListField(MultipleChoiceField):
widget = ListWidget
def prepare_value(self, value):
return [(x, x) for x in value]
I overrode SelectMultiple's render_options() to render the data as options instead of the pre-defined choices, and I overrode MultipleChoiceField's validation to remove the choices check and prepare_value to turn the list into a list of tuples (of option_value and option_label).
This works, for the most part. The problem is that it doesn't seem to save the field when it is submitted empty, and I don't understand why. This also happens when I use MultipleChoiceField itself instead of the custom form field and widget I wrote.
I tried to narrow this down using the debugger but I can't find what exactly is happening here. I can see that the form's cleaned data has the correct value ([], empty list), but when the field is saved, it has the old value.
What am I overlooking here?