在Django模型中存储列表的最有效方法是什么?


146

目前,我的代码中有很多类似于以下内容的python对象:

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

现在,我想将其转换为Django模型,其中self.myName是字符串字段,而self.myFriends是字符串列表。

from django.db import models

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

由于列表是python中如此常见的数据结构,因此我希望它有一个Django模型字段。我知道我可以使用ManyToMany或OneToMany关系,但是我希望避免代码中的额外间接访问。

编辑:

我添加了这个相关问题,人们可能会发现它很有用。


1
@drozzy:好吧,我本可以使用一个不同的短语,但是基本上我的意思是,我想传递一个字符串列表并返回一个字符串列表。我不想创建一堆Friend对象,并为每个对象调用inst.myFriends.add(friendObj)。并不是所有的事情都会那么艰难,但是……
感到悲痛的是

Answers:



129

“过早的优化是万恶之源。”

牢记这一点,让我们开始吧!一旦您的应用达到特定点,对数据进行非规范化就非常普遍。正确完成后,它可以节省大量昂贵的数据库查找,但需要多做一些整理工作。

要返回一个list朋友名称,我们需要创建一个自定义Django Field类,该类在访问时将返回一个列表。

David Cramer在他的博客上发布了有关创建SeperatedValueField的指南。这是代码:

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)

此代码的逻辑处理从数据库到Python的序列化和反序列化值,反之亦然。现在,您可以轻松地导入并使用模型类中的自定义字段:

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

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

8
+1是一个很好的答案,但我们已经在做这样的事情。它实际上是将所有值压缩为一个字符串,然后将其拆分。我想我希望有一个类似ListofStringsField的东西,它实际上可以构建单独的表并自动制作外键。我不确定在Django中是否可行。如果是这样,并且找到答案,我会将其发布在stackoverflow上。
悲伤

2
如果真是这样,那么您正在寻找initcrash的django-denorm。您可以在github上找到它:github.com/initcrash/django-denorm/tree/master
jb。

3
+1。但是字符串中的逗号可能存在​​问题。从json序列化和反序列化怎么办?
sbeliakov 2014年

尝试将其添加到现有模型中 my_vals = SeparatedValuesField(blank=True, default="")但由于NULL而导致IntegrityError。默认参数不能正确传递吗?
约翰·莱曼

1
请注意,在Django 2.1 to_python中,不再在读取时调用它。因此,要进行这项工作,您需要添加: def from_db_value(self, value, expression, connection, context): return self.to_python(value)
theadriangreen '19

46

在Django中存储列表的一种简单方法是将其转换为JSON字符串,然后将其另存为模型中的Text。然后,您可以通过将(JSON)字符串转换回python列表来检索列表。这是如何做:

“列表”将存储在您的Django模型中,如下所示:

class MyModel(models.Model):
    myList = models.TextField(null=True) # JSON-serialized (text) version of your list

在您的视图/控制器代码中:

将列表存储在数据库中:

import simplejson as json # this would be just 'import json' in Python 2.7 and later
...
...

myModel = MyModel()
listIWantToStore = [1,2,3,4,5,'hello']
myModel.myList = json.dumps(listIWantToStore)
myModel.save()

从数据库中检索列表:

jsonDec = json.decoder.JSONDecoder()
myPythonList = jsonDec.decode(myModel.myList)

从概念上讲,这是正在发生的事情:

>>> myList = [1,2,3,4,5,'hello']
>>> import simplejson as json
>>> myJsonList = json.dumps(myList)
>>> myJsonList
'[1, 2, 3, 4, 5, "hello"]'
>>> myJsonList.__class__
<type 'str'>
>>> jsonDec = json.decoder.JSONDecoder()
>>> myPythonList = jsonDec.decode(myJsonList)
>>> myPythonList
[1, 2, 3, 4, 5, u'hello']
>>> myPythonList.__class__
<type 'list'>

8
不幸的是,这无助于您使用django admin来管理列表
GreenAsJade 2014年

25

如果您将Django> = 1.9与Postgres一起使用,可以利用ArrayField的优势

用于存储数据列表的字段。可以使用大多数字段类型,您只需将另一个字段实例作为base_field传递即可。您也可以指定尺寸。可以嵌套ArrayField来存储多维数组。

也可以嵌套数组字段:

from django.contrib.postgres.fields import ArrayField
from django.db import models

class ChessBoard(models.Model):
    board = ArrayField(
        ArrayField(
            models.CharField(max_length=10, blank=True),
            size=8,
        ),
        size=8,
    )

正如@ thane-brimhall所提到的,也可以直接查询元素。文档参考


2
这样做的最大好处是,您可以直接从数组字段中查询元素。
塔娜·布里姆霍尔

@ThaneBrimhall你是对的。也许我应该用此信息更新答案。谢谢
wolendranh '17

可悲的是,没有针对mysql的解决方案
Joel G Mathew

应该指出的是,这仅适用于PostGres。
theadriangreen


15

由于这是一个古老的问题,自此之后Django技术必定发生了重大变化,因此该答案反映了Django 1.4版,并且很可能适用于v 1.5。

Django默认使用关系数据库。您应该利用'em。使用ManyToManyField将友谊映射到数据库关系(外键约束)。这样做使您可以将RelatedManagers用于使用智能查询集的朋友列表。您可以使用所有可用的方法,例如filtervalues_list

使用ManyToManyField关系和属性:

class MyDjangoClass(models.Model):
    name = models.CharField(...)
    friends = models.ManyToManyField("self")

    @property
    def friendlist(self):
        # Watch for large querysets: it loads everything in memory
        return list(self.friends.all())

您可以通过以下方式访问用户的朋友列表:

joseph = MyDjangoClass.objects.get(name="Joseph")
friends_of_joseph = joseph.friendlist

但是请注意,这些关系是对称的:如果约瑟夫是鲍勃的朋友,那么鲍勃是约瑟夫的朋友。


9
class Course(models.Model):
   name = models.CharField(max_length=256)
   students = models.ManyToManyField(Student)

class Student(models.Model):
   first_name = models.CharField(max_length=256)
   student_number = models.CharField(max_length=128)
   # other fields, etc...

   friends = models.ManyToManyField('self')

8

请记住,这最终必须在关系数据库中结束。因此,使用关系确实解决此问题的常用方法。如果绝对要在对象本身中存储列表,则可以使用逗号分隔列表,然后将其存储在字符串中,然后提供将字符串拆分为列表的访问器函数。这样,您将被限制为最大数量的字符串,并且您将失去有效的查询。


3
我对将它存储为关系的数据库很好,我希望Django模型已经为我提取了这一部分。从应用程序方面,我总是想将其视为字符串列表。
感到悲伤



3

在Django模型中存储字符串列表:

class Bar(models.Model):
    foo = models.TextField(blank=True)

    def set_list(self, element):
        if self.foo:
            self.foo = self.foo + "," + element
        else:
            self.foo = element

    def get_list(self):
        if self.foo:
            return self.foo.split(",")
        else:
            None

您可以这样称呼它:

bars = Bar()
bars.set_list("str1")
bars.set_list("str2")
list = bars.get_list()
if list is not None:
    for bar in list:
        print bar
else:
    print "List is empty."      

2

我的解决方案可能是它可以帮助某人:

import json
from django.db import models


class ExampleModel(models.Model):
    _list = models.TextField(default='[]')

    @property
    def list(self):
        return json.loads(self._list)

    @list.setter
    def list(self, value):
        self._list = json.dumps(self.list + value)

1

使用一对多关系(从Friend到父类的FK)将使您的应用程序更具可伸缩性(因为您可以使用简单名称之外的其他属性来简单地扩展Friend对象)。因此这是最好的方法


3
那不是可扩展性,而是可扩展性。通常,一个是以牺牲另一个为代价的。在这种情况下,如果您知道将始终需要一个字符串列表,则可以避免昂贵的连接,从而使您的代码更具可伸缩性(即,从非规范化实现的性能更高)。
达斯汀·拉森纳

上面有几点警告:1)您知道您永远不想查询该数据; 2)存储仍然比处理能力和内存便宜(谁知道,也许量子计算会改变这种情况)
Dustin Rasener 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.