[Django]-How to create list field in django

38👍

Revisiting this with a ListField type you can use. But it makes a few of assumptions, such as the fact that you’re not storing complex types in your list. For this reason I used ast.literal_eval() to enforce that only simple, built-in types can be stored as members in a ListField:

from django.db import models
import ast

class ListField(models.TextField):
    __metaclass__ = models.SubfieldBase
    description = "Stores a python list"

    def __init__(self, *args, **kwargs):
        super(ListField, self).__init__(*args, **kwargs)

    def to_python(self, value):
        if not value:
            value = []

        if isinstance(value, list):
            return value

        return ast.literal_eval(value)

    def get_prep_value(self, value):
        if value is None:
            return value

        return unicode(value)

    def value_to_string(self, obj):
        value = self._get_val_from_obj(obj)
        return self.get_db_prep_value(value)

class Dummy(models.Model):
    mylist = ListField()

Taking it for a spin:

>>> from foo.models import Dummy, ListField
>>> d = Dummy()
>>> d.mylist
[]
>>> d.mylist = [3,4,5,6,7,8]
>>> d.mylist
[3, 4, 5, 6, 7, 8]
>>> f = ListField()
>>> f.get_prep_value(d.numbers)
u'[3, 4, 5, 6, 7, 8]'

There you have it that a list is stored in the database as a unicode string, and when pulled back out it is run through ast.literal_eval().

Previously I suggested this solution from this blog post about Custom Fields in Django:

An alternative to the CommaSeparatedIntegerField, it allows you to
store any separated values. You can also optionally specify a token
parameter.

from django.db import models

class SeparatedValuesField(models.TextField):
    __metaclass__ = models.SubfieldBase

    def __init__(self, *args, **kwargs):
        self.token = kwargs.pop('token', ',')
        super(SeparatedValuesField, self).__init__(*args, **kwargs)

    def to_python(self, value):
        if not value: return
        if isinstance(value, list):
            return value
        return value.split(self.token)

    def get_db_prep_value(self, value):
        if not value: return
        assert(isinstance(value, list) or isinstance(value, tuple))
        return self.token.join([unicode(s) for s in value])

    def value_to_string(self, obj):
        value = self._get_val_from_obj(obj)
        return self.get_db_prep_value(value)

6👍

Try using a CommaSeparatedIntegerField which is documented here: http://docs.djangoproject.com/en/1.2/ref/models/fields/#commaseparatedintegerfield

6👍

Consider django-jsonfield, the advantages are:

  • Save and load native list objects, no conversion needed
  • Well tested and mature solution
  • Could have other advantages within your project
  • Supports filtering on the database and regex (if needed)

also:

  • Cross database support
  • Supports Python 2.7 to Python 3.4 and Django 1.4 to 1.8
  • Really easy 🙂
👤oden

2👍

While jathanism’s answer is great, I was getting the following error when trying to use the dumpdata command:

Error: Unable to serialize database: get_db_prep_value() takes at least 3 arguments (2 given)

The issue is that self.get_db_prep_value call in the value_to_string method requires a connection value to be provided (at least in Django 1.4.10, which is what I am using). In the end, I didn’t really see what was being gained by calling the value_to_string method in the first place and removed it, along with the unnecessary __init__ method. This is what I ended up with:

class ListField(models.TextField):
    __metaclass__ = models.SubfieldBase
    description = "Stores a python list"

    def to_python(self, value):
        if not value:
            value = []

        if isinstance(value, list):
            return value

        converted = ast.literal_eval(value)
        if not isinstance(converted, list):
            raise ValueError('Value "%s" not a list' % converted)

        return converted

2👍

If you are using postgresql, django supports postgres with arrayfield.

1👍

I do this:

def get_comma_field(self, field):
    data = getattr(self, field)
    if data:
        return data.split(',')
    return []


def set_comma_field(self, val, field):
    if isinstance(val, types.StringTypes):
        setattr(self, field, val)
    else:
        setattr(self, field, ','.join(val))


def make_comma_field(field):
    def getter(self):
        return get_comma_field(self, field)

    def setter(self, val):
        return set_comma_field(self, val, field)

    return property(getter, setter)

class MyModel(models.Model):
    _myfield = models.CharField(max_length=31)
    myfield = make_comma_field('_myfield')

But I suppose now it might be overkill. I needed quite a few of them, which is why I wrote the make_comma_field function.

0👍

Simply, you can store the list as a string and whenever use it, use ast.literal_eval() to convert into list first from string. eg:

import ast

class MyModel(models.Model):
    field_1 = models.any kind of field()
    list_field = models.CharField(max_length=255)

    def get_list(self):
        list = ast.literal_eval(self.list_field)
        return list

same way in views etc.
When saving, make oprations on list and lastly convert it into a string by:

model.list_field = str(list)
model.save()
👤sprksh

Leave a comment