├── README.md ├── static ├── assets │ ├── destroy.png │ ├── todos.css │ └── vendor │ │ ├── ember-data.js │ │ ├── ember.js │ │ ├── handlebars-1.0.rc.2.js │ │ └── jquery.js └── todos.js ├── templates └── todo.html └── todo.py /README.md: -------------------------------------------------------------------------------- 1 | # What is it # 2 | 3 | A demo web application in the spirit of [TodoMVC](http://todomvc.com/) showing how to use 4 | **RethinkDB as a backend for Bottle and Ember.js applications**. 5 | 6 | As any todo application, this one implements the following functionality: 7 | 8 | * Managing database connections 9 | * List existing todos 10 | * Create new todo 11 | * Retrieve a single todo 12 | * Edit a todo or mark a todo as done 13 | * Delete a todo 14 | 15 | _Open issues_: when editing a todo, a `PUT` request is sent after each typed character. This could be improved to send a request is only once when Enter is pressed (pull requests welcome!) 16 | 17 | # Complete stack # 18 | 19 | * [Bottle](http://bottlepy.org/) 20 | * [Ember (v1.0.0-pre.4-9-g6f709b0)](http://emberjs.com) 21 | * [RethinkDB](http://www.rethinkdb.com) 22 | 23 | # Installation # 24 | 25 | ``` 26 | git clone git://github.com/rethinkdb/rethinkdb-example-bottle-ember-todo.git 27 | pip install bottle 28 | pip install rethinkdb 29 | ``` 30 | 31 | _Note_: If you don't have RethinkDB installed, you can follow [these instructions to get it up and running](http://www.rethinkdb.com/docs/install/). 32 | 33 | # Running the application # 34 | 35 | Firstly we'll need to create the database `todoapp` (you can override the name of the database 36 | by setting the `TODO_DB` env variable) and the table used by this app: `todos`. You can 37 | do this by running: 38 | 39 | ``` 40 | python todo.py --setup 41 | ``` 42 | 43 | Running the Bottle application is as simple as: 44 | 45 | ``` 46 | python todo.py 47 | ``` 48 | 49 | Then open a browser: . 50 | 51 | 52 | # License # 53 | 54 | This demo application is licensed under the MIT license: 55 | -------------------------------------------------------------------------------- /static/assets/destroy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rethinkdb/rethinkdb-example-bottle-ember-todo/9d3d4e36b00e1ddaccf7b120e4548f887c5bf79e/static/assets/destroy.png -------------------------------------------------------------------------------- /static/assets/todos.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | button { 8 | margin: 0; 9 | padding: 0; 10 | border: 0; 11 | background: none; 12 | font-size: 100%; 13 | vertical-align: baseline; 14 | font-family: inherit; 15 | color: inherit; 16 | -webkit-appearance: none; 17 | /*-moz-appearance: none;*/ 18 | -ms-appearance: none; 19 | -o-appearance: none; 20 | appearance: none; 21 | } 22 | 23 | body { 24 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 25 | line-height: 1.4em; 26 | background: #eaeaea; 27 | color: #4d4d4d; 28 | width: 550px; 29 | margin: 0 auto; 30 | -webkit-font-smoothing: antialiased; 31 | -moz-font-smoothing: antialiased; 32 | -ms-font-smoothing: antialiased; 33 | -o-font-smoothing: antialiased; 34 | font-smoothing: antialiased; 35 | } 36 | 37 | #todoapp { 38 | background: #fff; 39 | background: rgba(255, 255, 255, 0.9); 40 | margin: 130px 0 40px 0; 41 | border: 1px solid #ccc; 42 | position: relative; 43 | border-top-left-radius: 2px; 44 | border-top-right-radius: 2px; 45 | box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2), 46 | 0 25px 50px 0 rgba(0, 0, 0, 0.15); 47 | } 48 | 49 | #todoapp:before { 50 | content: ''; 51 | border-left: 1px solid #f5d6d6; 52 | border-right: 1px solid #f5d6d6; 53 | width: 2px; 54 | position: absolute; 55 | top: 0; 56 | left: 40px; 57 | height: 100%; 58 | } 59 | 60 | #todoapp input::-webkit-input-placeholder { 61 | font-style: italic; 62 | } 63 | 64 | #todoapp input:-moz-placeholder { 65 | font-style: italic; 66 | color: #a9a9a9; 67 | } 68 | 69 | #todoapp h1 { 70 | position: absolute; 71 | top: -120px; 72 | width: 100%; 73 | font-size: 70px; 74 | font-weight: bold; 75 | text-align: center; 76 | color: #b3b3b3; 77 | color: rgba(255, 255, 255, 0.3); 78 | text-shadow: -1px -1px rgba(0, 0, 0, 0.2); 79 | -webkit-text-rendering: optimizeLegibility; 80 | -moz-text-rendering: optimizeLegibility; 81 | -ms-text-rendering: optimizeLegibility; 82 | -o-text-rendering: optimizeLegibility; 83 | text-rendering: optimizeLegibility; 84 | } 85 | 86 | #header { 87 | padding-top: 15px; 88 | border-radius: inherit; 89 | } 90 | 91 | #header:before { 92 | content: ''; 93 | position: absolute; 94 | top: 0; 95 | right: 0; 96 | left: 0; 97 | height: 15px; 98 | z-index: 2; 99 | border-bottom: 1px solid #6c615c; 100 | background: #8d7d77; 101 | background: -webkit-gradient(linear, left top, left bottom, from(rgba(132, 110, 100, 0.8)),to(rgba(101, 84, 76, 0.8))); 102 | background: -webkit-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); 103 | background: -moz-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); 104 | background: -o-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); 105 | background: -ms-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); 106 | background: linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); 107 | filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670'); 108 | border-top-left-radius: 1px; 109 | border-top-right-radius: 1px; 110 | } 111 | 112 | #new-todo, 113 | .edit { 114 | position: relative; 115 | margin: 0; 116 | width: 100%; 117 | font-size: 24px; 118 | font-family: inherit; 119 | line-height: 1.4em; 120 | border: 0; 121 | outline: none; 122 | color: inherit; 123 | padding: 6px; 124 | border: 1px solid #999; 125 | box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); 126 | -webkit-box-sizing: border-box; 127 | -moz-box-sizing: border-box; 128 | -ms-box-sizing: border-box; 129 | -o-box-sizing: border-box; 130 | box-sizing: border-box; 131 | -webkit-font-smoothing: antialiased; 132 | -moz-font-smoothing: antialiased; 133 | -ms-font-smoothing: antialiased; 134 | -o-font-smoothing: antialiased; 135 | font-smoothing: antialiased; 136 | } 137 | 138 | #new-todo { 139 | padding: 16px 16px 16px 60px; 140 | border: none; 141 | background: rgba(0, 0, 0, 0.02); 142 | z-index: 2; 143 | box-shadow: none; 144 | } 145 | 146 | #main { 147 | position: relative; 148 | z-index: 2; 149 | border-top: 1px dotted #adadad; 150 | } 151 | 152 | label[for='toggle-all'] { 153 | display: none; 154 | } 155 | 156 | #toggle-all { 157 | position: absolute; 158 | top: -42px; 159 | left: -4px; 160 | width: 40px; 161 | text-align: center; 162 | border: none; /* Mobile Safari */ 163 | } 164 | 165 | #toggle-all:before { 166 | content: '»'; 167 | font-size: 28px; 168 | color: #d9d9d9; 169 | padding: 0 25px 7px; 170 | } 171 | 172 | #toggle-all:checked:before { 173 | color: #737373; 174 | } 175 | 176 | #todo-list { 177 | margin: 0; 178 | padding: 0; 179 | list-style: none; 180 | } 181 | 182 | #todo-list li { 183 | position: relative; 184 | font-size: 24px; 185 | border-bottom: 1px dotted #ccc; 186 | } 187 | 188 | #todo-list li:last-child { 189 | border-bottom: none; 190 | } 191 | 192 | #todo-list li.editing { 193 | border-bottom: none; 194 | padding: 0; 195 | } 196 | 197 | #todo-list li.editing .edit { 198 | display: block; 199 | width: 506px; 200 | padding: 13px 17px 12px 17px; 201 | margin: 0 0 0 43px; 202 | } 203 | 204 | #todo-list li.editing .view { 205 | display: none; 206 | } 207 | 208 | #todo-list li .toggle { 209 | text-align: center; 210 | width: 40px; 211 | /* auto, since non-WebKit browsers doesn't support input styling */ 212 | height: auto; 213 | position: absolute; 214 | top: 0; 215 | bottom: 0; 216 | margin: auto 0; 217 | border: none; /* Mobile Safari */ 218 | -webkit-appearance: none; 219 | /*-moz-appearance: none;*/ 220 | -ms-appearance: none; 221 | -o-appearance: none; 222 | appearance: none; 223 | } 224 | 225 | #todo-list li .toggle:after { 226 | content: '✔'; 227 | line-height: 43px; /* 40 + a couple of pixels visual adjustment */ 228 | font-size: 20px; 229 | color: #d9d9d9; 230 | text-shadow: 0 -1px 0 #bfbfbf; 231 | } 232 | 233 | #todo-list li .toggle:checked:after { 234 | color: #85ada7; 235 | text-shadow: 0 1px 0 #669991; 236 | bottom: 1px; 237 | position: relative; 238 | } 239 | 240 | #todo-list li label { 241 | word-break: break-word; 242 | padding: 15px; 243 | margin-left: 45px; 244 | display: block; 245 | line-height: 1.2; 246 | -webkit-transition: color 0.4s; 247 | -moz-transition: color 0.4s; 248 | -ms-transition: color 0.4s; 249 | -o-transition: color 0.4s; 250 | transition: color 0.4s; 251 | } 252 | 253 | #todo-list li.completed label { 254 | color: #a9a9a9; 255 | text-decoration: line-through; 256 | } 257 | 258 | #todo-list li .destroy { 259 | display: none; 260 | position: absolute; 261 | top: 0; 262 | right: 10px; 263 | bottom: 0; 264 | width: 40px; 265 | height: 40px; 266 | margin: auto 0; 267 | font-size: 22px; 268 | color: #a88a8a; 269 | -webkit-transition: all 0.2s; 270 | -moz-transition: all 0.2s; 271 | -ms-transition: all 0.2s; 272 | -o-transition: all 0.2s; 273 | transition: all 0.2s; 274 | } 275 | 276 | #todo-list li .destroy:hover { 277 | text-shadow: 0 0 1px #000, 278 | 0 0 10px rgba(199, 107, 107, 0.8); 279 | -webkit-transform: scale(1.3); 280 | -moz-transform: scale(1.3); 281 | -ms-transform: scale(1.3); 282 | -o-transform: scale(1.3); 283 | transform: scale(1.3); 284 | } 285 | 286 | #todo-list li .destroy:after { 287 | content: '✖'; 288 | } 289 | 290 | #todo-list li:hover .destroy { 291 | display: block; 292 | } 293 | 294 | #todo-list li .edit { 295 | display: none; 296 | } 297 | 298 | #todo-list li.editing:last-child { 299 | margin-bottom: -1px; 300 | } 301 | 302 | #footer { 303 | color: #777; 304 | padding: 0 15px; 305 | position: absolute; 306 | right: 0; 307 | bottom: -31px; 308 | left: 0; 309 | height: 20px; 310 | z-index: 1; 311 | text-align: center; 312 | } 313 | 314 | #footer:before { 315 | content: ''; 316 | position: absolute; 317 | right: 0; 318 | bottom: 31px; 319 | left: 0; 320 | height: 50px; 321 | z-index: -1; 322 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3), 323 | 0 6px 0 -3px rgba(255, 255, 255, 0.8), 324 | 0 7px 1px -3px rgba(0, 0, 0, 0.3), 325 | 0 43px 0 -6px rgba(255, 255, 255, 0.8), 326 | 0 44px 2px -6px rgba(0, 0, 0, 0.2); 327 | } 328 | 329 | #todo-count { 330 | float: left; 331 | text-align: left; 332 | } 333 | 334 | #filters { 335 | margin: 0; 336 | padding: 0; 337 | list-style: none; 338 | position: absolute; 339 | right: 0; 340 | left: 0; 341 | } 342 | 343 | #filters li { 344 | display: inline; 345 | } 346 | 347 | #filters li a { 348 | color: #83756f; 349 | margin: 2px; 350 | text-decoration: none; 351 | } 352 | 353 | #filters li a.selected { 354 | font-weight: bold; 355 | } 356 | 357 | #clear-completed { 358 | float: right; 359 | position: relative; 360 | line-height: 20px; 361 | text-decoration: none; 362 | background: rgba(0, 0, 0, 0.1); 363 | font-size: 11px; 364 | padding: 0 10px; 365 | border-radius: 3px; 366 | box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2); 367 | } 368 | 369 | #clear-completed:hover { 370 | background: rgba(0, 0, 0, 0.15); 371 | box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3); 372 | } 373 | 374 | #info { 375 | margin: 65px auto 0; 376 | color: #a6a6a6; 377 | font-size: 12px; 378 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7); 379 | text-align: center; 380 | } 381 | 382 | #info a { 383 | color: inherit; 384 | } 385 | 386 | /* 387 | Hack to remove background from Mobile Safari. 388 | Can't use it globally since it destroys checkboxes in Firefox and Opera 389 | */ 390 | @media screen and (-webkit-min-device-pixel-ratio:0) { 391 | #toggle-all, 392 | #todo-list li .toggle { 393 | background: none; 394 | } 395 | 396 | #todo-list li .toggle { 397 | height: 40px; 398 | } 399 | 400 | #toggle-all { 401 | top: -56px; 402 | left: -15px; 403 | width: 65px; 404 | height: 41px; 405 | -webkit-transform: rotate(90deg); 406 | transform: rotate(90deg); 407 | -webkit-appearance: none; 408 | appearance: none; 409 | } 410 | } 411 | 412 | .hidden{ 413 | display:none; 414 | } -------------------------------------------------------------------------------- /static/assets/vendor/handlebars-1.0.rc.2.js: -------------------------------------------------------------------------------- 1 | // lib/handlebars/base.js 2 | 3 | /*jshint eqnull:true*/ 4 | this.Handlebars = {}; 5 | 6 | (function(Handlebars) { 7 | 8 | Handlebars.VERSION = "1.0.rc.2"; 9 | 10 | Handlebars.helpers = {}; 11 | Handlebars.partials = {}; 12 | 13 | Handlebars.registerHelper = function(name, fn, inverse) { 14 | if(inverse) { fn.not = inverse; } 15 | this.helpers[name] = fn; 16 | }; 17 | 18 | Handlebars.registerPartial = function(name, str) { 19 | this.partials[name] = str; 20 | }; 21 | 22 | Handlebars.registerHelper('helperMissing', function(arg) { 23 | if(arguments.length === 2) { 24 | return undefined; 25 | } else { 26 | throw new Error("Could not find property '" + arg + "'"); 27 | } 28 | }); 29 | 30 | var toString = Object.prototype.toString, functionType = "[object Function]"; 31 | 32 | Handlebars.registerHelper('blockHelperMissing', function(context, options) { 33 | var inverse = options.inverse || function() {}, fn = options.fn; 34 | 35 | 36 | var ret = ""; 37 | var type = toString.call(context); 38 | 39 | if(type === functionType) { context = context.call(this); } 40 | 41 | if(context === true) { 42 | return fn(this); 43 | } else if(context === false || context == null) { 44 | return inverse(this); 45 | } else if(type === "[object Array]") { 46 | if(context.length > 0) { 47 | return Handlebars.helpers.each(context, options); 48 | } else { 49 | return inverse(this); 50 | } 51 | } else { 52 | return fn(context); 53 | } 54 | }); 55 | 56 | Handlebars.K = function() {}; 57 | 58 | Handlebars.createFrame = Object.create || function(object) { 59 | Handlebars.K.prototype = object; 60 | var obj = new Handlebars.K(); 61 | Handlebars.K.prototype = null; 62 | return obj; 63 | }; 64 | 65 | Handlebars.logger = { 66 | DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3, 67 | 68 | methodMap: {0: 'debug', 1: 'info', 2: 'warn', 3: 'error'}, 69 | 70 | // can be overridden in the host environment 71 | log: function(level, obj) { 72 | if (Handlebars.logger.level <= level) { 73 | var method = Handlebars.logger.methodMap[level]; 74 | if (typeof console !== 'undefined' && console[method]) { 75 | console[method].call(console, obj); 76 | } 77 | } 78 | } 79 | }; 80 | 81 | Handlebars.log = function(level, obj) { Handlebars.logger.log(level, obj); }; 82 | 83 | Handlebars.registerHelper('each', function(context, options) { 84 | var fn = options.fn, inverse = options.inverse; 85 | var i = 0, ret = "", data; 86 | 87 | if (options.data) { 88 | data = Handlebars.createFrame(options.data); 89 | } 90 | 91 | if(context && typeof context === 'object') { 92 | if(context instanceof Array){ 93 | for(var j = context.length; i 2) { 301 | expected.push("'" + this.terminals_[p] + "'"); 302 | } 303 | if (this.lexer.showPosition) { 304 | errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + (this.terminals_[symbol] || symbol) + "'"; 305 | } else { 306 | errStr = "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == 1?"end of input":"'" + (this.terminals_[symbol] || symbol) + "'"); 307 | } 308 | this.parseError(errStr, {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected}); 309 | } 310 | } 311 | if (action[0] instanceof Array && action.length > 1) { 312 | throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol); 313 | } 314 | switch (action[0]) { 315 | case 1: 316 | stack.push(symbol); 317 | vstack.push(this.lexer.yytext); 318 | lstack.push(this.lexer.yylloc); 319 | stack.push(action[1]); 320 | symbol = null; 321 | if (!preErrorSymbol) { 322 | yyleng = this.lexer.yyleng; 323 | yytext = this.lexer.yytext; 324 | yylineno = this.lexer.yylineno; 325 | yyloc = this.lexer.yylloc; 326 | if (recovering > 0) 327 | recovering--; 328 | } else { 329 | symbol = preErrorSymbol; 330 | preErrorSymbol = null; 331 | } 332 | break; 333 | case 2: 334 | len = this.productions_[action[1]][1]; 335 | yyval.$ = vstack[vstack.length - len]; 336 | yyval._$ = {first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column}; 337 | if (ranges) { 338 | yyval._$.range = [lstack[lstack.length - (len || 1)].range[0], lstack[lstack.length - 1].range[1]]; 339 | } 340 | r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack); 341 | if (typeof r !== "undefined") { 342 | return r; 343 | } 344 | if (len) { 345 | stack = stack.slice(0, -1 * len * 2); 346 | vstack = vstack.slice(0, -1 * len); 347 | lstack = lstack.slice(0, -1 * len); 348 | } 349 | stack.push(this.productions_[action[1]][0]); 350 | vstack.push(yyval.$); 351 | lstack.push(yyval._$); 352 | newState = table[stack[stack.length - 2]][stack[stack.length - 1]]; 353 | stack.push(newState); 354 | break; 355 | case 3: 356 | return true; 357 | } 358 | } 359 | return true; 360 | } 361 | }; 362 | /* Jison generated lexer */ 363 | var lexer = (function(){ 364 | var lexer = ({EOF:1, 365 | parseError:function parseError(str, hash) { 366 | if (this.yy.parser) { 367 | this.yy.parser.parseError(str, hash); 368 | } else { 369 | throw new Error(str); 370 | } 371 | }, 372 | setInput:function (input) { 373 | this._input = input; 374 | this._more = this._less = this.done = false; 375 | this.yylineno = this.yyleng = 0; 376 | this.yytext = this.matched = this.match = ''; 377 | this.conditionStack = ['INITIAL']; 378 | this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0}; 379 | if (this.options.ranges) this.yylloc.range = [0,0]; 380 | this.offset = 0; 381 | return this; 382 | }, 383 | input:function () { 384 | var ch = this._input[0]; 385 | this.yytext += ch; 386 | this.yyleng++; 387 | this.offset++; 388 | this.match += ch; 389 | this.matched += ch; 390 | var lines = ch.match(/(?:\r\n?|\n).*/g); 391 | if (lines) { 392 | this.yylineno++; 393 | this.yylloc.last_line++; 394 | } else { 395 | this.yylloc.last_column++; 396 | } 397 | if (this.options.ranges) this.yylloc.range[1]++; 398 | 399 | this._input = this._input.slice(1); 400 | return ch; 401 | }, 402 | unput:function (ch) { 403 | var len = ch.length; 404 | var lines = ch.split(/(?:\r\n?|\n)/g); 405 | 406 | this._input = ch + this._input; 407 | this.yytext = this.yytext.substr(0, this.yytext.length-len-1); 408 | //this.yyleng -= len; 409 | this.offset -= len; 410 | var oldLines = this.match.split(/(?:\r\n?|\n)/g); 411 | this.match = this.match.substr(0, this.match.length-1); 412 | this.matched = this.matched.substr(0, this.matched.length-1); 413 | 414 | if (lines.length-1) this.yylineno -= lines.length-1; 415 | var r = this.yylloc.range; 416 | 417 | this.yylloc = {first_line: this.yylloc.first_line, 418 | last_line: this.yylineno+1, 419 | first_column: this.yylloc.first_column, 420 | last_column: lines ? 421 | (lines.length === oldLines.length ? this.yylloc.first_column : 0) + oldLines[oldLines.length - lines.length].length - lines[0].length: 422 | this.yylloc.first_column - len 423 | }; 424 | 425 | if (this.options.ranges) { 426 | this.yylloc.range = [r[0], r[0] + this.yyleng - len]; 427 | } 428 | return this; 429 | }, 430 | more:function () { 431 | this._more = true; 432 | return this; 433 | }, 434 | less:function (n) { 435 | this.unput(this.match.slice(n)); 436 | }, 437 | pastInput:function () { 438 | var past = this.matched.substr(0, this.matched.length - this.match.length); 439 | return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, ""); 440 | }, 441 | upcomingInput:function () { 442 | var next = this.match; 443 | if (next.length < 20) { 444 | next += this._input.substr(0, 20-next.length); 445 | } 446 | return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, ""); 447 | }, 448 | showPosition:function () { 449 | var pre = this.pastInput(); 450 | var c = new Array(pre.length + 1).join("-"); 451 | return pre + this.upcomingInput() + "\n" + c+"^"; 452 | }, 453 | next:function () { 454 | if (this.done) { 455 | return this.EOF; 456 | } 457 | if (!this._input) this.done = true; 458 | 459 | var token, 460 | match, 461 | tempMatch, 462 | index, 463 | col, 464 | lines; 465 | if (!this._more) { 466 | this.yytext = ''; 467 | this.match = ''; 468 | } 469 | var rules = this._currentRules(); 470 | for (var i=0;i < rules.length; i++) { 471 | tempMatch = this._input.match(this.rules[rules[i]]); 472 | if (tempMatch && (!match || tempMatch[0].length > match[0].length)) { 473 | match = tempMatch; 474 | index = i; 475 | if (!this.options.flex) break; 476 | } 477 | } 478 | if (match) { 479 | lines = match[0].match(/(?:\r\n?|\n).*/g); 480 | if (lines) this.yylineno += lines.length; 481 | this.yylloc = {first_line: this.yylloc.last_line, 482 | last_line: this.yylineno+1, 483 | first_column: this.yylloc.last_column, 484 | last_column: lines ? lines[lines.length-1].length-lines[lines.length-1].match(/\r?\n?/)[0].length : this.yylloc.last_column + match[0].length}; 485 | this.yytext += match[0]; 486 | this.match += match[0]; 487 | this.matches = match; 488 | this.yyleng = this.yytext.length; 489 | if (this.options.ranges) { 490 | this.yylloc.range = [this.offset, this.offset += this.yyleng]; 491 | } 492 | this._more = false; 493 | this._input = this._input.slice(match[0].length); 494 | this.matched += match[0]; 495 | token = this.performAction.call(this, this.yy, this, rules[index],this.conditionStack[this.conditionStack.length-1]); 496 | if (this.done && this._input) this.done = false; 497 | if (token) return token; 498 | else return; 499 | } 500 | if (this._input === "") { 501 | return this.EOF; 502 | } else { 503 | return this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(), 504 | {text: "", token: null, line: this.yylineno}); 505 | } 506 | }, 507 | lex:function lex() { 508 | var r = this.next(); 509 | if (typeof r !== 'undefined') { 510 | return r; 511 | } else { 512 | return this.lex(); 513 | } 514 | }, 515 | begin:function begin(condition) { 516 | this.conditionStack.push(condition); 517 | }, 518 | popState:function popState() { 519 | return this.conditionStack.pop(); 520 | }, 521 | _currentRules:function _currentRules() { 522 | return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules; 523 | }, 524 | topState:function () { 525 | return this.conditionStack[this.conditionStack.length-2]; 526 | }, 527 | pushState:function begin(condition) { 528 | this.begin(condition); 529 | }}); 530 | lexer.options = {}; 531 | lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) { 532 | 533 | var YYSTATE=YY_START 534 | switch($avoiding_name_collisions) { 535 | case 0: 536 | if(yy_.yytext.slice(-1) !== "\\") this.begin("mu"); 537 | if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1), this.begin("emu"); 538 | if(yy_.yytext) return 14; 539 | 540 | break; 541 | case 1: return 14; 542 | break; 543 | case 2: 544 | if(yy_.yytext.slice(-1) !== "\\") this.popState(); 545 | if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1); 546 | return 14; 547 | 548 | break; 549 | case 3: yy_.yytext = yy_.yytext.substr(0, yy_.yyleng-4); this.popState(); return 15; 550 | break; 551 | case 4: this.begin("par"); return 24; 552 | break; 553 | case 5: return 16; 554 | break; 555 | case 6: return 20; 556 | break; 557 | case 7: return 19; 558 | break; 559 | case 8: return 19; 560 | break; 561 | case 9: return 23; 562 | break; 563 | case 10: return 23; 564 | break; 565 | case 11: this.popState(); this.begin('com'); 566 | break; 567 | case 12: yy_.yytext = yy_.yytext.substr(3,yy_.yyleng-5); this.popState(); return 15; 568 | break; 569 | case 13: return 22; 570 | break; 571 | case 14: return 36; 572 | break; 573 | case 15: return 35; 574 | break; 575 | case 16: return 35; 576 | break; 577 | case 17: return 39; 578 | break; 579 | case 18: /*ignore whitespace*/ 580 | break; 581 | case 19: this.popState(); return 18; 582 | break; 583 | case 20: this.popState(); return 18; 584 | break; 585 | case 21: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 30; 586 | break; 587 | case 22: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\'/g,"'"); return 30; 588 | break; 589 | case 23: yy_.yytext = yy_.yytext.substr(1); return 28; 590 | break; 591 | case 24: return 32; 592 | break; 593 | case 25: return 32; 594 | break; 595 | case 26: return 31; 596 | break; 597 | case 27: return 35; 598 | break; 599 | case 28: yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2); return 35; 600 | break; 601 | case 29: return 'INVALID'; 602 | break; 603 | case 30: /*ignore whitespace*/ 604 | break; 605 | case 31: this.popState(); return 37; 606 | break; 607 | case 32: return 5; 608 | break; 609 | } 610 | }; 611 | lexer.rules = [/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|$)))/,/^(?:[\s\S]*?--\}\})/,/^(?:\{\{>)/,/^(?:\{\{#)/,/^(?:\{\{\/)/,/^(?:\{\{\^)/,/^(?:\{\{\s*else\b)/,/^(?:\{\{\{)/,/^(?:\{\{&)/,/^(?:\{\{!--)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{)/,/^(?:=)/,/^(?:\.(?=[} ]))/,/^(?:\.\.)/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}\}\})/,/^(?:\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@[a-zA-Z]+)/,/^(?:true(?=[}\s]))/,/^(?:false(?=[}\s]))/,/^(?:[0-9]+(?=[}\s]))/,/^(?:[a-zA-Z0-9_$-]+(?=[=}\s\/.]))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:\s+)/,/^(?:[a-zA-Z0-9_$-/]+)/,/^(?:$)/]; 612 | lexer.conditions = {"mu":{"rules":[4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,32],"inclusive":false},"emu":{"rules":[2],"inclusive":false},"com":{"rules":[3],"inclusive":false},"par":{"rules":[30,31],"inclusive":false},"INITIAL":{"rules":[0,1,32],"inclusive":true}}; 613 | return lexer;})() 614 | parser.lexer = lexer; 615 | function Parser () { this.yy = {}; }Parser.prototype = parser;parser.Parser = Parser; 616 | return new Parser; 617 | })();; 618 | // lib/handlebars/compiler/base.js 619 | Handlebars.Parser = handlebars; 620 | 621 | Handlebars.parse = function(string) { 622 | Handlebars.Parser.yy = Handlebars.AST; 623 | return Handlebars.Parser.parse(string); 624 | }; 625 | 626 | Handlebars.print = function(ast) { 627 | return new Handlebars.PrintVisitor().accept(ast); 628 | };; 629 | // lib/handlebars/compiler/ast.js 630 | (function() { 631 | 632 | Handlebars.AST = {}; 633 | 634 | Handlebars.AST.ProgramNode = function(statements, inverse) { 635 | this.type = "program"; 636 | this.statements = statements; 637 | if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); } 638 | }; 639 | 640 | Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) { 641 | this.type = "mustache"; 642 | this.escaped = !unescaped; 643 | this.hash = hash; 644 | 645 | var id = this.id = rawParams[0]; 646 | var params = this.params = rawParams.slice(1); 647 | 648 | // a mustache is an eligible helper if: 649 | // * its id is simple (a single part, not `this` or `..`) 650 | var eligibleHelper = this.eligibleHelper = id.isSimple; 651 | 652 | // a mustache is definitely a helper if: 653 | // * it is an eligible helper, and 654 | // * it has at least one parameter or hash segment 655 | this.isHelper = eligibleHelper && (params.length || hash); 656 | 657 | // if a mustache is an eligible helper but not a definite 658 | // helper, it is ambiguous, and will be resolved in a later 659 | // pass or at runtime. 660 | }; 661 | 662 | Handlebars.AST.PartialNode = function(partialName, context) { 663 | this.type = "partial"; 664 | this.partialName = partialName; 665 | this.context = context; 666 | }; 667 | 668 | var verifyMatch = function(open, close) { 669 | if(open.original !== close.original) { 670 | throw new Handlebars.Exception(open.original + " doesn't match " + close.original); 671 | } 672 | }; 673 | 674 | Handlebars.AST.BlockNode = function(mustache, program, inverse, close) { 675 | verifyMatch(mustache.id, close); 676 | this.type = "block"; 677 | this.mustache = mustache; 678 | this.program = program; 679 | this.inverse = inverse; 680 | 681 | if (this.inverse && !this.program) { 682 | this.isInverse = true; 683 | } 684 | }; 685 | 686 | Handlebars.AST.ContentNode = function(string) { 687 | this.type = "content"; 688 | this.string = string; 689 | }; 690 | 691 | Handlebars.AST.HashNode = function(pairs) { 692 | this.type = "hash"; 693 | this.pairs = pairs; 694 | }; 695 | 696 | Handlebars.AST.IdNode = function(parts) { 697 | this.type = "ID"; 698 | this.original = parts.join("."); 699 | 700 | var dig = [], depth = 0; 701 | 702 | for(var i=0,l=parts.length; i": ">", 782 | '"': """, 783 | "'": "'", 784 | "`": "`" 785 | }; 786 | 787 | var badChars = /[&<>"'`]/g; 788 | var possible = /[&<>"'`]/; 789 | 790 | var escapeChar = function(chr) { 791 | return escape[chr] || "&"; 792 | }; 793 | 794 | Handlebars.Utils = { 795 | escapeExpression: function(string) { 796 | // don't escape SafeStrings, since they're already safe 797 | if (string instanceof Handlebars.SafeString) { 798 | return string.toString(); 799 | } else if (string == null || string === false) { 800 | return ""; 801 | } 802 | 803 | if(!possible.test(string)) { return string; } 804 | return string.replace(badChars, escapeChar); 805 | }, 806 | 807 | isEmpty: function(value) { 808 | if (!value && value !== 0) { 809 | return true; 810 | } else if(Object.prototype.toString.call(value) === "[object Array]" && value.length === 0) { 811 | return true; 812 | } else { 813 | return false; 814 | } 815 | } 816 | }; 817 | })();; 818 | // lib/handlebars/compiler/compiler.js 819 | 820 | /*jshint eqnull:true*/ 821 | Handlebars.Compiler = function() {}; 822 | Handlebars.JavaScriptCompiler = function() {}; 823 | 824 | (function(Compiler, JavaScriptCompiler) { 825 | // the foundHelper register will disambiguate helper lookup from finding a 826 | // function in a context. This is necessary for mustache compatibility, which 827 | // requires that context functions in blocks are evaluated by blockHelperMissing, 828 | // and then proceed as if the resulting value was provided to blockHelperMissing. 829 | 830 | Compiler.prototype = { 831 | compiler: Compiler, 832 | 833 | disassemble: function() { 834 | var opcodes = this.opcodes, opcode, out = [], params, param; 835 | 836 | for (var i=0, l=opcodes.length; i 0) { 1295 | this.source[1] = this.source[1] + ", " + locals.join(", "); 1296 | } 1297 | 1298 | // Generate minimizer alias mappings 1299 | if (!this.isChild) { 1300 | var aliases = []; 1301 | for (var alias in this.context.aliases) { 1302 | this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias]; 1303 | } 1304 | } 1305 | 1306 | if (this.source[1]) { 1307 | this.source[1] = "var " + this.source[1].substring(2) + ";"; 1308 | } 1309 | 1310 | // Merge children 1311 | if (!this.isChild) { 1312 | this.source[1] += '\n' + this.context.programs.join('\n') + '\n'; 1313 | } 1314 | 1315 | if (!this.environment.isSimple) { 1316 | this.source.push("return buffer;"); 1317 | } 1318 | 1319 | var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"]; 1320 | 1321 | for(var i=0, l=this.environment.depths.list.length; i this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); } 1759 | return "stack" + this.stackSlot; 1760 | }, 1761 | 1762 | popStack: function() { 1763 | var item = this.compileStack.pop(); 1764 | 1765 | if (item instanceof Literal) { 1766 | return item.value; 1767 | } else { 1768 | this.stackSlot--; 1769 | return item; 1770 | } 1771 | }, 1772 | 1773 | topStack: function() { 1774 | var item = this.compileStack[this.compileStack.length - 1]; 1775 | 1776 | if (item instanceof Literal) { 1777 | return item.value; 1778 | } else { 1779 | return item; 1780 | } 1781 | }, 1782 | 1783 | quotedString: function(str) { 1784 | return '"' + str 1785 | .replace(/\\/g, '\\\\') 1786 | .replace(/"/g, '\\"') 1787 | .replace(/\n/g, '\\n') 1788 | .replace(/\r/g, '\\r') + '"'; 1789 | }, 1790 | 1791 | setupHelper: function(paramSize, name) { 1792 | var params = []; 1793 | this.setupParams(paramSize, params); 1794 | var foundHelper = this.nameLookup('helpers', name, 'helper'); 1795 | 1796 | return { 1797 | params: params, 1798 | name: foundHelper, 1799 | callParams: ["depth0"].concat(params).join(", "), 1800 | helperMissingParams: ["depth0", this.quotedString(name)].concat(params).join(", ") 1801 | }; 1802 | }, 1803 | 1804 | // the params and contexts arguments are passed in arrays 1805 | // to fill in 1806 | setupParams: function(paramSize, params) { 1807 | var options = [], contexts = [], types = [], param, inverse, program; 1808 | 1809 | options.push("hash:" + this.popStack()); 1810 | 1811 | inverse = this.popStack(); 1812 | program = this.popStack(); 1813 | 1814 | // Avoid setting fn and inverse if neither are set. This allows 1815 | // helpers to do a check for `if (options.fn)` 1816 | if (program || inverse) { 1817 | if (!program) { 1818 | this.context.aliases.self = "this"; 1819 | program = "self.noop"; 1820 | } 1821 | 1822 | if (!inverse) { 1823 | this.context.aliases.self = "this"; 1824 | inverse = "self.noop"; 1825 | } 1826 | 1827 | options.push("inverse:" + inverse); 1828 | options.push("fn:" + program); 1829 | } 1830 | 1831 | for(var i=0; i%@ %@ left'.fmt(remaining, plural); 105 | }.property('remaining'), 106 | 107 | completed: function() { 108 | return this.filterProperty('isCompleted', true).get('length'); 109 | }.property('@each.isCompleted'), 110 | 111 | hasCompleted: function() { 112 | return this.get('completed') > 0; 113 | }.property('completed'), 114 | 115 | allAreDone: function( key, value ) { 116 | if ( value !== undefined ) { 117 | this.setEach( 'isCompleted', value ); 118 | return value; 119 | } else { 120 | return !!this.get( 'length' ) && 121 | this.everyProperty( 'isCompleted', true ); 122 | } 123 | }.property( '@each.isCompleted' ) 124 | }); 125 | 126 | Todos.TodoController = Ember.ObjectController.extend({ 127 | isEditing: false, 128 | 129 | editTodo: function() { 130 | this.set('isEditing', true); 131 | }, 132 | 133 | removeTodo: function() { 134 | var todo = this.get('model'); 135 | 136 | todo.deleteRecord(); 137 | todo.get('store').commit(); 138 | } 139 | }); 140 | 141 | // ember.js views 142 | 143 | // todo view 144 | Todos.TodoView = Ember.View.extend({ 145 | tagName: 'li', 146 | classNameBindings: ['todo.isCompleted:completed', 'isEditing:editing'], 147 | 148 | doubleClick: function(event) { 149 | this.set('isEditing', true); 150 | } 151 | }); 152 | 153 | // edit todo view 154 | Todos.EditTodoView = Ember.TextField.extend({ 155 | classNames: ['edit'], 156 | 157 | valueBinding: 'todo.title', 158 | 159 | change: function() { 160 | var value = this.get('value'); 161 | 162 | if (Ember.isEmpty(value)) { 163 | this.get('controller').removeTodo(); 164 | } 165 | }, 166 | 167 | focusOut: function() { 168 | this.set('controller.isEditing', false); 169 | }, 170 | 171 | insertNewline: function() { 172 | this.set('controller.isEditing', false); 173 | }, 174 | 175 | didInsertElement: function() { 176 | this.$().focus(); 177 | } 178 | }); 179 | -------------------------------------------------------------------------------- /templates/todo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ember.js • TodoMVC 7 | 8 | 9 | 10 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /todo.py: -------------------------------------------------------------------------------- 1 | # A demo web application in the spirit of 2 | # [TodoMVC](http://addyosmani.github.com/todomvc/) showing how to use 3 | # **RethinkDB as a backend for Bottle and Ember.js applications**. 4 | # 5 | # For details about the complete stack, installation, and running the 6 | # app see the 7 | # [README](https://github.com/rethinkdb/rethinkdb-example-bottle-ember-todo). 8 | import argparse 9 | import json 10 | import os 11 | import socket 12 | 13 | import bottle 14 | from bottle import static_file, request 15 | 16 | import rethinkdb as r 17 | from rethinkdb.errors import RqlRuntimeError, RqlDriverError 18 | 19 | #### Connection details 20 | 21 | # We will use these settings later in the code to connect to the 22 | # RethinkDB server. 23 | RDB_HOST = os.getenv('RDB_HOST', 'localhost') 24 | RDB_PORT = os.getenv('RDB_PORT', 28015) 25 | TODO_DB = os.getenv('TODO_DB', 'todoapp') 26 | 27 | #### Setting up the app database 28 | 29 | # The app will use a table `todos` in the database specified by the 30 | # `TODO_DB` variable (defaults to `todoapp`). We'll create the database and table here using 31 | # [`db_create`](http://www.rethinkdb.com/api/python/db_create/) 32 | # and 33 | # [`table_create`](http://www.rethinkdb.com/api/python/table_create/) commands. 34 | def dbSetup(): 35 | connection = r.connect(host=RDB_HOST, port=RDB_PORT) 36 | try: 37 | r.db_create(TODO_DB).run(connection) 38 | r.db(TODO_DB).table_create('todos').run(connection) 39 | print ('Database setup completed. Now run the app without --setup: ' 40 | '`python todo.py`') 41 | except RqlRuntimeError: 42 | print ('App database already exists. Run the app without --setup: ' 43 | '`python todo.py`') 44 | finally: 45 | connection.close() 46 | 47 | 48 | #### Managing connections 49 | 50 | # The pattern we're using for managing database connections is to have **a connection per request**. 51 | # We're using Bottle's `@bottle.hook('before_request')` and `@bottle.hook('after_request')` for 52 | # [opening a database connection](http://www.rethinkdb.com/api/python/connect/) and 53 | # [closing it](http://www.rethinkdb.com/api/python/close/) respectively. 54 | 55 | @bottle.hook('before_request') 56 | def before_request(): 57 | if request.path.startswith('/static/'): 58 | return 59 | try: 60 | bottle.local.rdb_connection = r.connect(RDB_HOST, RDB_PORT, 61 | TODO_DB) 62 | except RqlDriverError: 63 | bottle.abort(503, "No database connection could be established.") 64 | 65 | @bottle.hook('after_request') 66 | def after_request(): 67 | if request.path.startswith('/static/'): 68 | return 69 | bottle.local.rdb_connection.close() 70 | 71 | #### Listing existing todos 72 | 73 | # To retrieve all existing tasks, we use the 74 | # [`r.table`](http://www.rethinkdb.com/api/python/table/) 75 | # command to query the database in response to a GET request from the 76 | # browser. When `table(table_name)` isn't followed by an additional 77 | # command, it returns all documents in the table. 78 | # 79 | # Running the query returns an iterator that automatically streams 80 | # data from the server in efficient batches. 81 | @bottle.get("/todos") 82 | def get_todos(): 83 | selection = list(r.table('todos').run(bottle.local.rdb_connection)) 84 | return json.dumps({'todos': selection}) 85 | 86 | #### Creating a todo 87 | 88 | # We will create a new todo in response to a POST request to `/todos` 89 | # with a JSON payload using 90 | # [`table.insert`](http://www.rethinkdb.com/api/python/insert/). 91 | # 92 | # The `insert` operation returns a single object specifying the number 93 | # of successfully created objects and their corresponding IDs: 94 | # 95 | # ``` 96 | # { 97 | # "inserted": 1, 98 | # "errors": 0, 99 | # "generated_keys": [ 100 | # "773666ac-841a-44dc-97b7-b6f3931e9b9f" 101 | # ] 102 | # } 103 | # ``` 104 | @bottle.post("/todos") 105 | def new_todo(): 106 | todo = request.json['todo'] 107 | inserted = (r.table('todos').insert(todo) 108 | .run(bottle.local.rdb_connection)) 109 | todo['id'] = inserted['generated_keys'][0] 110 | return json.dumps({'todo': todo}) 111 | 112 | 113 | #### Retrieving a single todo 114 | 115 | # Every new task gets assigned a unique ID. The browser can retrieve 116 | # a specific task by GETing `/todos/`. To query the database 117 | # for a single document by its ID, we use the 118 | # [`get`](http://www.rethinkdb.com/api/python/get/) 119 | # command. 120 | # 121 | # Using a task's ID will prove more useful when we decide to update 122 | # it, mark it completed, or delete it. 123 | @bottle.get("/todos/") 124 | def get_todo(todo_id): 125 | todo = r.table('todos').get(todo_id).run(bottle.local.rdb_connection) 126 | return json.dumps({'todo': todo}) 127 | 128 | #### Editing/Updating a task 129 | 130 | # Updating a todo (editing it or marking it completed) is performed on 131 | # a `PUT` request. To save the updated todo we'll do a 132 | # [`replace`](http://www.rethinkdb.com/api/python/replace/). 133 | @bottle.put("/todos/") 134 | def update_todo(todo_id): 135 | todo = {'id': todo_id} 136 | todo.update(request.json['todo']) 137 | return json.dumps(r.table('todos') 138 | .get(todo_id) 139 | .replace(todo) 140 | .run(bottle.local.rdb_connection)) 141 | 142 | # If you'd like the update operation to happen as the result of a 143 | # `PATCH` request (carrying only the updated fields), you can use the 144 | # [`update`](http://www.rethinkdb.com/api/python/update/) 145 | # command, which will merge the JSON object stored in the database 146 | # with the new one. 147 | @bottle.route("/todos/", method='PATCH') 148 | def patch_todo(todo_id): 149 | return json.dumps(r.table('todos') 150 | .get(todo_id) 151 | .update(request.json['todo']) 152 | .run(bottle.local.rdb_connection)) 153 | 154 | 155 | #### Deleting a task 156 | 157 | # To delete a todo item we'll call a 158 | # [`delete`](http://www.rethinkdb.com/api/python/delete/) 159 | # command on a `DELETE /todos/` request. 160 | @bottle.delete("/todos/") 161 | def delete_todo(todo_id): 162 | return json.dumps(r.table('todos') 163 | .get(todo_id) 164 | .delete() 165 | .run(bottle.local.rdb_connection)) 166 | 167 | @bottle.get("/") 168 | def show_todos(): 169 | return static_file('todo.html', root='templates/', 170 | mimetype='text/html') 171 | 172 | @bottle.route('/static/', method='GET') 173 | @bottle.route('/static/', method='HEAD') 174 | def send_static(filename): 175 | return static_file(filename, root='static/') 176 | 177 | if __name__ == "__main__": 178 | parser = argparse.ArgumentParser(description='Run the Bottle todo app') 179 | parser.add_argument('--setup', dest='run_setup', action='store_true') 180 | 181 | args = parser.parse_args() 182 | if args.run_setup: 183 | dbSetup() 184 | else: 185 | bottle.run(host='localhost', port=5000, debug=True, reloader=True) 186 | 187 | 188 | # ### Best practices ### 189 | # 190 | # #### Managing connections: a connection per request #### 191 | # 192 | # The RethinkDB server doesn't use a thread-per-connnection approach 193 | # so opening connections per request will not slow down your database. 194 | # 195 | # #### Fetching multiple rows: batched iterators #### 196 | # 197 | # When fetching multiple rows from a table, RethinkDB returns a 198 | # batched iterator initially containing a subset of the complete 199 | # result. Once the end of the current batch is reached, a new batch is 200 | # automatically retrieved from the server. From a coding point of view 201 | # this is transparent: 202 | # 203 | # for result in r.table('todos').run(connection): 204 | # print result 205 | # 206 | # 207 | # #### `replace` vs `update` #### 208 | # 209 | # Both `replace` and `update` operations can be used to modify one or 210 | # multiple rows. Their behavior is different: 211 | # 212 | # * `replace` will completely replace the existing rows with new values 213 | # * `update` will merge existing rows with the new values 214 | 215 | 216 | # 217 | # Licensed under the MIT license: 218 | # 219 | # Copyright (c) 2012 RethinkDB 220 | # 221 | --------------------------------------------------------------------------------