首页 文章

在django中创建动态模型字段

提问于
浏览
15

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

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

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

这里有两个问题:

  • 如何提供用户可以在运行时添加新字段的表单?

  • 如何将字段添加到数据库中以便以后检索/查询?

4 回答

  • 8

    有几种方法:

    • 键/值模型(简单,支持良好)

    • TextField中的JSON数据(简单,灵活,无法轻松搜索/索引)

    • 动态模型定义(不那么容易,很多隐藏的问题)

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

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

    Dynamic models

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

    • 1或2个模型用于存储字段的名称和类型

    • (可选)用于定义(子类)动态模型的通用功能的抽象模型

    • 在需要时构建(或重建)动态模型的函数

    • 在添加/删除/重命名字段时构建或更新数据库表的代码

    1. Storing the model definition

    这取决于你 . 我想你会有一个模型 CustomCarModelCustomField 让用户/管理员定义和存储你想要的字段的名称和类型 . 您不必直接镜像Django字段,您可以创建自己的类型,用户可以更好地理解 .

    使用带有内联formset的 forms.ModelForm 来让用户构建自定义类 .

    2. Abstract model

    同样,这很简单,只需使用所有动态模型的公共字段/方法创建基本模型 . 使这个模型抽象化 .

    3. Build a dynamic model

    定义一个获取所需信息的函数(可能是#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. Code to update the database tables

    上面的代码将为您生成动态模型,但不会创建数据库表 . 我建议使用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()
    

    Problems

    • 在运行创建它们的代码之前,您的模型对Django是隐藏的 . 但是,您可以为定义模型的 class_prepared 信号中的每个定义实例运行 get_custom_car_model .

    • ForeignKeys / ManyToManyFields 可能无效(我还没试过)

    • 您将希望使用Django 's model cache so you don' t必须运行查询并在每次要使用时创建模型 . 为简单起见,我把它留在了上面

    • 您可以将动态模型放入管理员,但您还需要动态创建管理类,并使用信号正确注册/重新注册/取消注册 .

    Overview

    如果你正在运行,它的工作原理与预期完全一样,感谢Django和Python 's flexibility. You can feed your model into Django' s ModelForm 让用户编辑他们的实例,并使用数据库执行查询's fields directly. If there is anything you don' t了解上面的内容,你're probably best off not taking this approach (I' ve故意不解释一些什么的概念适合初学者) . 把事情简单化!

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

  • 26

    Database

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

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

    所以试试吧:

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

    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)
    

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

    Forms

    基本上,使用此数据库设计,您需要两种表单来将对象添加到数据库中 . 具体来说,车辆为model form,属性为model formset . 您可以使用jQuery在 Attribute formset上动态添加更多项目 .


    Note

    您还可以将 Attribute 类分隔为 AttributeTypeAttributeValue ,这样您就不会在数据库中存储冗余属性类型,或者您希望限制用户的属性选择,但是可以使用Django管理站点添加更多类型 .

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

    提示:了解有关database normalization的更多信息 .


    Other solutions

    正如之前的回答中所建议的Stuart Marsh

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

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

  • 2

    这是我在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]
    

    看起来非常简单 - 不确定为什么它不应该被认为是一个选项 - 反射是非常常见的其他语言,如C#或Java-无论如何我是django事物的新手 -

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

    等等

相关问题