├── .gitignore ├── README ├── sample ├── app.py ├── data │ └── to_read.txt ├── handlers │ ├── __init__.py │ └── handlers.py ├── requirements.txt ├── static │ ├── css │ │ ├── jcarousel.css │ │ ├── notification.css │ │ └── popup.css │ ├── img │ │ └── close_pop.png │ └── js │ │ ├── jquery-1.7.2.min.js │ │ ├── jquery-ui-1.8.20.custom.min.js │ │ ├── jquery.jcarousel.min.js │ │ ├── jquery.tokeninput.js │ │ └── notification.js ├── templates │ ├── data_pusher.html │ ├── fb_demo.html │ ├── grav.html │ ├── hello.html │ ├── login.html │ ├── menu_tags.html │ ├── message.html │ ├── notification.html │ ├── popup.html │ ├── popup_dialog.html │ ├── register.html │ ├── slidy.html │ └── upload.html └── tests │ ├── __init__.py │ ├── lib │ ├── __init__.py │ └── http_test_client.py │ ├── test_client.py │ └── test_mocks.py └── splinter └── tests-splinter ├── __init__.py └── test_with_real_browser.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | 3 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Simple Tornado & Mongodb app using authentication 2 | 3 | 4 | HTML demos for slidys, notifications & popup windows 5 | 6 | # ----- to Run app: ------ 7 | sudo apt-get install python-dev 8 | sudo apt-get build-dep python-dev 9 | 10 | cd sample 11 | pip install -r requirements.txt 12 | python app.py 13 | 14 | # ----- Run tests: ------ 15 | cd sample 16 | python -m tornado.testing discover 17 | 18 | # ---- Run single test ------ 19 | cd sample 20 | python -m tornado.testing tests.test_client 21 | 22 | # ----- Run Splinter Web Acceptance tests ------- 23 | apt-get install libxml2-dev libxslt-dev 24 | pip install splinter 25 | cd splinter 26 | python -m tornado.testing discover 27 | -------------------------------------------------------------------------------- /sample/app.py: -------------------------------------------------------------------------------- 1 | # Python imports 2 | 3 | # Tornado imports 4 | import pymongo 5 | import uuid 6 | import tornado.ioloop 7 | import tornado.options 8 | import tornado.web 9 | from tornado.options import define, options 10 | from tornado.web import url 11 | 12 | from handlers.handlers import * 13 | 14 | 15 | define("port", default=8888, type=int) 16 | define("config_file", default="app_config.yml", help="app_config file") 17 | 18 | #MONGO_SERVER = '192.168.1.68' 19 | MONGO_SERVER = 'localhost' 20 | 21 | 22 | class Application(tornado.web.Application): 23 | def __init__(self, **overrides): 24 | #self.config = self._get_config() 25 | handlers = [ 26 | url(r'/', HelloHandler, name='index'), 27 | url(r'/hello', HelloHandler, name='hello'), 28 | url(r'/email', EmailMeHandler, name='email'), 29 | url(r'/message', MessageHandler, name='message'), 30 | url(r'/grav', GravatarHandler, name='grav'), 31 | url(r'/menu', MenuTagsHandler, name='menu'), 32 | url(r'/slidy', SlidyHandler, name='slidy'), 33 | url(r'/notification', NotificationHandler, name='notification'), 34 | url(r'/fb_demo', FacebookDemoHandler, name='fb_demo'), 35 | url(r'/popup', PopupHandler, name='popup_demo'), 36 | url(r'/tail', TailHandler, name='tail_demo'), 37 | url(r'/pusher', DataPusherHandler, name='push_demo'), 38 | url(r'/pusher_raw', DataPusherRawHandler, name='push_raw_demo'), 39 | url(r'/matcher/([^\/]+)/', WildcardPathHandler), 40 | url(r'/back_to_where_you_came_from', ReferBackHandler, name='referrer'), 41 | url(r'/thread', ThreadHandler, name='thread_handler'), 42 | url(r'/s3uploader', S3PhotoUploadHandler, name='photos'), 43 | 44 | url(r'/login_no_block', NoneBlockingLogin, name='login_no_block'), 45 | url(r'/login', LoginHandler, name='login'), 46 | url(r'/twitter_login', TwitterLoginHandler, name='twitter_login'), 47 | url(r'/facebook_login', FacebookLoginHandler, name='facebook_login'), 48 | url(r'/register', RegisterHandler, name='register'), 49 | url(r'/logout', LogoutHandler, name='logout'), 50 | 51 | ] 52 | 53 | #xsrf_cookies is for XSS protection add this to all forms: {{ xsrf_form_html() }} 54 | settings = { 55 | 'static_path': os.path.join(os.path.dirname(__file__), 'static'), 56 | 'template_path': os.path.join(os.path.dirname(__file__), 'templates'), 57 | "cookie_secret": base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes), 58 | 'twitter_consumer_key': 'KEY', 59 | 'twitter_consumer_secret': 'SECRET', 60 | 'facebook_app_id': '180378538760459', 61 | 'facebook_secret': '7b82b89eb6aa0d3359e2036e4d1eedf0', 62 | 'facebook_registration_redirect_url': 'http://localhost:8888/facebook_login', 63 | 'mandrill_key': 'KEY', 64 | 'mandrill_url': 'https://mandrillapp.com/api/1.0/', 65 | 66 | 'xsrf_cookies': False, 67 | 'debug': True, 68 | 'log_file_prefix': "tornado.log", 69 | } 70 | 71 | tornado.web.Application.__init__(self, handlers, **settings) 72 | 73 | self.syncconnection = pymongo.MongoClient(MONGO_SERVER, 27017) 74 | 75 | if 'db' in overrides: 76 | self.syncdb = self.syncconnection[overrides['db']] 77 | else: 78 | self.syncdb = self.syncconnection["test-thank"] 79 | 80 | #self.syncconnection.close() 81 | 82 | 83 | # to redirect log file run python with : --log_file_prefix=mylog 84 | def main(): 85 | tornado.options.parse_command_line() 86 | http_server = tornado.httpserver.HTTPServer(Application()) 87 | http_server.listen(options.port) 88 | tornado.ioloop.IOLoop.instance().start() 89 | 90 | if __name__ == '__main__': 91 | main() 92 | -------------------------------------------------------------------------------- /sample/data/to_read.txt: -------------------------------------------------------------------------------- 1 | hello! 2 | go to data/to_read.txt and add some lines on the bottom. Watch them load! 3 | 4 | -------------------------------------------------------------------------------- /sample/handlers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bootandy/tornado_sample/57e3a1e9dc0b3afcdc8743ce9a909921196458bd/sample/handlers/__init__.py -------------------------------------------------------------------------------- /sample/handlers/handlers.py: -------------------------------------------------------------------------------- 1 | import base64 2 | from bson.objectid import ObjectId 3 | import os 4 | import bcrypt 5 | import hashlib 6 | import urllib 7 | 8 | import boto 9 | import cStringIO 10 | 11 | import tornado.auth 12 | import tornado.escape 13 | import tornado.gen 14 | import tornado.httpserver 15 | import logging 16 | import bson.json_util 17 | import json 18 | import urlparse 19 | import time 20 | import threading 21 | import functools 22 | 23 | from PIL import Image 24 | from tornado.ioloop import IOLoop 25 | from tornado.web import asynchronous, RequestHandler, Application 26 | from tornado.httpclient import AsyncHTTPClient 27 | 28 | 29 | class BaseHandler(RequestHandler): 30 | def get_login_url(self): 31 | return u"/login" 32 | 33 | def get_current_user(self): 34 | user_json = self.get_secure_cookie("user") 35 | if user_json: 36 | return tornado.escape.json_decode(user_json) 37 | else: 38 | return None 39 | 40 | # Allows us to get the previous URL 41 | def get_referring_url(self): 42 | try: 43 | _, _, referer, _, _, _ = urlparse.urlparse(self.request.headers.get('Referer')) 44 | if referer: 45 | return referer 46 | # Test code will throw this if there was no 'previous' page 47 | except AttributeError: 48 | pass 49 | return '/' 50 | 51 | def get_flash(self): 52 | flash = self.get_secure_cookie('flash') 53 | self.clear_cookie('flash') 54 | return flash 55 | 56 | def get_essentials(self): 57 | mp = {k: ''.join(v) for k, v in self.request.arguments.iteritems()} 58 | print mp 59 | 60 | 61 | class NotificationHandler(BaseHandler): 62 | def get(self): 63 | messages = self.application.syncdb.messages.find() 64 | self.render("notification.html", messages=messages, notification='hello') 65 | 66 | 67 | class SlidyHandler(BaseHandler): 68 | def get(self): 69 | messages = self.application.syncdb.messages.find() 70 | self.render("slidy.html", messages=messages, notification=self.get_flash()) 71 | 72 | 73 | class PopupHandler(BaseHandler): 74 | def get(self): 75 | messages = self.application.syncdb.messages.find() 76 | self.render("popup.html", notification=self.get_flash()) 77 | 78 | 79 | class MenuTagsHandler(BaseHandler): 80 | def get(self): 81 | self.render("menu_tags.html", notification=self.get_flash()) 82 | 83 | 84 | class LoginHandler(BaseHandler): 85 | def get(self): 86 | messages = self.application.syncdb.messages.find() 87 | self.render("login.html", notification=self.get_flash()) 88 | 89 | def post(self): 90 | email = self.get_argument("email", "") 91 | password = self.get_argument("password", "") 92 | 93 | user = self.application.syncdb['users'].find_one({'user': email}) 94 | 95 | # Warning bcrypt will block IO loop: 96 | if user and user['password'] and bcrypt.hashpw(password, user['password']) == user['password']: 97 | self.set_current_user(email) 98 | self.redirect("hello") 99 | else: 100 | self.set_secure_cookie('flash', "Login incorrect") 101 | self.redirect(u"/login") 102 | 103 | def set_current_user(self, user): 104 | print "setting " + user 105 | if user: 106 | self.set_secure_cookie("user", tornado.escape.json_encode(user)) 107 | else: 108 | self.clear_cookie("user") 109 | 110 | 111 | class NoneBlockingLogin(BaseHandler): 112 | """ Runs Bcrypt in a thread - Allows tornado to server up other handlers but can not process multiple logins simultaneously""" 113 | def get(self): 114 | messages = self.application.syncdb.messages.find() 115 | self.render("login.html", notification=self.get_flash()) 116 | 117 | def initialize(self): 118 | self.thread = None 119 | 120 | @tornado.web.asynchronous 121 | def post(self): 122 | email = self.get_argument('email', '') 123 | password = self.get_argument('password', '') 124 | user = self.application.syncdb['users'].find_one({'user': email}) 125 | 126 | self.thread = threading.Thread(target=self.compute_password, args=(password, user,)) 127 | self.thread.start() 128 | 129 | def compute_password(self, password, user): 130 | if user and 'password' in user: 131 | if bcrypt.hashpw(password, user['password']) == user['password']: 132 | tornado.ioloop.IOLoop.instance().add_callback(functools.partial(self._password_correct_callback, user['user'])) 133 | return 134 | tornado.ioloop.IOLoop.instance().add_callback(functools.partial(self._password_fail_callback)) 135 | 136 | def _password_correct_callback(self, email): 137 | self.set_current_user(email) 138 | self.redirect(self.get_argument('next', '/')) 139 | 140 | def _password_fail_callback(self): 141 | self.set_flash('Error Login incorrect') 142 | self.redirect('/login') 143 | 144 | 145 | class RegisterHandler(LoginHandler): 146 | def get(self): 147 | self.render("register.html", next=self.get_argument("next", "/")) 148 | 149 | def post(self): 150 | email = self.get_argument("email", "") 151 | 152 | already_taken = self.application.syncdb['users'].find_one({'user': email}) 153 | if already_taken: 154 | error_msg = u"?error=" + tornado.escape.url_escape("Login name already taken") 155 | self.redirect(u"/login" + error_msg) 156 | 157 | # Warning bcrypt will block IO loop: 158 | password = self.get_argument("password", "").encode('utf-8') 159 | hashed_pass = bcrypt.hashpw(password, bcrypt.gensalt(8)) 160 | 161 | user = {} 162 | user['user'] = email 163 | user['password'] = hashed_pass 164 | 165 | auth = self.application.syncdb['users'].save(user) 166 | self.set_current_user(email) 167 | 168 | self.redirect("hello") 169 | 170 | 171 | class TwitterLoginHandler(LoginHandler, 172 | tornado.auth.TwitterMixin): 173 | @tornado.web.asynchronous 174 | def get(self): 175 | if self.get_argument("oauth_token", None): 176 | self.get_authenticated_user(self.async_callback(self._on_auth)) 177 | return 178 | self.authorize_redirect() 179 | 180 | def _on_auth(self, user): 181 | if not user: 182 | raise tornado.web.HTTPError(500, "Twitter auth failed") 183 | print "Auth worked" 184 | #user_details = self.application.syncdb['users'].find_one( {'twitter': tw_user['username'] } ) 185 | # Create user if user not found 186 | 187 | self.set_current_user(tw_user['username']) 188 | self.redirect("hello") 189 | 190 | 191 | class FacebookLoginHandler(LoginHandler, tornado.auth.FacebookGraphMixin): 192 | @tornado.web.asynchronous 193 | def get(self): 194 | if self.get_argument('code', False): 195 | self.get_authenticated_user( 196 | redirect_uri=self.settings['facebook_registration_redirect_url'], 197 | client_id=self.application.settings['facebook_app_id'], 198 | client_secret=self.application.settings['facebook_secret'], 199 | code=self.get_argument('code'), 200 | callback=self.async_callback(self._on_login) 201 | ) 202 | return 203 | self.authorize_redirect(redirect_uri=self.settings['facebook_registration_redirect_url'], 204 | client_id=self.settings['facebook_app_id'], 205 | extra_params={'scope': 'offline_access'}) # read_stream, 206 | 207 | def _on_login(self, fb_user): 208 | #user_details = self.application.syncdb['users'].find_one( {'facebook': fb_user['id']} ) 209 | # Create user if user not found 210 | self.set_current_user(fb_user['id']) 211 | self.redirect("hello") 212 | 213 | 214 | class LogoutHandler(BaseHandler): 215 | def get(self): 216 | self.clear_cookie("user") 217 | self.redirect(u"/login") 218 | 219 | 220 | class ThreadHandler(tornado.web.RequestHandler): 221 | def perform(self, callback): 222 | #do something cuz hey, we're in a thread! 223 | time.sleep(5) 224 | output = 'foo' 225 | tornado.ioloop.IOLoop.instance().add_callback(functools.partial(callback, output)) 226 | 227 | def initialize(self): 228 | self.thread = None 229 | 230 | @tornado.web.asynchronous 231 | def get(self): 232 | self.thread = threading.Thread(target=self.perform, args=(self.on_callback,)) 233 | self.thread.start() 234 | 235 | self.write('In the request') 236 | self.flush() 237 | 238 | def on_callback(self, output): 239 | logging.info('In on_callback()') 240 | self.write("Thread output: %s" % output) 241 | self.finish() 242 | 243 | 244 | class HelloHandler(BaseHandler): 245 | #@tornado.web.authenticated 246 | def get(self): 247 | messages = self.get_messages() 248 | self.render("hello.html", user=self.get_current_user(), messages=messages, notification=self.get_flash()) 249 | 250 | def get_messages(self): 251 | return self.application.syncdb.messages.find() 252 | 253 | def post(self): 254 | return self.get() 255 | 256 | 257 | class EmailMeHandler(BaseHandler): 258 | @tornado.web.asynchronous 259 | @tornado.gen.engine 260 | def get(self): 261 | http_client = AsyncHTTPClient() 262 | mail_url = self.settings["mandrill_url"] + "/messages/send.json" 263 | mail_data = { 264 | "key": self.settings["mandrill_key"], 265 | "message": { 266 | "html": "html email from tornado sample app bold", 267 | "text": "plain text email from tornado sample app", 268 | "subject": "from tornado sample app", 269 | "from_email": "hello@retechnica.com", 270 | "from_name": "Hello Team", 271 | "to": [{"email": "sample@retechnica.com"}] 272 | } 273 | } 274 | 275 | # mail_data = { 276 | # "key": self.settings["mandrill_key"], 277 | # } 278 | #mail_url = self.settings["mandrill_url"] + "/users/info.json" 279 | 280 | body = tornado.escape.json_encode(mail_data) 281 | response = yield tornado.gen.Task(http_client.fetch, mail_url, method='POST', body=body) 282 | logging.info(response) 283 | logging.info(response.body) 284 | 285 | if response.code == 200: 286 | self.set_secure_cookie('flash', "sent") 287 | self.redirect('/') 288 | else: 289 | self.set_secure_cookie('flash', "FAIL") 290 | self.redirect('/') 291 | 292 | 293 | class MessageHandler(BaseHandler): 294 | @tornado.web.authenticated 295 | def get(self): 296 | users = self.application.syncdb['users'].find() 297 | self.render("message.html", user=self.get_current_user(), users=users, notification=self.get_flash()) 298 | 299 | def post(self): 300 | sent_to = self.get_argument('to') 301 | sent_from = self.get_current_user() 302 | message = self.get_argument("message") 303 | msg = {} 304 | msg['from'] = sent_from 305 | msg['to'] = sent_to 306 | msg['message'] = message 307 | 308 | if self.save_message(msg): 309 | self.set_secure_cookie('flash', "Message Sent") 310 | self.redirect(u"/hello") 311 | else: 312 | print "error_msg" 313 | 314 | def save_message(self, msg): 315 | return self.application.syncdb['messages'].insert(msg) 316 | 317 | 318 | class FacebookDemoHandler(BaseHandler): 319 | @tornado.web.authenticated 320 | def get(self): 321 | self.render("fb_demo.html", user=self.get_current_user(), fb_app_id=self.settings['facebook_app_id']) 322 | 323 | 324 | class GravatarHandler(BaseHandler): 325 | def build_grav_url(self, email): 326 | 327 | #default = "http://thumbs.dreamstime.com/thumblarge_540/1284957171JgzjF1.jpg" 328 | # random patterned background: 329 | default = 'identicon' 330 | size = 40 331 | 332 | # construct the url 333 | gravatar_url = "http://www.gravatar.com/avatar/" + hashlib.md5(email.lower()).hexdigest() + "?" 334 | gravatar_url += urllib.urlencode({'d': default, 's': str(size)}) 335 | return gravatar_url 336 | 337 | def get(self): 338 | email = self.get_argument('email', "sample@gmail.com") 339 | self.render("grav.html", user=self.get_current_user(), email=email, icon=self.build_grav_url(email)) 340 | 341 | 342 | class WildcardPathHandler(BaseHandler): 343 | def initialize(self): 344 | self.supported_path = ['good', 'nice'] 345 | 346 | """ prepare() called just before either get or post 347 | like a later version of init() """ 348 | def prepare(self): 349 | print self.request.path 350 | action = self.request.path.split('/')[-2] 351 | if action not in self.supported_path: 352 | self.write('
I dont like that url') 353 | self.finish() 354 | return 355 | 356 | def get(self, action): 357 | self.write('I am happy you went to '+action+'') 358 | self.finish() 359 | 360 | def post(self, action): 361 | self.write('I am happy you went to '+action+'') 362 | self.finish() 363 | 364 | 365 | class ReferBackHandler(BaseHandler): 366 | def get(self): 367 | print 'returning back to previous page' 368 | self.set_secure_cookie("flash", "returning back to previous page") 369 | self.redirect(self.get_referring_url()) 370 | 371 | 372 | """ 373 | async demo - creates a constantly loading webpage which updates from a file. 374 | 'tail -f data/to_read.txt' >> webpage 375 | Blocks. Can't be used by more than 1 user at a time. 376 | """ 377 | class TailHandler(BaseHandler): 378 | @asynchronous 379 | def get(self): 380 | self.file = open('data/to_read.txt', 'r') 381 | self.pos = self.file.tell() 382 | 383 | def _read_file(): 384 | # Read some amout of bytes here. You can't read until newline as it 385 | # would block 386 | line = self.file.read(40) 387 | last_pos = self.file.tell() 388 | if not line: 389 | self.file.close() 390 | self.file = open('data/to_read.txt', 'r') 391 | self.file.seek(last_pos) 392 | pass 393 | else: 394 | self.write(line) 395 | self.flush() 396 | 397 | IOLoop.instance().add_timeout(time.time() + 1, _read_file) 398 | _read_file() 399 | 400 | 401 | class DataPusherHandler(BaseHandler): 402 | #@asynchronous 403 | def get(self): 404 | data = self.application.syncdb['data_pusher'].find() 405 | self.render("data_pusher.html", user=self.get_current_user(), data=data) 406 | 407 | def post(self): 408 | print 'POST DataPusherHandler' 409 | try: 410 | user = self.get_current_user() 411 | if not user: 412 | user = 'not logged in ' 413 | except: 414 | user = 'not logged in ' 415 | 416 | message = self.get_argument("message") 417 | msg = {} 418 | msg['message'] = user + ' : ' + message 419 | 420 | self.application.syncdb['data_pusher'].insert(msg) 421 | self.write('done') 422 | self.finish() 423 | return 424 | #self.redirect('/pusher') 425 | 426 | 427 | class DataPusherRawHandler(BaseHandler): 428 | def get(self): 429 | def _read_data(): 430 | m_id = self.get_argument('id', '') 431 | print m_id 432 | if m_id: 433 | data = list(self.application.syncdb['data_pusher'].find( 434 | {'_id': {'$gt': ObjectId(m_id)}})) 435 | else: 436 | data = list(self.application.syncdb['data_pusher'].find()) 437 | 438 | s = json.dumps(data, default=bson.json_util.default) 439 | self.write(s) 440 | self.flush() 441 | _read_data() 442 | 443 | 444 | class S3PhotoUploadHandler(BaseHandler): 445 | """ Ideally image resizing should be done with a queue and a seperate python process """ 446 | def get(self): 447 | template_vars = {} 448 | 449 | self.render('upload.html', 450 | **template_vars 451 | ) 452 | 453 | @tornado.web.asynchronous 454 | def post(self): 455 | file1 = self.request.files['file1'][0] 456 | img = cStringIO.StringIO(file1['body']) 457 | image = Image.open(img) 458 | 459 | thread = threading.Thread(target=self.resize_the_image, args=(image,)) 460 | thread.start() 461 | 462 | def resize_the_image(self, image): 463 | im2 = image.resize((100, 100), Image.NEAREST) 464 | out_im2 = cStringIO.StringIO() 465 | im2.save(out_im2, 'PNG') 466 | tornado.ioloop.IOLoop.instance().add_callback(functools.partial(self.upload_to_s3, out_im2)) 467 | 468 | def upload_to_s3(self, image2): 469 | AWS_ACCESS_KEY_ID = '' 470 | AWS_SECRET_ACCESS_KEY = '' 471 | 472 | #credentials can be stored in environment AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY 473 | conn = boto.connect_s3(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY) 474 | 475 | #Connect to bucket and create key 476 | b = conn.get_bucket('photos') 477 | k = b.new_key('example5.png') 478 | 479 | #Note we're setting contents from the in-memory string provided by cStringIO 480 | k.set_contents_from_string(image2.getvalue(), headers={"Content-Type": "image/png"}) 481 | tornado.ioloop.IOLoop.instance().add_callback(functools.partial(self.image_uploaded)) 482 | 483 | def image_uploaded(self): 484 | self.set_secure_cookie('flash', "File Uploaded") 485 | self.redirect("/") 486 | -------------------------------------------------------------------------------- /sample/requirements.txt: -------------------------------------------------------------------------------- 1 | pymongo==3.7.1 2 | six==1.11.0 3 | tornado==5.1.1 4 | -------------------------------------------------------------------------------- /sample/static/css/jcarousel.css: -------------------------------------------------------------------------------- 1 | .jcarousel-skin-tango .jcarousel-container { 2 | -moz-border-radius: 10px; 3 | -webkit-border-radius: 10px; 4 | border-radius: 10px; 5 | background: #F0F6F9; 6 | border: 1px solid #346F97; 7 | } 8 | 9 | .jcarousel-skin-tango .jcarousel-direction-rtl { 10 | direction: rtl; 11 | } 12 | 13 | .jcarousel-skin-tango .jcarousel-container-horizontal { 14 | width: 100%; 15 | padding: 20px 40px; 16 | } 17 | 18 | .jcarousel-skin-tango .jcarousel-container-vertical { 19 | width: 91%; 20 | height: 140px; 21 | padding: 10px 40px; 22 | } 23 | 24 | .jcarousel-skin-tango .jcarousel-clip { 25 | overflow: hidden; 26 | } 27 | 28 | .jcarousel-skin-tango .jcarousel-clip-horizontal { 29 | width: 100%; 30 | height: 75px; 31 | } 32 | 33 | .jcarousel-skin-tango .jcarousel-clip-vertical { 34 | width: 100%; 35 | height: 140px; 36 | } 37 | 38 | .jcarousel-skin-tango .jcarousel-item { 39 | width: 100%; 40 | height: 140px; 41 | } 42 | 43 | .jcarousel-skin-tango .jcarousel-item-horizontal { 44 | margin-left: 0; 45 | margin-right: 10px; 46 | } 47 | 48 | .jcarousel-skin-tango .jcarousel-direction-rtl .jcarousel-item-horizontal { 49 | margin-left: 10px; 50 | margin-right: 0; 51 | } 52 | 53 | .jcarousel-skin-tango .jcarousel-item-vertical { 54 | margin-bottom: 10px; 55 | } 56 | 57 | .jcarousel-skin-tango .jcarousel-item-placeholder { 58 | background: #fff; 59 | color: #000; 60 | } 61 | -------------------------------------------------------------------------------- /sample/static/css/notification.css: -------------------------------------------------------------------------------- 1 | 2 | #notification { 3 | font-family:Arial,Helvetica,sans-serif; 4 | position:fixed; 5 | top:0px; 6 | /*bottom:0px;*/ 7 | left:0px; 8 | width:100%; 9 | z-index:99; 10 | text-align:center; 11 | font-weight:bold; 12 | font-size:100%; 13 | color:white; 14 | padding:10px 0px 10px 0px; 15 | background-color:#8E1609; 16 | } 17 | 18 | #notification span { 19 | text-align: center; 20 | width: 95%; 21 | float:left; 22 | } 23 | 24 | .close-notify { 25 | white-space: nowrap; 26 | float:right; 27 | margin-right:10px; 28 | color:#fff; 29 | text-decoration:none; 30 | border:2px #fff solid; 31 | padding-left:3px; 32 | padding-right:3px 33 | } 34 | 35 | .close-notify a { 36 | color: #fff; 37 | } -------------------------------------------------------------------------------- /sample/static/css/popup.css: -------------------------------------------------------------------------------- 1 | /* Mask for background, by default is not display */ 2 | #mask { 3 | display: none; 4 | background: #000; 5 | position: fixed; left: 0; top: 0; 6 | z-index: 10; 7 | width: 100%; height: 100%; 8 | opacity: 0.8; 9 | z-index: 999; 10 | } 11 | 12 | /* You can customize to your needs */ 13 | .login-popup{ 14 | display:none; 15 | background: #333; 16 | padding: 10px; 17 | border: 2px solid #ddd; 18 | float: left; 19 | font-size: 1.2em; 20 | position: fixed; 21 | top: 50%; left: 50%; 22 | z-index: 99999; 23 | box-shadow: 0px 0px 20px #999; /* CSS3 */ 24 | -moz-box-shadow: 0px 0px 20px #999; /* Firefox */ 25 | -webkit-box-shadow: 0px 0px 20px #999; /* Safari, Chrome */ 26 | border-radius:3px 3px 3px 3px; 27 | -moz-border-radius: 3px; /* Firefox */ 28 | -webkit-border-radius: 3px; /* Safari, Chrome */ 29 | } 30 | 31 | img.btn_close {/* Position the close button*/ 32 | position:relative; 33 | margin: -28px -28px 0 0; 34 | float: right; 35 | } 36 | img.btn_close:hover{ 37 | box-shadow: 0px 0px 20px #999; /* CSS3 */ 38 | -moz-box-shadow: 0px 0px 20px #999; /* Firefox */ 39 | -webkit-box-shadow: 0px 0px 20px #999; /* Safari, Chrome */ 40 | } 41 | 42 | fieldset { 43 | border:none; 44 | } 45 | 46 | form.signin .textbox label { 47 | display:block; 48 | padding-bottom:7px; 49 | } 50 | 51 | form.signin .textbox span { 52 | display:block; 53 | } 54 | 55 | form.signin p, form.signin span { 56 | color:#999; 57 | font-size:11px; 58 | line-height:18px; 59 | } 60 | 61 | form.signin .textbox input { 62 | background:#666666; 63 | border-bottom:1px solid #333; 64 | border-left:1px solid #000; 65 | border-right:1px solid #333; 66 | border-top:1px solid #000; 67 | color:#fff; 68 | border-radius: 3px 3px 3px 3px; 69 | -moz-border-radius: 3px; 70 | -webkit-border-radius: 3px; 71 | font:13px Arial, Helvetica, sans-serif; 72 | padding:6px 6px 4px; 73 | width:200px; 74 | } 75 | 76 | form.signin input:-moz-placeholder { color:#bbb; text-shadow:0 0 2px #000; } 77 | form.signin input::-webkit-input-placeholder { color:#bbb; text-shadow:0 0 2px #000; } 78 | 79 | .button { 80 | background: -moz-linear-gradient(center top, #f3f3f3, #dddddd); 81 | background: -webkit-gradient(linear, left top, left bottom, from(#f3f3f3), to(#dddddd)); 82 | background: -o-linear-gradient(top, #f3f3f3, #dddddd); 83 | filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='#f3f3f3', EndColorStr='#dddddd'); 84 | border-color:#000; 85 | border-width:1px; 86 | border-radius:4px 4px 4px 4px; 87 | -moz-border-radius: 4px; 88 | -webkit-border-radius: 4px; 89 | color:#333; 90 | cursor:pointer; 91 | display:inline-block; 92 | padding:6px 6px 4px; 93 | margin-top:10px; 94 | font:12px; 95 | width:214px; 96 | } 97 | .button:hover { background:#ddd; } 98 | -------------------------------------------------------------------------------- /sample/static/img/close_pop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bootandy/tornado_sample/57e3a1e9dc0b3afcdc8743ce9a909921196458bd/sample/static/img/close_pop.png -------------------------------------------------------------------------------- /sample/static/js/jquery-1.7.2.min.js: -------------------------------------------------------------------------------- 1 | /*! jQuery v1.7.2 jquery.com | jquery.org/license */ 2 | (function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cu(a){if(!cj[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ck||(ck=c.createElement("iframe"),ck.frameBorder=ck.width=ck.height=0),b.appendChild(ck);if(!cl||!ck.createElement)cl=(ck.contentWindow||ck.contentDocument).document,cl.write((f.support.boxModel?"":"")+""),cl.close();d=cl.createElement(a),cl.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ck)}cj[a]=e}return cj[a]}function ct(a,b){var c={};f.each(cp.concat.apply([],cp.slice(0,b)),function(){c[this]=a});return c}function cs(){cq=b}function cr(){setTimeout(cs,0);return cq=f.now()}function ci(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ch(){try{return new a.XMLHttpRequest}catch(b){}}function cb(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){if(c!=="border")for(;et |
From | 57 |To | 58 |Message | 59 | {% for m in messages %} 60 |
---|---|---|
{{ m['from']}} | 62 |{{ m['to']}} | 63 |{{ m['message']}} | 64 |
This script uses jquery token input - read about it
18 | 19 | 20 | 21 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /sample/templates/message.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Hello {{user}} logoutlorum ipsum
21 |your url is: {{request.uri}}
22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /sample/templates/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |Send at least 2 messages to see this slidy working
14 |set your AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY keys first
10 |Choose a image to upload
11 | 12 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /sample/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bootandy/tornado_sample/57e3a1e9dc0b3afcdc8743ce9a909921196458bd/sample/tests/__init__.py -------------------------------------------------------------------------------- /sample/tests/lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bootandy/tornado_sample/57e3a1e9dc0b3afcdc8743ce9a909921196458bd/sample/tests/lib/__init__.py -------------------------------------------------------------------------------- /sample/tests/lib/http_test_client.py: -------------------------------------------------------------------------------- 1 | # copied from: https://github.com/peterbe/tornado-utils/blob/master/tornado_utils/http_test_client.py 2 | 3 | from urllib import urlencode 4 | import Cookie 5 | from tornado.httpclient import HTTPRequest 6 | from tornado import escape 7 | 8 | __version__ = '1.3' 9 | 10 | class LoginError(Exception): 11 | pass 12 | 13 | class HTTPClientMixin(object): 14 | 15 | def get(self, url, data=None, headers=None, follow_redirects=False): 16 | if data is not None: 17 | if isinstance(data, dict): 18 | data = urlencode(data, True) 19 | if '?' in url: 20 | url += '&%s' % data 21 | else: 22 | url += '?%s' % data 23 | return self._fetch(url, 'GET', headers=headers, 24 | follow_redirects=follow_redirects) 25 | 26 | def post(self, url, data, headers=None, follow_redirects=False): 27 | if data is not None: 28 | if isinstance(data, dict): 29 | for key, value in data.items(): 30 | if isinstance(value, unicode): 31 | data[key] = value.encode('utf-8') 32 | data = urlencode(data, True) 33 | return self._fetch(url, 'POST', data, headers, 34 | follow_redirects=follow_redirects) 35 | 36 | def _fetch(self, url, method, data=None, headers=None, follow_redirects=True): 37 | full_url = self.get_url(url) 38 | request = HTTPRequest(full_url, follow_redirects=follow_redirects, 39 | headers=headers, method=method, body=data) 40 | self.http_client.fetch(request, self.stop) 41 | return self.wait() 42 | 43 | 44 | class TestClient(HTTPClientMixin): 45 | def __init__(self, testcase): 46 | self.testcase = testcase 47 | self.cookies = Cookie.SimpleCookie() 48 | 49 | def _render_cookie_back(self): 50 | return ''.join(['%s=%s;' %(x, morsel.value) 51 | for (x, morsel) 52 | in self.cookies.items()]) 53 | 54 | def get(self, url, data=None, headers=None, follow_redirects=False): 55 | if self.cookies: 56 | if headers is None: 57 | headers = dict() 58 | headers['Cookie'] = self._render_cookie_back() 59 | response = self.testcase.get(url, data=data, headers=headers, 60 | follow_redirects=follow_redirects) 61 | 62 | self._update_cookies(response.headers) 63 | return response 64 | 65 | def post(self, url, data, headers=None, follow_redirects=False): 66 | if self.cookies: 67 | if headers is None: 68 | headers = dict() 69 | headers['Cookie'] = self._render_cookie_back() 70 | response = self.testcase.post(url, data=data, headers=headers, 71 | follow_redirects=follow_redirects) 72 | self._update_cookies(response.headers) 73 | return response 74 | 75 | def _update_cookies(self, headers): 76 | try: 77 | sc = headers['Set-Cookie'] 78 | cookies = escape.native_str(sc) 79 | 80 | while True: 81 | self.cookies.update(Cookie.SimpleCookie(cookies)) 82 | if cookies.find(',') == -1: 83 | break 84 | cookies = cookies[cookies.find(',') + len(',') :] 85 | except KeyError: 86 | return 87 | 88 | def login(self, email, password, url='/auth/login/'): 89 | data = dict(email=email, password=password) 90 | response = self.post(url, data, follow_redirects=False) 91 | if response.code != 302: 92 | raise LoginError(response.body) 93 | if 'Error' in response.body: 94 | raise LoginError(response.body) 95 | 96 | -------------------------------------------------------------------------------- /sample/tests/test_client.py: -------------------------------------------------------------------------------- 1 | # Runs tests using lib/http_test_client which caches cookie values allowing us to login 2 | 3 | from tests.lib.http_test_client import * 4 | from tornado.testing import LogTrapTestCase, AsyncHTTPTestCase,AsyncTestCase, AsyncHTTPClient 5 | from app import Application 6 | import bcrypt 7 | from tornado.ioloop import IOLoop 8 | import sys 9 | import unittest 10 | 11 | #from mock import MagicMock, Mock 12 | from handlers.handlers import FacebookLoginHandler 13 | from mox import Mox 14 | 15 | 16 | class BaseHTTPTestCase(AsyncHTTPTestCase, HTTPClientMixin, LogTrapTestCase): 17 | def get_app(self): 18 | return Application(db='testdb') 19 | 20 | def _create_user(self, email, password): 21 | hashed_pass = bcrypt.hashpw(password, bcrypt.gensalt(8)) 22 | user = {} 23 | user['user'] = email 24 | user['password'] = hashed_pass 25 | self.get_app().syncdb['users'].save(user) 26 | 27 | def setUp(self): 28 | super(BaseHTTPTestCase, self).setUp() 29 | self.client = TestClient(self) 30 | 31 | 32 | # Tests based around remembering the user id after login. 33 | class HandlersTestCase(BaseHTTPTestCase): 34 | def setUp(self): 35 | super(HandlersTestCase, self).setUp() 36 | self._create_user('test_user', 'secret') 37 | 38 | def test_homepage(self): 39 | response = self.client.get('/login') 40 | self.assertEqual(response.code, 200) 41 | self.assertTrue('please login' in response.body) 42 | 43 | data = {'email': 'test_user', 'password': 'secret'} 44 | response = self.client.post('/login', data) 45 | self.assertEqual(response.code, 302) 46 | 47 | response = self.client.get('/hello') 48 | self.assertEqual(response.code, 200) 49 | self.assertTrue('please login' not in response.body) 50 | self.assertTrue('Hello test_user' in response.body) 51 | 52 | 53 | class AsyncHandlerTestCase(BaseHTTPTestCase): 54 | def setUp(self): 55 | super(AsyncHandlerTestCase, self).setUp() 56 | self._create_user('test_user', 'secret') 57 | 58 | def test_email(self): 59 | self.client = AsyncHTTPClient(self.io_loop) 60 | response = self.fetch('/email' ) 61 | self.assertEqual(response.code, 200) 62 | self.assertIn('notification', response.body ) 63 | 64 | def get_new_ioloop(self): 65 | return IOLoop.instance() 66 | 67 | 68 | # class MockHandlerTestCase(BaseHTTPTestCase): 69 | # def setUp(self): 70 | # super(MockHandlerTestCase, self).setUp() 71 | 72 | # # def test_fb_call(self): 73 | # # response = self.fetch('/facebook_login?code=123' ) 74 | 75 | # # mock = Mock() 76 | # # mock['fb_user'] = {} 77 | # # self.authorize_redirect(mock) 78 | 79 | # # self.assertEqual(response.code, 302) 80 | # # self.assertIn('https://graph.facebook.com/oauth/access_token', response.effective_url ) 81 | 82 | # def test_fb_return_call(self): 83 | # r = HTTPRequest('/facebook_login', follow_redirects=True, 84 | # headers='', method='GET', body='') 85 | # handler = FacebookLoginHandler(self.get_app(), r) 86 | # handler._on_login( {'_id':'asdf'}) 87 | 88 | 89 | # def get_new_ioloop(self): 90 | # return IOLoop.instance() 91 | 92 | -------------------------------------------------------------------------------- /sample/tests/test_mocks.py: -------------------------------------------------------------------------------- 1 | import mox 2 | 3 | import uuid 4 | 5 | from tornado.testing import AsyncHTTPTestCase 6 | import tornado.web 7 | import tornado.testing 8 | 9 | from handlers.handlers import * 10 | 11 | 12 | class TestPictureUploadHandler(AsyncHTTPTestCase): 13 | # def setUp(self): 14 | # super(TestPictureUploadHandler, self).setUp() 15 | 16 | def get_app(self): 17 | app_settings = { 18 | 'static_path': os.path.join(os.path.dirname(__file__), 'static'), 19 | 'template_path': os.path.join(os.path.dirname(__file__), '../templates'), 20 | "cookie_secret": base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes), 21 | } 22 | 23 | app = tornado.web.Application([ 24 | (r'/hello', HelloHandler), 25 | ], **app_settings) 26 | 27 | return app 28 | 29 | def test_image_upload(self): 30 | self.mox = mox.Mox() 31 | 32 | self.mox.StubOutWithMock(HelloHandler, 'get_current_user') 33 | HelloHandler.get_current_user().AndReturn('testuser') 34 | HelloHandler.get_current_user().AndReturn('testuser') 35 | self.mox.StubOutWithMock(HelloHandler, 'get_messages') 36 | HelloHandler.get_messages().AndReturn([]) 37 | 38 | #home.HomePublicHandler.user_details = defaultdict(str) 39 | 40 | self.mox.ReplayAll() 41 | 42 | resp = self.fetch('/hello') 43 | #resp = self.post('/upload_picture') 44 | self.assertEqual(resp.code, 200) 45 | self.mox.VerifyAll() 46 | self.mox.UnsetStubs() 47 | -------------------------------------------------------------------------------- /splinter/tests-splinter/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bootandy/tornado_sample/57e3a1e9dc0b3afcdc8743ce9a909921196458bd/splinter/tests-splinter/__init__.py -------------------------------------------------------------------------------- /splinter/tests-splinter/test_with_real_browser.py: -------------------------------------------------------------------------------- 1 | # Acceptance Test Framework: http://splinter.cobrateam.info/ 2 | 3 | import unittest 4 | from splinter.browser import Browser 5 | 6 | print 'If you want to setup something do it here' 7 | 8 | @unittest.skip('Splinter samples') 9 | class SplinterSampleTests(unittest.TestCase): 10 | @classmethod 11 | def setUpClass(cls): 12 | cls.browser = Browser() 13 | 14 | @classmethod 15 | def tearDownClass(cls): 16 | cls.browser.quit() 17 | 18 | def test_visiting_google_com_returns_a_page_with_Google_in_title(self): 19 | self.browser.visit('http://www.google.com/') 20 | self.assertIn('Google', self.browser.title) 21 | 22 | def test_filling_Splinter_in_the_search_box_returns_Splinter_website(self): 23 | self.browser.visit('http://www.google.com/') 24 | self.browser.fill('q', 'Splinter python test') 25 | search_button = self.browser.find_by_name('btnG').first 26 | while not search_button.visible: 27 | # waits for the JavaScript to put the button on the page 28 | pass 29 | search_button.click() 30 | self.assertTrue(self.browser.is_text_present('splinter.cobrateam.info')) 31 | 32 | 33 | class SplinterWebAcceptanceTests(unittest.TestCase): 34 | @classmethod 35 | def setUpClass(cls): 36 | cls.browser = Browser() 37 | 38 | @classmethod 39 | def tearDownClass(cls): 40 | cls.browser.quit() 41 | 42 | def test_message_send(self): 43 | """ Start the server on http://localhost:8888/ 44 | create a login with name: andy password: andy for this test to work""" 45 | 46 | self.browser.visit('http://localhost:8888/login') 47 | self.browser.fill('email', 'andy') 48 | self.browser.fill('password', 'andy') 49 | self.browser.find_by_name('login').click() 50 | 51 | # Press the Login Button: 52 | self.assertTrue(self.browser.is_text_present('Hello andy'), 53 | 'Could not Login - You must create a user with name: andy password: andy first') 54 | 55 | #Look for the 'send new message' link and click it: 56 | self.browser.find_link_by_text('send new message').click() 57 | self.assertTrue(self.browser.is_text_present('Message to:')) 58 | 59 | # Write a message 60 | self.browser.choose('to', 'andy') 61 | self.browser.fill('message', 'I am sending a message via splinter') 62 | self.browser.find_by_name('send').click() 63 | 64 | # Check the message appears when we are forward back to the home page 65 | self.assertTrue(self.browser.is_text_present('I am sending a message via splinter')) 66 | self.assertTrue(self.browser.is_text_present('Message Sent')) 67 | --------------------------------------------------------------------------------