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