From c03a740f65ea24ba6567b0ef75f3785faae37d42 Mon Sep 17 00:00:00 2001 From: Nick Retallack Date: Sun, 4 Oct 2009 03:15:07 -0700 Subject: [PATCH] successfully ported everything to a flesh shabti template. shortnames work now, and so does all your stuff. Enjoy --- README.txt | 22 +++++ floof/config/environment.py | 20 ++--- floof/forms/__init__.py | 1 + floof/forms/validators/__init__.py | 1 + floof/forms/validators/unique.py | 52 ++++++++++++ floof/lib/base.py | 13 ++- floof/lib/fixtures.py | 139 +++++++++++++++++++++++++++++++ floof/lib/helpers.py | 21 ++++- floof/model/__init__.py | 43 +++++----- floof/model/meta.py | 10 ++- floof/public/bg.png | Bin 0 -> 339 bytes floof/public/favicon.ico | Bin 0 -> 2862 bytes floof/public/pylons-logo.gif | Bin 0 -> 2399 bytes floof/tests/__init__.py | 58 ++++++++++++- floof/tests/functional/test_appserver.py | 7 ++ floof/tests/functional/test_elixir.py | 13 +++ floof/tests/test_models.py | 22 +++++ floof/websetup.py | 22 +++-- setup.py | 2 +- test.ini | 2 + 20 files changed, 400 insertions(+), 48 deletions(-) create mode 100644 floof/forms/__init__.py create mode 100644 floof/forms/validators/__init__.py create mode 100644 floof/forms/validators/unique.py create mode 100644 floof/lib/fixtures.py create mode 100644 floof/public/bg.png create mode 100644 floof/public/favicon.ico create mode 100644 floof/public/pylons-logo.gif create mode 100644 floof/tests/functional/test_appserver.py create mode 100644 floof/tests/functional/test_elixir.py diff --git a/README.txt b/README.txt index a1f2914..68db46c 100644 --- a/README.txt +++ b/README.txt @@ -17,3 +17,25 @@ Tweak the config file as appropriate and then setup the application:: 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 ! + diff --git a/floof/config/environment.py b/floof/config/environment.py index 20683db..3470f2a 100644 --- a/floof/config/environment.py +++ b/floof/config/environment.py @@ -3,13 +3,12 @@ import os 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`` @@ -32,22 +31,21 @@ def load_environment(global_conf, app_conf): # 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) diff --git a/floof/forms/__init__.py b/floof/forms/__init__.py new file mode 100644 index 0000000..53f1f19 --- /dev/null +++ b/floof/forms/__init__.py @@ -0,0 +1 @@ +# Import your forms here diff --git a/floof/forms/validators/__init__.py b/floof/forms/validators/__init__.py new file mode 100644 index 0000000..53f1f19 --- /dev/null +++ b/floof/forms/validators/__init__.py @@ -0,0 +1 @@ +# Import your forms here diff --git a/floof/forms/validators/unique.py b/floof/forms/validators/unique.py new file mode 100644 index 0000000..4fb3c8f --- /dev/null +++ b/floof/forms/validators/unique.py @@ -0,0 +1,52 @@ +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 diff --git a/floof/lib/base.py b/floof/lib/base.py index 4de5e19..cb7c746 100644 --- a/floof/lib/base.py +++ b/floof/lib/base.py @@ -2,14 +2,17 @@ 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: @@ -22,4 +25,10 @@ class BaseController(WSGIController): # 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() diff --git a/floof/lib/fixtures.py b/floof/lib/fixtures.py new file mode 100644 index 0000000..0b2f3ec --- /dev/null +++ b/floof/lib/fixtures.py @@ -0,0 +1,139 @@ +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'] diff --git a/floof/lib/helpers.py b/floof/lib/helpers.py index ff55eb6..84c3394 100644 --- a/floof/lib/helpers.py +++ b/floof/lib/helpers.py @@ -1,15 +1,32 @@ +# -*- 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 + diff --git a/floof/model/__init__.py b/floof/model/__init__.py index 5e963e2..9cacd18 100644 --- a/floof/model/__init__.py +++ b/floof/model/__init__.py @@ -1,28 +1,33 @@ -# -# 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() + diff --git a/floof/model/meta.py b/floof/model/meta.py index 7992ce1..1a20aa7 100644 --- a/floof/model/meta.py +++ b/floof/model/meta.py @@ -1,11 +1,15 @@ -"""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() diff --git a/floof/public/bg.png b/floof/public/bg.png new file mode 100644 index 0000000000000000000000000000000000000000..69c1798415dadd81f6fac359d0c05e226ca0981f GIT binary patch literal 339 zcmeAS@N?(olHy`uVBq!ia0vp^j0_A+Y8-4p)+FTzdLV(~Aa^H*b?0PW0y%6+-tI08 z{~1m)FszNdXaf}CEbxddW?wc6x=<11Hv2m#DR*|dAc};cpTpw>d4n%z@h!mzD_cK zmTJRW2mNEJyc@Qad=RcNS$DroTlV47wV3M~yB3^ut-U%)RVnBpzxAtxviha52jujd^(J<`;#A5l zaz9q3dh?~>q)&dwny)l(J@xESNyW9?)z53^YE3d+e*fT?Ro4WLp47;-SGriE#@_!# g=p?&ospxX?!b*Q3iz6E+0bS1E>FVdQ&MBb@0B==)#Q*>R literal 0 HcmV?d00001 diff --git a/floof/public/favicon.ico b/floof/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..21e215e337ef0900fd805117ae992f7226269a5c GIT binary patch literal 2862 zcmeHJZ%iCj5Fd7rJy>qv?k)eXcn622KwG5&X>8;!N=l;*Acr~dY_IK=ZsE|Ynv}*r zrGyqF6iBRsB!tsxx8L4}_K(bo>F{c{kDJ&;o@f-~R)wO6}gXkhYC#E39 zPW4{>2>eZiZ#@fKjtfFPl%K*8rxEZ5Y2Ta_V0~fYJ5K!xP!XU#{GC+i3s~o2U=I+F z{wdH}2}gagLld;G9lS3aHAjKv=Yz{{qrNCkeW}gEP@##`&rNVf`k8^2>!Uh3)T;`D%MUh#Yu{z+Wqj_i@6Y#7V2Pc8?=B@y@_0N_(W5a%5hHum2DjT< zDkMKyVz;^7E7Df2h_@-L6L>!HfihdXn|xbJ*5eNs*YY{yQ#N;6_L}%d))u;IYctr| z^$)E{^F8MCIr3^9wGL;x&lekEQ@)W~$MREM^VG7ED!D3Ck~r`C#EjaNW`4o-`MlND z%WiY;!o0WN*~}$&yZ4SxVmjYi^CG+QOrxaFkD8FA*EGiJ729~dd<(C88%%o5u3K>< zkNy=`(z9Ya(j2Cqh7K!g`xP2iZ0um@eaV7ZTYZ?g_Y_+94P(akAXYXqeEtnfB*)0D zZ?~c&PdJ=vH+Ai7vtnI~f(>m9OSdGV<&_Un?EW651D_)OyD;J4HyFR?43=zOhQT%| zl56Z`&lL{M;~Ux(e0oPL#szwjy)}Vs|0gIPyM*%b-;s5kL#gX)6n76}d0jjP+vm6% z__R#Zo4dMI+_q0fb$QCUgs$-$Z0`ul?@UFoa^Gc?2d*M(8AVI?cgR-eeY#hLLrBh4SGaFeb;{Y!;#N z!QzDXMTPf9Z_;e&7>jkI{Ps_%oVj#LIrqkC6o4E)cV$9+O%bwHMm3Oa3`d0@2LZx3v zsqHi5thR>5unRHXR3wyaIwkHn`m4C}_#fu=^=GJV=|cSf8LPQuM~F*x?grjuJRz5) c{MUEJAU$9nl72KBFFNB*H{+Of-+Ise4S{?VMgRZ+ literal 0 HcmV?d00001 diff --git a/floof/public/pylons-logo.gif b/floof/public/pylons-logo.gif new file mode 100644 index 0000000000000000000000000000000000000000..61b2d9ab369e8d161b5c2fd9e484be28d11b2486 GIT binary patch literal 2399 zcmV-l383~zNk%w1VLJgt0M!Ekii(Q!^YeOnbtx$+T3TAy*Vi2#9ipP5=;-K3NJtbD z6eT4k#>U3-%rYS%A)k<085tS2wzh6zD6Nz%JUlzzzB!G1F2bonA0QtxGBGG9Crm^p z8yp)P8yp)N8XFuO92^`P8X6-bA|4(dA0HnlCnqK*CL$stARr(hA0Qte9w;a%BO@au zBqb*(C?q2zBqSs#Cnpsa78n^B7Z(>77#9~878n>95D*X&5)vvZDjORcD=RB+Zf^h9 zF#rGmA^8LW00093EC2ui06PIh000L6z=3c`EE78iP# zn0OZ!j&TG(prN9pq@V}~J%5r;2?PQHiUg~(kan53m<BnYCEvX<`=hwL5I_sL=bWM4J%JH}Gro)>@?PEuf77zjY_qfas{D|ITB zq1augaDFaF?Fo&({hjgvz72@C>7gsDUZWR^*p5^5?s z3Xw#->F5thI-yma3^mY2oDG?a}kf@GE2>J`5O7z%Tq2VN9tgFI6Iz*$;N*iLCXHI+Twz3TY<*pn3 zltC-C5`g1v?hw-d;5(HDVww_glF}}Z%^qJRIoD5HoMC&YZ41_@+dkt9gM^pi|n*}M)VLbcy15MO9E1xSwxU#b{YT!Esjm| z17I)oK-V=tfwN2`-dl<{fQ!;Lv-KEJxhNwgkwMY)8Syb+MkxEY)ru*`xUF<1KFtNI zcl+?!pafn2#N7%4+jl7E3Y~coyXV`)=Apbd`Y19+ zf>l)mMv7#pLMAwoIC(3D?TDAdKq<;!KV;lFJXnDZHco_bDo!k{fWR)1>R3xKVDQq% z1PW3AO-nPm;r6m9q=A{S2LkY51N3LcBEBd92}ooDis*zl{=!71iX-?2pp6LDF&KAr zq`dU_pCk0hY4!*s<2vX%5Ehbu%M-&DDJ8=$dUBH5pyLvVr$SARVv9KBq*M^8!9PN; zl)jW76An3s8g4^@E0kgp-WLTIzN!ee6a_B#_)0~=N|dV$Vx3I+M;IP~m}78eKmy=R z_bF3=O#EMd_;^cQlERgd5@$e=gUxIvjGNs=WiB65rtA^HfUA_H2}KpDJRX6I!;G3x zs8UaRVhW%6L?boDAkPCe!GNEHB5(j0of=NFlMho307X%shOUm3)M)@jBkCommBE7l z0kPyjS=cHOWpF_pvD7##X>YYwvvqb1pM(t*e zrG0II$RfDw4X_#8o60tb=Uua%Byb=bk}4K7zt{EK zKcV#?01NoSjB71#cRL0I##g@Q>=1+B(YYc)_@JFVqS5waVGO&tU>dG%f!pw1t0qgq z(5&w^?kl$jMwo&_7_mE0(&85z*aEFg}*DP+z`w#PvGG5bo_-2a%*TyT!FoaapEI@|fqc+RuAy6jk(SX2tMO@eyR z@?3UGQ_aFi9#A9lS;N-J^-1Q6D-j04pUabpmQfDWR3;W>o6vPVCU zPW3mp(P=r1%N3ewvb_=+1qxXD)|2jm(qNNBRr|UK-{tYIBeBR~8~ee*IPwzS<~%tBB5+Sty*v$M_ZZgatyKk~M?$L%v_lKb4~HgWUP&2DzDlGN>vceagb RV|m*<+V7V2y~z*+06TypDp&vj literal 0 HcmV?d00001 diff --git a/floof/tests/__init__.py b/floof/tests/__init__.py index d51f6f2..68cb322 100644 --- a/floof/tests/__init__.py +++ b/floof/tests/__init__.py @@ -16,14 +16,68 @@ from routes.util import URLGenerator 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): diff --git a/floof/tests/functional/test_appserver.py b/floof/tests/functional/test_appserver.py new file mode 100644 index 0000000..03ce23a --- /dev/null +++ b/floof/tests/functional/test_appserver.py @@ -0,0 +1,7 @@ +from floof.tests import * + +class TestAppServer(TestController): + def test_index(self): + response = self.app.get('/') + # Test response... + assert 'Elixir DSL' in response diff --git a/floof/tests/functional/test_elixir.py b/floof/tests/functional/test_elixir.py new file mode 100644 index 0000000..bd0228b --- /dev/null +++ b/floof/tests/functional/test_elixir.py @@ -0,0 +1,13 @@ +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' + diff --git a/floof/tests/test_models.py b/floof/tests/test_models.py index e69de29..9b27a5c 100644 --- a/floof/tests/test_models.py +++ b/floof/tests/test_models.py @@ -0,0 +1,22 @@ +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() + diff --git a/floof/websetup.py b/floof/websetup.py index fa97149..7caeed0 100644 --- a/floof/websetup.py +++ b/floof/websetup.py @@ -1,25 +1,31 @@ +# -*- 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..).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() diff --git a/setup.py b/setup.py index ebbed71..30bf697 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ setup( # ('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 diff --git a/test.ini b/test.ini index 80fbd20..a16fbdc 100644 --- a/test.ini +++ b/test.ini @@ -19,3 +19,5 @@ port = 5000 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 -- 2.7.4