Jul 05, 2012
|
Одной из причин отказа развивать ветку 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:
A. 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.
B. 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 <config_uri>\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
, запускаем проект, профит!