Динамическое создание моделей Django с типом

У меня есть более 20 таблиц MySQL, prm_a, prm_b,... с одинаковой базовой структурой, но разными именами, и я хотел бы связать их с классами модели Django, не записывая каждую вручную. Итак, чувствуя себя честолюбивым, я решил попробовать свои силы в использовании type() в качестве фабрики классов:

Следующие работы:

def get_model_meta_class(prm_name):
    class Meta:
        app_label = 'myapp'
    setattr(Meta, 'db_table', 'prm_%s' % prm_name)
    return Meta

prm_class_attrs = {
    'foo': models.ForeignKey(Foo),
    'val': models.FloatField(),
    'err': models.FloatField(blank=True, null=True),
    'source': models.ForeignKey(Source),
    '__module__': __name__,
}

###
prm_a_attrs = prm_class_attrs.copy()
prm_a_attrs['Meta'] = get_model_meta_class('a')
Prm_a = type('Prm_a', (models.Model,), prm_a_attrs)

prm_b_attrs = prm_class_attrs.copy()
prm_b_attrs['Meta'] = get_model_meta_class('b')
Prm_b = type('Prm_b', (models.Model,), prm_b_attrs)
###

Но если я попытаюсь создать классы моделей следующим образом:

###
prms = ['a', 'b']
for prm_name in prms:
    prm_class_name = 'Prm_%s' % prm_name
    prm_class = type(prm_class_name, (models.Model,), prm_class_attrs)
    setattr(prm_class, 'Meta', get_model_meta_class(prm_name))
    globals()[prm_class_name] = prm_class
###

Я получаю любопытное Exception на строке type() (учитывая, что __module__ на самом деле находится в словаре prm_class_attrs):

File ".../models.py", line 168, in <module>
    prm_class = type(prm_class_name, (models.Model,), prm_class_attrs)
  File ".../lib/python2.7/site-packages/django/db/models/base.py", line 79, in __new__
    module = attrs.pop('__module__')
KeyError: u'__module__' 

Итак, у меня есть два вопроса: что не так с моим вторым подходом, и правильный ли это способ создания моих моделей классов?

ОК - благодаря @Anentropic я вижу, что элементы в моем prm_class_attrs словаре удаляются Python, когда он создает классы. И теперь у меня это работает, но только если я сделаю это:

attrs = prm_class_attrs.copy()
attrs['Meta'] = get_model_meta_class(prm_name)
prm_class = type(prm_class_name, (models.Model,), attrs)

нет, если я установлю класс Meta в качестве атрибута с

setattr(prm_class, 'Meta', get_model_meta_class(prm_name))

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


person xnx    schedule 24.11.2014    source источник
comment
потому что вы не выполняете prm_class_attrs.copy() в своем цикле for, поэтому __modules__ выталкивается из dict на первой итерации   -  person Anentropic    schedule 24.11.2014
comment
Ага - вот оно. Спасибо!   -  person xnx    schedule 25.11.2014
comment
Я понятия не имел, что вы должны предоставить __module__! Этот вопрос был действительно полезным!!   -  person Brendan    schedule 26.09.2015


Ответы (3)


Непосредственная причина в том, что вы не выполняете prm_class_attrs.copy() в своем цикле for, поэтому ключ __modules__ выталкивается из dict на первой итерации.

Что касается того, почему это не работает:

setattr(prm_class, 'Meta', get_model_meta_class(prm_name))

... это связано с тем, что у Django models.Model есть метакласс. Но это метакласс Python, который настраивает создание класса модели и нечего делать. с внутренним классом Meta модели Django (который просто предоставляет «мета» информацию о модели).

На самом деле, несмотря на то, как это выглядит, когда вы определяете класс в своем models.py, результирующий класс не имеет атрибута Meta:

class MyModel(models.Model):
    class Meta:
        verbose_name = 'WTF'

>>> MyModel.Meta
AttributeError: type object 'MyModel' has no attribute 'Meta'

(Вы можете обращаться к классу Meta напрямую, но с псевдонимом MyModel._meta)

Модель, которую вы определяете в models.py, на самом деле является скорее шаблоном для класса модели, чем фактическим классом модели. Вот почему при доступе к атрибуту поля в экземпляре модели вы получаете значение этого поля, а не сам объект поля.

Django может немного упростить то, что вы делаем:

class GeneratedModelBase(models.Model):
    class Meta:
        abstract = True
        app_label = 'myapp'

    foo = models.ForeignKey(Foo)
    val = models.FloatField()
    err = models.FloatField(blank=True, null=True)
    source = models.ForeignKey(Source)

def generate_model(suffix):
    prm_class_name = 'Prm_%s' % prm_name
    prm_class = type(
        prm_class_name,
        (GeneratedModelBase,),
        {
            # this will get merged with the attrs from GeneratedModelBase.Meta
            'Meta': {'db_table', 'prm_%s' % prm_name},
            '__module__': __name__,
        }
    )
    globals()[prm_class_name] = prm_class
    return prm_class

prms = ['a', 'b']
for prm_name in prms:
    generate_model(prm_name)
person Anentropic    schedule 24.11.2014
comment
Я искал краткий конкретный пример того, как это сделать. Спасибо! - person Mike Covington; 30.06.2015

Вы можете использовать ./manage.py inspectdb Это распечатает файл моделей python для БД, на которую вы указываете в своем settings.py Документация

ИЗМЕНИТЬ Для динамических моделей попробуйте django-mutant или ознакомьтесь с эта ссылка

person Mounir    schedule 24.11.2014
comment
Я вижу, что это сработало бы, если бы база данных была статической и фиксированной, но если я хочу добавить таблицу, я бы хотел, чтобы код генерировал соответствующую модель на лету. - person xnx; 24.11.2014
comment
@xnx, вам наверняка придется написать кучу другого кода, чтобы использовать таблицу в вашем приложении? - person Anentropic; 24.11.2014

Для тех, кто все еще задается вопросом, как это сделать, я взял гениальный ответ от @Anentropic и заставил его работать с некоторыми изменениями.

Я также изменил имя таблицы db на что-то более похожее на то, что django использует для именования таблиц моделей (appname_lowercaseclassname), удалив все неалфавитные символы из имени класса и преобразовав полученную строку в нижний регистр.

Я тестировал его на Django 2.2.6.

def generate_model(class_name):

    clean_name_for_table = ''.join(filter(str.isalpha, class_name)).lower() # Could also use regex to remove all non alphabetic characters.
    db_table_name = f"{__package__}_{clean_name_for_table}"
    prm_class = type(
        class_name,
        (BaseClass,),
        {
            # this will get merged with the attrs from GeneratedModelBase.Meta
            'Meta': type("Meta",(),{'db_table':db_table_name}),
            '__module__': __name__,
        }
    )
    globals()[class_name] = prm_class
    return prm_class
person José R. Sánchez    schedule 19.09.2020