2010-09-14 31 views
15

Este es un problema relacionado con django. Tengo un modelo que dice "Automóviles". Esto tendrá algunos campos básicos como "Color", "Nombre del propietario del vehículo", "Costo del vehículo".Creación de campos de modelos dinámicos en django

Quiero proporcionar un formulario donde el usuario puede agregar campos adicionales en función del automóvil que está agregando. Por ejemplo, si el usuario está agregando un "automóvil", tendrá campos adicionales en el formulario, dinámicamente en tiempo de ejecución, como "Car Milage", "Cal Manufacturer". Suponga que si el usuario desea agregar un "Camión", agregará "Carga que se puede transportar", "Permiso", etc.

¿Cómo logro esto en django?

Hay dos cuestiones aquí:

  1. cómo proporcionar un formulario donde el usuario puede añadir nuevos campos en tiempo de ejecución?
  2. ¿Cómo agregar los campos a la base de datos para que pueda ser recuperada/consultada más tarde?
+2

** Puede que quiera echar un vistazo a esta respuesta con información reciente sobre el tema: ** http://stackoverflow.com/q/7933596/497056 –

Respuesta

1

¿Estás hablando de una interfaz de usuario o administrador de Django?

No se pueden crear campos reales sobre la marcha sin mucho trabajo bajo el capó. Cada modelo y campo en Django tiene una tabla y columna asociadas en la base de datos. Para agregar nuevos campos generalmente se requiere sql sin formato o migraciones con South.

Desde una interfaz frontal, puede crear pseudocampos y almacenarlos en formato json en un solo campo de modelo.

Por ejemplo, cree un campo de texto other_data en el modelo. Luego permita a los usuarios crear campos y almacenarlos como {'userfield': 'userdata', 'mileage': 54}

Pero creo que si usa una clase finita como vehículos, crearía un modelo base con las características básicas del vehículo, y luego cree modelos que hereden del modelo base para cada uno de los tipos de vehículos.

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

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

etc

25

Hay algunos enfoques:

clave
  • /modelo de valor (Fácil, bien apoyado)
  • datos JSON en un campo de texto (fácil, flexible, no puede buscar/indexar fácilmente)
  • Definición del modelo dinámico (no tan fácil, muchos problemas ocultos)

Parece que quieres la última, pero no estoy seguro de que sea la mejor para ti. Django es muy fácil de cambiar/actualizar, si los administradores del sistema quieren campos adicionales, solo agrégalos y utiliza el sur para migrar. No me gustan los esquemas genéricos de bases de datos clave/valor, el objetivo de un potente framework como Django es que puede escribir y reescribir fácilmente esquemas personalizados sin recurrir a enfoques genéricos.

Si debe permitir que los usuarios/administradores del sitio definan directamente sus datos, estoy seguro de que otros le mostrarán cómo hacer los dos primeros enfoques anteriores. El tercer enfoque es lo que estabas pidiendo, y un poco más loco, te mostraré cómo hacerlo. No recomiendo usarlo en casi todos los casos, pero a veces es apropiado.

modelos dinámicos

Una vez que sepa qué hacer, esto es relativamente sencillo. Necesitará:

  • 1 o 2 modelos para almacenar los nombres y tipos de los campos
  • (opcional) Un modelo abstracto para definir la funcionalidad común para sus subclases() modelos dinámicos
  • Una función para construir (o reconstruir) el modelo dinámico cuando sea necesario
  • Código para construir o actualizar las tablas de base de datos cuando se añaden campos/eliminados/renombrado

1. Almacenamiento de la definición del modelo

Depende de usted. Imagino que tendrá un modelo CustomCarModel y CustomField para permitir que el usuario/administrador defina y almacene los nombres y tipos de los campos que desee. No es necesario que duplique los campos de Django directamente, puede crear sus propios tipos que el usuario pueda comprender mejor.

Use un forms.ModelForm con formularios en línea para que el usuario cree su clase personalizada.

2. Modelo abstracto

Una vez más, esto es sencillo, basta con crear un modelo base con los campos/métodos comunes para todos los modelos dinámicos. Haz este modelo abstracto

3. Construir un modelo dinámico

Definir una función que toma la información requerida (tal vez una instancia de su clase desde el # 1) y produce una clase del modelo. Este es un ejemplo básico:

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. El código para actualizar las tablas de la base

El código anterior generará un modelo dinámico para ti, pero no va a crear las tablas de base de datos. Recomiendo usar el sur para la manipulación de la mesa. Aquí hay un par de funciones, que puede conectar a las señales de pre/post-guardado:

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) 

¡Y ya lo tiene! Puede llamar get_custom_car_model() entregar un modelo de Django, que se puede utilizar para realizar consultas django normales:

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

problemas

  • Sus modelos están ocultas a Django hasta que el código de la creación de ellos está dirigido. Sin embargo, puede ejecutar get_custom_car_model para cada instancia de sus definiciones en la señal class_prepared para su modelo de definición.
  • ForeignKeys/ManyToManyFields no pueden trabajar (no he probado)
  • Usted tendrá que usar caché modelo de Django por lo que no tiene que ejecutar consultas y crear el modelo cada vez que se desea utilizar este. Lo he omitido anteriormente por simplicidad
  • Puede obtener sus modelos dinámicos en el administrador, pero también deberá crear dinámicamente la clase de administrador y registrar/volver a registrar/anular el registro de forma apropiada mediante señales.

general

Si estás bien con la complicación añadida y problemas, disfrutar! Uno se está ejecutando, funciona exactamente como se esperaba gracias a la flexibilidad de Django y Python. Puede alimentar su modelo con ModelForm de Django para permitir que el usuario edite sus instancias y realizar consultas utilizando directamente los campos de la base de datos. Si hay algo que no entiendes en lo anterior, es mejor que no tomes este enfoque (no he explicado intencionadamente algunos de los conceptos para principiantes). ¡Mantenlo simple!

Realmente no creo que mucha gente lo necesite, pero yo mismo lo he usado, donde teníamos muchos datos en las tablas y realmente, realmente necesitábamos que los usuarios personalizaran las columnas, lo que cambiaba raramente.

+0

+1 para dar solución a la pregunta original, por cierto, sincronización agradable :) –

+0

Desafortunadamente, no se puede usar South con Django 1.8+. ¿Crees que todavía es posible crear tablas en tiempo de ejecución? Intenté usar las migraciones de django, pero no tuve éxito –

7

base de datos

Tenga en cuenta su diseño de la base de datos una vez más.

Debería pensar en cómo esos objetos que desea representar se relacionan entre sí en el mundo real y luego tratar de generalizar esas relaciones tanto como pueda (así que en lugar de decir que cada camión tiene un permiso, usted dice que cada vehículo tiene un atributo que puede ser un permiso, una cantidad de carga o lo que sea).

Así que vamos a probarlo:

Si usted dice que tiene un vehículo y cada vehículo puede tener muchos atributos especificados por el usuario consideran los siguientes modelos:

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

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

Como se ha señalado antes, esta es una idea general que le permite agregar tantos atributos a cada vehículo como desee.

Si desea que un conjunto específico de atributos esté disponible para el usuario, puede usar choices en el campo Attribute.type.

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

Ahora, quizás desee que cada tipo de vehículo tenga su propio conjunto de atributos disponibles. Esto se puede hacer agregando otro modelo y establecer relaciones de clave externa desde los modelos Vehicle y Attribute.

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) 

De esta manera usted tiene una idea clara de cómo cada atributo se relaciona con algún vehículo.

Formas

Básicamente, con este diseño de base de datos, se requerirían dos formas para añadir objetos en la base de datos. Específicamente, un model form para un vehículo y un model formset para los atributos.Puede usar jQuery para agregar dinámicamente más elementos en el formset Attribute.


Nota

También puede separar Attribute clase para AttributeTypeAttributeValue y por lo que no tiene los tipos de atributos redundantes almacenados en su base de datos o si desea limitar las opciones de atributos para el usuario, pero mantenga la posibilidad de agregar más tipos con el sitio de administración de Django.

Para ser totalmente genial, puede utilizar la función de autocompletar en su formulario para sugerir tipos de atributos existentes al usuario.

Sugerencia: sepa más sobre database normalization.


Otras soluciones

Como se sugiere en la respuesta anterior Stuart Marsh

Por otro lado se podría codificar sus modelos para cada tipo de vehículo, de modo que cada tipo de vehículo está representado por el la subclase del vehículo base y cada subclase puede tener sus propios atributos específicos, pero esas soluciones no son muy flexibles (si requiere flexibilidad).

También podría mantener la representación JSON de los atributos de objetos adicionales en un campo de base de datos, pero no estoy seguro de que esto sea útil al consultar los atributos.

+0

+1: si algo parece ser demasiado difícil en Django, reconsidere su enfoque porque casi siempre hay una forma elegante de lograr el resultado que desea. –

2

Aquí es mi simple análisis de Django en Shell-Me acaba de escribir en y parece trabajar fino



    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] 

Parece muy simple-no está seguro de por qué no debería ser un considerado un reflejo opción- es muy común es otros lenguajes como C# o Java- De todos modos, soy muy nuevo en cosas de django-

Cuestiones relacionadas