2010-09-14 55 views
15

這是一個關於django的問題。 我有一個模型說「汽車」。這將有一些基本的領域,如「顏色」,「車主名稱」,「車輛成本」。在django中創建動態模型字段

我想提供一個表格,用戶可以根據他添加的汽車添加額外的字段。例如,如果用戶添加「汽車」,他會在表單中額外的字段,在運行時動態顯示,如「Car Milage」,「Cal製造商」。 假設用戶想要添加一個「卡車」,他會添加「可以載入的載入」,「許可證」等。

如何在django中實現此目的?這裏

有兩個問題:

  1. 如何提供一個表單,用戶可以在運行時添加新的領域?
  2. 如何將字段添加到數據庫中,以便稍後可以檢索/查詢它?
+2

**你可能想看看這個問題的主題:http://stackoverflow.com/q/7933596/497056 – 2012-02-19 13:40:31

回答

1

你是說在前端界面還是在Django管理員?

如果沒有大量的工作,你就無法像這樣創建真正的領域。 Django中的每個模型和字段在數據庫中都有一個關聯的表和列。添加新字段通常需要原始sql或使用South進行遷移。

從前端界面,您可以創建僞字段,並將它們以json格式存儲在單個模型字段中。

例如,在模型中創建other_data文本字段。然後允許用戶創建字段,並將它們存儲爲{'userfield':'userdata','mileage':54}

但是我認爲如果您使用的是像車輛這樣的有限類,您可以創建一個基本模型具有基本的車輛特性,然後創建從每種車型的基本模型繼承的模型。

class base_vehicle(models.Model): 
    color = models.CharField() 
    owner_name = models.CharField() 
    cost = models.DecimalField() 

class car(base_vehicle): 
    mileage = models.IntegerField(default=0) 

25

有幾個方法:

  • 鍵/值模型(簡單,很好的支持)
  • JSON數據的文本字段(方便,靈活,不能搜索/索引很容易)
  • 動態模型定義(不那麼容易,很多隱藏問題)

這聽起來像你想要的最後一個,但我不知道這是最適合你的。 Django非常易於更改/更新,如果系統管理員需要額外的字段,只需爲它們添加它們並使用south進行遷移即可。我不喜歡通用鍵/值數據庫模式,像Django這樣的強大框架的重點在於,您可以輕鬆編寫和重寫自定義模式,而無需使用通用方法。

如果您必須允許網站用戶/管理員直接定義他們的數據,我相信其他人會告訴您如何執行上述前兩種方法。第三種方法是你所要求的,並且更瘋狂一些,我會告訴你如何去做。我不建議在幾乎所有情況下都使用它,但有時候這是適當的。

動態模型

一旦你知道該怎麼做,這是相對簡單的。你需要:

  • 1或2個型號存儲字段的名稱和類型
  • (可選)的抽象模型,爲您的(子類)動態模型
  • 函數定義常用功能需要
  • 代碼來構建或更新數據庫表時,當字段被添加/移除建立(或重建)的動態模型/重命名

1.存儲所述模型定義

這取決於你。我想你會有一個模型CustomCarModelCustomField讓用戶/管理員定義和存儲你想要的字段的名稱和類型。您不必直接鏡像Django字段,您可以製作自己的類型,用戶可以更好地理解。

使用帶內聯窗體集的forms.ModelForm可讓用戶構建自定義類。

2.抽象模型

再次,這很簡單,只需創建爲您的所有動態模型的公共字段/方法的基本模型。使這個模型抽象。

3.建立動態模型

定義一個函數,它需要的信息(可能的#1你的類的實例),併產生一個模型類。這是一個基本的例子:

from django.db.models.loading import cache 
from django.db import models 


def get_custom_car_model(car_model_definition): 
    """ Create a custom (dynamic) model class based on the given definition. 
    """ 
    # What's the name of your app? 
    _app_label = 'myapp' 

    # you need to come up with a unique table name 
    _db_table = 'dynamic_car_%d' % car_model_definition.pk 

    # you need to come up with a unique model name (used in model caching) 
    _model_name = "DynamicCar%d" % car_model_definition.pk 

    # Remove any exist model definition from Django's cache 
    try: 
    del cache.app_models[_app_label][_model_name.lower()] 
    except KeyError: 
    pass 

    # We'll build the class attributes here 
    attrs = {} 

    # Store a link to the definition for convenience 
    attrs['car_model_definition'] = car_model_definition 

    # Create the relevant meta information 
    class Meta: 
     app_label = _app_label 
     db_table = _db_table 
     managed = False 
     verbose_name = 'Dynamic Car %s' % car_model_definition 
     verbose_name_plural = 'Dynamic Cars for %s' % car_model_definition 
     ordering = ('my_field',) 
    attrs['__module__'] = 'path.to.your.apps.module' 
    attrs['Meta'] = Meta 

    # All of that was just getting the class ready, here is the magic 
    # Build your model by adding django database Field subclasses to the attrs dict 
    # What this looks like depends on how you store the users's definitions 
    # For now, I'll just make them all CharFields 
    for field in car_model_definition.fields.all(): 
    attrs[field.name] = models.CharField(max_length=50, db_index=True) 

    # Create the new model class 
    model_class = type(_model_name, (CustomCarModelBase,), attrs) 

    return model_class 

4.代碼更新數據庫表

上面的代碼會爲你生成一個動態模型,但不會創建數據庫表。我建議使用South進行表格操作。這裏有幾個功能,你可以連接到保存前/保存後的信號:

import logging 
from south.db import db 
from django.db import connection 

def create_db_table(model_class): 
    """ Takes a Django model class and create a database table, if necessary. 
    """ 
    table_name = model_class._meta.db_table 
    if (connection.introspection.table_name_converter(table_name) 
        not in connection.introspection.table_names()): 
    fields = [(f.name, f) for f in model_class._meta.fields] 
    db.create_table(table_name, fields) 
    logging.debug("Creating table '%s'" % table_name) 

def add_necessary_db_columns(model_class): 
    """ Creates new table or relevant columns as necessary based on the model_class. 
    No columns or data are renamed or removed. 
    XXX: May need tweaking if db_column != field.name 
    """ 
    # Create table if missing 
    create_db_table(model_class) 

    # Add field columns if missing 
    table_name = model_class._meta.db_table 
    fields = [(f.column, f) for f in model_class._meta.fields] 
    db_column_names = [row[0] for row in connection.introspection.get_table_description(connection.cursor(), table_name)] 

    for column_name, field in fields: 
    if column_name not in db_column_names: 
     logging.debug("Adding field '%s' to table '%s'" % (column_name, table_name)) 
     db.add_column(table_name, column_name, field) 

那裏你有它!您可以撥打get_custom_car_model()提供一個Django模型,你可以用它做普通的Django查詢:

CarModel = get_custom_car_model(my_definition) 
CarModel.objects.all() 

問題

  • 你的模型是從Django中隱藏,直到代碼創建它們運行。但是,您可以在class_prepared信號中爲定義模型運行get_custom_car_model,以獲得您定義的每個實例。
  • ForeignKeys/ManyToManyFields可能無法正常工作(我沒試過)
  • 您將要使用Django的模型緩存,所以你不必運行查詢和創建模型要使用這個每一次。爲簡單起見,我已將上述內容留在了上面
  • 您可以將動態模型添加到管理員中,但您還需要動態創建管理員類,並使用信號適當地註冊/重新註冊/取消註冊。

概述

如果你是罰款增加複雜性和問題,盡情享受!它正在運行,它的工作原理與Django和Python的靈活性一樣。您可以將模型提供給Django的ModelForm以讓用戶編輯他們的實例,並直接使用數據庫的字段執行查詢。如果上面有任何你不明白的地方,你可能最好不要採用這種方法(我故意沒有解釋一些概念適用於初學者)。把事情簡單化!

我真的不認爲很多人需要這個,但是我自己也使用過它,我們在表中有很多數據,真的需要讓用戶自定義列,而這些列很少會改變。

+0

+1給原來的問題的解決方案,順便說一句好的時機:) – 2010-09-14 22:59:24

+0

不幸的是,你不能使用Django 1.8+以南。你認爲它仍然可以在運行時創建表嗎?我嘗試使用django遷移,但沒有成功 – 2016-01-28 14:21:01

7

數據庫

考慮您的數據庫設計一次。

你應該根據你想表示的那些對象如何在現實世界中相互關聯,然後嘗試儘可能多地概括這些關係(所以不要說每輛卡車都有許可證,你說每輛車都有一個屬性,可以是許可證,裝載量或其他)。

所以讓我們嘗試一下:

如果你說你有一個車輛每輛車可以有許多用戶指定的屬性考慮以下型號:

class Attribute(models.Model): 
    type = models.CharField() 
    value = models.CharField() 

class Vehicle(models.Model): 
    attribute = models.ManyToMany(Attribute) 

正如前面提到的,這是一個總體思路這使您可以根據需要爲每輛車添加儘可能多的屬性。

如果您想讓特定的一組屬性可供用戶使用,您可以在Attribute.type字段中使用choices

ATTRIBUTE_CHOICES = (
    (1, 'Permit'), 
    (2, 'Manufacturer'), 
) 
class Attribute(models.Model): 
    type = models.CharField(max_length=1, choices=ATTRIBUTE_CHOICES) 
    value = models.CharField() 

現在,也許你會希望每個車輛都有它自己的可用屬性集。這可以通過添加另一個模型並將來自VehicleAttribute模型的外鍵關係設置爲它來完成。

class VehicleType(models.Model): 
    name = models.CharField() 

class Attribute(models.Model): 
    vehicle_type = models.ForeigngKey(VehicleType) 
    type = models.CharField() 
    value = models.CharField() 

class Vehicle(models.Model): 
    vehicle_type = models.ForeigngKey(VehicleType) 
    attribute = models.ManyToMany(Attribute) 

這樣您就可以清楚地瞭解每個屬性與某些車輛的關係。

形式

基本上,這個數據庫的設計,您將需要兩個表格對象添加到數據庫中。具體來說,車輛的model form和屬性的model formset。您可以使用jQuery在Attribute表單集上動態添加更多項目。


你也可以分開AttributeAttributeTypeAttributeValue所以你不必存儲在數據庫中,或者如果要限制對屬性的選擇冗餘屬性類型用戶,但保留添加更多類型與Django管理站點的能力。

要完全酷,您可以在表單上使用自動填充功能向用戶建議現有屬性類型。

提示:瞭解有關database normalization的更多信息。


其他解決方案

正如Stuart Marsh

在前面的答案建議在另一方面,你可以硬編碼您的模型對每個車輛類型,這樣的車輛類型是由代表基礎車輛的子類和每個子類都可以有其自己的特定屬性,但解決方案不是非常靈活(如果需要靈活性)。

您還可以在一個數據庫字段中保留附加對象屬性的JSON表示,但我不確定在查詢屬性時這會有幫助。

+0

+1:如果Django中的某些東西似乎太難了,請重新考慮一下你的方法,因爲幾乎總是有一種優雅的方式來實現你想要的結果。 – 2010-09-14 23:00:54

2

這是我在Django簡單的測試殼 - 我剛纔輸入,它似乎工作細



    In [25]: attributes = { 
       "__module__": "lekhoni.models", 
       "name": models.CharField(max_length=100), 
       "address": models.CharField(max_length=100), 
      } 

    In [26]: Person = type('Person', (models.Model,), attributes) 

    In [27]: Person 
    Out[27]: class 'lekhoni.models.Person' 

    In [28]: p1= Person() 

    In [29]: p1.name= 'manir' 

    In [30]: p1.save() 

    In [31]: Person.objects.a 
    Person.objects.aggregate Person.objects.all  Person.objects.annotate 

    In [32]: Person.objects.all() 

    Out[33]: [Person: Person object] 

似乎很簡單 - 不知道爲什麼它不應該是一個考慮的一個選項 - 反思是非常常見的是其他語言,如C#或Java-無論如何,我對django的東西很陌生 -