├── .gitignore ├── LICENSE ├── README ├── app.py ├── database.py ├── forms.py ├── models.py ├── requirements.txt ├── static └── favicon.ico ├── templates ├── base.html ├── index.html └── uimodules │ └── form.html ├── uimodules.py └── util.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.so 3 | *~ 4 | build 5 | dist/ 6 | MANIFEST 7 | tornado.egg-info 8 | _auto2to3* 9 | .DS_Store 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2011 by Haldun Bayhantopcu 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | A tornado app template preconfigured for sqlalchemy and wtforms 2 | 3 | Moved to bitbucket: https://bitbucket.org/haldun/tornado-sqlalchemy-template 4 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | # Python imports 2 | import os 3 | 4 | # Tornado imports 5 | import tornado.auth 6 | import tornado.httpserver 7 | import tornado.ioloop 8 | import tornado.options 9 | import tornado.web 10 | 11 | from tornado.options import define, options 12 | from tornado.web import url 13 | 14 | # Sqlalchemy imports 15 | from sqlalchemy import create_engine 16 | from sqlalchemy.orm import scoped_session, sessionmaker 17 | 18 | # App imports 19 | import forms 20 | import models 21 | import uimodules 22 | 23 | # Options 24 | define("port", default=8888, help="run on the given port", type=int) 25 | define("debug", default=False, type=bool) 26 | define("db_path", default='sqlite:////tmp/test.db', type=str) 27 | 28 | class Application(tornado.web.Application): 29 | def __init__(self): 30 | handlers = [ 31 | url(r'/', IndexHandler, name='index'), 32 | ] 33 | settings = dict( 34 | debug=options.debug, 35 | static_path=os.path.join(os.path.dirname(__file__), "static"), 36 | template_path=os.path.join(os.path.dirname(__file__), 'templates'), 37 | xsrf_cookies=True, 38 | # TODO Change this to a random string 39 | cookie_secret="nzjxcjasduuqwheazmu293nsadhaslzkci9023nsadnua9sdads/Vo=", 40 | ui_modules=uimodules, 41 | ) 42 | tornado.web.Application.__init__(self, handlers, **settings) 43 | engine = create_engine(options.db_path, convert_unicode=True, echo=options.debug) 44 | models.init_db(engine) 45 | self.db = scoped_session(sessionmaker(bind=engine)) 46 | 47 | 48 | class BaseHandler(tornado.web.RequestHandler): 49 | @property 50 | def db(self): 51 | return self.application.db 52 | 53 | 54 | class IndexHandler(BaseHandler): 55 | def get(self): 56 | form = forms.HelloForm() 57 | self.render('index.html', form=form) 58 | 59 | def post(self): 60 | form = forms.HelloForm(self) 61 | if form.validate(): 62 | self.write('Hello %s' % form.planet.data) 63 | else: 64 | self.render('index.html', form=form) 65 | 66 | 67 | # Write your handlers here 68 | 69 | def main(): 70 | tornado.options.parse_command_line() 71 | http_server = tornado.httpserver.HTTPServer(Application()) 72 | http_server.listen(options.port) 73 | tornado.ioloop.IOLoop.instance().start() 74 | 75 | if __name__ == '__main__': 76 | main() 77 | -------------------------------------------------------------------------------- /database.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import create_engine 2 | from sqlalchemy import Column, Integer, String, DateTime, Boolean 3 | from sqlalchemy.orm import scoped_session, sessionmaker 4 | from sqlalchemy.ext.declarative import declarative_base 5 | 6 | engine = create_engine('sqlite:////tmp/test.db', convert_unicode=True, echo=True) 7 | db_session = scoped_session(sessionmaker(autocommit=False, 8 | autoflush=False, 9 | bind=engine)) 10 | Base = declarative_base() 11 | Base.query = db_session.query_property() 12 | 13 | def init_db(): 14 | Base.metadata.create_all(bind=engine) 15 | 16 | if __name__ == '__main__': 17 | init_db() 18 | -------------------------------------------------------------------------------- /forms.py: -------------------------------------------------------------------------------- 1 | from wtforms import * 2 | from wtforms.validators import * 3 | 4 | from util import MultiValueDict 5 | 6 | class BaseForm(Form): 7 | def __init__(self, handler=None, obj=None, prefix='', formdata=None, **kwargs): 8 | if handler: 9 | formdata = MultiValueDict() 10 | for name in handler.request.arguments.keys(): 11 | formdata.setlist(name, handler.get_arguments(name)) 12 | Form.__init__(self, formdata, obj=obj, prefix=prefix, **kwargs) 13 | 14 | 15 | # TODO Put your forms here 16 | 17 | class HelloForm(BaseForm): 18 | planet = TextField('name', validators=[Required()]) 19 | 20 | -------------------------------------------------------------------------------- /models.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import create_engine 2 | from sqlalchemy import Column, Integer, String, DateTime, Boolean 3 | from sqlalchemy.orm import scoped_session, sessionmaker 4 | from sqlalchemy.ext.declarative import declarative_base 5 | 6 | Base = declarative_base() 7 | 8 | def init_db(engine): 9 | Base.metadata.create_all(bind=engine) 10 | 11 | # Put your models here 12 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | tornado 2 | wtforms 3 | sqlalchemy -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haldun/tornado-sqlalchemy-template/c40177ee73eaba4f5fc928328a0aee7f179c9bc7/static/favicon.ico -------------------------------------------------------------------------------- /templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | base 6 | 7 | 8 | {% block content %} 9 | {% end %} 10 | 11 | 12 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |
5 | {% module Form(form) %} 6 | 7 |
8 | {% end %} 9 | -------------------------------------------------------------------------------- /templates/uimodules/form.html: -------------------------------------------------------------------------------- 1 | {% raw xsrf_form_html() %} 2 |
3 | {% for field in form %} 4 |
5 | {% raw field.label() %} 6 | {% raw field() %} 7 | {% if field.errors %} 8 | {% for error in field.errors %} 9 | {{ error }} 10 | {% end %} 11 | {% end %} 12 |
13 | {% end %} 14 |
15 | -------------------------------------------------------------------------------- /uimodules.py: -------------------------------------------------------------------------------- 1 | import tornado.web 2 | 3 | class Form(tornado.web.UIModule): 4 | """ 5 | Generic form rendering module. Works with wtforms. 6 | Use this in your template code as: 7 | 8 | {% module Form(form) %} 9 | 10 | where `form` is a wtforms.Form object. Note that this module does not render 11 |
tag and any buttons. 12 | """ 13 | 14 | def render(self, form): 15 | """docstring for render""" 16 | return self.render_string('uimodules/form.html', form=form) 17 | 18 | 19 | # Put your uimodules here 20 | -------------------------------------------------------------------------------- /util.py: -------------------------------------------------------------------------------- 1 | # Copied from django with some modifications 2 | import copy 3 | 4 | class MultiValueDict(dict): 5 | """ 6 | A subclass of dictionary customized to handle multiple values for the 7 | same key. 8 | 9 | >>> d = MultiValueDict({'name': ['Adrian', 'Simon'], 'position': ['Developer']}) 10 | >>> d['name'] 11 | 'Simon' 12 | >>> d.getlist('name') 13 | ['Adrian', 'Simon'] 14 | >>> d.get('lastname', 'nonexistent') 15 | 'nonexistent' 16 | >>> d.setlist('lastname', ['Holovaty', 'Willison']) 17 | 18 | This class exists to solve the irritating problem raised by cgi.parse_qs, 19 | which returns a list for every key, even though most Web forms submit 20 | single name-value pairs. 21 | """ 22 | def __init__(self, key_to_list_mapping=()): 23 | super(MultiValueDict, self).__init__(key_to_list_mapping) 24 | 25 | def __repr__(self): 26 | return "<%s: %s>" % (self.__class__.__name__, 27 | super(MultiValueDict, self).__repr__()) 28 | 29 | def __getitem__(self, key): 30 | """ 31 | Returns the last data value for this key, or [] if it's an empty list; 32 | raises KeyError if not found. 33 | """ 34 | try: 35 | list_ = super(MultiValueDict, self).__getitem__(key) 36 | except KeyError: 37 | raise MultiValueDictKeyError("Key %r not found in %r" % (key, self)) 38 | try: 39 | return list_[-1] 40 | except IndexError: 41 | return [] 42 | 43 | def __setitem__(self, key, value): 44 | super(MultiValueDict, self).__setitem__(key, [value]) 45 | 46 | def __copy__(self): 47 | return self.__class__([ 48 | (k, v[:]) 49 | for k, v in self.lists() 50 | ]) 51 | 52 | def __deepcopy__(self, memo=None): 53 | if memo is None: 54 | memo = {} 55 | result = self.__class__() 56 | memo[id(self)] = result 57 | for key, value in dict.items(self): 58 | dict.__setitem__(result, copy.deepcopy(key, memo), 59 | copy.deepcopy(value, memo)) 60 | return result 61 | 62 | def __getstate__(self): 63 | obj_dict = self.__dict__.copy() 64 | obj_dict['_data'] = dict([(k, self.getlist(k)) for k in self]) 65 | return obj_dict 66 | 67 | def __setstate__(self, obj_dict): 68 | data = obj_dict.pop('_data', {}) 69 | for k, v in data.items(): 70 | self.setlist(k, v) 71 | self.__dict__.update(obj_dict) 72 | 73 | def get(self, key, default=None): 74 | """ 75 | Returns the last data value for the passed key. If key doesn't exist 76 | or value is an empty list, then default is returned. 77 | """ 78 | try: 79 | val = self[key] 80 | except KeyError: 81 | return default 82 | if val == []: 83 | return default 84 | return val 85 | 86 | def getlist(self, key): 87 | """ 88 | Returns the list of values for the passed key. If key doesn't exist, 89 | then an empty list is returned. 90 | """ 91 | try: 92 | return super(MultiValueDict, self).__getitem__(key) 93 | except KeyError: 94 | return [] 95 | 96 | def setlist(self, key, list_): 97 | super(MultiValueDict, self).__setitem__(key, list_) 98 | 99 | def setdefault(self, key, default=None): 100 | if key not in self: 101 | self[key] = default 102 | return self[key] 103 | 104 | def setlistdefault(self, key, default_list=()): 105 | if key not in self: 106 | self.setlist(key, default_list) 107 | return self.getlist(key) 108 | 109 | def appendlist(self, key, value): 110 | """Appends an item to the internal list associated with key.""" 111 | self.setlistdefault(key, []) 112 | super(MultiValueDict, self).__setitem__(key, self.getlist(key) + [value]) 113 | 114 | def items(self): 115 | """ 116 | Returns a list of (key, value) pairs, where value is the last item in 117 | the list associated with the key. 118 | """ 119 | return [(key, self[key]) for key in self.keys()] 120 | 121 | def iteritems(self): 122 | """ 123 | Yields (key, value) pairs, where value is the last item in the list 124 | associated with the key. 125 | """ 126 | for key in self.keys(): 127 | yield (key, self[key]) 128 | 129 | def lists(self): 130 | """Returns a list of (key, list) pairs.""" 131 | return super(MultiValueDict, self).items() 132 | 133 | def iterlists(self): 134 | """Yields (key, list) pairs.""" 135 | return super(MultiValueDict, self).iteritems() 136 | 137 | def values(self): 138 | """Returns a list of the last value on every key list.""" 139 | return [self[key] for key in self.keys()] 140 | 141 | def itervalues(self): 142 | """Yield the last value on every key list.""" 143 | for key in self.iterkeys(): 144 | yield self[key] 145 | 146 | def copy(self): 147 | """Returns a shallow copy of this object.""" 148 | return copy(self) 149 | 150 | def update(self, *args, **kwargs): 151 | """ 152 | update() extends rather than replaces existing key lists. 153 | Also accepts keyword args. 154 | """ 155 | if len(args) > 1: 156 | raise TypeError("update expected at most 1 arguments, got %d" % len(args)) 157 | if args: 158 | other_dict = args[0] 159 | if isinstance(other_dict, MultiValueDict): 160 | for key, value_list in other_dict.lists(): 161 | self.setlistdefault(key, []).extend(value_list) 162 | else: 163 | try: 164 | for key, value in other_dict.items(): 165 | self.setlistdefault(key, []).append(value) 166 | except TypeError: 167 | raise ValueError("MultiValueDict.update() takes either a MultiValueDict or dictionary") 168 | for key, value in kwargs.iteritems(): 169 | self.setlistdefault(key, []).append(value) 170 | 171 | --------------------------------------------------------------------------------