在django中创建动态模型字段

the*_*ent 16 django dynamic models

这是关于django的问题.我有一个模特说"汽车".这将有一些基本字段,如"颜色","车辆所有者名称","车辆成本".

我想提供一个表单,用户可以根据他添加的汽车添加额外的字段.例如,如果用户正在添加"Car",则他将在运行时动态地在表单中添加额外字段,例如"Car Milage","Cal Manufacturer".假设用户想要添加"卡车",他将添加"可以携带的负载","允许"等.

我如何在django中实现这一目标?

这里有两个问题:

  1. 如何提供用户可以在运行时添加新字段的表单?
  2. 如何将字段添加到数据库中以便以后检索/查询?

Wil*_*rdy 27

有几种方法:

  • 键/值模型(简单,支持良好)
  • TextField中的JSON数据(简单,灵活,无法轻松搜索/索引)
  • 动态模型定义(不那么容易,很多隐藏的问题)

听起来你想要最后一个,但我不确定它对你来说是最好的.Django很容易更改/更新,如果系统管理员想要额外的字段,只需为它们添加它们并使用south进行迁移.我不喜欢通用键/值数据库模式,像Django这样强大的框架的重点在于,您可以轻松编写和重写自定义模式,而无需采用通用方法.

如果您必须允许站点用户/管理员直接定义他们的数据,我相信其他人会告诉您如何执行上述前两种方法.第三种方法是你要求的,而且有点疯狂,我会告诉你该怎么做.我不建议在几乎所有情况下使用它,但有时它是合适的.

动态模型

一旦你知道该怎么做,这是相对简单的.你需要:

  • 1或2个模型用于存储字段的名称和类型
  • (可选)用于定义(子类)动态模型的通用功能的抽象模型
  • 在需要时构建(或重建)动态模型的函数
  • 添加/删除/重命名字段时构建或更新数据库表的代码

1.存储模型定义

这取决于你.我想你会有一个模型CustomCarModel,CustomField让用户/管理员定义和存储你想要的字段的名称和类型.您不必直接镜像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
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

你有它!您可以调用get_custom_car_model()提供django模型,您可以使用它来执行常规的django查询:

CarModel = get_custom_car_model(my_definition)
CarModel.objects.all()
Run Code Online (Sandbox Code Playgroud)

问题

  • 在运行创建它们的代码之前,您的模型对Django是隐藏的.但是get_custom_car_model,您可以在class_prepared定义模型的信号中运行定义的每个实例.
  • ForeignKeys/ ManyToManyFields可能不起作用(我还没试过)
  • 您将需要使用Django的模型缓存,因此您不必每次要使用它时都运行查询并创建模型.为简单起见,我把它留在了上面
  • 您可以将动态模型放入管理员,但您还需要动态创建管理类,并使用信号正确注册/重新注册/取消注册.

概观

如果您对增加的并发症和问题感到满意,请尽情享受!由于Django和Python的灵活性,它正在运行,它完全按预期工作.您可以将模型提供给Django ModelForm,让用户编辑他们的实例,并直接使用数据库的字段执行查询.如果上面有任何你不理解的内容,你可能最好不采取这种方法(我故意没有解释一些初学者的概念).把事情简单化!

我真的不认为很多人需要这个,但我自己使用它,我们在表中有很多数据,真的,真的需要让用户自定义列,这些列很少变化.


Dav*_*cic 8

数据库

再考虑一下您的数据库设计.

你应该考虑一下你想要代表的物体如何在现实世界中相互关联,然后尽可能多地概括这些关系,(所以不要说每辆卡车都有许可证,你说每辆车有一个属性,可以是许可,负载金额等等.

所以试试吧:

如果您说您有车辆且每辆车都有许多用户指定的属性,请考虑以下型号:

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

class Vehicle(models.Model):
    attribute = models.ManyToMany(Attribute)
Run Code Online (Sandbox Code Playgroud)

如前所述,这是一个通用的想法,使您可以根据需要为每辆车添加尽可能多的属性.

如果您希望用户可以使用特定的属性集,则可以choices在该Attribute.type字段中使用.

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

现在,也许您希望每辆车排序都拥有自己的可用属性集.这可以通过添加另一个模型并将两者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)
Run Code Online (Sandbox Code Playgroud)

通过这种方式,您可以清楚地了解每个属性与某些车辆的关系.

形式

基本上,使用此数据库设计,您需要两种表单来将对象添加到数据库中.具体为模型形式的用于车辆和模型表单集的属性.您可以使用jQuery在Attributeformset 上动态添加更多项.


注意

您还可以将Attribute类分隔为AttributeType,AttributeValue因此您没有在数据库中存储冗余属性类型,或者您希望限制用户的属性选择,但是能够使用Django管理站点添加更多类型.

为了完全酷,您可以在表单上使用自动填充功能向用户建议现有的属性类型.

提示:了解有关数据库规范化的更多信息.


其他方案

正如Stuart Marsh先前的回答中所建议的那样

另一方面,您可以为每种车型硬编码模型,以便每种车辆类型由基础车辆的子类表示,每个子类可以有自己的特定属性,但解决方案不是很灵活(如果您需要灵活性) .

您还可以在一个数据库字段中保留其他对象属性的JSON表示,但我不确定在查询属性时这会有用.


Mar*_*man 5

这是我在 django shell 中的简单测试 - 我刚刚输入,它似乎工作正常 -



    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]

Run Code Online (Sandbox Code Playgroud)

它看起来很简单 - 不知道为什么它不应该被视为一个选项 - 反射在其他语言(如 C# 或 Java)中很常见 - 不管怎样,我对 django 很陌生 -


Stu*_*rsh 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)
Run Code Online (Sandbox Code Playgroud)

ETC