Fork me on GitHub
11/4/2007

Django и SQLAlchemy

В августе мы с вами говорили о создание новой ветки в Django для поддержки SQLAlchemy. Спустя более чем полгода я с сожалением констатирую факт, что в этой ветке SQLAlchemy и не пахнет.

Самый быстрый способ использования SQLAlchemy в Django - описать свои модели в SQLAlchemy-терминах.

Давайте рассмотрим на примере кода из Django Live Tutorial. Для тех, кто не присутствовал на этом мероприятии, поясню, что это модельный веб-сервис для скачивания файлов по cron'у. Оригинальный код можно посмотреть здесь.

Напомню, как выглядит оригинальная модель:

from django.db import models
import datetime

class DownloadItem(models.Model):

    class Admin:
        pass

    class Meta:
        get_latest_by = 'added_at'

    url = models.CharField(maxlength=255, null=False)
    added_at = models.DateTimeField(auto_now_add=True)
    started_at = models.DateTimeField(null=True, blank=True)
    finished_at = models.DateTimeField(null=True, blank=True)

    def __str__(self):
        return self.url

    def is_started(self):
        return self.started_at is not None and datetime.datetime.now() > self.started_at

    def is_finished(self):
        return self.finished_at is not None and datetime.datetime.now() > self.finished_at

Давайте теперь в нашем приложении (dlt.downloader) создадим описание модели для SQLAlchemy, dlt.downloader.sa_models:

import datetime
import sqlalchemy as sa
from django.conf import settings

meta = sa.BoundMetaData(settings.SQLALCHEMY_DB_URI)

download_item_table = sa.Table('downloader_downloaditem', meta, autoload=True)

class DownloadItem(object):

    def __str__(self):
        return self.url

    def __repr__(self):
        return "%s(%s)" % (self.__class__.__name__, self.url)

    def is_started(self):
        return self.started_at is not None and datetime.datetime.now() > self.started_at

    def is_finished(self):
        return self.finished_at is not None and datetime.datetime.now() > self.finished_at

orm.mapper(DownloadItem, download_item_table)

Здесь всё привычно и обычно для тех, кто читал документацию по SQLAlchemy. Единственно, что обращу ваше внимание на параметр SQLALCHEMY_DB_URI, который нужно указать в settings.py.

Теперь напишем простенькую вьюшку с использованием SQLAlchemy ORM:

from sqlalchemy import orm
from django.shortcuts import render_to_response
from dlt.downloader.models_sa import DownloadItem

def list_items(request);
    dbsession = orm.create_session()
    query = dbsession.query(DownloadItem)
    items = query.select()
    context = {'items': items}
    return render_to_response('list_items.html')

Вроде всё правильно. Добавляем в urls.py нашу новую вьюшку и пробуем...

Для разминки на SQLAlchemy ORM сделаем и "качалку":

import os
import time
import datetime

os.environ['DJANGO_SETTINGS_MODULE'] = 'dlt.settings'

from sqlalchemy import orm
from dlt.downloader.models_sa import DownloadItem

dbsession = orm.create_session()
query = dbsession.query(DownloadItem)

for item in query.select_by(started_at=None):
    print u"Начинаем качать с %s" % item.url
    item.started_at = datetime.datetime.now()
    dbsession.flush()
    time.sleep(10)
    print u"Закончили качать с %s" % item.url
    item.finished_at = datetime.datetime.now()
    dbsession.flush()

Всё достаточно просто и понятно.

Давайте оглянемся назад и посмотрим, что же у нас получилось... Настройки дублируются (нужно указывать параметры БД как для Django ORM, так и для SQLAlchemy), код дублируется (модели описываются и для Django ORM, и для SQLAlchemy).

Эти проблемы можно решить так:

  1. Сконвертировать параметры БД из Django в SQLAlchemy
  2. Имя таблицы, которое явно указывается в SQLAlchemy, можно узнать из Django: models.DownloadItem._meta.db_table.
  3. По идее, можно "маппить" SQLAlchemy ORM к Django-моделям (правда, я не гарантирую отсутствия "наводок" между Django ORM и SQLAlchemy, так что действуйте на свой страх и риск).

В итоге, учитывая эти соображения, получаем такой код:

import sqlalchemy as sa
import sqlalchemy.orm as orm
from django.conf import settings

from dlt.downloader.models import DownloadItem

def get_db_uri():
    ...
    # составляем URI из параметров Django
    # не особо интересно
    # так что пропустим реальный код

meta = sa.BoundMetaData(get_db_uri())

download_item_table = sa.Table(DownloadItem._meta.db_table, meta, autoload=True)

orm.mapper(DownloadItem, download_item_table)

Полный код, как всегда, - на code.google.com

Перспективы

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

Комментарии

Все статьи