Создание полей динамической модели в Django - PullRequest
15 голосов
/ 15 сентября 2010

Это проблема, связанная с django.У меня есть модель сказать "Автомобили".Это будет иметь несколько основных полей, таких как «Цвет», «Имя владельца транспортного средства», «Стоимость транспортного средства».

Я хочу предоставить форму, в которой пользователь может добавлять дополнительные поля в зависимости от автомобиля, который он добавляет.Например, если пользователь добавляет «Автомобиль», он будет заполнять дополнительные поля в форме, динамически во время выполнения, например, «Car Milage», «Cal Manufacturer».Предположим, что если пользователь хочет добавить «Грузовик», он добавит «Груз, который можно нести», «Разрешение» и т. Д.

Как мне добиться этого в django?

Естьздесь два вопроса:

  1. Как предоставить форму, в которой пользователь может добавлять новые поля во время выполнения?
  2. Как добавить поля в базу данных, чтобы ее можно было извлечь /позже?

Ответы [ 4 ]

27 голосов
/ 15 сентября 2010

Есть несколько подходов:

  • модель ключ / значение (легко, хорошо поддерживается)
  • Данные JSON в TextField (легко, гибко, не могут легко искать / индексировать)
  • Определение динамической модели (не так просто, много скрытых проблем)

Звучит так, будто ты хочешь последний, но я не уверен, что он тебе подходит. Django очень легко изменить / обновить, если системные администраторы хотят получить дополнительные поля, просто добавьте их для них и используйте юг для миграции. Мне не нравятся универсальные схемы базы данных ключ / значение, суть такой мощной инфраструктуры, как 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

4. Код для обновления базы данных таблиц

Приведенный выше код создаст для вас динамическую модель, но не создаст таблицы базы данных. Я рекомендую использовать Юг для манипулирования таблицами. Вот пара функций, которые вы можете подключить к сигналам до / после сохранения:

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 до запуска кода, создающего их. Однако вы можете запустить get_custom_car_model для каждого экземпляра ваших определений в сигнале class_prepared для вашей модели определения.
  • ForeignKeys / ManyToManyFields может не работать (я не пробовал)
  • Вы захотите использовать кеш модели Django, чтобы вам не приходилось выполнять запросы и создавать модель каждый раз, когда вы захотите использовать это. Я упустил это выше для простоты
  • Вы можете передать свои динамические модели администратору, но вам также необходимо динамически создать класс администратора и соответствующим образом зарегистрировать / перерегистрировать / отменить регистрацию с помощью сигналов.

Обзор

Если у вас все в порядке с дополнительными сложностями и проблемами, наслаждайтесь! Когда он запущен, он работает именно так, как и ожидалось, благодаря гибкости Django и Python. Вы можете вставить свою модель в Django ModelForm, чтобы позволить пользователю редактировать свои экземпляры и выполнять запросы, используя поля базы данных напрямую. Если есть что-то, что вы не понимаете в вышеприведенном, вам, вероятно, лучше не использовать этот подход (я намеренно не объяснил, что некоторые концепции для начинающих). Сохраняйте это простым!

Я действительно не думаю, что многим это нужно, но я использовал это сам, где у нас было много данных в таблицах и действительно, действительно нужно было позволить пользователям настраивать столбцы, которые редко менялись.

8 голосов
/ 15 сентября 2010

База данных

Рассмотрим дизайн вашей базы данных еще раз.

Вы должны подумать о том, как те объекты, которые вы хотите представить, связаны друг с другом в реальном мире, а затем попытаться обобщить эти отношения настолько, насколько это возможно, (поэтому вместо того, чтобы говорить, что у каждого грузовика естьразрешение, вы говорите, что у каждого транспортного средства есть атрибут, который может быть разрешением, суммой загрузки или любым другим).

Итак, давайте попробуем:

Если вы говорите, что у вас есть транспортное средство, и каждое транспортное средство можетчтобы многие пользовательские атрибуты учитывали следующие модели:

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

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

Как отмечалось выше, это общая идея, которая позволяет добавлять столько атрибутов к каждому транспортному средству, сколько вы хотите.

Есливы хотите, чтобы конкретный набор атрибутов был доступен для пользователя, который вы можете использовать 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()

Теперь, возможно, вы захотите, чтобы каждая сортировка транспортного средстваиметь свой собственный набор доступных атрибутов.Это можно сделать, добавив еще одну модель и установив в нее отношения внешнего ключа из моделей Vehicle и 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)

Таким образом, у вас есть четкое представление о том, как каждый атрибут связан с некоторымиvehicle.

Forms

По сути, при таком дизайне базы данных вам потребуется две формы для добавления объектов в базу данных.В частности, модель формы для автомобиля и модель формы для атрибутов.Вы можете использовать jQuery для динамического добавления дополнительных элементов в набор форм Attribute.


Примечание

Вы также можете отделить Attribute class AttributeType и AttributeValue, чтобы у вас не было избыточных типов атрибутов, хранящихся в вашей базе данных, или если вы хотите ограничить выбор атрибутов для пользователя, но сохраняете возможность добавлять больше типов с помощью сайта администратора Django.

Чтобы быть полностью крутым, вы можете использовать автозаполнение в своей форме, чтобы предложить пользователю существующие типы атрибутов.

Подсказка : узнайте больше о нормализации базы данных .


Другие решения

Как предложено в предыдущем ответе Стюарт Марш

С другой стороны, выможет жестко закодировать ваши модели для каждого типа транспортного средства, чтобы каждый тип транспортного средства был представлен подклассом базового транспортного средства, и каждый подкласс мог иметь свои собственные специфические атрибуты, но решения не были бы очень гибкими (если вам требуется гибкость).

Вы также можете хранить представление JSON дополнительных атрибутов объекта в одном поле базы данных, но я не уверен, что это будет полезно при запросе атрибутов.

2 голосов
/ 28 августа 2011

Вот мой простой тест в оболочке 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 -

1 голос
/ 15 сентября 2010

Вы говорите во внешнем интерфейсе или в администраторе Django?

Вы не можете создавать настоящие поля на лету, как это, без большой работы под капотом.Каждая модель и поле в Django имеют связанную таблицу и столбец в базе данных.Для добавления новых полей обычно требуется либо raw sql, либо миграция с использованием South.

В интерфейсном интерфейсе можно создавать псевдополя и сохранять их в формате json в одном поле модели.

Например, создайте текстовое поле other_data в модели.Затем разрешите пользователям создавать поля и хранить их как {'userfield': 'userdata', 'm miles': 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)

и т. д.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...