Все мы любим Django за ее магию и батарейки. Но любой инструмент, даже магический, лучше использовать понимая как он устроен. В этом докладе я попробую раскрыть некоторые секреты волшебства django-моделей.
обычный класс class RegularClass(object): field = models.CharField(max_length=150) class ModelClass(models.Model): field = models.CharField(max_length=150)
сами классы >>> print RegularClass.field <django.db.models.fields.CharField object at 0xb59a32c> >>> print ModelClass.field Traceback (most recent call last): File "<console>", line 1, in <module> AttributeError: type object 'ModelClass' has no attribute 'field'
это класс, который формирует другой класс в момент интерпретации кода. class Base(type): def __new__(cls, name, bases, attrs): new_cls = super(Base, cls).__new__(cls, name, bases, attrs) setattr(new_cls, 'HACKED', "!") return new_cls class Parent(object): __metaclass__ = Base class Main(Parent): data = "child"
это класс, который формирует другой класс в момент интерпретации кода. class Base(type): def __new__(cls, name, bases, attrs): new_cls = super(Base, cls).__new__(cls, name, bases, attrs) setattr(new_cls, 'HACKED', "!") return new_cls class Parent(object): __metaclass__ = Base class Main(Parent): data = "child" Сам метакласс
это класс, который формирует другой класс в момент интерпретации кода. class Base(type): def __new__(cls, name, bases, attrs): new_cls = super(Base, cls).__new__(cls, name, bases, attrs) setattr(new_cls, 'HACKED', "!") return new_cls class Parent(object): __metaclass__ = Base class Main(Parent): data = "child" Имя класса, который будет сгенерирован "Parent" "Main"
это класс, который формирует другой класс в момент интерпретации кода. class Base(type): def __new__(cls, name, bases, attrs): new_cls = super(Base, cls).__new__(cls, name, bases, attrs) setattr(new_cls, 'HACKED', "!") return new_cls class Parent(object): __metaclass__ = Base class Main(Parent): data = "child" Список классов-родителей [object] [Parent]
это класс, который формирует другой класс в момент интерпретации кода. class Base(type): def __new__(cls, name, bases, attrs): new_cls = super(Base, cls).__new__(cls, name, bases, attrs) setattr(new_cls, 'HACKED', "!") return new_cls class Parent(object): __metaclass__ = Base class Main(Parent): data = "child" Словарь аттрибутов будущего класса
это класс, который формирует другой класс в момент интерпретации кода. class Base(type): def __new__(cls, name, bases, attrs): new_cls = super(Base, cls).__new__(cls, name, bases, attrs) setattr(new_cls, 'HACKED', "!") return new_cls class Parent(object): __metaclass__ = Base class Main(Parent): data = "child" Словарь аттрибутов будущего класса Parent { '__metaclass__': < class '__main__.Base' > , '__module__': '__main__' }
это класс, который формирует другой класс в момент интерпретации кода. class Base(type): def __new__(cls, name, bases, attrs): new_cls = super(Base, cls).__new__(cls, name, bases, attrs) setattr(new_cls, 'HACKED', "!") return new_cls class Parent(object): __metaclass__ = Base class Main(Parent): data = "child" Словарь аттрибутов будущего класса Main { 'data': 'child', '__module__': '__main__' }
это класс, который формирует другой класс в момент интерпретации кода. class Base(type): def __new__(cls, name, bases, attrs): new_cls = super(Base, cls).__new__(cls, name, bases, attrs) setattr(new_cls, 'HACKED', "!") return new_cls class Parent(object): __metaclass__ = Base class Main(Parent): data = "child"
нам обернуть сотни методов в десятке классов? class MethodCounterBase(type): def __new__(cls, name, bases, attrs): new_cls = super(MethodCounterBase, cls).__new__(cls, name, bases, attrs) for obj_name, obj in attrs.items(): if callable(obj) and not isinstance(obj, type): setattr(new_cls, obj_name, calls_counter(obj_name, obj)) return new_cls
пустой класс (без атрибутов) super_new = super(ModelBase, cls).__new__ ... new_class = super_new(cls, name, bases, {'__module__': module}) Получаем класс Meta из декларации модели или унаследованный attr_meta = attrs.pop('Meta', None) abstract = getattr(attr_meta, 'abstract', False) if not attr_meta: meta = getattr(new_class, 'Meta', None) else: meta = attr_meta
вычисленного meta в модель добавляется атрибут _meta new_class.add_to_class('_meta', Options(meta, **kwargs)) Потом будет еще несколько проверок на наследование свойств, от родительских моделей.
присваиваются все атрибуты, указанные в коде модели for obj_name, obj in attrs.items(): new_class.add_to_class(obj_name, obj) def add_to_class(cls, name, value): if hasattr(value, 'contribute_to_class'): value.contribute_to_class(cls, name) else: setattr(cls, name, value)
происходит магия с полями моделей #Базовая имплементация contribute_to_class в models.fields.Field def contribute_to_class(self, cls, name): self.set_attributes_from_name(name) self.model = cls cls._meta.add_field(self) if self.choices: setattr(cls, 'get_%s_display' % self.name, curry(cls._get_FIELD_display, field=self)) Поля добавляются не в класс модели, а во вложенный класс Options и доступны через cls._meta.fields
- если модель не абстрактная, она регистрируется в пуле моделей и возвращается уже из этого пула new_class._prepare() register_models(new_class._meta.app_label, new_class) return get_model(new_class._meta.app_label, name, seed_cache=False, only_installed=False) Абстрактная же, нигде не регистрируется if abstract: attr_meta.abstract = False new_class.Meta = attr_meta return new_class
так и не понятно главное - как же возвращаются и устанавливается значения полей - строки, числа и связанные модели Значения индивидуальны для каждого экземпляра модели (объекта) Значит, присвоение происходит где-то при работе с экземпляром Заглянем в метод __init__, класса Model
__init__, класса Model def __init__(self, *args, **kwargs): fields_iter = iter(self._meta.fields) ... if not kwargs: for val, field in izip(args, fields_iter): setattr(self, field.attname, val) else: for val, field in izip(args, fields_iter): setattr(self, field.attname, val) kwargs.pop(field.name, None) if isinstance(field.rel, ManyToOneRel): kwargs.pop(field.attname, None)
__init__, класса Model def __init__(self, *args, **kwargs): fields_iter = iter(self._meta.fields) ... if not kwargs: for val, field in izip(args, fields_iter): setattr(self, field.attname, val) else: for val, field in izip(args, fields_iter): setattr(self, field.attname, val) kwargs.pop(field.name, None) if isinstance(field.rel, ManyToOneRel): kwargs.pop(field.attname, None) MyModel(field1=1, field2="text") Этот код ищет в self._meta.fields поля field1 и field2, и устанавливает экземпляру аттрибуты self.field1 = 1 self.field2 = "text"
списку полей ... for field in fields_iter: ... val = field.get_default() ... if is_related_object: setattr(self, field.name, rel_obj) else: setattr(self, field.attname, val)
Как решаются переопределения и наследования значений Meta • Как распределяются простые и related поля при создании класса модели • Как вообще обрабатываются related-поля • Как создаются менеджеры для моделей