Django field that stores a list as a comma separated string

Viewed 7550

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?

0 Answers
Related