Создание динамических полей модели в django

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

Я хочу предоставить форму, в которой пользователь может добавлять дополнительные поля в зависимости от автомобиля, который он добавляет. Например, если пользователь добавляет "Автомобиль", он будет добавлять дополнительные поля в форме, динамически во время выполнения, например, "Автомобильное противостояние", "Производитель Cal". Предположим, если пользователь хочет добавить "Грузовик", он добавит "Нагрузка, которую можно перевозить", "Разрешить" и т.д.

Как достичь этого в django?

Здесь есть два вопроса:

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

Ответы

Ответ 1

Существует несколько подходов:

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

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

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

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

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

Ответ 2

База данных

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

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

Так что давайте попробуем:

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

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)

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

Формы

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


Примечание

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

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

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


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

Как было предложено в предыдущем ответе Stuart Marsh

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

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

Ответ 3

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

Кажется очень простым - не уверен, почему это не должно считаться опцией. Отражение очень распространено, это другие языки, такие как С# или Java. Во всяком случае, я очень новичок в вещах django -

Ответ 4

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

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

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

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

и т.д.