Trường mô hình động Django


161

Tôi đang làm việc trên một ứng dụng nhiều bên thuê, trong đó một số người dùng có thể xác định các trường dữ liệu của riêng họ (thông qua quản trị viên) để thu thập dữ liệu bổ sung trong các biểu mẫu và báo cáo về dữ liệu. Bit sau làm cho JSONField không phải là một lựa chọn tuyệt vời, vì vậy thay vào đó tôi có giải pháp sau:

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

Lưu ý cách CustomDataField có ForeignKey to Site - mỗi Trang web sẽ có một bộ trường dữ liệu tùy chỉnh khác nhau, nhưng sử dụng cùng một cơ sở dữ liệu. Sau đó, các trường dữ liệu cụ thể khác nhau có thể được định nghĩa là:

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

Điều này dẫn đến việc sử dụng sau đây:

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?

Nhưng điều này cảm thấy rất cồng kềnh, đặc biệt với nhu cầu tạo thủ công các dữ liệu liên quan và liên kết nó với mô hình cụ thể. Có một cách tiếp cận tốt hơn?

Các tùy chọn đã bị loại bỏ trước:

  • SQL tùy chỉnh để sửa đổi các bảng đang hoạt động. Một phần vì điều này sẽ không mở rộng quy mô và một phần vì nó quá nhiều hack.
  • Các giải pháp không có lược đồ như NoQuery. Tôi không có gì chống lại họ, nhưng họ vẫn không phù hợp. Cuối cùng, dữ liệu này được nhập và khả năng sử dụng ứng dụng báo cáo của bên thứ ba.
  • JSONField, như được liệt kê ở trên, vì nó sẽ không hoạt động tốt với các truy vấn.

6
Trước emptively, đây không phải là bất kỳ những câu hỏi sau: stackoverflow.com/questions/7801729/... stackoverflow.com/questions/2854656/...
GDorn

Câu trả lời:


277

Cho đến hôm nay, có bốn cách tiếp cận khả dụng, hai trong số chúng yêu cầu một phụ trợ lưu trữ nhất định:

  1. Django-eav (gói ban đầu không còn được bảo dưỡng nữa nhưng có một số nhánh phát triển mạnh )

    Giải pháp này dựa trên mô hình dữ liệu Giá trị thuộc tính thực thể , về cơ bản, nó sử dụng một số bảng để lưu trữ các thuộc tính động của các đối tượng. Phần lớn về giải pháp này là nó:

    • sử dụng một số mô hình Django thuần túy và đơn giản để biểu diễn các trường động, giúp cho việc hiểu và cơ sở dữ liệu trở nên đơn giản;
    • cho phép bạn gắn / tách hiệu quả lưu trữ thuộc tính động vào mô hình Django bằng các lệnh đơn giản như:

      eav.unregister(Encounter)
      eav.register(Patient)
    • Tích hợp độc đáo với quản trị viên Django ;

    • Đồng thời là thực sự mạnh mẽ.

    Nhược điểm:

    • Không hiệu quả lắm. Đây là một sự chỉ trích nhiều hơn về chính mẫu EAV, đòi hỏi phải hợp nhất thủ công dữ liệu từ định dạng cột thành một tập hợp các cặp khóa-giá trị trong mô hình.
    • Khó bảo trì hơn. Việc duy trì tính toàn vẹn dữ liệu đòi hỏi một ràng buộc khóa duy nhất nhiều cột, có thể không hiệu quả trên một số cơ sở dữ liệu.
    • Bạn sẽ cần phải chọn một trong các dĩa , vì gói chính thức không còn được duy trì và không có nhà lãnh đạo rõ ràng.

    Việc sử dụng khá đơn giản:

    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. Các trường Hstore, JSON hoặc JSONB trong PostgreSQL

    PostgreSQL hỗ trợ một số loại dữ liệu phức tạp hơn. Hầu hết được hỗ trợ thông qua các gói của bên thứ ba, nhưng trong những năm gần đây Django đã áp dụng chúng vào django.contrib.postgres.fields.

    HStoreField :

    Django-hstore ban đầu là gói của bên thứ ba, nhưng Django 1.8 đã thêm HStoreField dưới dạng tích hợp, cùng với một số loại trường khác được PostgreQuery hỗ trợ.

    Cách tiếp cận này tốt theo nghĩa là nó cho phép bạn có cả hai thế giới tốt nhất: trường động và cơ sở dữ liệu quan hệ. Tuy nhiên, hstore không phảihiệu suất lý tưởng , đặc biệt nếu bạn cuối cùng sẽ lưu trữ hàng ngàn mặt hàng trong một lĩnh vực. Nó cũng chỉ hỗ trợ chuỗi cho các giá trị.

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

    Trong shell của Django, bạn có thể sử dụng nó như thế này:

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

    Bạn có thể đưa ra các truy vấn được lập chỉ mục đối với các trường 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')    

    Năng suất JSON :

    Các trường JSON / JSONB hỗ trợ bất kỳ loại dữ liệu có thể mã hóa JSON nào, không chỉ các cặp khóa / giá trị, mà còn có xu hướng nhanh hơn và (đối với JSONB) nhỏ gọn hơn Hstore. Một số gói triển khai các trường JSON / JSONB bao gồm django-pgfields , nhưng kể từ Django 1.9, JSONField là một tích hợp sử dụng JSONB để lưu trữ. JSONField tương tự như HStoreField và có thể hoạt động tốt hơn với các từ điển lớn. Nó cũng hỗ trợ các loại khác ngoài chuỗi, chẳng hạn như số nguyên, booleans và từ điển lồng nhau.

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

    Tạo trong trình bao:

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

    Các truy vấn được lập chỉ mục gần giống với HStoreField, ngoại trừ việc lồng nhau là có thể. Các chỉ mục phức tạp có thể yêu cầu tạo thủ công (hoặc di chuyển theo kịch bản).

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

    Hoặc các điều chỉnh khác của NoQuery Django - với chúng, bạn có thể có các mô hình hoàn toàn năng động.

    Các thư viện Django của NoQuery rất tuyệt, nhưng hãy nhớ rằng chúng không tương thích 100% với Django, ví dụ, để di chuyển sang Django-nonrel từ Django tiêu chuẩn, bạn sẽ cần thay thế ManyToMany bằng ListField trong số những thứ khác.

    Kiểm tra ví dụ Django MongoDB này:

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

    Bạn thậm chí có thể tạo danh sách nhúng của bất kỳ mô hình Django nào:

    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: Các mô hình động dựa trên syncdb và South-hook

    Django-mutant thực hiện các trường Foreign Key và m2m hoàn toàn năng động. Và được lấy cảm hứng từ các giải pháp đáng kinh ngạc nhưng hơi khó tin của Will Hardy và Michael Hall.

    Tất cả những thứ này đều dựa trên móc Django South, mà theo Will Hardy tại DjangoCon 2011 (xem nó!) Tuy nhiên vẫn mạnh mẽ và được thử nghiệm trong sản xuất ( mã nguồn có liên quan ).

    Đầu tiên để thực hiện điều nàyMichael Hall .

    Vâng, đây là điều kỳ diệu, với những cách tiếp cận này, bạn có thể đạt được các ứng dụng, mô hình và trường Django hoàn toàn năng động với bất kỳ phụ trợ cơ sở dữ liệu quan hệ nào. Nhưng với chi phí nào? Sự ổn định của ứng dụng sẽ bị ảnh hưởng khi sử dụng nặng? Đây là những câu hỏi được xem xét. Bạn cần chắc chắn duy trì một khóa thích hợp để cho phép các yêu cầu thay đổi cơ sở dữ liệu đồng thời.

    Nếu bạn đang sử dụng lib Michael Halls, mã của bạn sẽ như thế này:

    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
chủ đề này gần đây đã nói về tại DjangoCon 2013 châu Âu: slideshare.net/schacki/...youtube.com/watch?v=67wcGdk4aCc
Aleck Landgraf

Cũng có thể đáng chú ý rằng việc sử dụng django-pgjson trên Postgres> = 9.2 cho phép sử dụng trực tiếp trường json của postgresql. Trên Django> = 1.7, API bộ lọc cho các truy vấn tương đối lành mạnh. Postgres> = 9,4 cũng cho phép các trường jsonb có chỉ mục tốt hơn để truy vấn nhanh hơn.
GDorn

1
Cập nhật ngày hôm nay để lưu ý việc áp dụng HStoreField và JSONField của Django vào đóng góp. Nó bao gồm một số tiện ích biểu mẫu không tuyệt vời, nhưng sẽ hoạt động nếu bạn cần điều chỉnh dữ liệu trong quản trị viên.
GDorn

13

Tôi đã làm việc để thúc đẩy ý tưởng django-máy phát điện hơn nữa. Dự án vẫn chưa có giấy tờ nhưng bạn có thể đọc mã tại https://github.com/charettes/django-mutant .

Trên thực tế, các trường FK và M2M (xem contrib.related) cũng hoạt động và thậm chí có thể xác định trình bao bọc cho các trường tùy chỉnh của riêng bạn.

Ngoài ra còn có hỗ trợ cho các tùy chọn mô hình như unique_together và sắp xếp cộng với các cơ sở Mô hình để bạn có thể phân lớp proxy mô hình, trừu tượng hoặc mixins.

Tôi thực sự đang làm việc trên một cơ chế khóa không có trong bộ nhớ để đảm bảo các định nghĩa mô hình có thể được chia sẻ qua nhiều trường hợp chạy django trong khi ngăn chúng sử dụng định nghĩa lỗi thời.

Dự án vẫn còn rất alpha nhưng đó là công nghệ nền tảng cho một trong những dự án của tôi nên tôi sẽ phải đưa nó vào sản xuất. Kế hoạch lớn cũng hỗ trợ django-nonrel để chúng tôi có thể tận dụng trình điều khiển mongodb.


1
Xin chào, Simon! Tôi đã bao gồm một liên kết đến dự án của bạn trong câu trả lời wiki của tôi ngay sau khi bạn tạo nó trên github. :))) Rất vui được gặp bạn trên stackoverflow!
Ivan Kharlamov

4

Nghiên cứu sâu hơn cho thấy rằng đây là một trường hợp hơi đặc biệt của mẫu thiết kế Giá trị thuộc tính thực thể , đã được triển khai cho Django bởi một vài gói.

Đầu tiên, có dự án eav-django ban đầu , trên PyPi.

Thứ hai, có một ngã ba gần đây của dự án đầu tiên, django-eav , chủ yếu là một công cụ tái cấu trúc để cho phép sử dụng EAV với các mô hình hoặc mô hình riêng của django trong các ứng dụng của bên thứ ba.


Tôi sẽ đưa nó vào wiki.
Ivan Kharlamov

1
Tôi sẽ tranh luận theo cách khác, rằng EAV là một trường hợp đặc biệt của mô hình động. Nó được sử dụng nhiều trong cộng đồng "web ngữ nghĩa" nơi nó được gọi là "bộ ba" hoặc "bộ tứ" nếu nó bao gồm một ID duy nhất. Tuy nhiên, nó không bao giờ có hiệu quả như một cơ chế có thể tự động tạo và sửa đổi các bảng SQL.
Cerin

@GDom là eav-django sự lựa chọn đầu tiên của bạn? Ý tôi là bạn đã chọn phương án nào ở trên?
Moreno

1
@Moreno Sự lựa chọn đúng đắn sẽ phụ thuộc rất nhiều vào trường hợp sử dụng cụ thể của bạn. Tôi đã sử dụng cả EAV và JsonField vì những lý do khác nhau. Cái sau được Django hỗ trợ trực tiếp ngay bây giờ, vì vậy đối với một dự án mới tôi sẽ sử dụng nó trước trừ khi tôi có nhu cầu cụ thể để có thể truy vấn trên bảng EAV. Lưu ý rằng bạn cũng có thể truy vấn trên JsonFields.
GDorn
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.