Понадобилось использовать Django ORM вне контекста самой Django. Конечно, проще и правильнее было бы использовать SQLAlchemy т.к. она рассчитана на такое использование, но мне нужно было написать паука, сохраняющего данные в БД и веб-интерфейс к этим данным. Веб-интерфейс точно решил делать на Django, а в пауке использовать те-же самые модели, чтобы не проделывать одну и ту же работу дважды. Т.к. паук был первым этапом работ а админка - вторым, появилась необходимость создавать таблицы из моделей без использования "syncdb".
В качестве основы использовалкод модуля django-standalone истатью Django ORM без Джанги -эта статья практически полностью решает проблему, но есть и несколько недостатков:
- Модели для создания таблиц нужно перечислять вручную
- Не создаются внешние ключи
- По - мелочи - нужно каждой модели вручную задавать app_label
Попытаемся исправить эти недостатки.
Сразу предупреждаю, что от необходимости устанавливать Django нас статья не освобождает. Так что первым шагом устанавливаем Django:
sudo pip install Django
Собственно, приведу сразу код с комментариями
#/usr/bin/env python
# -*- coding: utf8 -*-
from django.conf import settings
settings.configure(
# задаем параметры подключения к БД
DATABASES={
'default': {
'ENGINE': 'postgresql_psycopg2', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
'NAME': '***', # Or path to database file if using sqlite3.
'USER': '***', # Not used with sqlite3.
'PASSWORD': '***', # Not used with sqlite3.
'HOST': '****', # Set to empty string for localhost. Not used with sqlite3.
'PORT': '****', # Set to empty string for default. Not used with sqlite3.
}
},
# этот префикс будет добавляться к именам таблиц вместо имени приложения
# его нужно указывать только при использовании в скрипте. При использовании в
# Django этот параметр лучше убрать.
STANDALONE_APP_NAME='some_prefix'
)
from django.db import models
class StandAloneMetaclass(models.base.ModelBase):
"""Метакласс, который задает app_label моделям используя
значение из STANDALONE_APP_NAME"""
def __new__(cls, name, bases, attrs):
app_name = settings.STANDALONE_APP_NAME or None
if app_name:
if 'Meta' in attrs:
if not hasattr(attrs['Meta'], 'app_label'):
# устанавливаем app_label только если он уже не указан в самой модели
setattr(attrs['Meta'], 'app_label', app_name)
return super(StandAloneMetaclass, cls).__new__(cls, name, bases, attrs)
class BlogNote(models.Model):
__metaclass__ = StandAloneMetaclass # всем моделям нужно прописать этот метакласс
text=models.TextField()
# необходимо добавить атрибут Meta всем моделям для корректной работы метакласса
class Meta:
pass
class BlogComment(models.Model):
__metaclass__ = StandAloneMetaclass
text=models.TextField()
note=models.ForeignKey(BlogNote) # внешние ключи тоже работают
class Meta:
pass
def create_tables(tables):
from django.db import connection, transaction
from django.core.management.color import no_style
pending_references = {} # список внешних колючей, ожидающих создания связываемых таблиц
seen_models = set() # список созданных
style = no_style() # указывает, что не нужно раскрашивать синтаксис сгенерированного SQL
for model in tables: # проходим по списку моделей
sql, references = connection.creation.sql_create_model(model, style) # генерируем SQL и объекты внешних ключей
seen_models.add(model)
for refto, refs in references.items():
pending_references.setdefault(refto, []).extend(refs)
if refto in seen_models:
sql.extend( # если все таблицы внешнего ключа существуют, то добавляем сам ключ
connection.creation.sql_for_pending_references(
refto,
style,
pending_references))
sql.extend(
connection.creation.sql_for_pending_references(
model,
style,
pending_references))
cursor = connection.cursor()
for stmt in sql: # выполняем запросы к БД
print stmt
try:
cursor.execute(stmt)
except Exception, e:
print e
pass
print
transaction.commit_unless_managed() # завершаем транзакцию
for model in tables: # создаем индексы
index_sql = connection.creation.sql_indexes_for_model(model, style)
if index_sql:
try:
for stmt in index_sql:
print stmt
cursor.execute(stmt)
except Exception, e:
transaction.rollback_unless_managed()
else:
transaction.commit_unless_managed()
if __name__ == "__main__":
# находим все классы Django моделей в локальной области видимости и создаем для них
# таблицы в БД
create_tables(model for model in locals().values() if type(model) == StandAloneMetaclass)
Большая часть содержимого create_table() скопировано из файла
django/core/management/commands/syncdb.py. По сравнению с оригинальной статьей отпала необходимость вручную указывать для каждой модели app_label и вручную передавать список моделей для создания. Но появилась необходимость прописывать
__metaclass__
. Успокаивает то, что он один и тот же для всех классов. Ну и необходимо всем прописывать
class Meta: pass
. Можно это поправить, если создать базовый класс абстрактной модели с этими атрибутами и наследовать все модели от него.
Ну и общее впечатление - Django ORM не самое удачное решение для использования отдельно от Django. Если есть возможность, стоит использовать более подходящие библиотеки, например SQLAlchemy. Она хоть и более сложна для освоения, но и более мощная, поддерживает пуллинг соединений, более управляемая и рассчитана на такое использование.
Я для таких задач пишу django management command, которая либо запускается через cron, либо запускается при старте системы и в цикле что-то делает.
Да, так несомненно в большинстве случаев так проще и правильнее. Просто в моем случае был веб-паук на движке Scrapy у которого свои management_команды/интерфейс_командной_строки и т.п. и, в завершение, — он работает поверх Twisted, который c Django ну никак не пересекается. В этом случае использовать в качестве точки входа джанговский manage.py было бы довольно затруднительно.
Еще интересный момент в этом проекте — нужно было создать и поддерживать не одно подключение к БД а целый пул (10-15 одновременных соединений) т.к. данные приходят быстро, запросов к БД много и в EventLoop твистеда это уже не запихнешь. Расстроило то, что в Django ORM я не нашел никаких средств для управления пулом соединений, единственная возможность — это в каждом потоке заново выполнять
import models
тогда для каждого потока создается свое подключение.
Здравствуйте, всё бы хорошо, да только не работают ManyToManyField.
взял сначала этот пример http://imbolc.name/2009/12/django-orm.html
долго не мог понять куда девается ManyToManyField, потом ваш пример, тоже самое.. =(
Хм, очень может быть кстати, т.к. ManyToMany создает отдельную таблицу.
А каким образом не работает? Выдает ошибку? Если да то какую?
Да, как раз, не создаёт эти самые дополнительные таблицы.
Хм, очень может быть, т.к. функцию create_tables я не проверял на ManyToMany связях. Попробуйте пока задавать отдельную модельку для связи. Я попробую найти решение, если получится — напишу.
Но на крайний случай можно создать отдельную модельку а в ManyToMany задать параметр through https://docs.djangoproject.com/en/1.3/ref/models/fields/#django.db.models.ManyToManyField.through
есть же PEEWEE. Django ORM отдельно от джанго. Не требует никаких костылей, и все отлично работает.
https://github.com/coleifer/peewee
В первом же абзаце есть ответ на ваш комментарий.
Но на всякий случай повторю:
1) Если бы и решил использовать какую-то другую ORM вместо Django, то это была бы SQLAlchemy
2) Решил использовать Django т.к. все модели уже были задекларированы с помощью Django ORM.
3) Про peewee до вашего комментария ничего раньше не слышал. На вид — гораздо мощнее Django ORM, но пока будет не хватать библиотеки для миграции схемы.