[python] 장고 동적 모델 필드

일부 사용자가 양식을 통해 추가 데이터를 수집하고 데이터에 대해보고하기 위해 관리자를 통해 자신의 데이터 필드를 정의 할 수 있는 다중 테넌트 응용 프로그램을 만들고 있습니다. 후자의 비트는 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입니다.


답변

현재로서는 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',
                   )

답변

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

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

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

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

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


답변

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

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

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


답변