장고 동적 모델 필드
저는 관리자를 통해 일부 사용자가 자신의 데이터 필드를 정의하여 양식의 추가 데이터를 수집하고 데이터에 대해 보고할 수 있는 멀티 테넌트(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가지는 특정 스토리지 백엔드를 필요로 합니다.
장고이브(원래 패키지는 더 이상 유지보수되지 않지만 일부 번창하는 포크가 있음)
이 솔루션은 엔티티 속성 값 데이터 모델을 기반으로 하며, 기본적으로 여러 테이블을 사용하여 개체의 동적 속성을 저장합니다.이 솔루션의 장점은 다음과 같습니다.
- 여러 개의 순수하고 간단한 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)
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')
-
또는 다른 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')] )
장고 돌연변이: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
'programing' 카테고리의 다른 글
Python이 동일한 폴더에서 모듈을 찾을 수 없음 (0) | 2023.06.20 |
---|---|
상태 변수 트리거 변환 오류 (0) | 2023.06.20 |
sql COUNT를 사용하여 변수 설정 (0) | 2023.06.20 |
Git - 로컬 변경사항을 무시하는 준비되지 않은 파일의 체크아웃을 취소하는 방법 (0) | 2023.06.20 |
UICollectionView, 전체 너비 셀, 동적 높이 자동 레이아웃 허용? (0) | 2023.06.20 |