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)を呼び出します。それほど難しいことではありませんが、...
悲しみ

回答:


77

このリレーションシップは、Friendsテーブルとの1対多の外部キーリレーションシップとして適切に表現されませんか?myFriends単なる文字列であることは理解していますが、Friendモデルを作成MyClassし、結果のテーブルへの外部キーの関係を含める方が良いデザインだと思います。


15
これはおそらく私が最終的に行うことですが、これの基礎となる構造が組み込まれることを本当に望んでいました。私は怠惰になっていると思います。
悲しむ

エレガントで最も美しく表現されています。
Tessaracter


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して正解ですが、すでにこのようなことを行っています。これは、実際にすべての値を1つの文字列に圧縮してから、それらを分割することです。実際には別のテーブルを構築して外部キーを自動的に作成するListofStringsFieldのようなものを期待していたと思います。Djangoでそれが可能かどうかはわかりません。そうであれば、答えを見つけたら、stackoverflowに投稿します。
悲しむ

2
その場合は、initcrashのdjango-denormを探しています。github:github.com/initcrash/django-denorm/tree/master
jbにあります。

3
+1。しかし、文字列内のコンマで起こりうる問題。jsonからのシリアライズとデシリアライズはどうですか?
スベリアコフ2014年

これを既存のモデルに追加しようとするmy_vals = SeparatedValuesField(blank=True, default="")と、NULLのためにIntegrityErrorが発生します。デフォルトの引数が正しく渡されていませんか?
John Lehmann、2015年

1
Django 2.1 to_pythonでは、読み込み時に呼び出されなくなったことに注意してください。したがって、あなたが追加する必要があり、この動作させるために: def from_db_value(self, value, expression, connection, context): return self.to_python(value)
theadriangreen

46

リストを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'>

8
残念ながら、これはdjango adminを使用してリストを管理するのに役立ちません
GreenAsJade 14年

25

PostgresでDjango> = 1.9を使用している場合は、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
これの大きな利点は、配列フィールドから直接要素をクエリできることです。
Thane Brimhall 2017

@ThaneBrimhallあなたは正しいです。多分私はこの情報で答えを更新する必要があります。ありがとう
wolendranh

悲しいことに、mysqlの解決策はありません
Joel G Mathew

これはPostGresでのみ機能することに注意してください。
theadriangreen

1
Django 1.8にもArrayFieldがあります:docs.djangoproject.com/en/1.8/ref/contrib/postgres/fields
kontextify

15

これは古い質問であり、Djangoのテクニックは大幅に変更されているはずなので、この回答はDjangoバージョン1.4を反映しており、v 1.5に当てはまる可能性が高いです。

Djangoはデフォルトでリレーショナルデータベースを使用します。あなたはそれらを利用するべきです。ManyToManyFieldを使用して、友情をデータベース関係(外部キー制約)にマップします。これにより、スマートクエリセットを使用するフレンドリストにRelatedManagersを使用できるようになります。filterまたはなどの使用可能なすべてのメソッドを使用できますvalues_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

1対多の関係(Friendから親クラスへのFK)を使用すると、アプリのスケーラビリティが向上します(単純な名前以外の属性を追加してFriendオブジェクトを簡単に拡張できるため)。これが最良の方法です


3
それはスケーラビリティではなく、拡張性です。多くの場合、一方が他方を犠牲にしています。この場合、常に文字列のリストが必要であることがわかっている場合は、高価な結合を回避できるため、コードのスケーラビリティが高くなります(つまり、非正規化によりパフォーマンスが向上します)。
ダスティンRasener

上記のいくつかの注意点:1)データに対してクエリを実行したくないこと、および2)処理能力とメモリよりもストレージの方が安価であることがわかっている(たぶん、これは量子計算で変化する)
Dustin Rasener
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.