What is the most efficient way to store a list in the Django models?


Question

Currently I have a lot of python objects in my code similar to the following:

class MyClass():
  def __init__(self, name, friends):
      self.myName = name
      self.myFriends = [str(x) for x in friends]

Now I want to turn this into a Django model, where self.myName is a string field, and self.myFriends is a list of strings.

from django.db import models

class myDjangoModelClass():
    myName = models.CharField(max_length=64)
    myFriends = ??? # what goes here?

Since the list is such a common data structure in python, I sort of expected there to be a Django model field for it. I know I can use a ManyToMany or OneToMany relationship, but I was hoping to avoid that extra indirection in the code.

Edit:

I added this related question, which people may find useful.

1
119
9/6/2017 4:01:35 PM

Accepted Answer

Would this relationship not be better expressed as a one-to-many foreign key relationship to a Friends table? I understand that myFriends are just strings but I would think that a better design would be to create a Friend model and have MyClass contain a foreign key realtionship to the resulting table.

61
2/17/2017 1:19:28 PM

"Premature optimization is the root of all evil."

With that firmly in mind, let's do this! Once your apps hit a certain point, denormalizing data is very common. Done correctly, it can save numerous expensive database lookups at the cost of a little more housekeeping.

To return a list of friend names we'll need to create a custom Django Field class that will return a list when accessed.

David Cramer posted a guide to creating a SeperatedValueField on his blog. Here is the code:

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)

The logic of this code deals with serializing and deserializing values from the database to Python and vice versa. Now you can easily import and use our custom field in the model class:

from django.db import models
from custom.fields import SeparatedValuesField 

class Person(models.Model):
    name = models.CharField(max_length=64)
    friends = SeparatedValuesField()

Licensed under: CC-BY-SA with attribution
Not affiliated with: Stack Overflow
Icon