2010-09-14 74 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的东西很陌生 -