Показаны сообщения с ярлыком Django. Показать все сообщения
Показаны сообщения с ярлыком Django. Показать все сообщения

15 марта 2014

Перенос БД с sqlite на postgres

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

# Выгрузка в JSON
python manage.py dumpdata myapp.A > a.json

# Загрузка из JSON
python manage.py loaddata a.json

Т.е. мы выгружаем данные из sqlite в JSON формат, затем меняем строку подключения на postgres и выполняем загрузку из JSON. Очень удобно, но почему то этот метод не работает, либо работает только при переносе из sqlite -> sqlite, что в принципе не очень интересно, точнее бессмысленно. Есть какие то решений с бубном как это вот http://macrotoma.blogspot.ru/2012/10/solved-move-django-from-sqlite-to-mysql.html, http://blog.abourget.net/2009/7/7/exporting-sql-schemas-from-sqlalchemy-table-definitions/.

Эти методы не универсальны, потому что имеют привязку к моделям(ORM), требуют для переноса проект на Django и ручные действия вроде создания схемы и выполнения миграций(далеко не всегда миграции созданы правильно).

Я написал небольшой пример как можно перевести данные не имея фреймворков, не привязываясь к моделям, указав только две строки подключения откуда переносить и куда(вроде было что то похожее на руби). За основы взят пример из этой статьи http://www.tylerlesmann.com/2009/apr/27/copying-databases-across-platforms-sqlalchemy/. Где предлагается указать дополнительно названия таблиц.
В sqlalchemy с весии 9.1 появилась встроенная возможность автоматического определения схемы БД http://docs.sqlalchemy.org/en/rel_0_9/orm/extensions/automap.html. Правда намного раньше появились сторонние решения: http://turbogears.org/2.1/docs/main/Utilities/sqlautocode.html или https://sqlsoup.readthedocs.org/en/latest/.

Первое что мы сделаем, это получим схему БД
    
from sqlalchemy.ext.automap import automap_base

def get_metadata(self, engine):
   # produce our own MetaData object
   metadata = MetaData()

   # we can reflect it ourselves from a database, using options
   # such as 'only' to limit what tables we look at...
   # only = ['news_news', 'pages_page']
   if self.only:
       metadata.reflect(engine, only=self.only)
   else:
       metadata.reflect(engine)

   # we can then produce a set of mappings from this MetaData.
   Base = automap_base(metadata=metadata)

   # calling prepare() just sets up mapped classes and relationships.
   Base.prepare()
   return metadata, Base


Дальше получаем все таблицы
tables = Base.classes


Создаем такую же структуру БД в postgres:
metadata.create_all(self.engine_dst)


По очереди проходим каждую таблицу и переносим из нее данные в новую БД
for table in tables:
    columns = table.__table__.c.keys()
    print 'Transferring records to %s' % table.__table__.name
    for record in self.session.query(table).all():
        data = dict(
            [(str(column), getattr(record, column)) for column in columns]
        )
        NewRecord = quick_mapper(table.__table__)
        self.session_dst.merge(NewRecord(**data))
        self.session_dst.commit()


Если возникли конфликты можно их решить переопределением типов полей. Например при переносе из sqlite в postgres тип полей DATETIME нужно заменить на DateTime
dialect = self.engine.dialect.name
dialect_dst = self.engine_dst.dialect.name
if dialect == dialect_dst:
    return
for table in self.tables:
    columns = table.__table__.c
    for column in columns:
        if dialect_dst == 'postgresql':
            # DATETIME->DateTime
            if isinstance(column.type, DATETIME):
                column.type = DateTime()


 Пример запуска:
python convertdbdata.py -f "sqlite:///fromMydb.sqlite"
-t "postgresql://postgres:postgres@localhost/toMydb" -i "auth_user,news_news"

 -i параметр указывает какие таблицы нужно запускать первыми, например в такой ситуации:
    
DETAIL:  Ключ (user_id)=(1) отсутствует в таблице "auth_user".
 'INSERT INTO django_admin_log (id, action_time, user_id, content_type_id, object_id, object_repr, action_flag,
 change_message) VALUES (%(id)s, %(action_time)s, %(user_id)s, %(content_type_id)s, %(object_id)s, %(object_repr)s,
 %(action_flag)s, %(change_message)s)' {'action_flag': 1, 'action_time': datetime.datetime(2014, 2, 5, 13, 15, 27, 948000),
 'user_id': 1, 'content_type_id': 39, 'object_repr': u'dfgsdfg', 'object_id': u'1', 'change_message': u'', 'id': 1}

Код полностью https://github.com/ITCase/convertdbdata 

Сейчас скрипт не рассчитан на большие объемы данных и учитывает только разницу в DateTime поле, но думаю это легко исправить по мере поступления задач.

02 февраля 2014

Django - кот в мешке.

Django это фреймворк, при помощи которого быстро пишутся сайты. Так ли это?
Фичи джанги:
  • Админка
  • куча аппликэйшинов
  • Шаблоны
  • ORM
  • DebugToolbar

 

 

 

 

Админка

Из коробки у вас есть админка. Для простых сайтов её нужно кастомайзить долго и упорно, правда, обычно, это делается один раз. В сайте среднего уровня жангоадминка уже не прокатит. Вывод админка даёт профит в простых сайтах.

 

Аппликашины

Обычно очень посредственного качества и не совместимы друг с другом, поэтому танцы с бубном и велосипеды наше всё + djangosnippets

 

Шаблоны

  • нету break, continue  
 jinja2.ext.loopcontrols This extension adds support for break and continue in loops.
  • рекурсия Jinja
    <ul class="sitemap">
    {% for item in sitemap recursive %}
        <li><a href="{{ item.href|e }}">{{ item.title }}</a>
        {% if item.children -%}
            <ul class="submenu">{{ loop(item.children) }}</ul>
        {% endif %}</li>
    {% endfor %}
    </ul>
     а в джанге это видимо не нужно
  • не работает вызов функций типа item() (лолшто?)
  • [:-1] не работает, нужно писать |latest
  • для js выражение типа: 
         var foo = [{% for item in items %}{{ item.paren }}{% endfor %}]
        не работает, потому что js выполняется до рендеринга таких {%%} конструкций.
  • коменты {# #} неработают в несколько строк
  • вместо {{ list[5] }} -> {{ arr.5 }}
  • для словарей {{ dict.foo }} не работает, да здравствует template tag
  • для itertools.chain можно сосать лапу с таким фокусом {{ list[5] }}
  • о джанга ты прекрасна {{ myval|add:"-5" }} вместо уродского {{ myval - 5 }}
  • нельзя сделать так {{ dir(foo) }}
  • как узнать длинну itertools? в jinje делается так {{ list(foo).length }}

 

ORM

  • Page.objects.filter(parent__in=objects) вместо DBSession.query(Page).filter(parent in objects)
  • великолепный foomodel_set__barmodel__name лолшто?!
  • Entry.objects.all()[:1].get() вместо DBSession.query(Entry).first() или one()
  • добавляем в модель: class Meta: app_label = 'asdasda' и переходим к разделу дебаг!

Debug

Дебаг джанго кормит вас! Потому что вы Django программист и половину рабочего времени пытаетесь понять что за !@#$% произошла, получая за это зарплату.

class Meta: app_label = 'asdasda' генерит трейс:
django.core.management.base.CommandError: One or more models did not validate:
gallery.galleryimage: 'gallery' has a relation with model <class 'gallery.models.Gallery'>, which has either not been installed or is abstract.
Шыкарные красные ошибки джанги. Очень информативно, ищется аникейством и вспоминанием чё правил.

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

Квик старт
  1. Установка по pip install -r requirements.txt
  2. Далее введим python manage.py syncdb и видим ImportError: cannot import name SEOModel Супер информативный вывод, все сразу стало понятно.
    • 2.1 Прошел все модели в проекте, SEOModel не нашел, при учете что у меня gallery стоит в virtualenv
    • 2.2 может во вью? Во вью то же нету
    • 2.3 ищем по проекту тупо по всем файлам, нету.
    • 2.4 идем в settings/apps.py и смотрим какое г может это содержать(так на угад), смотрим common опа вот он в моделях, осталось найти где он импортируется неправильно.
    • 2.5 ради интереса запускаю сервер python manage.py runserver и получаю:
      File ".../local/lib/python2.7/site-packages/gallery/models.py", line 12, in <module> from website.models import SEOModel, VisibleModel ImportError: cannot import name SEOModel 
т.е. в некоторых случаях джангу надо дебажить runserver'ом

Pages
  1. захожу в pages в админке
  2. добавляю дерево
  3. тыкаю по дереву(вложенность 2го уровня) что бы развернуть список и получаю:
Error while loading the data from the server.







  



и трэйс:
Internal Server Error: /admin/pages/page/tree_json/
Traceback (most recent call last):
  File "/home/uralbash/.virtualenvs/django-hyango/local/lib/python2.7/site-packages/django/core/handlers/base.py", line 115, in get_response
    response = callback(request, *callback_args, **callback_kwargs)
  File "/home/uralbash/.virtualenvs/django-hyango/local/lib/python2.7/site-packages/django_mptt_admin/admin.py", line 50, in wrapper
    return self.admin_site.admin_view(view)(*args, **kwargs)
  File "/home/uralbash/.virtualenvs/django-hyango/local/lib/python2.7/site-packages/django/utils/decorators.py", line 91, in _wrapped_view
    response = view_func(request, *args, **kwargs)
  File "/home/uralbash/.virtualenvs/django-hyango/local/lib/python2.7/site-packages/django/views/decorators/cache.py", line 89, in _wrapped_view_func
    response = view_func(request, *args, **kwargs)
  File "/home/uralbash/.virtualenvs/django-hyango/local/lib/python2.7/site-packages/django/contrib/admin/sites.py", line 202, in inner
    return view(request, *args, **kwargs)
  File "/home/uralbash/.virtualenvs/django-hyango/local/lib/python2.7/site-packages/django_mptt_admin/admin.py", line 157, in tree_json_view
    node = self.model.objects.get(id=node_id)
  File "/home/uralbash/.virtualenvs/django-hyango/local/lib/python2.7/site-packages/django/db/models/manager.py", line 143, in get
    return self.get_query_set().get(*args, **kwargs)
  File "/home/uralbash/.virtualenvs/django-hyango/local/lib/python2.7/site-packages/django/db/models/query.py", line 404, in get
    self.model._meta.object_name)
DoesNotExist: Page matching query does not exist.
[16/Dec/2013 23:42:28] "GET /admin/pages/page/tree_json/?node=5&_=1387215746550 HTTP/1.1" 500 22668

бесполезный трейс и непонятная ошибка КРУТО!
Если я зайду на адрес /admin/pages/page/tree_json/?node=5&_=1387215746550 то получу статический trace и хер знает куда bp вставить что бы хоть за че-то зацепиться.

Ставлю django-extension через pip и wergzeug что бы получить хоть какой то интерактив на том же трэйсе.

Запускаю командой python manage.py runserver_plus, отваливается debug_toolbar. Но и интерактивный трейс мало чем помогает в этом случае. Джанга каким то чудом обходит то место которое все ломает, ГРЕБАННЫЙ СТЫД! Ошибка ищется тупым эникейством. Предполагая что django идеальна и в ней нет ошибок, django_mptt_admin на example работает хорошо, может быть что то в Pages??? Ад же какойто?

22 декабря 2013

Причины названий python, django и pyramid

Python

 

 



Monty Python's Flying Circus.jpg Название языка произошло вовсе не от вида пресмыкающихся. Автор назвал язык в честь популярного британского комедийного телешоу 70x «Летающий цирк Монти Пайтона».  Но потом все стали его ассоциировать со змеей, ибо змея она и в африке змея.
Django

Джанго Рейнхардт — музыкант, в честь которого получил название фреймворк
Pyramid
 
stevepiercy ответил так :)
stevepiercy: because we have pointy heads
Потому что у нас такие же острые умы.

stevepiercy: but seriously, the decision to merge web app frameworks into one was made at the Luxor in Las Vegas, which is a hotel shaped like a pyramid, and the name starts with "Py", so....
Но если серьезно, то решение об объединении фреймворков в один, было принято в отеле Luxor в Лас-Вегасе, который имеет форму пирамиды и название начинается с "Py"...

02 ноября 2012

Redactor-js WYSIWYG редактор и python

Redactor-js - это простой вайсвиг с возможностью загрузки изображений.
demo: http://imperavi.com/redactor/
github: https://github.com/dybskiy/redactor-js
Как сочленить это с php можно прочитать из документации http://imperavi.com/redactor/docs/images/. Там же есть ссылка на пример с Django от Patrick Altman. Я же постараюсь описать как загружать картинки при помощи pyramid фреймворка, думаю переписать на что то другое не составит труда.
js функция redactor'а у меня выглядит вот так:




 
вьюха для загрузки изображений:
@view_config(route_name='upload_image', renderer='json')
def upload_image(request):
    # путь к директории куда загружать картинку
    path = request.registry.settings.get('redactor_images')

    filename = str(time.time()) + request.POST['file'].filename
    input_file = request.POST['file'].file

    # Using the filename like this without cleaning it is very
    # insecure so please keep that in mind when writing your own
    # file handling.
    file_path = os.path.join(path, filename)
    output_file = open(file_path, 'wb')

    # Finally write the data to the output file
    input_file.seek(0)
    while 1:
        data = input_file.read(2 << 16)
        if not data:
            break
        output_file.write(data)
    output_file.close()

    return {"filelink": "/static/uploaded/images/" + filename}
И вьюха для выбора изображений из уже загруженных:
@view_config(route_name='GetJson'), renderer='json')
def imageGetJson(request):
    path = request.registry.settings.get('redactor_images')
    types = ('*.jpg', '*.jpeg', '*.gif')  # the tuple of file types

    files_grabbed = []
    for files in types:
        files_grabbed.extend(glob.glob(path + "/" + files))
    images = []
    for file in files_grabbed:
        file = file.replace(path, "")
        images.append({"thumb": "/static/uploaded/images/" + file,
             "image": "/static/uploaded/images/" + file,
             "title": file, "folder": "images"})

    return images
Пользуйтесь, хороший редактор.

05 июля 2012

Структура Pyramid приложений как в Django

Одной из причин отказа развивать ветку Pylons стала его архитектура проекта. Все контроллеры хранятся в директории controllers, модели в models, шаблоны в templates. Это очень удобно когда у вас маленький проект, но если он разрастается до десятков и сотен сущностей, то становится крайне сложно скакать по этим папкам выискивая нужный файл, относящийся именно к этой сущности. В Django сделано по другому, в проекте хранятся приложения(application) - это такие маленькие подпрограммы которые отвечают за конкретный функционал проекта(например фотогалерея django-photologue или дерево сайта django-sitetree и прочее). Каждое такое приложение имеет свою папку и уже в ней хранятся контроллеры(views в данном случае) и модели(models). Т.е. вместо такой архитектуры Pylons:
Project/
    controllers/
        controllers1.py
        controllers2.py
    model/
        models1.py
        models2.py
    templates/
        templates1/
        templates2/
    routes.py
мы получаем
Project/
    app1/
        models.py
        routes.py
        views.py
    app2/
        models.py
        routes.py
        views.py
    templates/
        templates1/
        templates2/

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

Стоит ли сейчас выбирать Pylons? Да, если вы его хорошо знаете и не планируете создавать слишком масштабное приложение, иначе лучше все таки выбрать Pyramid.
Посмотрим как в нем организовать структуру проекта:

После создания проект выглядит так
MyProject/
├── CHANGES.txt
├── development.ini
├── MANIFEST.in
├── myproject
│   ├── __init__.py
│   ├── models.py
│   ├── scripts/
│   ├── static/
│   ├── templates/
│   ├── tests.py
│   └── views.py
├── production.ini
├── README.txt
├── setup.cfg
└── setup.py

Наша задача перенести models.py, views.py, tests.py в app1. Создаем папку app1 и переносим файлы.
Должно получится так:
MyProject/
.
├── CHANGES.txt
├── development.ini
├── MANIFEST.in
├── myproject
│   ├── app1
│   │   ├── __init__.py
│   │   ├── models.py
│   │   ├── tests.py
│   │   └── views.py
│   ├── __init__.py
│   ├── scripts
│   │   ├── initializedb.py
│   │   └── __init__.py
│   ├── static
│   │   ├── favicon.ico
│   │   ├── footerbg.png
│   │   ├── headerbg.png
│   │   ├── ie6.css
│   │   ├── middlebg.png
│   │   ├── pylons.css
│   │   ├── pyramid.png
│   │   ├── pyramid-small.png
│   │   └── transparent.gif
│   └── templates
│       └── mytemplate.pt
├── production.ini
├── README.txt
├── setup.cfg
└── setup.py

Меняем __init__.py в проекте:

Вместо
from pyramid.config import Configurator
from sqlalchemy import engine_from_config

from .models import DBSession

def main(global_config, **settings):
    """ This function returns a Pyramid WSGI application.
    """
    engine = engine_from_config(settings, 'sqlalchemy.')
    DBSession.configure(bind=engine)
    config = Configurator(settings=settings)
    config.add_static_view('static', 'static', cache_max_age=3600)
    config.add_route('home', '/')
    config.scan()
    return config.make_wsgi_app()

Делаем
from pyramid.config import Configurator
from sqlalchemy import engine_from_config

from .models import DBSession

def main(global_config, **settings):
    """ This function returns a Pyramid WSGI application.
    """
    engine = engine_from_config(settings, 'sqlalchemy.')
    DBSession.configure(bind=engine)
    config = Configurator(settings=settings)
    config.add_static_view('static', 'static', cache_max_age=3600)

    # add config for each of your subapps
    config.include('myproject.app1')

    # pyramid_jinja2 configuration
    config.include('pyramid_jinja2')
    config.add_jinja2_search_path("myproject:templates")

    return config.make_wsgi_app()

Теперь в этом __init__.py мы будем хранить глобальные настройки, а в инитах апликайшинах настройки самих апликайшинов.

Добавляем models.py в основной проект
MyProject/
.
├── CHANGES.txt
├── development.ini
├── MANIFEST.in
├── myproject
│   ├── app1
│   │   ├── __init__.py
│   │   ├── models.py
│   │   ├── tests.py
│   │   └── views.py
│   ├── __init__.py
│   ├── scripts
│   │   ├── initializedb.py
│   │   └── __init__.py
│   ├── models.py
│   ├── static
│   │   ├── favicon.ico
│   │   ├── footerbg.png
│   │   ├── headerbg.png
│   │   ├── ie6.css
│   │   ├── middlebg.png
│   │   ├── pylons.css
│   │   ├── pyramid.png
│   │   ├── pyramid-small.png
│   │   └── transparent.gif
│   └── templates
│       └── mytemplate.pt
├── production.ini
├── README.txt
├── setup.cfg
└── setup.py

В нем будет хранится сессия SQLAlchemy общая для всех проектов
from zope.sqlalchemy import ZopeTransactionExtension
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import (
    scoped_session,
    sessionmaker,
    )

DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))
Base = declarative_base()

Меняем __init__.py в app1
def includeme(config):
    config.add_route('home', '/')
    config.scan()

Меняем models.py в app1
from sqlalchemy import (
    Column,
    Integer,
    Text,
    )

from sqlalchemy.ext.declarative import declarative_base

from sqlalchemy.orm import (
    scoped_session,
    sessionmaker,
    )

from ..models import (
    DBSession,
    Base,
    )

class MyModel(Base):
    __tablename__ = 'models'
    id = Column(Integer, primary_key=True)
    name = Column(Text, unique=True)
    value = Column(Integer)

    def __init__(self, name, value):
        self.name = name
        self.value = value


Меняем tests.py
import unittest
import transaction

from pyramid import testing

from ..models import DBSession

class TestMyView(unittest.TestCase):
    def setUp(self):
        self.config = testing.setUp()
        from sqlalchemy import create_engine
        engine = create_engine('sqlite://')
        from .models import (
            Base,
            MyModel,
            )
        DBSession.configure(bind=engine)
        Base.metadata.create_all(engine)
        with transaction.manager:
            model = MyModel(name='one', value=55)
            DBSession.add(model)

    def tearDown(self):
        DBSession.remove()
        testing.tearDown()

    def test_it(self):
        from .views import my_view
        request = testing.DummyRequest()
        info = my_view(request)
        self.assertEqual(info['one'].name, 'one')
        self.assertEqual(info['project'], 'MyProject')

Меняем views.py
from sqlalchemy.exc import DBAPIError

from ..models import DBSession
from .models import MyModel

@view_config(route_name='home', renderer='../templates/mytemplate.pt')
def my_view(request):
    try:
        one = DBSession.query(MyModel).filter(MyModel.name=='one').first()
    except DBAPIError:
        return Response(conn_err_msg, content_type='text/plain', status_int=500)
    return {'one':one, 'project':'MyProject'}

conn_err_msg = """\
Pyramid is having a problem using your SQL database.  The problem
might be caused by one of the following things:

1.  You may need to run the "initialize_MyProject_db" script
    to initialize your database tables.  Check your virtual 
    environment's "bin" directory for this script and try to run it.

2.  Your database server may not be running.  Check that the
    database server referred to by the "sqlalchemy.url" setting in
    your "development.ini" file is running.

After you fix the problem, please restart the Pyramid application to
try it again.
"""


Меняем initializedb.py в scripts
import os
import sys
import transaction

from sqlalchemy import engine_from_config

from pyramid.paster import (
    get_appsettings,
    setup_logging,
    )

from ..models import (
    DBSession,
    Base,
    )

from ..app1.models import MyModel

def usage(argv):
    cmd = os.path.basename(argv[0])
    print('usage: %s \n'
          '(example: "%s development.ini")' % (cmd, cmd)) 
    sys.exit(1)

def main(argv=sys.argv):
    if len(argv) != 2:
        usage(argv)
    config_uri = argv[1]
    setup_logging(config_uri)
    settings = get_appsettings(config_uri)
    engine = engine_from_config(settings, 'sqlalchemy.')
    DBSession.configure(bind=engine)
    Base.metadata.create_all(engine)
    with transaction.manager:
        model = MyModel(name='one', value=1)
        DBSession.add(model)

Выполняем python setup.py install, запускаем проект, профит!
Остальные апликайшины добавляются по аналогии.
Навеянно этим http://stackoverflow.com/questions/6012991/pyramid-project-structure

27 октября 2011

Pylons + SQLalchemy расширенная модель (Mixin)

Часто при проектировании структуры БД появляется необходимость повторять одни и те же действия с таблицами. Добавлять одинаковые поля, ссылки, счетчики и т.д. Модели в Pylons(SQLAlchemy), как и в большинстве других фреймворках использующих паттерн MVC, являются классом и соответственно могут быть унаследованы от других классов. Это позволяет нам избежать рутинной работы с повторяющимися действиями.

Все расширения для наших моделей будем добавлять в 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

24 октября 2011

SQLalchemy UML диаграмма

Для визуализации своей базы в SQLAlchemy удобно использовать graphviz и библиотеку sqlalchemy_schemadisplay.
Установка:
apt-get install graphviz
pip install sqlalchemy_schemadisplay
Далее читаем доки SQLAlchemy Schema Display
Для Ъ:
Схема БД строится на основании данных базы.

from sqlalchemy import MetaData
from sqlalchemy_schemadisplay import create_schema_graph

# create the pydot graph object by autoloading all tables via a bound metadata object
graph = create_schema_graph(metadata=MetaData('postgres://user:pwd@host/database'),
   show_datatypes=False, # The image would get nasty big if we'd show the datatypes
   show_indexes=False, # ditto for indexes
   rankdir='LR', # From left to right (instead of top to bottom)
   concentrate=False # Don't try to join the relation lines together
)
graph.write_png('dbschema.png') # write out the file

Схема БД Postgres

Схема UML строится по моделям проекта.

from myapp import model
from sqlalchemy_schemadisplay import create_uml_graph
from sqlalchemy.orm import class_mapper

# lets find all the mappers in our model
mappers = []
for attr in dir(model):
    if attr[0] == '_': continue
    try:
        cls = getattr(model, attr)
        mappers.append(class_mapper(cls))
    except:
        pass

# pass them to the function and set some formatting options
graph = create_uml_graph(mappers,
    show_operations=False, # not necessary in this case
    show_multiplicity_one=False # some people like to see the ones, some don't
)
graph.write_png('schema.png') # write out the file

Схема моделей в Pylons
 
Для Django кодеров есть модуль django-extension который добавляет много полезных команд для manage.py. Вот мой вариант скрипта для Django:
project_dir/_visualozation/visualized.sh
curent_d="`date +%H%M_%d%m%y`" 
exec python ../manage.py graph_models -a -g -o scheme_of_$curent_d.png 

пример django-extension + graphviz
UPD: sadisplay - замечательная штука!