Django и SQLAlchemy
Originally published at Pythy. You can comment here or there.
В августе мы с вами говорили о создание новой ветки в 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).
Эти проблемы можно решить так:
Сконвертировать параметры БД из Django в SQLAlchemy
Имя таблицы, которое явно указывается в SQLAlchemy, можно узнать из Django:models.DownloadItem._meta.db_table.
По идее, можно "маппить" 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-репозитории.