Все расширения для наших моделей будем добавлять в models/common.py
Создадим базовую модель в которой будет поле id, автоматическая генерация названия таблицы (__tablename__) и метод выбора элемента по id (SELECT * FROM table WHERE id=integer):
class Base(object): """Базовая модель. Добавляет во всех наследников поле id и атрибут __tablename__ который заполняется автоматически. Имя таблицы берется из названия класса и переводится в нижний регистр. Таблица наследник имеет по умолчанию название и поле id, сильно облегчая жизнь. """ @declared_attr def __tablename__(cls): if (has_inherited_table(cls) and Tablename not in cls.__bases__): return None return cls.__name__.lower() # Method "byId" for use in code like this: # session.query(Table).byId(5) # # SQL statement like: # SELECT * FROM Table WHERE id = 5; @classmethod def byId(cls, id) : return Session.query(cls).filter_by(id = id).first() id = Column(Integer, autoincrement=True, primary_key=True)Метод byId сильно сокращает запись в коде например:
before: Session.query(net).filter_by(id = id).first()
after: net.byId(id)
Теперь создадим нашу модель унаследовав все плюшки с базовой модели:
class Net(Base, DeclarativeBase): """Net or subnet.""" cidr = Column(postgresql.CIDR, index = True) description = Column(UnicodeText()) def __init__(self, cidr=''): self.cidr = cidr def __repr__(self): return "%s" % self.cidrНаша модель связанна с БД при помощи наследования от DeclarativeBase SQLalchemy, имеет название, поле id и метод byId благодаря наследованию от базовой модели Base из файла common.py.
Для более наглядного примера создадим типовую модель для таблиц которые должны содержать служебную информацию. Модель будет добавлять в другие модели поля:
created_by - кто создал
updated_by - последний кто обновил
created_at - дата создания
updated_at - дата последнего обновления
Поля заполняются автоматически. Кто создал и обновил ссылаются на модель auth.User. Пользователь берется из текущей сессии, при помощи библиотеки lib.auth и метода get_user.Откуда взялась модель User можно узнать из этой статьи Авторизация в Pylons за 5 мин при помощи repoze.what.
class CreatedMixin(object): """Абстрактная примесь которая добавляет в другие модели поля: created_by - кто создал updated_by - последний кто обновил created_at - дата создания updated_at - дата последнего обновления Поля заполняются автоматически. Кто создал и обновил ссылаются на модель auth.User. Пользователь берется из текущей сессии, при помощи библиотеки lib.auth и метода get_user """ @declared_attr def created_by(cls): return Column(Integer, ForeignKey('user.user_id', onupdate="cascade", ondelete="restrict")) @declared_attr def updated_by(cls): return Column(Integer, ForeignKey('user.user_id', onupdate="cascade", ondelete="restrict")) created_at = Column(DateTime, nullable=False, default=dt.now()) updated_at = Column(DateTime, nullable=False, default=dt.now(), onupdate=dt.now())Внешние ссылки и другие атрибуты отличающиеся от обычных полей нужно добавлять при помощи декоратора declare_attr. Теперь меняем нашу модель Net просто добавив CreateMixin:
class Net(Base, DeclarativeBase, CreatedMixin):
Вот полный листинг common.py:
# coding=utf-8 """Модуль с типовыми моделями """ from sqlalchemy import Column, ForeignKey from sqlalchemy.orm import relation, relationship from sqlalchemy.types import Integer, String, DateTime from sqlalchemy.ext.declarative import declared_attr from sqlalchemy.ext.declarative import has_inherited_table from gottlieb.model.auth import User from gottlieb.lib import auth from datetime import datetime as dt class Base(object): """Базовая модель. Добавляет во всех наследников поле id и атрибут __tablename__ который заполняется автоматически. Имя таблицы берется из названия класса и переводится в нижний регистр. Таблица наследник имеет по умолчанию название и поле id, сильно облегчая жизнь. """ @declared_attr def __tablename__(cls): if (has_inherited_table(cls) and Tablename not in cls.__bases__): return None return cls.__name__.lower() # Method "byId" for use in code like this: # session.query(Table).byId(5) # # SQL statement like: # SELECT * FROM Table WHERE id = 5; @classmethod def byId(cls, id) : return Session.query(cls).filter_by(id = id).first() id = Column(Integer, autoincrement=True, primary_key=True) class CreatedMixin(object): """Абстрактная примесь которая добавляет в другие модели поля: created_by - кто создал updated_by - последний кто обновил created_at - дата создания updated_at - дата последнего обновления Поля заполняются автоматически. Кто создал и обновил ссылаются на модель auth.User. Пользователь берется из текущей сессии, при помощи библиотеки lib.auth и метода get_user """ @declared_attr def created_by(cls): return Column(Integer, ForeignKey('user.user_id', onupdate="cascade", ondelete="restrict")) @declared_attr def updated_by(cls): return Column(Integer, ForeignKey('user.user_id', onupdate="cascade", ondelete="restrict")) created_at = Column(DateTime, nullable=False, default=dt.now()) updated_at = Column(DateTime, nullable=False, default=dt.now(), onupdate=dt.now())
Для полной картины приведу аналогичный пример на Django + DjangoORM.
Создадим файл myapp/accompaniment/models.py
from django.db import models from django.contrib.auth.models import User # Create your models here. class ExtendedModel(models.Model): created_by = models.ForeignKey(User, null=True, blank=True, editable=False, related_name='%(class)s_creator') created_time = models.DateTimeField(auto_now_add=True, editable=False) modified_by = models.ForeignKey(User, null=True, blank=True, editable=False, related_name='%(class)s_modifier') modified_time = models.DateTimeField(auto_now=True, editable=False) class Meta: abstract = TrueВ папке accompaniment я привык держать всякие такие хелперы для проекта. Теперь используем эту модель в нашем проекте myapp/projectname/models.py
from django.db import models from accompaniment.models import ExtendedModel class Ticket(ExtendedModel): OPEN_STATUS = 1 REOPENED_STATUS = 2 RESOLVED_STATUS = 3 CLOSED_STATUS = 4 DUPLICATE_STATUS = 5 STATUS_CHOICES = ( (OPEN_STATUS, _('Open')), (REOPENED_STATUS, _('Reopened')), (RESOLVED_STATUS, _('Resolved')), (CLOSED_STATUS, _('Closed')), (DUPLICATE_STATUS, _('Duplicate')), ) PRIORITY_CHOICES = ( (1, _('1. Critical')), (2, _('2. High')), (3, _('3. Normal')), (4, _('4. Low')), (5, _('5. Very Low')), ) title = models.CharField( _('Title'), max_length=200, ) queue = models.ForeignKey( Queue, verbose_name=_('Queue'), ) assigned_to = models.ForeignKey( User, related_name='assigned_to', blank=True, null=True, verbose_name=_('Assigned to'), ) status = models.IntegerField( _('Status'), choices=STATUS_CHOICES, default=OPEN_STATUS, ) description = models.TextField( _('Description'), blank=True, null=True, help_text=_('The content of the customers query.'), ) priority = models.IntegerField( _('Priority'), choices=PRIORITY_CHOICES, default=3, blank=3, ) class Meta: get_latest_by = "created" verbose_name = u'Заявки' verbose_name_plural = u'Заявки' def __unicode__(self): return u'%s' % self.title def save(self, force_insert=False, force_update=False): if not self.priority: self.priority = 3 super(Ticket, self).save(force_insert, force_update)
Такой несложный метод освобождает нас от размножения кучи одинаковых полей в моделях.
Update: в моделях Mixin был атрибут __abstract__ = True это неправильно, так-как все таблицы стают абстрактными. НО! Это вполне прокатит на версиях меньше 0.7, там этот атрибут почему-то не учитывается. На 0.7 версии работает как надо. Вот описание проблемы: stackoverflow
Комментариев нет:
Отправить комментарий