Создание динамических моделей в реалтайме в Django

Spread the love

Динамическое создание моделей или полей к уже существующей модели в ORM Django редко встречаемая задача, но иногда специфика бизнеса требует ее реализации. К примеру может возникнуть необходимость получение данных из внешней БД и при этом сами данные могут иметь либо очень большее количество полей (более 100), либо иметь постоянно меняющиеся поля. Но вы должны быть осторожны, если пойдете по этому пути, особенно если ваши модели настроены на изменение во время выполнения. В этой статье я рассмотрю ряд вопросов, которые следует учитывать при создание динамических моделей.

Создание фабрики динамических моделей

Основным принципом, который позволяет нам создавать динамические классы, является встроенная функция type(). Рассмотрим обычный синтаксис определения класса в Python:

class Person(object):
    name = "Julia"


Функция type() так же может использоваться для создания того же класса:

Person = type("Person", (object,), {'name': "Julia"})

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

Модели Django

Модели Django могут быть по существу определены таким же образом, с одним дополнительным требованием, необходимо определить атрибут __module__. Вот простая модель Django:

class Animal(models.Model):
    name = models.CharField(max_length=32)

А вот эквивалентный класс, построенный с использованием type():

attrs = {
    'name': models.CharField(max_length=32),
    '__module__': 'myapp.models'
}
Animal = type("Animal", (models.Model,), attrs)

Любая модель Django, которая может быть определена обычным способом, может быть создана с использованием type().

Кеш модели Джанго

Django автоматически кэширует классы моделей, когда вы создаете подкласс models.Model. Если вы генерируете модель с именем, которое может уже существовать, вы должны сначала удалить существующий кэшированный класс.

Официального, документированного способа сделать это не существует, но текущие версии Django позволяют вам напрямую удалять записи из кэша:

from django.db.models.loading import cache
try:
    del cache.app_models[appname][modelname]
except KeyError:
    pass

Использование API модели

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

Во-первых, вы можете использовать синтаксис Python ** для передачи объекта сопоставления в виде набора ключевых аргументов. Это не так элегантно, как обычный синтаксис, но работает:

kwargs = {'name': "Jenny", 'color': "Blue"}
print People.objects.filter(**kwargs)

Второй подход заключается в создании подкласса django.db.models.query.QuerySet и реализации ваших собственных методов для поддержания чистоты. Вы можете присоединить настроенный класс QuerySet, перегрузив метод get_query_set вашего менеджера модели. Остерегайтесь, однако, делать слишком нестандартные вещи, заставляя других разработчиков изучать ваше новое API.

from django.db.models.query import QuerySet
from django.db import models

class MyQuerySet(QuerySet):
    def filter(self, *args, **kwargs):
        kwargs.update((args[i],args[i+1]) for i in range(0, len(args), 2))
        return super(MyQuerySet, self).filter(**kwargs)

class MyManager(models.Manager):
    def get_query_set(self):
        return MyQuerySet(self.model)

# XXX Добавьте менеджера в вашу динамическую модель...

# Warning: Этот проект использует пользовательский метод фильтра!
print People.objects.filter(name="Jenny").filter('color', 'blue')

Третий подход – просто предоставить вспомогательную функцию, которая создает либо подготовленное отображение kwargs, либо возвращает объект django.db.models.Q, которого можно напрямую передать в QuerySet. Это похоже на создание нового API, но оно немного более явное, чем создание подкласса QuerySet.

from django.db.models import Q

def my_query(*args, **kwargs):
    """ превращаем my_query(key, val, key, val, key=val) в объект Q. """
    kwargs.update((args[i],args[i+1]) for i in range(0, len(args), 2))
    return Q(**kwargs)

print People.objects.filter(my_query('color', 'blue', name="Jenny"))

Проверка на наличие конфликтов

Иногда может быть полезно выполнить дополнительную проверку, если возможны конфликты имен. Это означает, что система будет реагировать так, как вам нужно, например, переименовывая или удаляя существующие таблицы.

Django предоставляет интерфейс через который можно легко обнаружить существующие имена и описания таблиц:

from django.db.connection import introspection
from django.db import connection

name = introspection.table_name_converter(table_name)

print name in introspection.table_names()

description = introspection.get_table_description(connection.cursor(), name)
db_column_names = [row[0] for row in description]

print myfield.column in db_column_names

Обратите внимание, что оно ограничено стандартными типами полей.

На этом все. Надеюсь эта короткая заметка поможет вам сэкономить немного времени при поиске решений подобных задач.

Была ли вам полезна эта статья?
[5 / 3.4]

Spread the love
Подписаться
Уведомление о
guest
2 Комментарий
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Vadim
Vadim
4 лет назад

Судя по 2му питону – оригинальная статья древняя

edteam
Администратор
4 лет назад
Reply to  Vadim

Да так и есть, но описанная идея работает и сегодня.