programing

장고 동적 모델 필드

magicmemo 2023. 6. 20. 21:31
반응형

장고 동적 모델 필드

저는 관리자를 통해 일부 사용자가 자신의 데이터 필드를 정의하여 양식의 추가 데이터를 수집하고 데이터에 대해 보고할 수 있는 멀티 테넌트(Multi-tenant) 애플리케이션을 개발하고 있습니다.후자의 비트는 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가 사이트에 대한 외부 키를 갖는 방법에 주목합니다. 각 사이트는 서로 다른 사용자 정의 데이터 필드 집합을 가지지만 동일한 데이터베이스를 사용합니다.그런 다음 다양한 구체적인 데이터 필드를 다음과 같이 정의할 수 있습니다.

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 모델을 사용하여 동적 필드를 나타내므로 이해하기 쉽고 데이터베이스에 구애받지 않습니다.
    • 다음과 같은 간단한 명령을 사용하여 Django 모델에 동적 속성 스토리지를 효과적으로 연결/분리할 수 있습니다.

      eav.unregister(Encounter)
      eav.register(Patient)
      
    • Django admin과 잘 통합됩니다.

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

    단점:

    • 그다지 효율적이지 않습니다.이는 열 형식의 데이터를 모델의 키-값 쌍 집합으로 수동으로 병합해야 하는 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. Postgre의 Hstore, JSON 또는 JSONB 필드SQL

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

    Hstore 필드:

    Django-hstore는 원래 타사 패키지였지만, Django 1.8은 여러 Postgre와 함께 HStoreField를 기본 제공자로 추가했습니다.SQL 지원 필드 유형입니다.

    이 접근 방식은 동적 필드와 관계형 데이터베이스라는 두 가지 장점을 모두 가질 수 있다는 점에서 유용합니다.그러나 특히 한 필드에 수천 개의 항목을 저장하게 될 경우 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)
    

    장고의 셸에서는 다음과 같이 사용할 수 있습니다.

    >>> 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를 사용하는 내장형입니다.JSONField는 HStoreField와 유사하며 대규모 사전에서 더 나은 성능을 발휘할 수 있습니다.또한 정수, 부울 및 중첩 사전과 같은 문자열 이외의 유형도 지원합니다.

    #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 장고 적응형으로 완전히 동적인 모델을 만들 수 있습니다.

    NoSQL Django 라이브러리는 훌륭하지만, 예를 들어 표준 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 및 south-hook 기반의 동적 모델

    Django-mutant는 완전히 동적인 Foreign Key 및 m2m 필드를 구현합니다.그리고 윌 하디와 마이클 의 놀랍지만 다소 진부한 해결책에 영감을 받았습니다.

    이 모든 것은 장고 사우스 훅에 기반을 두고 있으며, 윌 하디의 장고콘 2011(watch it!)에서의 강연에 따르면 그럼에도 불구하고 강력하고 프로덕션(관련 소스 코드)에서 테스트되었습니다.

    이것을 처음으로 구현한 사람은 마이클 홀이었습니다.

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

    Michael Hallslib을 사용하는 경우 코드는 다음과 같습니다.

    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 필드(contribute.related 참조)도 작동하며 사용자 정의 필드에 대한 래퍼를 정의할 수도 있습니다.

unique_together 및 ordering plus model base와 같은 모델 옵션도 지원되므로 모델 프록시, 추상화 또는 믹스인을 하위 분류할 수 있습니다.

저는 실제로 모델 정의를 여러 django 실행 인스턴스 간에 공유할 수 있는 동시에 오래된 정의를 사용하지 못하도록 하기 위해 비메모리 잠금 메커니즘을 연구하고 있습니다.

이 프로젝트는 여전히 매우 중요하지만 제 프로젝트 중 하나의 초석이 되는 기술이기 때문에 준비된 상태로 생산에 가져가야 할 것입니다.큰 계획은 mongodb 드라이버를 활용할 수 있도록 django-nonrel도 지원하는 것입니다.

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

먼저, 파이파이에 있는 오리지널 eav-django 프로젝트가 있습니다.

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

언급URL : https://stackoverflow.com/questions/7933596/django-dynamic-model-fields

반응형