Bir listeyi Django modellerinde depolamanın en etkili yolu nedir?


146

Şu anda kodumda aşağıdakine benzer bir sürü python nesnesi var:

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

Şimdi bunu Django modeline dönüştürmek istiyorum.

from django.db import models

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

Liste python'da çok yaygın bir veri yapısı olduğundan, bunun için bir Django model alanı olması bekleniyordu. Ben bir ManyToMany veya OneToMany ilişki kullanabilirsiniz biliyorum, ama kod bu ekstra dolaylı önlemek için umuyordum.

Düzenle:

İnsanların yararlı bulacağı bu ilgili soruyu ekledim .


1
@drozzy: Muhtemelen farklı bir kelime öbeği kullanabilirdim, ama temelde kastettiğim, bir dize listesine geçmek ve bir dize listesi geri almak istiyorum. Bir grup Friend nesnesi oluşturmak ve her biri için inst.myFriends.add (friendObj) çağırmak istemiyorum. Hepsi bu kadar zor olmaz, ama ...
yas

Yanıtlar:


77

Bu ilişki bir Friendstablonun birebir yabancı anahtar ilişkisi olarak daha iyi ifade edilmez mi ? Bunun myFriendssadece dizeler olduğunu anlıyorum ama daha iyi bir tasarımın bir Friendmodel oluşturmak MyClassve sonuçta ortaya çıkan tabloya yabancı bir anahtarlık içermek olduğunu düşünürdüm .


15
Muhtemelen bunu yapacağım, ama bunun altında yatan yapının inşa edileceğini umuyordum. Sanırım tembelleşeceğim.
yas

Zarif ve en güzel açıkladı.
Tessaracter


129

"Erken optimizasyon tüm kötülüklerin köküdür."

Bunu akılda tutarak, hadi yapalım! Uygulamalarınız belirli bir noktaya geldiğinde, verilerin normalleştirilmesi çok yaygındır. Doğru bir şekilde yapıldığında, biraz daha fazla temizlik maliyetiyle çok sayıda pahalı veritabanı aramasını kaydedebilir.

Bir dönmek için listarkadaşı isimleri biz erişildiğinde listesini döndürür özel Django Saha sınıf oluşturmak gerekir.

David Cramer, blogunda SeperatedValueField oluşturmak için bir rehber yayınladı. İşte kod:

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)

Bu kodun mantığı, veritabanından Python'a veya tam tersi değerlerin serileştirilmesi ve serileştirilmesi ile ilgilidir. Artık model sınıfındaki özel alanımızı kolayca içe aktarabilir ve kullanabilirsiniz:

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

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

8
Harika bir cevap için +1, ancak zaten böyle bir şey yapıyoruz. Gerçekten tüm değerleri tek bir dizeye sıkıştırıp sonra böler. Sanırım daha çok ListofStringsField gibi bir şey umuyordum ki bu aslında ayrı bir tablo oluşturuyor ve yabancı anahtarları otomatik yapıyor. Django'da bunun mümkün olup olmadığından emin değilim. Öyleyse ve bir cevap bulursam, stackoverflow'a gönderirim.
grieve

2
Bu durumda, initcrash'in django-denorm'unu arıyorsunuz. Bunu github'da
jb.

3
+1. Ancak dizelerde virgülle olası sorunlar. Json'dan serileştirme ve serileştirme yapmaya ne dersiniz?
sbeliakov

Bunu mevcut modele eklemeye çalışıyorum, my_vals = SeparatedValuesField(blank=True, default="")ancak NULL'lar nedeniyle IntegrityError alıyorum. Varsayılan argüman doğru bir şekilde iletilmiyor mu?
John Lehmann

1
Django 2.1'de to_pythonartık okuma üzerine çağrılmadığını unutmayın. Böylece bu işi yapmak için eklemeniz gerekir: def from_db_value(self, value, expression, connection, context): return self.to_python(value)
theadriangreen

46

Bir listeyi Django'da depolamanın basit bir yolu, listeyi sadece bir JSON dizesine dönüştürmek ve ardından bunu modelde Metin olarak kaydetmektir. Daha sonra (JSON) dizesini tekrar bir python listesine dönüştürerek listeyi alabilirsiniz. Bunu nasıl yapacağınız aşağıda açıklanmıştır:

"Liste" Django modelinizde şu şekilde saklanır:

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

Görünüm / denetleyici kodunuzda:

Listenin veritabanında saklanması:

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

Listeyi veritabanından alma:

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

Kavramsal olarak, neler oluyor:

>>> 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
Ne yazık ki bu, django admin
GreenAsJade

25

Postgres ile Django> = 1.9 kullanıyorsanız ArrayField avantajlarından faydalanabilirsiniz

Veri listelerini depolamak için bir alan. Çoğu alan türü kullanılabilir, sadece base_field olarak başka bir alan örneğini iletirsiniz. Bir boyut da belirleyebilirsiniz. ArrayField çok boyutlu dizileri saklamak için iç içe yerleştirilebilir.

Dizi alanlarını yuvalamak da mümkündür:

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'ın belirttiği gibi, elemanları doğrudan sorgulamak da mümkündür. Dokümantasyon referansı


2
Bunun en büyük avantajı, elemanları doğrudan dizi alanından sorgulayabilmenizdir.
Thane Brimhall

@ThaneBrimhall haklısın. Belki de cevabı bu bilgilerle güncellemeliyim. Thanks
wolendranh

Ne yazık ki, mysql için çözüm yok
Joel G Mathew

Bunun sadece PostGres ile çalıştığı belirtilmelidir.
theadriangreen


15

Bu eski bir soru olduğundan ve Django tekniklerinin önemli ölçüde değişmiş olması gerektiğinden, bu cevap Django 1.4 sürümünü yansıtmaktadır ve büyük olasılıkla v 1.5 için geçerlidir.

Django varsayılan olarak ilişkisel veritabanlarını kullanır; onlardan faydalanmalısın. ManyToManyField kullanarak arkadaşlıkları veritabanı ilişkilerine (yabancı anahtar kısıtlamaları) eşleyin. Bunu yapmak akıllı sorgu kümeleri kullanan arkadaş listeleri için RelatedManager'ları kullanmanıza izin verir. Aşağıdaki gibi mevcut tüm yöntemleri kullanabilirsiniz filterveya values_list.

ManyToManyFieldİlişkileri ve özellikleri kullanma :

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

Bir kullanıcının arkadaş listesine şu şekilde erişebilirsiniz:

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

Ancak bu ilişkilerin simetrik olduğuna dikkat edin: eğer Joseph bir Bob arkadaşıysa, o zaman Bob bir Joseph arkadaşıdır.


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

Bunun sonunda ilişkisel bir veritabanına dönüşmesi gerektiğini unutmayın. Dolayısıyla ilişkileri kullanmak , bu sorunu çözmenin ortak yoludur. Bir listeyi nesnenin kendisinde saklamakta ısrar ediyorsanız, örneğin virgülle ayrılmış hale getirebilir ve bir dizede saklayabilir ve ardından dizeyi bir listeye ayıran erişimci işlevleri sağlayabilirsiniz. Bununla, maksimum sayıda dizeyle sınırlı olacak ve verimli sorguları kaybedeceksiniz.


3
Ben bir ilişki olarak depolamak veritabanı ile iyiyim, Django modelleri zaten benim için o kısmı soyutlama umuyordu. Uygulama tarafından her zaman dizelerin bir listesi olarak ele almak isteyeceğim.
yas



3

Dizelerin listesini Django modelinde saklama:

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

ve şöyle diyebilirsiniz:

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."      

1

Bire çok ilişkisi kullanmak (Arkadaştan ana sınıfa FK) uygulamanızı daha ölçeklenebilir hale getirir (Friend nesnesini basit adın ötesinde ek niteliklerle önemsiz bir şekilde genişletebilirsiniz). Ve böylece bu en iyi yol


3
Bu ölçeklenebilirlik değil, genişletilebilirlik. Genellikle biri diğerinin maliyetindedir. Bu durumda, her zaman bir dize listesine ihtiyacınız olacağını biliyorsanız, pahalı bir birleştirmeden kaçınabilirsiniz, böylece kodunuzu daha ölçeklenebilir hale getirebilirsiniz (yani denormalizasyondan daha yüksek performans).
Dustin Rasener

Birkaç uyarı ile yukarıdakiler: 1) bu verilere karşı asla sorgulamak istemediğinizi biliyorsunuz ve 2) depolama gücü ve hafızayı işlemekten hala daha ucuzdur (kim bilir, belki bu kuantum hesaplama ile değişir)
Dustin Rasener

1

Benim çözümüm, birine yardımcı olabilir:

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)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.