장고 동적 모델 필드


161

일부 사용자가 양식을 통해 추가 데이터를 수집하고 데이터에 대해보고하기 위해 관리자를 통해 자신의 데이터 필드를 정의 할 수 있는 다중 테넌트 응용 프로그램을 만들고 있습니다. 후자의 비트는 JSONField를 훌륭한 옵션으로 만들지 않으므로 대신 다음 해결책이 있습니다.

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에 ForeignKey to Site가있는 방법에 유의하십시오. 각 사이트에는 다른 사용자 정의 데이터 필드 세트가 있지만 동일한 데이터베이스를 사용합니다. 그런 다음 다양한 구체적인 데이터 필드를 다음과 같이 정의 할 수 있습니다.

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

이것은 다음과 같은 용도로 사용됩니다.

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?

그러나 이는 특히 관련 데이터를 수동으로 작성하고 구체적 모델과 연관시켜야 할 필요성으로 인해 매우 어색합니다. 더 나은 접근법이 있습니까?

선제 적으로 버린 옵션 :

  • 테이블을 즉시 수정하기위한 사용자 지정 SQL 이것은 부분적으로 확장되지 않기 때문에 부분적으로 너무 많은 해킹이기 때문입니다.
  • NoSQL과 같은 스키마없는 솔루션 나는 그들에 대해 아무것도 없지만 여전히 적합하지 않습니다. 궁극적으로이 데이터 입력되며 타사보고 응용 프로그램을 사용할 가능성이 있습니다.
  • 쿼리와 잘 작동하지 않으므로 위에 나열된 JSONField입니다.

6
선점

답변:


278

현재로서는 4 가지 방법이 있으며 그 중 2 가지 방법에는 특정 스토리지 백엔드가 필요합니다.

  1. Django-eav (원래 패키지는 더 이상 유지되지 않지만 번성하는 포크가 있습니다 )

    이 솔루션은 엔티티 속성 값 데이터 모델을 기반으로 하며 본질적으로 여러 테이블을 사용하여 오브젝트의 동적 속성을 저장합니다. 이 솔루션의 중요한 부분은 다음과 같습니다.

    • 몇 가지 순수하고 간단한 Django 모델을 사용하여 동적 필드를 나타내므로 이해하기 쉽고 데이터베이스에 구애받지 않습니다.
    • 다음과 같은 간단한 명령으로 동적 속성 저장소를 Django 모델에 효과적으로 연결 / 분리 할 수 ​​있습니다.

      eav.unregister(Encounter)
      eav.register(Patient)
    • 장고 관리자와 잘 통합됩니다 .

    • 동시에 정말 강력합니다.

    단점 :

    • 매우 효율적이지 않습니다. 이것은 EAV 패턴 자체에 대한 비판에 더 가깝습니다. 데이터를 열 형식에서 모델의 키-값 쌍으로 수동으로 병합해야합니다.
    • 유지하기 어렵다. 데이터 무결성을 유지하려면 여러 열의 고유 키 제약 조건이 필요하며 일부 데이터베이스에서는 비효율적 일 수 있습니다.
    • 공식 패키지는 더 이상 유지 관리되지 않으며 명확한 리더가 없으므로 포크 중 하나 를 선택해야합니다 .

    사용법은 매우 간단합니다.

    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의 Hstore, JSON 또는 JSONB 필드

    PostgreSQL은 몇 가지 더 복잡한 데이터 유형을 지원합니다. 대부분은 타사 패키지를 통해 지원되지만 최근 몇 년 동안 Django는 django.contrib.postgres.fields에 채택했습니다.

    HStoreField :

    Django-hstore 는 원래 타사 패키지 였지만 Django 1.8은 HPostField 를 다른 PostgreSQL 지원 필드 유형과 함께 내장으로 추가 했습니다 .

    이 방법은 동적 필드와 관계형 데이터베이스와 같은 두 가지 세계를 모두 활용할 수 있다는 점에서 좋습니다. 그러나 hstore는 성능 측면 에서 이상적이지 않습니다 . 특히 한 필드에 수천 개의 항목을 저장하려는 경우에는 더욱 그렇습니다. 또한 값에 대한 문자열 만 지원합니다.

    #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의 쉘에서 다음과 같이 사용할 수 있습니다.

    >>> 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 필드에 대해 색인화 된 쿼리를 발행 할 수 있습니다.

    # 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')    

    JSON 필드 :

    JSON / JSONB 필드는 키 / 값 쌍뿐만 아니라 Hstore보다 더 빠르며 (JSONB의 경우) 더 컴팩트 한 경향이있는 모든 JSON 입력 가능 데이터 유형을 지원합니다. django-pgfields를 포함하여 여러 패키지가 JSON / JSONB 필드를 구현 하지만 Django 1.9부터 JSONField 는 JSONB를 사용하여 저장됩니다. JSONFieldHStoreField 와 유사하며 큰 사전에서 더 잘 수행 될 수 있습니다. 또한 정수, 부울 및 중첩 사전과 같이 문자열 이외의 유형도 지원합니다.

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

    쉘에서 생성 :

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

    중첩 된 쿼리를 제외하고 인덱스 된 쿼리는 HStoreField와 거의 동일합니다. 복잡한 인덱스는 수동 생성 (또는 스크립트 마이그레이션)이 필요할 수 있습니다.

    >>> Something.objects.filter(data__a=1)
    >>> Something.objects.filter(data__nested__c=3)
    >>> Something.objects.filter(data__has_key='a')
  3. 장고 몽고 DB

    또는 다른 NoSQL Django 어댑테이션을 사용하면 완전히 동적 인 모델을 가질 수 있습니다.

    NoSQL Django 라이브러리는 훌륭하지만 Django와 100 % 호환되지는 않습니다. 예를 들어 표준 Django에서 Django-nonrel 로 마이그레이션하려면 ManyToMany를 ListField 로 대체해야합니다 .

    이 Django MongoDB 예제를 확인하십시오.

    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, ...}

    Django 모델의 내장 목록 을 만들 수도 있습니다 .

    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. 장고 돌연변이 : syncdb 및 사우스 훅을 기반으로하는 동적 모델

    Django-mutant 는 완전 동적 외래 키 및 m2m 필드를 구현합니다. Will Hardy의 믿을 수는 없지만 다소 해킹 된 솔루션에서 영감을 얻었습니다. 와 Michael Hall의 .

    이 모든 것들이 Django South 훅을 기반으로 하고 있습니다. DjangoCon 2011에서의 Will Hardy의 이야기에 따르면 (볼 수 있습니다!) 그럼에도 불구하고 생산에서 강력하고 테스트를 거쳤습니다 ( 관련 소스 코드). ).

    이것을 최초로 구현 한 것은 Michael Hall 이었습니다 이었습니다.

    예, 이것은 마술입니다. 이러한 접근 방식을 사용하면 관계형 데이터베이스 백엔드로 완전히 역동적 인 Django 앱, 모델 및 필드 를 얻을 수 있습니다 . 그러나 어떤 비용으로? 많이 사용하면 응용 프로그램의 안정성이 저하됩니까? 이들은 고려해야 할 질문입니다. 적절한 잠금 장치 를 유지해야합니다동시 데이터베이스 변경 요청을 허용하려면 합니다.

    Michael Halls lib를 사용하는 경우 코드는 다음과 같습니다.

    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
:이 주제는 최근 DjangoCon 2013 유럽에 대해 이야기했다 slideshare.net/schacki/...을 하고 youtube.com/watch?v=67wcGdk4aCc
체하는 랜드 그래프

또한 사용하는 것을주의 가치가있을 수 있습니다 장고 - pgjson을 포스트 그레스> = 9.2에하는 PostgreSQL의의 JSON 필드를 직접 사용할 수 있습니다. Django> = 1.7에서 쿼리에 대한 필터 API는 비교적 제정신입니다. Postgres> = 9.4는 jsonb 필드가 더 빠른 쿼리를 위해 더 나은 색인을 갖도록 허용합니다.
GDorn

1
Django가 HStoreField 및 JSONField를 고려하여 채택하도록 오늘 업데이트되었습니다. 굉장하지 않은 양식 위젯이 포함되어 있지만 관리자의 데이터를 조정 해야하는 경우 작동합니다.
GDorn

13

장고 다이나모 아이디어를 더욱 발전시키기 위해 노력하고 있습니다. 프로젝트는 아직 문서화되어 있지 않지만 https://github.com/charettes/django-mutant 에서 코드를 읽을 수 있습니다 .

실제로 FK 및 M2M 필드 (contrib.related 참조)도 작동하며 사용자 정의 필드에 대한 래퍼를 정의 할 수도 있습니다.

unique_together 및 ordering plus Model base와 같은 모델 옵션도 지원하므로 모델 프록시, 추상 또는 믹스 인을 서브 클래 싱 할 수 있습니다.

실제로 메모리가 아닌 잠금 메커니즘을 사용하여 모델 정의가 여러 장고 실행 인스턴스에서 공유 될 수 있도록하고 오래된 정의를 사용하지 못하게합니다.

프로젝트는 여전히 알파이지만 내 프로젝트 중 하나의 초석 기술이므로 생산 준비에 가져 가야합니다. 큰 계획은 django-nonrel도 지원하므로 mongodb 드라이버를 활용할 수 있습니다.


1
안녕, 사이먼! github 에서 프로젝트를 만든 직후 위키 답변에 프로젝트 링크를 포함 시켰 습니다. :))) stackoverflow에서 만나서 반가워요!
Ivan Kharlamov 2018 년

4

추가 연구에 따르면 이것은 장고에 대해 몇 가지 패키지로 구현 된 엔티티 속성 값 디자인 패턴 의 다소 특별한 경우입니다 .

먼저, PyPi에 있는 최초의 eav-django 프로젝트가 있습니다.

둘째, 첫 번째 프로젝트 인 django-eav 는 최근 에 django의 자체 모델 또는 타사 앱에서 EAV를 사용할 수 있도록하는 리 팩터 인 django-eav 입니다.


나는 그것을 위키에 포함시킬 것이다.
Ivan Kharlamov 2018

1
다른 방법으로 EAV는 동적 모델링의 특별한 경우라고 주장합니다. "시맨틱 웹"커뮤니티에서 많이 사용되며, 고유 ID가 포함 된 경우 "트리플"또는 "쿼드"라고합니다. 그러나 동적으로 SQL 테이블을 작성하고 수정할 수있는 메커니즘만큼 효율적일 수는 없습니다.
Cerin

@GDom은 eav-django가 당신의 첫 번째 선택입니까? 위의 어떤 옵션을 선택 했습니까?
Moreno

1
@Moreno 올바른 선택은 특정 사용 사례에 따라 크게 달라집니다. 나는 다른 이유로 EAV와 JsonFields를 모두 사용했습니다. 후자는 Django에서 직접 지원하므로 새 프로젝트의 경우 EAV 테이블에서 쿼리 할 필요가없는 한 먼저 사용합니다. JsonFields에서도 쿼리 할 수 ​​있습니다.
GDorn
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.