paster setup-app config.ini
Then you are ready to go.
+
+Creating models
+======================
+
+To create a new model class, type::
+
+ paster model mymodel
+
+Once you have defined your model classes in mymodel, import them in floof/models/__init__.py::
+
+ from mymodel import MyEntity
+
+To create tables use create_sql::
+
+ paster create_sql
+
+To drop tables use drop_sql::
+
+ paster drop_sql
+
+Note that you must first import your classes into floof/models/__init__.py in order for the database commands to work !
+
from mako.lookup import TemplateLookup
from pylons import config
-from pylons.error import handle_mako_error
from sqlalchemy import engine_from_config
import floof.lib.app_globals as app_globals
import floof.lib.helpers
-from floof import model
from floof.config.routing import make_map
+import floof.model as model
def load_environment(global_conf, app_conf):
"""Configure the Pylons environment via the ``pylons.config``
# Create the Mako TemplateLookup, with the default auto-escaping
config['pylons.app_globals'].mako_lookup = TemplateLookup(
directories=paths['templates'],
- error_handler=handle_mako_error,
module_directory=os.path.join(app_conf['cache_dir'], 'templates'),
- input_encoding='utf-8', default_filters=['escape'],
- imports=['from webhelpers.html import escape'])
-
- # Setup the SQLAlchemy database engine
+ input_encoding='utf-8', output_encoding='utf-8',
+ imports=['from webhelpers.html import escape'],
+ default_filters=['escape'])
+
+ # Setup the SQLAlchemy^W Elixir database engine
engine = engine_from_config(config, 'sqlalchemy.')
-
if model.elixir.options_defaults.get('autoload'):
+ # Reflected tables
model.elixir.bind = engine
model.metadata.bind = engine
model.elixir.setup_all()
else:
+ # Non-reflected tables
model.init_model(engine)
-
- model.meta.engine = engine
-
+
# CONFIGURATION OPTIONS HERE (note: all config options will override
# any Pylons config options)
--- /dev/null
+# Import your forms here
--- /dev/null
+# Import your forms here
--- /dev/null
+from formencode import *
+from formencode import validators
+import pylons
+
+_ = validators._ # dummy translation string
+
+# Custom schemas
+
+class FilteringSchema(Schema):
+ "Schema with extra fields filtered by default"
+ filter_extra_fields = True
+ allow_extra_fields = True
+
+# Model-based validators
+
+class Unique(validators.FancyValidator):
+
+ """
+ Checks if given value is unique to the model.Will check the state: if state object
+ is the same as the instance, or the state contains a property with the same name
+ as the context name. For example:
+
+ validator = validators.Unique(model.NewsItem, "title", context_name="news_item")
+
+ This will check if there is an existing instance with the same "title". If there
+ is a matching instance, will check if the state passed into the validator is the
+ same instance, or if the state contains a property "news_item" which is the same
+ instance.
+ """
+
+ __unpackargs__ = ('model', 'attr', "model_name", "context_name", "attribute_name")
+ messages = {
+ 'notUnique' : _("%(modelName)s already exists with this %(attrName)s"),
+ }
+
+ model_name = "Item"
+ attribute_name = None
+ context_name = None
+
+ def validate_python(self, value, state):
+ instance = self.model.get_by(**{self.attr : value})
+ if instance:
+ context_name = self.context_name or self.model.__name__.lower()
+ if state != instance and \
+ getattr(state, context_name, None) != instance:
+ attr_name = self.attribute_name or self.attr
+ raise Invalid(self.message('notUnique', state,
+ modelName=self.model_name,
+ attrName=attr_name),
+ value, state)
+
+validators.Unique = Unique
Provides the BaseController class for subclassing.
"""
-from pylons import session, tmpl_context as c
from pylons.controllers import WSGIController
from pylons.templating import render_mako as render
+from pylons import config
+from floof import model
+from pylons import session, tmpl_context as c
from floof.model.users import User
class BaseController(WSGIController):
+ # NOTE: This could have been implemented as a middleware =]
def __before__(self, action, **params):
# Fetch current user object
try:
# WSGIController.__call__ dispatches to the Controller method
# the request is routed to. This routing information is
# available in environ['pylons.routes_dict']
- return WSGIController.__call__(self, environ, start_response)
+
+ # Insert any code to be run per request here.
+
+ try:
+ return WSGIController.__call__(self, environ, start_response)
+ finally:
+ model.Session.remove()
--- /dev/null
+import types
+import sys
+import os
+import simplejson
+
+from floof import model as model
+
+"""
+This module can be used for loading data into your models, for example when setting up default application data,
+unit tests, JSON export/import and importing/exporting legacy data. Data is serialized to and from the JSON format.
+"""
+
+VALID_FIXTURE_FILE_EXTENSIONS = ['.json']
+
+def load_data(model, filename=None, base_dir=None):
+ """Installs provided fixture files into given model. Filename may be directory, file or list of dirs or files. If filename is
+ None, assumes that source file is located in fixtures/model_module_name/model_tablename.yaml of your application directory,
+ for example MyProject/fixtures/news/newsitems.yaml. The base_dir argument is the top package of the application unless
+ specified. You can also pass the name of a table instead of a model class."""
+
+ if type(model) is types.StringType:
+ return load_data_to_table(model, filename, base_dir)
+ else:
+ if filename is None:
+ filename = _default_fixture_path_for_model(model, base_dir)
+ return _load_data_from_file(model, filename)
+
+def load_data_to_table(table, filename=None, base_dir=None):
+ """Installs data directly into a table. Useful if table does not have a corresponding model, for example a many-to-many join table.
+ """
+
+ if filename is None:
+ filename = _default_fixture_path_for_table(table, base_dir)
+ _load_data_to_table(table, filename)
+
+def dump_data(model, filename=None, **params):
+ """Dumps data to given destination. Params are optional arguments for selecting data. If filename is None, assumes that destination
+ file is located in fixtures/model_module_name/model_name_lowercase.yaml of your application directory, for example
+ MyProject/fixtures/news/newsitem.yaml.
+ """
+
+ if filename is None:
+ filename = _default_fixture_path_for_model(model)
+ _dump_data_to_file(model, filename, **params)
+
+_base_dir = os.path.dirname(os.path.dirname(__file__))
+
+def _default_fixture_path_for_model(model, base_dir=None):
+ if base_dir is None:
+ base_dir = _base_dir
+ path = os.path.join(base_dir, 'fixtures')
+ module_dirs = model.__module__.split('.', 2)[-1].split('.')
+ for dir in module_dirs:
+ path = os.path.join(path, dir)
+ return os.path.join(path, model.table.name + '.json')
+
+def _default_fixture_path_for_table(table, base_dir=None):
+ if base_dir is None:
+ base_dir = _base_dir
+ module_dirs = table.split('.')
+ path = os.path.join(base_dir, 'fixtures')
+ for name in module_dirs:
+ path = os.path.join(path, name)
+ return path + ".json"
+
+def _is_fixture_file(filename):
+ basename, ext = os.path.splitext(filename)
+ return (ext.lower() in VALID_FIXTURE_FILE_EXTENSIONS)
+
+def _load_data_from_dir(model, dirname):
+ for dirpath, dirnames, filenames in os.walk(dirname):
+ for filename in filenames:
+ _load_data_from_file(model, filename)
+
+def _load_data_from_file(model, filename):
+ if not _is_fixture_file(filename):
+ return
+ fp = file(filename, 'r')
+ data = simplejson.load(fp)
+ fp.close()
+ retval = None
+ if type(data) is types.ListType:
+ retval = []
+ for item in data:
+ retval.append(_load_instance_from_dict(model, item))
+ elif type(data) is types.DictType:
+ retval = {}
+ for key, item in data.iteritems():
+ retval[key] = _load_instance_from_dict(model, item)
+ return retval
+
+def _load_data_to_table(tablename, filename):
+ if not _is_fixture_file(filename):
+ return
+ fp = file(filename, 'r')
+ data = simplejson.load(fp)
+ fp.close()
+ tablename = tablename.split(".")[-1]
+ table = model.context.metadata.tables[tablename]
+ if type(data) is types.ListType:
+ for item in data:
+ table.insert(item).execute()
+ elif type(data) is types.DictType:
+ for key, item in data.iteritems():
+ table.insert(item).execute()
+ return data
+
+def _dump_data_to_file(model, filename, **params):
+ if params:
+ queryset = model.select_by(**params)
+ else:
+ queryset = model.select()
+ data = []
+ for instance in queryset:
+ data.append(_dump_instance_to_dict(instance))
+ fp = file(filename, 'w')
+ simplejson.dump(data, fp)
+ fp.close()
+
+def _load_instance_from_dict(model, dict):
+ if not dict: return
+ instance = model()
+ fields = model._descriptor.fields.keys()
+ for k, v in dict.iteritems():
+ if k in fields:
+ setattr(instance, k, v)
+ instance.flush()
+ return instance
+
+def _dump_instance_to_dict(instance):
+ if hasattr(instance, 'to_json'):
+ return instance.to_json()
+ d = {}
+ fields = instance._descriptor.fields.keys()
+ for field in fields:
+ d[field] = getattr(instance, field)
+ return d
+
+__all__ = ['load_data', 'dump_data']
+# -*- coding: utf-8 -*-
"""Helper functions
Consists of functions to typically be used within templates, but also
-available to Controllers. This module is available to templates as 'h'.
+available to Controllers. This module is available to both as 'h'.
"""
# Import helpers as desired, or define your own, ie:
-#from webhelpers.html.tags import checkbox, password
+# from webhelpers.html.tags import checkbox, password
from webhelpers import *
from routes import url_for, redirect_to
+# Scaffolding helper imports
from webhelpers.html.tags import *
from webhelpers.html import literal
from webhelpers.pylonslib import Flash
import sqlalchemy.types as types
flash = Flash()
+# End of.
+
+def get_object_or_404(model, **kw):
+ from pylons.controllers.util import abort
+ """
+ Returns object, or raises a 404 Not Found is object is not in db.
+ Uses elixir-specific `get_by()` convenience function (see elixir source:
+ http://elixir.ematia.de/trac/browser/elixir/trunk/elixir/entity.py#L1082)
+ Example: user = get_object_or_404(model.User, id = 1)
+ """
+ obj = model.get_by(**kw)
+ if obj is None:
+ abort(404)
+ return obj
+
-#
-# floof/floof/model/__init__.py
-#
-# Copyright (c) 2009 Scribblr
-#
-# See: http://bel-epa.com/docs/elixir_pylons/
-#
-
"""The application's model objects"""
-
-from floof.model import art, users
-from floof.model import meta
import elixir
+from floof.model import meta
-elixir.options_defaults.update({ 'autoload': True, 'shortnames': True })
-
+Session = elixir.session = meta.Session
+# Uncomment if using reflected tables
+# elixir.options_defaults.update({'autoload': True})
+elixir.options_defaults.update({'shortnames':True})
metadata = elixir.metadata
+# this will be called in config/environment.py
+# if not using reflected tables
def init_model(engine):
- elixir.session.configure(bind=engine)
+ """Call me before using any of the tables or classes in the model"""
+ meta.Session.configure(bind=engine)
metadata.bind = engine
- if elixir.options_defaults.get('autoload', False):
- if not metadata.is_bound():
- elixir.delay_setup = True
- else:
- elixir.setup_all(True)
+# Delay the setup if using reflected tables
+if elixir.options_defaults.get('autoload', False) \
+ and not metadata.is_bound():
+ elixir.delay_setup = True
+
+# # import other entities here, e.g.
+# from floof.model.blog import BlogEntry, BlogComment
+from floof.model.art import Art
+from floof.model.users import User, IdentityURL
+
+# Finally, call elixir to set up the tables.
+# but not if using reflected tables
+if not elixir.options_defaults.get('autoload', False):
+ elixir.setup_all()
+
-"""SQLAlchemy Metadata object"""
+"""SQLAlchemy Metadata and Session object"""
from sqlalchemy import MetaData
from sqlalchemy.orm import scoped_session, sessionmaker
-__all__ = ['engine', 'metadata']
+__all__ = ['Session', 'engine', 'metadata']
# SQLAlchemy database engine. Updated by model.init_model()
engine = None
-metadata = MetaData()
+# SQLAlchemy session manager. Updated by model.init_model()
+Session = scoped_session(sessionmaker())
+# Global metadata. If you have multiple databases with overlapping table
+# names, you'll need a metadata for each database
+metadata = MetaData()
from webtest import TestApp
import pylons.test
+from elixir import *
+from floof.model import *
+from floof.model import meta
+from floof import model as model
+from sqlalchemy import engine_from_config
+
+__all__ = ['environ', 'url', 'TestController', 'TestModel']
-__all__ = ['environ', 'url', 'TestController']
# Invoke websetup with the current config file
-SetupCommand('setup-app').run([config['__file__']])
+# SetupCommand('setup-app').run([config['__file__']])
+
+# additional imports ...
+import os
+from paste.deploy import appconfig
+from floof.config.environment import load_environment
+here_dir = os.path.dirname(__file__)
+conf_dir = os.path.dirname(os.path.dirname(here_dir))
+
+test_file = os.path.join(conf_dir, 'test.ini')
+conf = appconfig('config:' + test_file)
+load_environment(conf.global_conf, conf.local_conf)
environ = {}
+engine = engine_from_config(config, 'sqlalchemy.')
+model.init_model(engine)
+metadata = elixir.metadata
+Session = elixir.session = meta.Session
+
+class Individual(Entity):
+ """Table 'Individual'.
+
+ >>> me = Individual('Groucho')
+
+ # 'name' field is converted to lowercase
+ >>> me.name
+ 'groucho'
+ """
+ name = Field(String(20), unique=True)
+ favorite_color = Field(String(20))
+
+ def __init__(self, name, favorite_color=None):
+ self.name = str(name).lower()
+ self.favorite_color = favorite_color
+
+setup_all()
+
+def setup():
+ pass
+
+def teardown():
+ pass
+
+class TestModel(TestCase):
+ def setUp(self):
+ setup_all(True)
+
+ def tearDown(self):
+ drop_all(engine)
+
+
class TestController(TestCase):
def __init__(self, *args, **kwargs):
--- /dev/null
+from floof.tests import *
+
+class TestAppServer(TestController):
+ def test_index(self):
+ response = self.app.get('/')
+ # Test response...
+ assert '<span style="color:lime">Elixir DSL</span>' in response
--- /dev/null
+from floof.tests import *
+from floof.tests import Session, metadata
+
+class TestElixir(TestModel):
+ def setUp(self):
+ TestModel.setUp(self)
+
+ def test_metadata(self):
+ assert 'A collection of Tables and their associated schema constructs.' in metadata.__doc__
+
+ def test_session(self):
+ assert Session.connection().dialect.name is 'sqlite'
+
+from sqlalchemy.exceptions import IntegrityError
+from floof.tests import *
+from floof.tests import Session, metadata, Individual, create_all, drop_all
+
+class TestMyModel(TestModel):
+
+ def test_simpleassert(self):
+ """test description
+ """
+ einstein = Individual('einstein')
+
+ Session.commit()
+
+ ind1 = Individual.get_by(name = 'einstein')
+ assert ind1 == einstein
+
+ def test_exception(self):
+ me = Individual('giuseppe')
+ me_again = Individual('giuseppe')
+ self.assertRaises(IntegrityError, Session.commit)
+ Session.rollback()
+
+# -*- coding: utf-8 -*-
"""Setup the floof application"""
-import elixir
import logging
from floof.config.environment import load_environment
-from floof.model import meta
-from floof.model.users import IdentityURL, User
log = logging.getLogger(__name__)
+from pylons import config
+from elixir import *
+from floof import model as model
+
def setup_app(command, conf, vars):
"""Place any commands to setup floof here"""
load_environment(conf.global_conf, conf.local_conf)
+ model.metadata.create_all()
- # Create the tables if they don't already exist
- elixir.create_all(bind=meta.engine, checkfirst=False)
-
- ### Load some basic data
+ # Initialisation here ... this sort of stuff:
# Users
identity_url = IdentityURL(url=u'http://eevee.livejournal.com/')
user = User(name=u'Eevee')
user.identity_urls.append(identity_url)
- elixir.session.commit()
+
+ # some_entity = model.Session.query(model.<modelfile>.<Some_Entity>).get(1)
+ # e.g. foo = model.Session.query(model.identity.User).get(1)
+ # from datetime import datetime
+ # some_entity.poked_on = datetime.now()
+ # model.Session.add(some_entity)
+ model.Session.commit()
# ('templates/**.mako', 'mako', {'input_encoding': 'utf-8'}),
# ('public/**', 'ignore', None)]},
zip_safe=False,
- paster_plugins=['PasteScript', 'Pylons'],
+ paster_plugins=['Elixir', 'PasteScript', 'Pylons', 'Shabti'],
entry_points="""
[paste.app_factory]
main = floof.config.middleware:make_app
use = config:development.ini
# Add additional test specific configuration options as necessary.
+sqlalchemy.url = sqlite:///%(here)s/nosetest.db
+sqlalchemy.echo = False
\ No newline at end of file