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