Apa cara paling efisien untuk menyimpan daftar dalam model Django?

146

Saat ini saya memiliki banyak objek python dalam kode saya mirip dengan yang berikut:

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

Sekarang saya ingin mengubahnya menjadi model Django, di mana self.myName adalah bidang string, dan self.myFriends adalah daftar string.

from django.db import models

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

Karena daftar adalah struktur data yang umum di python, saya semacam berharap akan ada bidang model Django untuk itu. Saya tahu saya bisa menggunakan hubungan ManyToMany atau OneToMany, tapi saya berharap untuk menghindari tipuan ekstra dalam kode.

Edit:

Saya menambahkan pertanyaan terkait ini , yang mungkin bermanfaat bagi orang.

bersedih
sumber
1
@drozzy: Yah saya mungkin bisa menggunakan frasa yang berbeda, tetapi pada dasarnya apa yang saya maksudkan, adalah saya ingin memberikan daftar string dan mendapatkan kembali daftar string. Saya tidak ingin membuat banyak objek Teman, dan memanggil inst.myFriends.add (friendObj) untuk masing-masing. Bukannya akan terlalu sulit, tapi ...
bersedihlah

Jawaban:

77

Apakah hubungan ini tidak akan lebih baik diungkapkan sebagai hubungan kunci asing satu-ke-banyak ke Friendsmeja? Saya mengerti itu myFriendshanya string tetapi saya akan berpikir bahwa desain yang lebih baik adalah untuk membuat Friendmodel dan MyClassberisi realtionship kunci asing ke tabel yang dihasilkan.

Andrew Hare
sumber
15
Mungkin inilah yang akhirnya akan saya lakukan, tetapi saya benar-benar berharap struktur yang mendasari ini akan dibangun. Saya kira saya malas.
berduka
Elegan dan paling indah dijelaskan.
Tessaracter
129

"Optimalisasi prematur adalah akar dari semua kejahatan."

Dengan mengingat hal itu, mari kita lakukan ini! Setelah aplikasi Anda mencapai titik tertentu, denormalkan data sangat umum. Dilakukan dengan benar, dapat menghemat banyak pencarian basis data yang mahal dengan biaya sedikit lebih banyak pembenahan.

Untuk mengembalikan listnama teman, kita harus membuat kelas Django Field khusus yang akan mengembalikan daftar saat diakses.

David Cramer memposting panduan untuk membuat SeperatedValueField di blognya. Ini kodenya:

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)

Logika kode ini berkaitan dengan nilai-nilai serialisasi dan deserializing dari database ke Python dan sebaliknya. Sekarang Anda dapat dengan mudah mengimpor dan menggunakan bidang khusus kami di kelas model:

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

class Person(models.Model):
    name = models.CharField(max_length=64)
    friends = SeparatedValuesField()
jb.
sumber
8
Memberi +1 untuk jawaban yang bagus, tetapi kami sudah melakukan hal seperti ini. Ini benar-benar memadatkan semua nilai menjadi satu string kemudian membaginya. Saya kira saya berharap untuk sesuatu yang lebih seperti ListofStringsField, yang sebenarnya membangun tabel terpisah dan membuat kunci asing secara otomatis. Saya tidak yakin apakah itu mungkin di Django. Jika ya, dan saya menemukan jawaban, saya akan mempostingnya di stackoverflow.
berduka
2
Jika itu yang terjadi maka Anda sedang mencari django-denorm initcrash. Anda akan menemukannya di github: github.com/initcrash/django-denorm/tree/master
jb.
3
+1. Tetapi kemungkinan masalah dengan koma di string. Bagaimana dengan serialisasi dan deserializing dari json?
sbeliakov
Mencoba menambahkan ini ke model yang ada dengan my_vals = SeparatedValuesField(blank=True, default="")tetapi mendapatkan IntegrityError karena NULLs. Apakah argumen default tidak diteruskan dengan benar?
John Lehmann
1
Perhatikan bahwa dalam Django 2.1 to_pythontidak lagi dipanggil saat dibaca. Jadi untuk membuat pekerjaan ini, Anda perlu menambahkan: def from_db_value(self, value, expression, connection, context): return self.to_python(value)
theadriangreen
46

Cara sederhana untuk menyimpan daftar di Django adalah dengan hanya mengubahnya menjadi string JSON, dan kemudian menyimpannya sebagai Teks dalam model. Anda kemudian dapat mengambil daftar dengan mengubah string (JSON) kembali ke daftar python. Begini caranya:

"Daftar" akan disimpan dalam model Django Anda seperti:

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

Dalam kode view / controller Anda:

Menyimpan daftar dalam database:

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()

Mengambil daftar dari database:

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

Secara konseptual, inilah yang terjadi:

>>> 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'>
mindthief
sumber
8
Sayangnya ini tidak membantu Anda mengelola daftar menggunakan admin django
GreenAsJade
25

Jika Anda menggunakan Django> = 1.9 dengan Postgres Anda dapat memanfaatkan keunggulan ArrayField

Bidang untuk menyimpan daftar data. Sebagian besar tipe bidang dapat digunakan, Anda cukup mengirimkan instance bidang lain sebagai base_field. Anda juga dapat menentukan ukuran. ArrayField dapat disarangkan untuk menyimpan array multi dimensi.

Dimungkinkan juga untuk memasukkan field array:

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,
    )

Seperti @ thane-brimhall sebutkan juga dimungkinkan untuk meminta elemen secara langsung. Referensi dokumentasi

wolendranh
sumber
2
Keuntungan besar dari ini adalah Anda dapat meminta elemen langsung dari bidang array.
Thane Brimhall
@ThaneBrimhall Anda benar. Mungkin saya harus memperbarui jawaban dengan informasi ini. Terima kasih
wolendranh
Sayangnya, tidak ada solusi untuk mysql
Joel G Mathew
Harus disebutkan bahwa ini hanya berfungsi pada PostGres.
theadriangreen
1
Django 1.8 juga memiliki ArrayField: docs.djangoproject.com/en/1.8/ref/contrib/postgres/fields
kontextify
15

Karena ini adalah pertanyaan lama, dan teknik Django pasti telah berubah secara signifikan sejak itu, jawaban ini mencerminkan Django versi 1.4, dan kemungkinan besar berlaku untuk v 1.5.

Django secara default menggunakan database relasional; Anda harus menggunakan mereka. Peta pertemanan dengan hubungan basis data (batasan kunci asing) dengan penggunaan ManyToManyField. Dengan melakukannya, Anda dapat menggunakan RelatedManagers untuk daftar teman, yang menggunakan kueryset cerdas. Anda dapat menggunakan semua metode yang tersedia seperti filteratau values_list.

Menggunakan ManyToManyFieldrelasi dan properti:

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())

Anda dapat mengakses daftar teman pengguna dengan cara ini:

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

Namun perlu dicatat bahwa hubungan ini simetris: jika Joseph adalah teman Bob, maka Bob adalah teman Joseph.

sleblanc
sumber
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')
Andriy Drozdyuk
sumber
8

Ingatlah bahwa ini pada akhirnya harus berakhir pada basis data relasional. Jadi menggunakan hubungan benar - benar adalah cara yang umum untuk menyelesaikan masalah ini. Jika Anda benar-benar bersikeras untuk menyimpan daftar di objek itu sendiri, Anda bisa membuatnya misalnya dipisahkan dengan koma, dan menyimpannya dalam string, dan kemudian menyediakan fungsi pengaksesor yang membagi string menjadi daftar. Dengan itu, Anda akan dibatasi hingga jumlah maksimum string, dan Anda akan kehilangan kueri yang efisien.

Martin v. Löwis
sumber
3
Saya baik-baik saja dengan database yang menyimpannya sebagai relasi, saya berharap model Django sudah mengabstraksi bagian itu untuk saya. Dari sisi aplikasi saya selalu ingin memperlakukannya sebagai daftar string.
berduka
7

Jika Anda menggunakan postgres, Anda dapat menggunakan sesuatu seperti ini:

class ChessBoard(models.Model):

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

jika Anda memerlukan detail lebih lanjut, Anda dapat membaca di tautan di bawah ini: https://docs.djangoproject.com/pt-br/1.9/ref/contrib/postgres/fields/

Marcos Souza
sumber
3

Menyimpan daftar string dalam model 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

dan Anda bisa menyebutnya seperti ini:

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."      
Ahtisham
sumber
2

Solusi saya, semoga membantu seseorang:

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)
stefanitsky
sumber
1

Menggunakan hubungan satu-ke-banyak (FK dari kelas Teman ke orang tua) akan membuat aplikasi Anda lebih skalabel (karena Anda dapat secara sepele memperluas objek Teman dengan atribut tambahan di luar nama sederhana). Dan inilah cara terbaik

Menjaga
sumber
3
Itu bukan skalabilitas, itu diperpanjang. Seringkali yang satu mengorbankan yang lain. Dalam hal ini, jika Anda tahu bahwa Anda akan selalu memerlukan daftar string, Anda dapat menghindari gabungan yang mahal, sehingga membuat kode Anda lebih terukur (yaitu lebih banyak pemain dari denasionalisasi).
Dustin Rasener
Di atas dengan beberapa peringatan: 1) Anda tahu Anda tidak pernah ingin menanyakan data itu dan 2) penyimpanan masih lebih murah daripada kekuatan pemrosesan dan memori (siapa tahu, mungkin ini berubah dengan komputasi kuantum)
Dustin Rasener