Django dinamik model alanları


161

Bazı kullanıcıların formlarda ek veri toplamak ve veriler hakkında rapor vermek için kendi veri alanlarını (yönetici aracılığıyla) tanımlayabileceği çok kiracılı bir uygulama üzerinde çalışıyorum . İkinci bit JSONField büyük bir seçenek değil yapar, bu yüzden bunun yerine aşağıdaki çözüm var:

class CustomDataField(models.Model):
    """
    Abstract specification for arbitrary data fields.
    Not used for holding data itself, but metadata about the fields.
    """
    site = models.ForeignKey(Site, default=settings.SITE_ID)
    name = models.CharField(max_length=64)

    class Meta:
        abstract = True

class CustomDataValue(models.Model):
    """
    Abstract specification for arbitrary data.
    """
    value = models.CharField(max_length=1024)

    class Meta:
        abstract = True

CustomDataField öğesinin nasıl Siteye bir ForeignKey'i olduğunu unutmayın - her Sitenin farklı bir özel veri alanı kümesi olur, ancak aynı veritabanını kullanır. Daha sonra çeşitli somut veri alanları şöyle tanımlanabilir:

class UserCustomDataField(CustomDataField):
    pass

class UserCustomDataValue(CustomDataValue):
    custom_field = models.ForeignKey(UserCustomDataField)
    user = models.ForeignKey(User, related_name='custom_data')

    class Meta:
        unique_together=(('user','custom_field'),)

Bu, aşağıdaki kullanıma yol açar:

custom_field = UserCustomDataField.objects.create(name='zodiac', site=my_site) #probably created in the admin
user = User.objects.create(username='foo')
user_sign = UserCustomDataValue(custom_field=custom_field, user=user, data='Libra')
user.custom_data.add(user_sign) #actually, what does this even do?

Ancak bu, özellikle ilgili verileri manuel olarak oluşturma ve bunu somut modelle ilişkilendirme ihtiyacı ile çok karmaşık hissettiriyor. Daha iyi bir yaklaşım var mı?

Önceden etkili bir şekilde atılan seçenekler:

  • Anında tabloları değiştirmek için özel SQL. Kısmen bu ölçeklenmeyeceğinden ve kısmen de bir hack olduğu için.
  • NoSQL gibi şema içermeyen çözümler. Onlara karşı hiçbir şeyim yok, ama hala iyi değiller. Sonuçta bu veriler olduğunu yazmış ve olasılık bir üçüncü taraf raporlama uygulamayı kullanmanın bulunmaktadır.
  • JSONField, yukarıda listelendiği gibi, sorgularla iyi çalışmayacaktır.

Yanıtlar:


278

Bugün itibariyle, ikisi belirli bir depolama arka ucuna ihtiyaç duyan dört mevcut yaklaşım vardır:

  1. Django-eav (orijinal paket artık muhafaza edilmiyor , ancak gelişen çatalları var )

    Bu çözüm, Varlık Özellik Değeri veri modelini temel alır, temel olarak nesnelerin dinamik niteliklerini depolamak için birkaç tablo kullanır. Bu çözümün harika kısımları:

    • dinamik alanları temsil etmek için anlaşılması ve veritabanından agnostik hale getirilmesini sağlayan birkaç saf ve basit Django modeli kullanır;
    • dinamik özellik depolamasını Django modeline aşağıdaki gibi basit komutlarla etkili bir şekilde eklemenizi / ayırmanızı sağlar:

      eav.unregister(Encounter)
      eav.register(Patient)
    • Django admin ile iyi bütünleşir ;

    • Aynı zamanda gerçekten güçlü olmak.

    Downsides:

    • Çok verimli değil. Bu, verilerin bir sütun biçiminden modeldeki bir dizi anahtar / değer çiftine manuel olarak birleştirilmesini gerektiren EAV modelinin kendisinin bir eleştirisidir.
    • Bakımı daha zor. Veri bütünlüğünü korumak, bazı veritabanlarında verimsiz olabilecek çok sütunlu benzersiz bir anahtar kısıtlaması gerektirir.
    • Resmi paket artık korunmadığından ve net bir lider olmadığından çatallardan birini seçmeniz gerekecektir .

    Kullanımı oldukça basittir:

    import eav
    from app.models import Patient, Encounter
    
    eav.register(Encounter)
    eav.register(Patient)
    Attribute.objects.create(name='age', datatype=Attribute.TYPE_INT)
    Attribute.objects.create(name='height', datatype=Attribute.TYPE_FLOAT)
    Attribute.objects.create(name='weight', datatype=Attribute.TYPE_FLOAT)
    Attribute.objects.create(name='city', datatype=Attribute.TYPE_TEXT)
    Attribute.objects.create(name='country', datatype=Attribute.TYPE_TEXT)
    
    self.yes = EnumValue.objects.create(value='yes')
    self.no = EnumValue.objects.create(value='no')
    self.unkown = EnumValue.objects.create(value='unkown')
    ynu = EnumGroup.objects.create(name='Yes / No / Unknown')
    ynu.enums.add(self.yes)
    ynu.enums.add(self.no)
    ynu.enums.add(self.unkown)
    
    Attribute.objects.create(name='fever', datatype=Attribute.TYPE_ENUM,\
                                           enum_group=ynu)
    
    # When you register a model within EAV,
    # you can access all of EAV attributes:
    
    Patient.objects.create(name='Bob', eav__age=12,
                               eav__fever=no, eav__city='New York',
                               eav__country='USA')
    # You can filter queries based on their EAV fields:
    
    query1 = Patient.objects.filter(Q(eav__city__contains='Y'))
    query2 = Q(eav__city__contains='Y') |  Q(eav__fever=no)
  2. PostgreSQL içindeki Hstore, JSON veya JSONB alanları

    PostgreSQL çok daha karmaşık veri türlerini destekler. Çoğu üçüncü taraf paketleriyle destekleniyor, ancak son yıllarda Django bunları django.contrib.postgres.fields olarak benimsedi.

    HStoreField :

    Django-hstore aslında üçüncü taraf bir paketti , ancak Django 1.8, HStoreField'ı yerleşik olarak ve diğer PostgreSQL destekli alan türleriyle birlikte ekledi .

    Bu yaklaşım, her iki dünyanın da en iyisini elde etmenizi sağlayan bir anlamda iyidir: dinamik alanlar ve ilişkisel veritabanı. Bununla birlikte, hstore , özellikle bir alanda binlerce öğeyi depolayacaksanız, performans açısından ideal değildir . Ayrıca yalnızca değerler için dizeleri destekler.

    #app/models.py
    from django.contrib.postgres.fields import HStoreField
    class Something(models.Model):
        name = models.CharField(max_length=32)
        data = models.HStoreField(db_index=True)

    Django'nun kabuğunda şöyle kullanabilirsiniz:

    >>> instance = Something.objects.create(
                     name='something',
                     data={'a': '1', 'b': '2'}
               )
    >>> instance.data['a']
    '1'        
    >>> empty = Something.objects.create(name='empty')
    >>> empty.data
    {}
    >>> empty.data['a'] = '1'
    >>> empty.save()
    >>> Something.objects.get(name='something').data['a']
    '1'

    Hstore alanlarına karşı dizine alınmış sorgular yayınlayabilirsiniz:

    # equivalence
    Something.objects.filter(data={'a': '1', 'b': '2'})
    
    # subset by key/value mapping
    Something.objects.filter(data__a='1')
    
    # subset by list of keys
    Something.objects.filter(data__has_keys=['a', 'b'])
    
    # subset by single key
    Something.objects.filter(data__has_key='a')    

    JSONField :

    JSON / JSONB alanları, yalnızca anahtar / değer çiftlerini değil, aynı zamanda Hstore'dan daha hızlı ve (JSONB için) daha kompakt olma eğilimi gösteren herhangi bir JSON ile kodlanabilir veri türünü destekler. Birkaç paket , django-pgfields dahil olmak üzere JSON / JSONB alanlarını uygular , ancak Django 1.9'dan itibaren JSONField, depolama için JSONB kullanan bir yerleşiktir. JSONField , HStoreField'a benzer ve büyük sözlüklerde daha iyi performans gösterebilir. Ayrıca tamsayılar, booleans ve iç içe sözlükler gibi dizeler dışındaki türleri de destekler.

    #app/models.py
    from django.contrib.postgres.fields import JSONField
    class Something(models.Model):
        name = models.CharField(max_length=32)
        data = JSONField(db_index=True)

    Kabukta oluşturma:

    >>> instance = Something.objects.create(
                     name='something',
                     data={'a': 1, 'b': 2, 'nested': {'c':3}}
               )

    Endekslenen sorgular, yuvalama yapılabilmesi dışında HStoreField ile hemen hemen aynıdır. Karmaşık dizinler el ile oluşturma (veya komut dosyası geçirilmiş taşıma) gerektirebilir.

    >>> Something.objects.filter(data__a=1)
    >>> Something.objects.filter(data__nested__c=3)
    >>> Something.objects.filter(data__has_key='a')
  3. Django MongoDB

    Veya diğer NoSQL Django uyarlamaları - onlarla tamamen dinamik modellere sahip olabilirsiniz.

    NoSQL Django kütüphaneleri harika, ancak standart Django'dan Django-nonrel'e geçmek için% 100 Django uyumlu olmadıklarını unutmayın. ManyToMany'yi ListField ile değiştirmelisiniz .

    Bu Django MongoDB örneğine göz atın:

    from djangotoolbox.fields import DictField
    
    class Image(models.Model):
        exif = DictField()
    ...
    
    >>> image = Image.objects.create(exif=get_exif_data(...))
    >>> image.exif
    {u'camera_model' : 'Spamcams 4242', 'exposure_time' : 0.3, ...}

    Herhangi bir Django modelinin gömülü listelerini bile oluşturabilirsiniz :

    class Container(models.Model):
        stuff = ListField(EmbeddedModelField())
    
    class FooModel(models.Model):
        foo = models.IntegerField()
    
    class BarModel(models.Model):
        bar = models.CharField()
    ...
    
    >>> Container.objects.create(
        stuff=[FooModel(foo=42), BarModel(bar='spam')]
    )
  4. Django-mutant: Syncdb ve Güney kancalarına dayanan dinamik modeller

    Django-mutant tamamen dinamik Yabancı Anahtar ve m2m alanları uygular. Will Hardy ve Michael Hall'un inanılmaz ama biraz hackish çözümlerinden ilham alıyor .

    Bunların hepsi, Will Hardy'nin DjangoCon 2011'deki konuşmasına (izleyin!) Göre Django South kancalarına dayanıyor , yine de sağlam ve üretimde test edildi ( ilgili kaynak kodu ).

    İlk Bunu uygulamak oldu Michael Hall .

    Evet, bu sihir, bu yaklaşımlarla herhangi bir ilişkisel veritabanı arka ucuyla tamamen dinamik Django uygulamaları, modelleri ve alanları elde edebilirsiniz . Ama ne pahasına olursa olsun? Ağır kullanımda uygulama stabilitesi etkilenecek mi? Bunlar dikkate alınması gereken sorular. Eşzamanlı veritabanı değiştirme isteklerine izin vermek için uygun bir kilit sağladığınızdan emin olmanız gerekir .

    Michael Halls lib kullanıyorsanız, kodunuz şöyle görünecektir:

    from dynamo import models
    
    test_app, created = models.DynamicApp.objects.get_or_create(
                          name='dynamo'
                        )
    test, created = models.DynamicModel.objects.get_or_create(
                      name='Test',
                      verbose_name='Test Model',
                      app=test_app
                   )
    foo, created = models.DynamicModelField.objects.get_or_create(
                      name = 'foo',
                      verbose_name = 'Foo Field',
                      model = test,
                      field_type = 'dynamiccharfield',
                      null = True,
                      blank = True,
                      unique = False,
                      help_text = 'Test field for Foo',
                   )
    bar, created = models.DynamicModelField.objects.get_or_create(
                      name = 'bar',
                      verbose_name = 'Bar Field',
                      model = test,
                      field_type = 'dynamicintegerfield',
                      null = True,
                      blank = True,
                      unique = False,
                      help_text = 'Test field for Bar',
                   )

3
bu konu DjangoCon 2013 Europe'da son zamanlarda konuşuldu: slideshare.net/schacki/… ve youtube.com/watch?v=67wcGdk4aCc
Aleck Landgraf

Postgres> = 9.2'de django-pgjson kullanmanın postgresql'nin json alanının doğrudan kullanımına izin verdiğini de belirtmek gerekir . Django> = 1.7'de, sorgular için filtre API'si nispeten aklı başındadır. Postgres> = 9.4 ayrıca daha hızlı sorgular için daha iyi dizinlere sahip jsonb alanlarına da izin verir.
GDorn

1
Bugün Django'nun HStoreField ve JSONField'ı benimsemeye katkıda bulunduğu not edildi Müthiş olmayan bazı form widget'ları içerir, ancak yöneticide veri ayarlamanız gerekiyorsa çalışır.
GDorn

13

Django-dinamo fikrini daha ileriye taşımak için çalışıyorum. Proje hala belgelenmemiştir ancak kodu https://github.com/charettes/django-mutant adresinden okuyabilirsiniz .

Aslında FK ve M2M alanları (bkz. İlgili. İlgili) da çalışır ve kendi özel alanlarınız için sarıcı tanımlamak bile mümkündür.

Ayrıca, unique_together ve ordering artı Model bazları gibi model seçenekleri için destek vardır, böylece model proxy, soyut veya mixins'i alt sınıflandırabilirsiniz.

Aslında model tanımlarını birden fazla django çalışan örnekleri arasında paylaşılabilir emin olmak için bir bellek içi kilit mekanizması üzerinde çalışıyorum ve eski tanımı kullanarak onları önler.

Proje hala çok alfa ama projemden biri için bir köşe taşı teknolojisi, bu yüzden onu üretime hazır hale getirmem gerekecek. Büyük plan, django-nonrel'i de destekliyor, böylece mongodb sürücüsünü kaldırabiliriz.


1
Selam Simon! Projenizi , github'da oluşturduktan hemen sonra wiki yanıtıma ekledim. :))) Stackoverflow sizi görmek güzel!
Ivan Kharlamov

4

Daha fazla araştırma , bunun Django için birkaç paket tarafından uygulanan Varlık Özellik Değeri tasarım modelinin biraz özel bir örneği olduğunu ortaya koyuyor .

İlk olarak, PyPi'de bulunan orijinal eav-django projesi var.

İkincisi, ilk projenin daha yeni bir çatalı var, django-eav , esas olarak EAV'ın django'nun kendi modelleri veya üçüncü taraf uygulamalarında modelleriyle kullanılmasına izin veren bir refaktör .


Bunu wiki'ye ekleyeceğim.
Ivan Kharlamov

1
Diğer taraftan, EAV'ın özel bir dinamik modelleme örneği olduğunu savunuyorum. Benzersiz bir kimlik içeriyorsa "üçlü" veya "dörtlü" olarak adlandırılan "anlamsal web" topluluğunda yoğun olarak kullanılır. Bununla birlikte, SQL tablolarını dinamik olarak oluşturabilen ve değiştirebilen bir mekanizma kadar verimli olması pek olası değildir.
Cerin

@GDom ilk tercihiniz eav-django mu? Yani yukarıdaki hangi seçeneği seçtiniz?
Moreno

1
@Moreno Doğru seçim, büyük ölçüde sizin özel kullanım durumunuza bağlı olacaktır. Hem EAV hem de JsonField'leri farklı nedenlerle kullandım. İkincisi şimdi Django tarafından doğrudan desteklenmektedir, bu yüzden yeni bir proje için EAV tablosunda sorgulama yapabilmek için özel bir ihtiyaç duymadıkça ilk önce bunu kullanacağım. JsonFields üzerinde de sorgulayabilirsiniz unutmayın.
GDorn
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.