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

Posted

技术标签:

【中文标题】在 Django 模型中存储列表的最有效方法是啥?【英文标题】:What is the most efficient way to store a list in the Django models?在 Django 模型中存储列表的最有效方法是什么? 【发布时间】:2010-11-09 18:09:48 【问题描述】:

目前我的代码中有很多 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 关系,但我希望在代码中避免这种额外的间接性。

编辑:

我添加了这个related question,人们可能会觉得它有用。

【问题讨论】:

@drozzy:好吧,我可能会使用不同的短语,但基本上我的意思是,我想传入一个字符串列表并返回一个字符串列表。我不想创建一堆 Friend 对象,并为每个对象调用 inst.myFriends.add(friendObj) 。并不是说它会那么难,但是...... 【参考方案1】:

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

牢记这一点,让我们这样做!一旦您的应用程序达到某个点,非规范化数据就很常见了。如果处理得当,它可以节省大量昂贵的数据库查找,但需要多做一些内务处理。

要返回好友名称的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()

【讨论】:

+1 以获得很好的答案,但我们已经在做类似的事情了。它实际上是将所有值压缩到一个字符串中,然后将它们拆分出来。我想我希望有更像 ListofStringsField 的东西,它实际上构建了单独的表并自动生成外键。我不确定这在 Django 中是否可行。如果是,并且我找到了答案,我会将其发布在 *** 上。 如果是这种情况,那么您正在寻找 initcrash 的 django-denorm。你会在 github 上找到它:github.com/initcrash/django-denorm/tree/master +1。但是字符串中的逗号可能存在​​问题。从 json 序列化和反序列化呢? 尝试使用my_vals = SeparatedValuesField(blank=True, default="") 将其添加到现有模型中,但由于 NULL 导致 IntegrityError。默认参数是否没有正确传递? 请注意,在 Django 2.1 中,to_python 不再在读取时被调用。因此,要完成这项工作,您需要添加:def from_db_value(self, value, expression, connection, context): return self.to_python(value)【参考方案2】:

这种关系不是更好地表达为与Friends 表的一对多外键关系吗?我知道myFriends 只是字符串,但我认为更好的设计是创建一个Friend 模型并让MyClass 包含与结果表的外键关系。

【讨论】:

这可能是我最终会做的事情,但我真的希望能够内置它的底层结构。我想我太懒了。 优雅,最精美的解释。 见docs.djangoproject.com/en/3.0/topics/db/examples/many_to_one【参考方案3】:

在 Django 中存储列表的一种简单方法是将其转换为 JSON 字符串,然后将其保存为模型中的文本。然后,您可以通过将 (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'>

【讨论】:

不幸的是,这并不能帮助您使用 django admin 管理列表【参考方案4】:

如果你使用 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 提到的,也可以直接查询元素。文档reference

【讨论】:

这样做的一大好处是可以直接从数组字段中查询元素。 @ThaneBrimhall 你是对的。也许我应该用这些信息更新答案。谢谢 遗憾的是,mysql没有解决方案 需要说明的是,这只适用于 PostGres。 Django 1.8 也有 ArrayField:docs.djangoproject.com/en/1.8/ref/contrib/postgres/fields【参考方案5】:

由于这是一个老问题,而且 Django 技术肯定已经发生了重大变化,因此这个答案反映了 Django 版本 1.4,并且很可能适用于 v 1.5。

Django 默认使用关系数据库;你应该利用它们。使用 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

但是请注意,这些关系是对称的:如果 Joseph 是 Bob 的朋友,那么 Bob 是 Joseph 的朋友。

【讨论】:

【参考方案6】:
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')

【讨论】:

【参考方案7】:

请记住,这最终必须在关系数据库中结束。所以使用关系真的解决这个问题的常用方法。如果您绝对坚持将列表存储在对象本身中,则可以将其例如以逗号分隔,并将其存储在字符串中,然后提供将字符串拆分为列表的访问器函数。这样一来,您将受到最大字符串数的限制,并且您将失去高效的查询。

【讨论】:

我对数据库将其存储为关系很好,我希望 Django 模型已经为我抽象了这部分。在应用程序方面,我总是希望将其视为字符串列表。【参考方案8】:

如果你使用的是 postgres,你可以使用类似这样的东西:

class ChessBoard(models.Model):

    board = ArrayField(
        ArrayField(
            models.CharField(max_length=10, blank=True),
            size=8,
        ),
        size=8,
    )

如果您需要更多详细信息,可以阅读以下链接: https://docs.djangoproject.com/pt-br/1.9/ref/contrib/postgres/fields/

【讨论】:

【参考方案9】:

因为 2021 年这篇文章在谷歌搜索结果中排名第一。 这几天存在很好的优雅解决方案

MySql Docs

PostgreSQL

from django.db.models import CharField, Model
from django_mysql.models import ListCharField

class Person(Model):
    name = CharField()
    post_nominals = ListCharField(
        base_field=CharField(max_length=10),
        size=6,
        max_length=(6 * 11)  # 6 * 10 character nominals, plus commas
    )

表格

from django.db.models import IntegerField, Model
from django_mysql.models import ListTextField

class Widget(Model):
    widget_group_ids = ListTextField(
        base_field=IntegerField(),
        size=100,  # Maximum of 100 ids in list
    )

查询

>>> Person.objects.create(name='Horatio', post_nominals=['PhD', 'Esq.', 'III'])
>>> Person.objects.create(name='Severus', post_nominals=['PhD', 'DPhil'])
>>> Person.objects.create(name='Paulus', post_nominals=[])

>>> Person.objects.filter(post_nominals__contains='PhD')
[<Person: Horatio>, <Person: Severus>]

>>> Person.objects.filter(post_nominals__contains='Esq.')
[<Person: Horatio>]

>>> Person.objects.filter(post_nominals__contains='DPhil')
[<Person: Severus>]

>>> Person.objects.filter(Q(post_nominals__contains='PhD') & Q(post_nominals__contains='III'))
[<Person: Horatio>]

【讨论】:

【参考方案10】:

在 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(",")
        return None

你可以这样称呼它:

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

【讨论】:

【参考方案11】:

您可以使用 Django Pickle Field 存储几乎任何对象,就像这个 sn-p:

http://www.djangosnippets.org/snippets/513/

【讨论】:

不,不是。阅读 sn-p 的描述。 新的pypi.python.org/pypi/django-picklefield 执行相同的任务(也独立于数据库)【参考方案12】:

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

【讨论】:

这不是可扩展性,这是可扩展性。通常一个是以另一个为代价的。在这种情况下,如果您知道您将始终需要一个字符串列表,您可以避免昂贵的连接,从而使您的代码更具可扩展性(即非规范化的性能更高)。 上面有几个警告:1) 你知道你永远不想查询那些数据,2) 存储仍然比处理能力和内存便宜(谁知道呢,也许这会随着量子计算而改变)【参考方案13】:

由于 ListCharField 是 CharField 的子类,因此也可以设置任何 CharField 选项。最重要的是,您需要设置 max_length 以确定要在数据库中保留多少字符。

实例化示例:

from django.db.models import CharField, Model
from django_mysql.models import ListCharField


class Person(Model):
    name = CharField()
    post_nominals = ListCharField(
        base_field=CharField(max_length=10),
        size=6,
        max_length=(6 * 11),  # 6 * 10 character nominals, plus commas
    )

【讨论】:

以上是关于在 Django 模型中存储列表的最有效方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

在数据库中存储标签的最有效方法是啥?

在 c# 中拥有一个采用任何类型的可索引列表的方法的最有效方法是啥

将数千条记录插入表中的最有效方法是啥(MySQL,Python,Django)

在 SQLCe 数据库中存储对象的最有效方法是啥?

在 Marklogic 数据库中存储名称/值对的最有效方法是啥

在 C 中存储和使用具有 1,000,000 位有效数字的浮点数的最有效方法是啥?