commit ed870464276b0749af08daee3c08cc8754076010 Author: Gael Pasgrimaud Date: Sat Jan 15 13:33:31 2011 +0100 formalchemy.ext.pyramid as moved diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cd5c2a1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.pyc +*.pt.py diff --git a/CHANGES.txt b/CHANGES.txt new file mode 100644 index 0000000..35a34f3 --- /dev/null +++ b/CHANGES.txt @@ -0,0 +1,4 @@ +0.0 +--- + +- Initial version diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..92e3aa6 --- /dev/null +++ b/README.txt @@ -0,0 +1,4 @@ +pyramid_formalchemy README + + + diff --git a/bootstrap.py b/bootstrap.py new file mode 100644 index 0000000..5f2cb08 --- /dev/null +++ b/bootstrap.py @@ -0,0 +1,260 @@ +############################################################################## +# +# Copyright (c) 2006 Zope Foundation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Bootstrap a buildout-based project + +Simply run this script in a directory containing a buildout.cfg. +The script accepts buildout command-line options, so you can +use the -c option to specify an alternate configuration file. +""" + +import os, shutil, sys, tempfile, textwrap, urllib, urllib2, subprocess +from optparse import OptionParser + +if sys.platform == 'win32': + def quote(c): + if ' ' in c: + return '"%s"' % c # work around spawn lamosity on windows + else: + return c +else: + quote = str + +# See zc.buildout.easy_install._has_broken_dash_S for motivation and comments. +stdout, stderr = subprocess.Popen( + [sys.executable, '-Sc', + 'try:\n' + ' import ConfigParser\n' + 'except ImportError:\n' + ' print 1\n' + 'else:\n' + ' print 0\n'], + stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() +has_broken_dash_S = bool(int(stdout.strip())) + +# In order to be more robust in the face of system Pythons, we want to +# run without site-packages loaded. This is somewhat tricky, in +# particular because Python 2.6's distutils imports site, so starting +# with the -S flag is not sufficient. However, we'll start with that: +if not has_broken_dash_S and 'site' in sys.modules: + # We will restart with python -S. + args = sys.argv[:] + args[0:0] = [sys.executable, '-S'] + args = map(quote, args) + os.execv(sys.executable, args) +# Now we are running with -S. We'll get the clean sys.path, import site +# because distutils will do it later, and then reset the path and clean +# out any namespace packages from site-packages that might have been +# loaded by .pth files. +clean_path = sys.path[:] +import site +sys.path[:] = clean_path +for k, v in sys.modules.items(): + if k in ('setuptools', 'pkg_resources') or ( + hasattr(v, '__path__') and + len(v.__path__)==1 and + not os.path.exists(os.path.join(v.__path__[0],'__init__.py'))): + # This is a namespace package. Remove it. + sys.modules.pop(k) + +is_jython = sys.platform.startswith('java') + +setuptools_source = 'http://peak.telecommunity.com/dist/ez_setup.py' +distribute_source = 'http://python-distribute.org/distribute_setup.py' + +# parsing arguments +def normalize_to_url(option, opt_str, value, parser): + if value: + if '://' not in value: # It doesn't smell like a URL. + value = 'file://%s' % ( + urllib.pathname2url( + os.path.abspath(os.path.expanduser(value))),) + if opt_str == '--download-base' and not value.endswith('/'): + # Download base needs a trailing slash to make the world happy. + value += '/' + else: + value = None + name = opt_str[2:].replace('-', '_') + setattr(parser.values, name, value) + +usage = '''\ +[DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options] + +Bootstraps a buildout-based project. + +Simply run this script in a directory containing a buildout.cfg, using the +Python that you want bin/buildout to use. + +Note that by using --setup-source and --download-base to point to +local resources, you can keep this script from going over the network. +''' + +parser = OptionParser(usage=usage) +parser.add_option("-v", "--version", dest="version", + help="use a specific zc.buildout version") +parser.add_option("-d", "--distribute", + action="store_true", dest="use_distribute", default=False, + help="Use Distribute rather than Setuptools.") +parser.add_option("--setup-source", action="callback", dest="setup_source", + callback=normalize_to_url, nargs=1, type="string", + help=("Specify a URL or file location for the setup file. " + "If you use Setuptools, this will default to " + + setuptools_source + "; if you use Distribute, this " + "will default to " + distribute_source +".")) +parser.add_option("--download-base", action="callback", dest="download_base", + callback=normalize_to_url, nargs=1, type="string", + help=("Specify a URL or directory for downloading " + "zc.buildout and either Setuptools or Distribute. " + "Defaults to PyPI.")) +parser.add_option("--eggs", + help=("Specify a directory for storing eggs. Defaults to " + "a temporary directory that is deleted when the " + "bootstrap script completes.")) +parser.add_option("-t", "--accept-buildout-test-releases", + dest='accept_buildout_test_releases', + action="store_true", default=False, + help=("Normally, if you do not specify a --version, the " + "bootstrap script and buildout gets the newest " + "*final* versions of zc.buildout and its recipes and " + "extensions for you. If you use this flag, " + "bootstrap and buildout will get the newest releases " + "even if they are alphas or betas.")) +parser.add_option("-c", None, action="store", dest="config_file", + help=("Specify the path to the buildout configuration " + "file to be used.")) + +options, args = parser.parse_args() + +# if -c was provided, we push it back into args for buildout's main function +if options.config_file is not None: + args += ['-c', options.config_file] + +if options.eggs: + eggs_dir = os.path.abspath(os.path.expanduser(options.eggs)) +else: + eggs_dir = tempfile.mkdtemp() + +if options.setup_source is None: + if options.use_distribute: + options.setup_source = distribute_source + else: + options.setup_source = setuptools_source + +if options.accept_buildout_test_releases: + args.append('buildout:accept-buildout-test-releases=true') +args.append('bootstrap') + +try: + import pkg_resources + import setuptools # A flag. Sometimes pkg_resources is installed alone. + if not hasattr(pkg_resources, '_distribute'): + raise ImportError +except ImportError: + ez_code = urllib2.urlopen( + options.setup_source).read().replace('\r\n', '\n') + ez = {} + exec ez_code in ez + setup_args = dict(to_dir=eggs_dir, download_delay=0) + if options.download_base: + setup_args['download_base'] = options.download_base + if options.use_distribute: + setup_args['no_fake'] = True + ez['use_setuptools'](**setup_args) + if 'pkg_resources' in sys.modules: + reload(sys.modules['pkg_resources']) + import pkg_resources + # This does not (always?) update the default working set. We will + # do it. + for path in sys.path: + if path not in pkg_resources.working_set.entries: + pkg_resources.working_set.add_entry(path) + +cmd = [quote(sys.executable), + '-c', + quote('from setuptools.command.easy_install import main; main()'), + '-mqNxd', + quote(eggs_dir)] + +if not has_broken_dash_S: + cmd.insert(1, '-S') + +find_links = options.download_base +if not find_links: + find_links = os.environ.get('bootstrap-testing-find-links') +if find_links: + cmd.extend(['-f', quote(find_links)]) + +if options.use_distribute: + setup_requirement = 'distribute' +else: + setup_requirement = 'setuptools' +ws = pkg_resources.working_set +setup_requirement_path = ws.find( + pkg_resources.Requirement.parse(setup_requirement)).location +env = dict( + os.environ, + PYTHONPATH=setup_requirement_path) + +requirement = 'zc.buildout' +version = options.version +if version is None and not options.accept_buildout_test_releases: + # Figure out the most recent final version of zc.buildout. + import setuptools.package_index + _final_parts = '*final-', '*final' + def _final_version(parsed_version): + for part in parsed_version: + if (part[:1] == '*') and (part not in _final_parts): + return False + return True + index = setuptools.package_index.PackageIndex( + search_path=[setup_requirement_path]) + if find_links: + index.add_find_links((find_links,)) + req = pkg_resources.Requirement.parse(requirement) + if index.obtain(req) is not None: + best = [] + bestv = None + for dist in index[req.project_name]: + distv = dist.parsed_version + if _final_version(distv): + if bestv is None or distv > bestv: + best = [dist] + bestv = distv + elif distv == bestv: + best.append(dist) + if best: + best.sort() + version = best[-1].version +if version: + requirement = '=='.join((requirement, version)) +cmd.append(requirement) + +if is_jython: + import subprocess + exitcode = subprocess.Popen(cmd, env=env).wait() +else: # Windows prefers this, apparently; otherwise we would prefer subprocess + exitcode = os.spawnle(*([os.P_WAIT, sys.executable] + cmd + [env])) +if exitcode != 0: + sys.stdout.flush() + sys.stderr.flush() + print ("An error occurred when trying to install zc.buildout. " + "Look above this message for any errors that " + "were output by easy_install.") + sys.exit(exitcode) + +ws.add_entry(eggs_dir) +ws.require(requirement) +import zc.buildout.buildout +zc.buildout.buildout.main(args) +if not options.eggs: # clean up temporary egg directory + shutil.rmtree(eggs_dir) diff --git a/buildout.cfg b/buildout.cfg new file mode 100644 index 0000000..f95c4c3 --- /dev/null +++ b/buildout.cfg @@ -0,0 +1,25 @@ +[buildout] +newest = false +extensions = gp.vcsdevelop +parts = eggs test +develop = . pyramidapp + +[eggs] +recipe = zc.recipe.egg +eggs = + pyramid_formalchemy + pyramidapp + WebTest + PasteScript + Sphinx + +[test] +recipe = zc.recipe.egg +eggs = + ${eggs:eggs} + nose +initialization = + import os + os.chdir('pyramidapp') +scripts= + nosetests=nosetests diff --git a/development.ini b/development.ini new file mode 100644 index 0000000..0f93efc --- /dev/null +++ b/development.ini @@ -0,0 +1,44 @@ +[app:pyramid_formalchemy] +use = egg:pyramid_formalchemy +reload_templates = true +debug_authorization = false +debug_notfound = false +debug_routematch = false +debug_templates = true +default_locale_name = en + +[pipeline:main] +pipeline = + egg:WebError#evalerror + pyramid_formalchemy + +[server:main] +use = egg:Paste#http +host = 0.0.0.0 +port = 6543 + +# Begin logging configuration + +[loggers] +keys = root + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = INFO +handlers = console + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s + +# End logging configuration diff --git a/pyramid_formalchemy/__init__.py b/pyramid_formalchemy/__init__.py new file mode 100644 index 0000000..7727143 --- /dev/null +++ b/pyramid_formalchemy/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +def include_formalchemy(config): + pass diff --git a/pyramid_formalchemy/configure.zcml b/pyramid_formalchemy/configure.zcml new file mode 100644 index 0000000..3547854 --- /dev/null +++ b/pyramid_formalchemy/configure.zcml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pyramid_formalchemy/resources.py b/pyramid_formalchemy/resources.py new file mode 100644 index 0000000..892d1e7 --- /dev/null +++ b/pyramid_formalchemy/resources.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- + +class AdminView(object): + def __init__(self, request): + self.request = request + request.model_name = None + request.model_id = None + request.format = 'html' + self.__parent__ = self.__name__ = None + def __getitem__(self, item): + if item in ('json',): + self.request.format = item + return self + model = ModelListing(self.request, item) + model.__parent__ = self + return model + +class ModelListing(object): + def __init__(self, request, name): + self.request = request + request.model_name = name + self.__name__ = name + self.__parent__ = None + def __getitem__(self, item): + if item in ('json',): + self.request.format = item + return self + if item in ('new',): + raise KeyError() + model = ModelItem(self.request, item) + model.__parent__ = self + return model + +class ModelItem(object): + def __init__(self, request, name): + request.model_id = name + self.__name__ = name + self.__parent__ = None + diff --git a/pyramid_formalchemy/static/add.png b/pyramid_formalchemy/static/add.png new file mode 100644 index 0000000..1aa7f09 Binary files /dev/null and b/pyramid_formalchemy/static/add.png differ diff --git a/pyramid_formalchemy/static/admin.css b/pyramid_formalchemy/static/admin.css new file mode 100644 index 0000000..c96cdc1 --- /dev/null +++ b/pyramid_formalchemy/static/admin.css @@ -0,0 +1,470 @@ +/** + * Blue Box main CSS file + * @version 1.0.0 + * @author Aaron D. Campbell http://xavisys.com/ + */ + +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, font, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td { + border: 0; + font-family: inherit; + font-size: 100%; + font-style: inherit; + font-weight: inherit; + margin: 0; + outline: 0; + padding: 0; + vertical-align: baseline; +} +h1,h2,h3,h4,h5,h6 { + font-weight:bold; +} +h1 { + font-size:2em; +} +h2 { + font-size:1.5em; +} +h3 { + font-size:1.3em; +} +h4 { + font-size:1.1em; +} +h5 { + font-size:1em; +} +h6 { + font-size:.8em; +} + +body { + background-color:#D5D6D7; + color:#333; + font:80% Verdana,"Trebuchet MS",Georgia,"Times New Roman",Times,serif; + margin:20px; + min-width:675px; + padding:0pt; +} + +table { + margin:1em; +} + +table, table tr { + border-collapse:collapse; + padding:0; +} + + +table th, +table td { + border:thin solid #D5D6D7; + border-collapse:collapse; + margin:0; + padding:0.2em 0.5em; +} + +table th { + font-weight:bold; + background:#EFEFEF; +} + +td a { + display:block; +} + +tr.odd { + background:#EFEFEF; +} + +p { + line-height:1.5em; + margin:1em 0; +} + +#header { + background-color:#73A0C5; + border:1px solid #666; + border-bottom:none; + height:70px; + position:relative; +} + +#title { + float:left; + margin:5px 0 0 1em; +} + +#header h1 { + font-size:2em; + padding:0pt; +} +#header #tagline { + color:#DDD; + font-size:0.9em; + font-style:italic; + margin:0; + text-align:right; +} + +#header h1, +#header h1 a, +#header h1 a:hover, +#header h1 a:visited { + color:#FFF; + text-decoration:none; +} + +h1#header { + color: white; + height: 1.5em; + padding: 0.3em 1em; +} + +h1#header a { + color: white; +} + +.breadcrumb { float:right; font-size: 0.7em;} + +#nav { + bottom:0; + position:absolute; + right:0; +} + +#nav a { + background-color:#EFEFEF; + border:1px solid #666; + border-bottom:0; + color:#259; + font-size:1.2em; + font-weight:bold; + margin:0 .1em; + padding:.2em; + padding-bottom:0; + text-decoration:none; +} + +#nav a:hover, +#nav a.current { + background-color:#FFF; +} + +#content { + background-color:#FFF; + border:1px solid #666; + border-width:0 1px; + padding:1em; +} +#content ol { + margin-left:2em; +} +#content ul { + margin-left:1.5em; +} +#sidebar { + float:right; + margin:1em; + width:260px; +} +#sidebar .box p { + background-color:#F2F2F2; + margin:.5em; +} +#sidebar .box ul li { + border-bottom:1px solid #73A0C5; + list-style-type:none; +} +#sidebar .box ul li a { + display:block; + padding:.5em; +} +#sidebar .box label { + display:block; + float:left; + height:21px; + margin-right:10px; + width:70px; +} +#footer { + background-color:#EFEFEF; + border:1px solid #666; + border-top:none; + font-size:.8em; + padding:1em; + text-align:center; +} +#footer p { + margin:0; +} + +a, +a:link { + color:#06C; + text-decoration:none; +} +a:visited { + color:#147; +} +a:hover, +a:active { + color:#147; + text-decoration:underline; +} + +blockquote, +code { + background-color:#F2F2F2; + border-left:4px solid #73A0C5; + display:block; + font-style:oblique; + line-height:20px; + margin:0 1em; + padding:0 1em; +} + +code { + white-space:pre; +} + +.box{ + border:1px solid #999; + margin:0 0 1em 0; + overflow:auto; +} +.box p, +.box ul, +.box ol, +.box div.cont, +.box form { + margin:.5em; +} +.box h1, +.box h2, +.box h3, +.box h4, +.box h5, +.box h6 { + background-color:#73A0C5; + display:block; + padding:0 5px; + color:white; +} +.more { + display:block; + font-size:.8em; + text-align:right; +} + +/********* + * Forms * + *********/ +.admin-flash, +fieldset { + color:#777; + margin-top:15px; + padding:10px; +} + +.admin-flash { + background:#EFEFEF; + margin-bottom:15px; + font-weight:bold; +} + +.message, +fieldset, +input, +button, +fieldset textarea, +fieldset select { + border:1px solid #F5F5F5; + border-left-color:#DDD; + border-top-color:#DDD; +} + +legend { + color:#73A0C5; + font-weight:bold; + padding:5px 10px; +} + +input, +button, +fieldset textarea, +fieldset select { + color:#777; + font:90% Verdana; + padding:4px; +} + +fieldset textarea { + width:430px; +} + +option { + padding:0 10px 0 5px; +} +fieldset label, +fieldset p.label { + color:#777; + text-align:right; + width:145px; +} +fieldset label { + float:left; + margin:5px 0; + margin-right:10px; +} + +fieldset p { + margin:0; +} +fieldset div { + padding:5px 0; + position:relative; +} +fieldset div div { + margin:0; +} +fieldset p.label { + left:0; + position:absolute; +} + +.radio { + margin-left:160px; +} +.radio label, +.radio input { + background:none; + border:none; + display:inline; + float:none; + vertical-align:middle; + width:auto; +} +.radio div { + clear:none; + white-space:nowrap; +} +#sidebar form { + margin:0 0 1em 0; +} + +.submit, +#sidebar .submit { + text-align:right; +} + +.ui-widget-link, +.submit input, +#sidebar .submit input { + background-color:#F9F9F9; + border:1px solid #F5F5F5; + border-left-color:#DDD; + border-top-color:#DDD; + cursor:pointer; + padding:0 21px; + text-transform:lowercase; + width:auto; +} + +.ui-widget-link input { + border:0px; + background:transparent; +} + +a.ui-widget-link { + color:#777777; + font-family:Verdana; +} + +.ui-widget-link { + cursor:pointer; + margin-right:0.3em; +} + + +td form { text-align:center;} + +td input.ui-icon { + background-color:transparent; + border:0px; + width:16px; + height: 0px; + overflow:hidden; + padding-bottom:13px; + cursor:pointer; +} + +.ui-icon-pencil { + background-image: url(./edit.png); +} +.ui-icon-circle-close { + background-image: url(./delete.png); +} + +#sidebar input { + border:1px solid #DDD; + border-bottom-color:#F5F5F5; + border-right-color:#F5F5F5; + width:100%; +} + +#sidebar label { + height:auto; + margin-bottom:0; + width:auto; +} + +.button { + width:auto; +} + +.form_controls { + margin-left:155px; +} + +.icon { + display:block; + height:0px; + padding-top:16px; + width:16px; + overflow:hidden; +} + +input.icon { + border:none; + width:16px; + height:16px; + padding:0; + cursor:pointer; +} + +#pager { + text-align: center; + margin-top: 0.5em; +} +#pager a, #pager .pager_curpage { + padding: 0 0.2em; + border:thin solid #114477; + background: #73A0C5; + color: white; +} + +#pager a:hover, #pager .pager_curpage { + text-decoration:none; + background:white; + color:#114477; +} + +/*****************************/ diff --git a/pyramid_formalchemy/static/delete.png b/pyramid_formalchemy/static/delete.png new file mode 100644 index 0000000..00b654e Binary files /dev/null and b/pyramid_formalchemy/static/delete.png differ diff --git a/pyramid_formalchemy/static/edit.png b/pyramid_formalchemy/static/edit.png new file mode 100644 index 0000000..188e1c1 Binary files /dev/null and b/pyramid_formalchemy/static/edit.png differ diff --git a/pyramid_formalchemy/templates/admin/edit.pt b/pyramid_formalchemy/templates/admin/edit.pt new file mode 100644 index 0000000..b09fdc5 --- /dev/null +++ b/pyramid_formalchemy/templates/admin/edit.pt @@ -0,0 +1,13 @@ + + +
+
+
+ +
+
+ +
+ + + diff --git a/pyramid_formalchemy/templates/admin/listing.pt b/pyramid_formalchemy/templates/admin/listing.pt new file mode 100644 index 0000000..a0ae26a --- /dev/null +++ b/pyramid_formalchemy/templates/admin/listing.pt @@ -0,0 +1,15 @@ + + +
+
+ +

+ + + ${F_('New')} ${model_name} + +

+ + + diff --git a/pyramid_formalchemy/templates/admin/master.pt b/pyramid_formalchemy/templates/admin/master.pt new file mode 100644 index 0000000..4ac20b7 --- /dev/null +++ b/pyramid_formalchemy/templates/admin/master.pt @@ -0,0 +1,34 @@ + + + + + + + + + +
+

+ + + + + + ${F_('Cancel')} + +

+
diff --git a/pyramid_formalchemy/templates/admin/models.pt b/pyramid_formalchemy/templates/admin/models.pt new file mode 100644 index 0000000..ac0ed9b --- /dev/null +++ b/pyramid_formalchemy/templates/admin/models.pt @@ -0,0 +1,12 @@ + + +
+
+
+ +
+
+
+ + diff --git a/pyramid_formalchemy/templates/admin/new.pt b/pyramid_formalchemy/templates/admin/new.pt new file mode 100644 index 0000000..1941bcf --- /dev/null +++ b/pyramid_formalchemy/templates/admin/new.pt @@ -0,0 +1,15 @@ + + +
+
+
+
+
+ +
+ + + + diff --git a/pyramid_formalchemy/templates/admin/restfieldset.pt b/pyramid_formalchemy/templates/admin/restfieldset.pt new file mode 100644 index 0000000..470fd36 --- /dev/null +++ b/pyramid_formalchemy/templates/admin/restfieldset.pt @@ -0,0 +1,96 @@ +{{if template_engine == 'mako'}} +# -*- coding: utf-8 -*- +<%! +from formalchemy.ext.pylons.controller import model_url +from pylons import url +%> +<%def name="h1(title, href=None)"> +

+ %if breadcrumb: + + %endif + %if href: + ${title.title()} + %else: + ${title.title()} + %endif +

+ +<%def name="buttons()"> +

+ + + + + + ${F_('Cancel')} + +

+ + + + + ${collection_name.title()} + + + + +
+ %if isinstance(models, dict): +

${F_('Models')}

+ %for name in sorted(models): +

+ ${name} +

+ %endfor + %elif is_grid: + ${h1(model_name)} +
+ ${pager|n} +
+
+ ${fs.render()|n} +
+

+ + + ${F_('New')} ${model_name} + +

+ %else: + ${h1(model_name, href=model_url(collection_name))} + %if action == 'show': + + ${fs.render()|n} +
+

+ + + ${F_('Edit')} + +

+ %elif action == 'edit': +
+ ${fs.render()|n} + + ${buttons()} +
+ %else: +
+ ${fs.render()|n} + ${buttons()} +
+ %endif + %endif +
+ + +{{endif}} + diff --git a/pyramid_formalchemy/templates/admin/show.pt b/pyramid_formalchemy/templates/admin/show.pt new file mode 100644 index 0000000..d34f85a --- /dev/null +++ b/pyramid_formalchemy/templates/admin/show.pt @@ -0,0 +1,10 @@ + + +
+
+
+
+
+ + + diff --git a/pyramid_formalchemy/templates/mytemplate.pt b/pyramid_formalchemy/templates/mytemplate.pt new file mode 100644 index 0000000..076856b --- /dev/null +++ b/pyramid_formalchemy/templates/mytemplate.pt @@ -0,0 +1,76 @@ + + + + The Pyramid Web Application Development Framework + + + + + + + + + + +
+
+
+
pyramid
+
+
+
+
+

+ Welcome to ${project}, an application generated by
+ the Pyramid web application development framework. +

+
+
+
+
+
+

Search documentation

+
+ + +
+
+ +
+
+
+ + + \ No newline at end of file diff --git a/pyramid_formalchemy/views.py b/pyramid_formalchemy/views.py new file mode 100644 index 0000000..d06a7ff --- /dev/null +++ b/pyramid_formalchemy/views.py @@ -0,0 +1,458 @@ +# -*- coding: utf-8 -*- +import os +from webhelpers.paginate import Page +from sqlalchemy.orm import class_mapper, object_session +from formalchemy.fields import _pk +from formalchemy.fields import _stringify +from formalchemy import Grid, FieldSet +from formalchemy.i18n import get_translator +from formalchemy.fields import Field +from formalchemy import fatypes +from pyramid.view import view_config +from pyramid.renderers import render +from pyramid.renderers import get_renderer +from pyramid import httpexceptions as exc + +try: + from formalchemy.ext.couchdb import Document +except ImportError: + Document = None + +try: + import simplejson as json +except ImportError: + import json + +class Session(object): + """A abstract class to implement other backend than SA""" + def add(self, record): + """add a record""" + def update(self, record): + """update a record""" + def delete(self, record): + """delete a record""" + def commit(self): + """commit transaction""" + +class ModelView(object): + """A RESTful view bound to a model""" + + engine = prefix_name = None + pager_args = dict(link_attr={'class': 'ui-pager-link ui-state-default ui-corner-all'}, + curpage_attr={'class': 'ui-pager-curpage ui-state-highlight ui-corner-all'}) + + def __init__(self, context, request): + self.context = context + self.request = request + self.settings = request.registry.settings + self.model = self.settings['fa.models'] + self.forms = self.settings.get('fa.forms', None) + self.FieldSet = self.settings.get('fa.fieldset', FieldSet) + self.Grid = self.settings.get('fa.grid', Grid) + + @property + def model_name(self): + """return ``model_name`` from ``pylons.routes_dict``""" + try: + return self.request.model_name + except AttributeError: + return None + + def route_url(self, *args): + return self.request.route_url('fa_admin', traverse='/'.join([str(a) for a in args])) + + def Session(self): + """return a Session object. You **must** override this.""" + return self.settings['fa.session_factory']() + + def models(self, **kwargs): + """Models index page""" + request = self.request + models = {} + if isinstance(self.model, list): + for model in self.model: + key = model.__name__ + models[key] = self.route_url(key, request.format) + else: + for key, obj in self.model.__dict__.iteritems(): + if not key.startswith('_'): + if Document is not None: + try: + if issubclass(obj, Document): + models[key] = self.route_url(key, request.format) + continue + except: + pass + try: + class_mapper(obj) + except: + continue + if not isinstance(obj, type): + continue + models[key] = self.route_url(key, request.format) + return self.render(models=models) + + def get_model(self): + if isinstance(self.model, list): + for model in self.model: + if model.__name__ == self.model_name: + return model + elif hasattr(self.model, self.model_name): + return getattr(self.model, self.model_name) + raise exc.HTTPNotFound(description='model %s not found' % self.model_name) + + def get_fieldset(self, id): + if self.forms and hasattr(self.forms, self.model_name): + fs = getattr(self.forms, self.model_name) + fs.engine = fs.engine or self.engine + return id and fs.bind(self.get(id)) or fs + raise KeyError(self.model_name) + + def get_add_fieldset(self): + if self.forms and hasattr(self.forms, '%sAdd' % self.model_name): + fs = getattr(self.forms, '%sAdd' % self.model_name) + fs.engine = fs.engine or self.engine + return fs + return self.get_fieldset(id=None) + + def get_grid(self): + model_name = self.model_name + if self.forms and hasattr(self.forms, '%sGrid' % model_name): + g = getattr(self.forms, '%sGrid' % model_name) + g.engine = g.engine or self.engine + g.readonly = True + self.update_grid(g) + return g + raise KeyError(self.model_name) + + def sync(self, fs, id=None): + """sync a record. If ``id`` is None add a new record else save current one. + + Default is:: + + S = self.Session() + if id: + S.merge(fs.model) + else: + S.add(fs.model) + S.commit() + """ + S = self.Session() + if id: + S.merge(fs.model) + else: + S.add(fs.model) + + def breadcrumb(self, fs=None, **kwargs): + """return items to build the breadcrumb""" + items = [] + request = self.request + model_name = request.model_name + id = request.model_id + items.append((self.route_url(), 'root')) + if self.model_name: + items.append((self.route_url(model_name), model_name)) + if id and hasattr(fs.model, '__unicode__'): + items.append((self.route_url(model_name, id), u'%s' % fs.model)) + elif id: + items.append((self.route_url(model_name, id), id)) + return items + + def render(self, **kwargs): + """render the form as html or json""" + request = self.request + if request.format != 'html': + meth = getattr(self, 'render_%s_format' % request.format, None) + if meth is not None: + return meth(**kwargs) + else: + return exc.HTTPNotfound() + kwargs.update( + main = get_renderer('pyramid_formalchemy:templates/admin/master.pt').implementation(), + model_name=self.model_name, + breadcrumb=self.breadcrumb(**kwargs), + F_=get_translator().gettext) + return kwargs + + def render_grid(self, **kwargs): + """render the grid as html or json""" + return self.render(is_grid=True, **kwargs) + + def render_json_format(self, fs=None, **kwargs): + request = self.request + request.override_renderer = 'json' + if fs: + try: + fields = fs.jsonify() + except AttributeError: + fields = dict([(field.renderer.name, field.model_value) for field in fs.render_fields.values()]) + data = dict(fields=fields) + pk = _pk(fs.model) + if pk: + data['item_url'] = request.route_url('fa_admin', traverse='%s/json/%s' % (self.model_name, pk)) + else: + data = {} + data.update(kwargs) + return data + + def render_xhr_format(self, fs=None, **kwargs): + response.content_type = 'text/html' + if fs is not None: + if 'field' in request.GET: + field_name = request.GET.get('field') + fields = fs.render_fields + if field_name in fields: + field = fields[field_name] + return field.render() + else: + return exc.HTTPNotfound() + return fs.render() + return '' + + def get_page(self, **kwargs): + """return a ``webhelpers.paginate.Page`` used to display ``Grid``. + + Default is:: + + S = self.Session() + query = S.query(self.get_model()) + kwargs = request.environ.get('pylons.routes_dict', {}) + return Page(query, page=int(request.GET.get('page', '1')), **kwargs) + """ + S = self.Session() + def get_page_url(page, partial=None): + url = "%s?page=%s" % (self.request.path, page) + if partial: + url += "&partial=1" + return url + options = dict(collection=S.query(self.get_model()), + page=int(self.request.GET.get('page', '1')), + url=get_page_url) + options.update(kwargs) + collection = options.pop('collection') + return Page(collection, **options) + + def get(self, id=None): + """return correct record for ``id`` or a new instance. + + Default is:: + + S = self.Session() + model = self.get_model() + if id: + model = S.query(model).get(id) + else: + model = model() + return model or abort(404) + + """ + S = self.Session() + model = self.get_model() + if id: + model = S.query(model).get(id) + if model: + return model + raise exc.HTTPNotFound() + + def get_fieldset(self, id=None): + """return a ``FieldSet`` object bound to the correct record for ``id``. + + Default is:: + + fs = self.FieldSet(self.get(id)) + fs.engine = fs.engine or self.engine + return fs + """ + if self.forms and hasattr(self.forms, self.model_name): + fs = getattr(self.forms, self.model_name) + fs.engine = fs.engine or self.engine + return id and fs.bind(self.get(id)) or fs + fs = self.FieldSet(self.get(id)) + fs.engine = fs.engine or self.engine + return fs + + def get_add_fieldset(self): + """return a ``FieldSet`` used for add form. + + Default is:: + + fs = self.get_fieldset() + for field in fs.render_fields.itervalues(): + if field.is_readonly(): + del fs[field.name] + return fs + """ + fs = self.get_fieldset() + for field in fs.render_fields.itervalues(): + if field.is_readonly(): + del fs[field.name] + return fs + + def get_grid(self): + """return a Grid object + + Default is:: + + grid = self.Grid(self.get_model()) + grid.engine = self.engine + self.update_grid(grid) + return grid + """ + model_name = self.model_name + if self.forms and hasattr(self.forms, '%sGrid' % model_name): + g = getattr(self.forms, '%sGrid' % model_name) + g.engine = g.engine or self.engine + g.readonly = True + self.update_grid(g) + return g + grid = self.Grid(self.get_model()) + grid.engine = self.engine + self.update_grid(grid) + return grid + + + def update_grid(self, grid): + """Add edit and delete buttons to ``Grid``""" + try: + grid.edit + except AttributeError: + def edit_link(): + return lambda item: ''' +
+ +
+ ''' % dict(url=self.route_url(self.model_name, _pk(item), 'edit'), + label=get_translator().gettext('edit')) + def delete_link(): + return lambda item: ''' +
+ +
+ ''' % dict(url=self.route_url(self.model_name, _pk(item), 'delete'), + label=get_translator().gettext('delete')) + grid.append(Field('edit', fatypes.String, edit_link())) + grid.append(Field('delete', fatypes.String, delete_link())) + grid.readonly = True + + def listing(self, **kwargs): + """listing page""" + page = self.get_page() + fs = self.get_grid() + fs = fs.bind(instances=page) + fs.readonly = True + if self.request.format == 'json': + values = [] + request = self.request + for item in page: + pk = _pk(item) + fs._set_active(item) + value = dict(id=pk, + item_url=self.route_url(request.model_name, pk)) + if 'jqgrid' in request.GET: + fields = [_stringify(field.render_readonly()) for field in fs.render_fields.values()] + value['cell'] = [pk] + fields + else: + value.update(dict([(field.key, field.model_value) for field in fs.render_fields.values()])) + values.append(value) + return self.render_json_format(rows=values, + records=len(values), + total=page.page_count, + page=page.page) + if 'pager' not in kwargs: + pager = page.pager(**self.pager_args) + else: + pager = kwargs.pop('pager') + return self.render_grid(fs=fs, id=None, pager=pager) + + def create(self): + """REST api""" + request = self.request + S = self.Session() + fs = self.get_add_fieldset() + + if request.format == 'json' and request.method == 'PUT': + data = json.load(request.body_file) + else: + data = request.POST + + try: + fs = fs.bind(data=data, session=S) + except: + # non SA forms + fs = fs.bind(self.get_model(), data=data, session=S) + if fs.validate(): + fs.sync() + self.sync(fs) + S.flush() + if request.format == 'html': + if request.is_xhr: + response.content_type = 'text/plain' + return '' + return exc.HTTPFound( + location=self.route_url(request.model_name)) + else: + fs.rebind(fs.model, data=None) + return self.render(fs=fs) + return self.render(fs=fs, action='new', id=None) + + def delete(self, **kwargs): + """REST api""" + request = self.request + id = request.model_id + record = self.get(id) + if record: + S = self.Session() + S.delete(record) + if request.format == 'html': + if request.is_xhr: + response = Response() + response.content_type = 'text/plain' + return response + return exc.HTTPFound(location=self.route_url(request.model_name)) + return self.render(id=id) + + def show(self): + """REST api""" + id = self.request.model_id + fs = self.get_fieldset(id=id) + fs.readonly = True + return self.render(fs=fs, action='show', id=id) + + def new(self, **kwargs): + """REST api""" + fs = self.get_add_fieldset() + fs = fs.bind(session=self.Session()) + return self.render(fs=fs, action='new', id=None) + + def edit(self, id=None, **kwargs): + """REST api""" + id = self.request.model_id + fs = self.get_fieldset(id) + return self.render(fs=fs, action='edit', id=id) + + def update(self, **kwargs): + """REST api""" + request = self.request + S = self.Session() + id = request.model_id + fs = self.get_fieldset(id) + if not request.POST: + raise ValueError(request.POST) + fs = fs.bind(data=request.POST) + if fs.validate(): + fs.sync() + self.sync(fs, id) + S.flush() + if request.format == 'html': + if request.is_xhr: + response.content_type = 'text/plain' + return '' + return exc.HTTPFound( + location=self.route_url(request.model_name, _pk(fs.model))) + else: + return self.render(fs=fs, status=0) + if request.format == 'html': + return self.render(fs=fs, action='edit', id=id) + else: + return self.render(fs=fs, status=1) + diff --git a/pyramidapp/CHANGES.txt b/pyramidapp/CHANGES.txt new file mode 100644 index 0000000..35a34f3 --- /dev/null +++ b/pyramidapp/CHANGES.txt @@ -0,0 +1,4 @@ +0.0 +--- + +- Initial version diff --git a/pyramidapp/README.txt b/pyramidapp/README.txt new file mode 100644 index 0000000..b4cee23 --- /dev/null +++ b/pyramidapp/README.txt @@ -0,0 +1,4 @@ +pyramidapp README + + + diff --git a/pyramidapp/development.ini b/pyramidapp/development.ini new file mode 100644 index 0000000..32fbd1d --- /dev/null +++ b/pyramidapp/development.ini @@ -0,0 +1,54 @@ +[app:pyramidapp] +use = egg:pyramidapp +reload_templates = true +debug_authorization = false +debug_notfound = false +debug_routematch = false +debug_templates = true +default_locale_name = en +sqlalchemy.url = sqlite:///%(here)s/pyramidapp.db + +[pipeline:main] +pipeline = + egg:WebError#evalerror + egg:repoze.tm2#tm + pyramidapp + +[server:main] +use = egg:Paste#http +host = 0.0.0.0 +port = 6543 + +# Begin logging configuration + +[loggers] +keys = root, sqlalchemy + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = INFO +handlers = console + +[logger_sqlalchemy] +level = INFO +handlers = +qualname = sqlalchemy.engine +# "level = INFO" logs SQL queries. +# "level = DEBUG" logs SQL queries and results. +# "level = WARN" logs neither. (Recommended for production systems.) + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s + +# End logging configuration diff --git a/pyramidapp/pyramidapp.db b/pyramidapp/pyramidapp.db new file mode 100644 index 0000000..02fabba Binary files /dev/null and b/pyramidapp/pyramidapp.db differ diff --git a/pyramidapp/pyramidapp/__init__.py b/pyramidapp/pyramidapp/__init__.py new file mode 100644 index 0000000..fae3a7b --- /dev/null +++ b/pyramidapp/pyramidapp/__init__.py @@ -0,0 +1,25 @@ +from pyramid.config import Configurator +from sqlalchemy import engine_from_config + +from pyramidapp.models import initialize_sql + +def main(global_config, **settings): + """ This function returns a Pyramid WSGI application. + """ + engine = engine_from_config(settings, 'sqlalchemy.') + initialize_sql(engine) + config = Configurator(settings=settings) + config.add_static_view('static', 'pyramidapp:static') + config.add_route('home', '/', view='pyramidapp.views.my_view', + view_renderer='templates/mytemplate.pt') + config.load_zcml('pyramid_formalchemy:configure.zcml') + config.add_route('fa_admin', '/admin/*traverse', + factory='pyramid_formalchemy.resources.AdminView') + config.registry.settings.update({ + 'fa.models': config.maybe_dotted('pyramidapp.models'), + 'fa.forms': config.maybe_dotted('pyramidapp.forms'), + 'fa.session_factory': config.maybe_dotted('pyramidapp.models.DBSession'), + }) + return config.make_wsgi_app() + + diff --git a/pyramidapp/pyramidapp/forms.py b/pyramidapp/pyramidapp/forms.py new file mode 100644 index 0000000..633f866 --- /dev/null +++ b/pyramidapp/pyramidapp/forms.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- + diff --git a/pyramidapp/pyramidapp/models.py b/pyramidapp/pyramidapp/models.py new file mode 100644 index 0000000..287e0ad --- /dev/null +++ b/pyramidapp/pyramidapp/models.py @@ -0,0 +1,47 @@ +import transaction + +from sqlalchemy import Column +from sqlalchemy import Integer +from sqlalchemy import Unicode + +from sqlalchemy.exc import IntegrityError +from sqlalchemy.ext.declarative import declarative_base + +from sqlalchemy.orm import scoped_session +from sqlalchemy.orm import sessionmaker + +from zope.sqlalchemy import ZopeTransactionExtension + +DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension())) +Base = declarative_base() + +class MyModel(Base): + __tablename__ = 'models' + id = Column(Integer, primary_key=True) + name = Column(Unicode(255), unique=True) + value = Column(Integer) + +class Foo(Base): + __tablename__ = 'foo' + id = Column(Integer, primary_key=True) + bar = Column(Unicode(255)) + +def populate(): + session = DBSession() + model = MyModel(name=u'root',value=55) + session.add(model) + session.flush() + for i in range(50): + model = MyModel(name=u'root%i' % i,value=i) + session.add(model) + session.flush() + transaction.commit() + +def initialize_sql(engine): + DBSession.configure(bind=engine) + Base.metadata.bind = engine + Base.metadata.create_all(engine) + try: + populate() + except IntegrityError: + DBSession.rollback() diff --git a/pyramidapp/pyramidapp/resources.py b/pyramidapp/pyramidapp/resources.py new file mode 100644 index 0000000..3d81189 --- /dev/null +++ b/pyramidapp/pyramidapp/resources.py @@ -0,0 +1,3 @@ +class Root(object): + def __init__(self, request): + self.request = request diff --git a/pyramidapp/pyramidapp/static/favicon.ico b/pyramidapp/pyramidapp/static/favicon.ico new file mode 100644 index 0000000..71f837c Binary files /dev/null and b/pyramidapp/pyramidapp/static/favicon.ico differ diff --git a/pyramidapp/pyramidapp/static/footerbg.png b/pyramidapp/pyramidapp/static/footerbg.png new file mode 100644 index 0000000..1fbc873 Binary files /dev/null and b/pyramidapp/pyramidapp/static/footerbg.png differ diff --git a/pyramidapp/pyramidapp/static/headerbg.png b/pyramidapp/pyramidapp/static/headerbg.png new file mode 100644 index 0000000..0596f20 Binary files /dev/null and b/pyramidapp/pyramidapp/static/headerbg.png differ diff --git a/pyramidapp/pyramidapp/static/ie6.css b/pyramidapp/pyramidapp/static/ie6.css new file mode 100644 index 0000000..b7c8493 --- /dev/null +++ b/pyramidapp/pyramidapp/static/ie6.css @@ -0,0 +1,8 @@ +* html img, +* html .png{position:relative;behavior:expression((this.runtimeStyle.behavior="none")&&(this.pngSet?this.pngSet=true:(this.nodeName == "IMG" && this.src.toLowerCase().indexOf('.png')>-1?(this.runtimeStyle.backgroundImage = "none", +this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.src + "',sizingMethod='image')", +this.src = "static/transparent.gif"):(this.origBg = this.origBg? this.origBg :this.currentStyle.backgroundImage.toString().replace('url("','').replace('")',''), +this.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + this.origBg + "',sizingMethod='crop')", +this.runtimeStyle.backgroundImage = "none")),this.pngSet=true) +);} +#wrap{display:table;height:100%} diff --git a/pyramidapp/pyramidapp/static/middlebg.png b/pyramidapp/pyramidapp/static/middlebg.png new file mode 100644 index 0000000..2369cfb Binary files /dev/null and b/pyramidapp/pyramidapp/static/middlebg.png differ diff --git a/pyramidapp/pyramidapp/static/pylons.css b/pyramidapp/pyramidapp/static/pylons.css new file mode 100644 index 0000000..fd1914d --- /dev/null +++ b/pyramidapp/pyramidapp/static/pylons.css @@ -0,0 +1,65 @@ +html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;outline:0;font-size:100%;/* 16px */ +vertical-align:baseline;background:transparent;} +body{line-height:1;} +ol,ul{list-style:none;} +blockquote,q{quotes:none;} +blockquote:before,blockquote:after,q:before,q:after{content:'';content:none;} +:focus{outline:0;} +ins{text-decoration:none;} +del{text-decoration:line-through;} +table{border-collapse:collapse;border-spacing:0;} +sub{vertical-align:sub;font-size:smaller;line-height:normal;} +sup{vertical-align:super;font-size:smaller;line-height:normal;} +ul,menu,dir{display:block;list-style-type:disc;margin:1em 0;padding-left:40px;} +ol{display:block;list-style-type:decimal-leading-zero;margin:1em 0;padding-left:40px;} +li{display:list-item;} +ul ul,ul ol,ul dir,ul menu,ul dl,ol ul,ol ol,ol dir,ol menu,ol dl,dir ul,dir ol,dir dir,dir menu,dir dl,menu ul,menu ol,menu dir,menu menu,menu dl,dl ul,dl ol,dl dir,dl menu,dl dl{margin-top:0;margin-bottom:0;} +ol ul,ul ul,menu ul,dir ul,ol menu,ul menu,menu menu,dir menu,ol dir,ul dir,menu dir,dir dir{list-style-type:circle;} +ol ol ul,ol ul ul,ol menu ul,ol dir ul,ol ol menu,ol ul menu,ol menu menu,ol dir menu,ol ol dir,ol ul dir,ol menu dir,ol dir dir,ul ol ul,ul ul ul,ul menu ul,ul dir ul,ul ol menu,ul ul menu,ul menu menu,ul dir menu,ul ol dir,ul ul dir,ul menu dir,ul dir dir,menu ol ul,menu ul ul,menu menu ul,menu dir ul,menu ol menu,menu ul menu,menu menu menu,menu dir menu,menu ol dir,menu ul dir,menu menu dir,menu dir dir,dir ol ul,dir ul ul,dir menu ul,dir dir ul,dir ol menu,dir ul menu,dir menu menu,dir dir menu,dir ol dir,dir ul dir,dir menu dir,dir dir dir{list-style-type:square;} +.hidden{display:none;} +p{line-height:1.5em;} +h1{font-size:1.75em;line-height:1.7em;font-family:helvetica,verdana;} +h2{font-size:1.5em;line-height:1.7em;font-family:helvetica,verdana;} +h3{font-size:1.25em;line-height:1.7em;font-family:helvetica,verdana;} +h4{font-size:1em;line-height:1.7em;font-family:helvetica,verdana;} +html,body{width:100%;height:100%;} +body{margin:0;padding:0;background-color:#ffffff;position:relative;font:16px/24px "Nobile","Lucida Grande",Lucida,Verdana,sans-serif;} +a{color:#1b61d6;text-decoration:none;} +a:hover{color:#e88f00;text-decoration:underline;} +body h1, +body h2, +body h3, +body h4, +body h5, +body h6{font-family:"Neuton","Lucida Grande",Lucida,Verdana,sans-serif;font-weight:normal;color:#373839;font-style:normal;} +#wrap{min-height:100%;} +#header,#footer{width:100%;color:#ffffff;height:40px;position:absolute;text-align:center;line-height:40px;overflow:hidden;font-size:12px;vertical-align:middle;} +#header{background:#000000;top:0;font-size:14px;} +#footer{bottom:0;background:#000000 url(footerbg.png) repeat-x 0 top;position:relative;margin-top:-40px;clear:both;} +.header,.footer{width:750px;margin-right:auto;margin-left:auto;} +.wrapper{width:100%} +#top,#top-small,#bottom{width:100%;} +#top{color:#000000;height:230px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;} +#top-small{color:#000000;height:60px;background:#ffffff url(headerbg.png) repeat-x 0 top;position:relative;} +#bottom{color:#222;background-color:#ffffff;} +.top,.top-small,.middle,.bottom{width:750px;margin-right:auto;margin-left:auto;} +.top{padding-top:40px;} +.top-small{padding-top:10px;} +#middle{width:100%;height:100px;background:url(middlebg.png) repeat-x;border-top:2px solid #ffffff;border-bottom:2px solid #b2b2b2;} +.app-welcome{margin-top:25px;} +.app-name{color:#000000;font-weight:bold;} +.bottom{padding-top:50px;} +#left{width:350px;float:left;padding-right:25px;} +#right{width:350px;float:right;padding-left:25px;} +.align-left{text-align:left;} +.align-right{text-align:right;} +.align-center{text-align:center;} +ul.links{margin:0;padding:0;} +ul.links li{list-style-type:none;font-size:14px;} +form{border-style:none;} +fieldset{border-style:none;} +input{color:#222;border:1px solid #ccc;font-family:sans-serif;font-size:12px;line-height:16px;} +input[type=text],input[type=password]{width:205px;} +input[type=submit]{background-color:#ddd;font-weight:bold;} +/*Opera Fix*/ +body:before{content:"";height:100%;float:left;width:0;margin-top:-32767px;} diff --git a/pyramidapp/pyramidapp/static/pyramid-small.png b/pyramidapp/pyramidapp/static/pyramid-small.png new file mode 100644 index 0000000..a5bc0ad Binary files /dev/null and b/pyramidapp/pyramidapp/static/pyramid-small.png differ diff --git a/pyramidapp/pyramidapp/static/pyramid.png b/pyramidapp/pyramidapp/static/pyramid.png new file mode 100644 index 0000000..347e055 Binary files /dev/null and b/pyramidapp/pyramidapp/static/pyramid.png differ diff --git a/pyramidapp/pyramidapp/static/transparent.gif b/pyramidapp/pyramidapp/static/transparent.gif new file mode 100644 index 0000000..0341802 Binary files /dev/null and b/pyramidapp/pyramidapp/static/transparent.gif differ diff --git a/pyramidapp/pyramidapp/templates/mytemplate.pt b/pyramidapp/pyramidapp/templates/mytemplate.pt new file mode 100644 index 0000000..5eb1be6 --- /dev/null +++ b/pyramidapp/pyramidapp/templates/mytemplate.pt @@ -0,0 +1,76 @@ + + + + The Pyramid Web Application Development Framework + + + + + + + + + + +
+
+
+
pyramid
+
+
+
+
+

+ Welcome to ${project}, an application generated by
+ the Pyramid web application development framework. +

+
+
+
+
+
+

Search documentation

+
+ + +
+
+ +
+
+
+ + + \ No newline at end of file diff --git a/pyramidapp/pyramidapp/tests.py b/pyramidapp/pyramidapp/tests.py new file mode 100644 index 0000000..8fdba31 --- /dev/null +++ b/pyramidapp/pyramidapp/tests.py @@ -0,0 +1,103 @@ +import unittest +from pyramid.config import Configurator +from pyramid import testing + +import os +from webtest import TestApp +from pyramidapp import main +from paste.deploy import loadapp + +dirname = os.path.abspath(__file__) +dirname = os.path.dirname(dirname) +dirname = os.path.dirname(dirname) + +def _initTestingDB(): + from sqlalchemy import create_engine + from pyramidapp.models import initialize_sql + session = initialize_sql(create_engine('sqlite://')) + return session + +class TestController(unittest.TestCase): + + def setUp(self): + app = loadapp('config:%s' % os.path.join(dirname, 'development.ini')) + self.app = TestApp(app) + self.config = Configurator(autocommit=True) + self.config.begin() + #_initTestingDB() + + def tearDown(self): + self.config.end() + + + def test_index(self): + # index + resp = self.app.get('/admin/') + resp.mustcontain('/admin/Foo') + resp = resp.click('Foo') + + ## Simple model + + # add page + resp.mustcontain('/admin/Foo/new') + resp = resp.click('New Foo') + resp.mustcontain('/admin/Foo"') + form = resp.forms[0] + form['Foo--bar'] = 'value' + resp = form.submit() + assert resp.headers['location'] == 'http://localhost/admin/Foo', resp + + # model index + resp = resp.follow() + resp.mustcontain('value') + + # edit page + form = resp.forms[0] + resp = form.submit() + form = resp.forms[0] + form['Foo-1-bar'] = 'new value' + #form['_method'] = 'PUT' + resp = form.submit() + resp = resp.follow() + + # model index + resp.mustcontain('new value') + + # delete + resp = self.app.get('/admin/Foo') + resp.mustcontain('new value') + resp = resp.forms[1].submit() + resp = resp.follow() + + assert 'new value' not in resp, resp + + def test_json(self): + # index + response = self.app.get('/admin/json') + response.mustcontain('{"models": {', '"Foo": "http://localhost/admin/Foo/json"') + + ## Simple model + + # add page + response = self.app.post('/admin/Foo/json', + {'Foo--bar': 'value'}) + + data = response.json + id = data['item_url'].split('/')[-1] + + response.mustcontain('"Foo-%s-bar": "value"' % id) + + + # get data + response = self.app.get(str(data['item_url'])) + response.mustcontain('"Foo-%s-bar": "value"' % id) + + # edit page + response = self.app.post(str(data['item_url']), {'Foo-%s-bar' % id: 'new value'}) + response.mustcontain('"Foo-%s-bar": "new value"' % id) + + # delete + response = self.app.delete(str(data['item_url'])) + + + diff --git a/pyramidapp/pyramidapp/views.py b/pyramidapp/pyramidapp/views.py new file mode 100644 index 0000000..906e586 --- /dev/null +++ b/pyramidapp/pyramidapp/views.py @@ -0,0 +1,7 @@ +from pyramidapp.models import DBSession +from pyramidapp.models import MyModel + +def my_view(request): + dbsession = DBSession() + root = dbsession.query(MyModel).filter(MyModel.name==u'root').first() + return {'root':root, 'project':'pyramidapp'} diff --git a/pyramidapp/setup.cfg b/pyramidapp/setup.cfg new file mode 100644 index 0000000..3f9d9d6 --- /dev/null +++ b/pyramidapp/setup.cfg @@ -0,0 +1,27 @@ +[nosetests] +match=^test +nocapture=1 +cover-package=pyramidapp +with-coverage=1 +cover-erase=1 + +[compile_catalog] +directory = pyramidapp/locale +domain = pyramidapp +statistics = true + +[extract_messages] +add_comments = TRANSLATORS: +output_file = pyramidapp/locale/pyramidapp.pot +width = 80 + +[init_catalog] +domain = pyramidapp +input_file = pyramidapp/locale/pyramidapp.pot +output_dir = pyramidapp/locale + +[update_catalog] +domain = pyramidapp +input_file = pyramidapp/locale/pyramidapp.pot +output_dir = pyramidapp/locale +previous = true diff --git a/pyramidapp/setup.py b/pyramidapp/setup.py new file mode 100644 index 0000000..5347902 --- /dev/null +++ b/pyramidapp/setup.py @@ -0,0 +1,47 @@ +import os +import sys + +from setuptools import setup, find_packages + +here = os.path.abspath(os.path.dirname(__file__)) +README = open(os.path.join(here, 'README.txt')).read() +CHANGES = open(os.path.join(here, 'CHANGES.txt')).read() + +requires = [ + 'pyramid', + 'SQLAlchemy', + 'transaction', + 'repoze.tm2', + 'zope.sqlalchemy', + 'WebError', + ] + +if sys.version_info[:3] < (2,5,0): + requires.append('pysqlite') + +setup(name='pyramidapp', + version='0.0', + description='pyramidapp', + long_description=README + '\n\n' + CHANGES, + classifiers=[ + "Programming Language :: Python", + "Framework :: Pylons", + "Topic :: Internet :: WWW/HTTP", + "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", + ], + author='', + author_email='', + url='', + keywords='web wsgi bfg pylons pyramid', + packages=find_packages(), + include_package_data=True, + zip_safe=False, + test_suite='pyramidapp', + install_requires = requires, + entry_points = """\ + [paste.app_factory] + main = pyramidapp:main + """, + paster_plugins=['pyramid'], + ) + diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..1aabd25 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,27 @@ +[nosetests] +match = ^test +nocapture = 1 +cover-package = pyramidapp +with-coverage = 1 +cover-erase = 1 + +[compile_catalog] +directory = pyramid_formalchemy/locale +domain = pyramid_formalchemy +statistics = true + +[extract_messages] +add_comments = TRANSLATORS: +output_file = pyramid_formalchemy/locale/pyramid_formalchemy.pot +width = 80 + +[init_catalog] +domain = pyramid_formalchemy +input_file = pyramid_formalchemy/locale/pyramid_formalchemy.pot +output_dir = pyramid_formalchemy/locale + +[update_catalog] +domain = pyramid_formalchemy +input_file = pyramid_formalchemy/locale/pyramid_formalchemy.pot +output_dir = pyramid_formalchemy/locale +previous = true diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..ad2bb72 --- /dev/null +++ b/setup.py @@ -0,0 +1,30 @@ +import os + +from setuptools import setup, find_packages + +here = os.path.abspath(os.path.dirname(__file__)) +README = open(os.path.join(here, 'README.txt')).read() +CHANGES = open(os.path.join(here, 'CHANGES.txt')).read() + +requires = ['pyramid', 'WebError', 'FormAlchemy'] + +setup(name='pyramid_formalchemy', + version='0.0', + description='pyramid_formalchemy', + long_description=README + '\n\n' + CHANGES, + classifiers=[ + "Programming Language :: Python", + "Framework :: Pylons", + "Topic :: Internet :: WWW/HTTP", + "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", + ], + author='Gael Pasgrimaud', + author_email='gael@gawel.org', + url='', + keywords='web pyramid pylons formalchemy', + packages=find_packages(exclude=['pyramidapp']), + include_package_data=True, + zip_safe=False, + install_requires=requires, + ) +