├── screenshot.png ├── static ├── images │ ├── spinner.gif │ └── poweredby.png └── css │ └── main.css ├── main.py ├── models.py ├── views ├── base.mako └── list.mako ├── controllers.py └── README.asciidoc /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/srackham/bottle-mongodb-example/HEAD/screenshot.png -------------------------------------------------------------------------------- /static/images/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/srackham/bottle-mongodb-example/HEAD/static/images/spinner.gif -------------------------------------------------------------------------------- /static/images/poweredby.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/srackham/bottle-mongodb-example/HEAD/static/images/poweredby.png -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from bottle import run, debug 3 | 4 | import controllers 5 | 6 | DEBUG = True 7 | 8 | debug(DEBUG) 9 | run(host='localhost', port=8080, reloader=DEBUG) 10 | 11 | -------------------------------------------------------------------------------- /models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import datetime 3 | from mongoengine import Document, connect 4 | from mongoengine import StringField, DateTimeField, FileField 5 | 6 | DB_NAME = 'bottle_mongodb_example_mongoengine' 7 | 8 | class Message(Document): 9 | nickname = StringField(required=True) 10 | text = StringField(required=True) 11 | date = DateTimeField(required=True, default=datetime.datetime.now) 12 | image_filename = StringField() 13 | image = FileField() 14 | thumb = FileField() 15 | 16 | connect(DB_NAME) 17 | -------------------------------------------------------------------------------- /views/base.mako: -------------------------------------------------------------------------------- 1 | ## base.html 2 | 3 | 4 | 5 | 6 | ${self.attr.title} 7 | 8 | 14 | 15 | 16 |

${self.attr.title}

17 | ${self.body()} 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /static/css/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | max-width: 800px; 3 | font-family: Tahoma, Arial, sans-serif; 4 | } 5 | 6 | h1, h2 { color: green; } 7 | h1 { text-decoration: underline; } 8 | h2 { margin-bottom: 5px; } 9 | 10 | table { 11 | border: 2px solid green; 12 | background: #FFFFF8; 13 | -moz-box-shadow: 3px 3px 3px silver; 14 | -webkit-box-shadow: 3px 3px 3px silver; 15 | box-shadow: 3px 3px 3px silver; 16 | } 17 | table td { 18 | vertical-align: top; 19 | padding: 5px; 20 | } 21 | table#messages { 22 | width: 800px; 23 | border-collapse: collapse; 24 | } 25 | table#messages tr { 26 | height: 60px; 27 | } 28 | table#messages td { 29 | border: 1px solid green; 30 | } 31 | table#messages td.nickname { 32 | min-width: 120px; 33 | } 34 | table#messages td.text { 35 | min-width: 350px; 36 | } 37 | table#messages td.date { 38 | min-width: 150px; 39 | } 40 | 41 | textarea, input[type="text"], input[type="file"] { 42 | min-width: 300px; 43 | } 44 | 45 | img { 46 | border: none; 47 | } 48 | #poweredby img { 49 | width: 150px; 50 | } 51 | img#spinner { 52 | width: 15px; 53 | } 54 | 55 | #navigation { 56 | float: left; 57 | margin-top: 10px; 58 | } 59 | #navigation a { 60 | color: green; 61 | font-weight: bold; 62 | } 63 | #poweredby { 64 | float: right; 65 | margin-top: 5px; 66 | } 67 | -------------------------------------------------------------------------------- /views/list.mako: -------------------------------------------------------------------------------- 1 | ## list.html 2 | <%inherit file="base.mako"/> 3 | 4 | <%! 5 | title = 'Bottle + MongoDB Example' 6 | %> 7 | 8 | ## Start body. 9 |

New message

10 | ${self.create_form()} 11 |

Messages

12 | ${self.messages_table()} 13 | ${self.footer()} 14 | ## End body. 15 | 16 | <%def name="create_form()"> 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 32 | 33 | 34 | 35 | 39 | 40 |
Message:
Nickname:
Image: 30 | 31 |
36 | 37 | 38 |
41 |
42 | 43 | 44 | <%def name="messages_table()"> 45 | 46 | %for message in messages: 47 | 48 | 55 | 58 | 61 | 64 | 65 | %endfor 66 |
49 | %if message.image.grid_id is not None: 50 | 51 | 52 | 53 | %endif 54 | 56 | ${message.nickname} 57 | 59 | ${message.text} 60 | 62 | ${message.date.strftime('%X %d %b %y')} 63 |
67 | 68 | 69 | <%def name="footer()"> 70 | 78 |
79 | Powered by Bottle + MongoDB 80 |
81 | 82 | -------------------------------------------------------------------------------- /controllers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import mimetypes 3 | import cStringIO as StringIO 4 | 5 | from bottle import request, response, get, post 6 | from bottle import static_file, redirect, HTTPResponse 7 | from bottle import mako_view as view 8 | from PIL import Image 9 | from pymongo.objectid import ObjectId 10 | from models import Message 11 | 12 | PAGE_SIZE = 5 13 | 14 | @get(['/', '/list', '/list/:page#\d+#']) 15 | @view('list.mako') 16 | def list(page=0): 17 | ''' List messages. ''' 18 | page = int(page) 19 | prev_page = None 20 | next_page = None 21 | if page > 0: 22 | prev_page = page - 1 23 | if Message.objects.count() > (page + 1) * PAGE_SIZE: 24 | next_page = page + 1 25 | msgs = (Message.objects 26 | .order_by('-date') 27 | .skip(page * PAGE_SIZE) 28 | .limit(PAGE_SIZE)) 29 | return {'messages': msgs, 30 | 'prev_page': prev_page, 31 | 'next_page': next_page, 32 | } 33 | 34 | @post('/create') 35 | def create(): 36 | ''' Save new message. ''' 37 | if not (request.POST.get('nickname') and request.POST.get('text')): 38 | redirect('/') 39 | msg = Message() 40 | msg.nickname = request.POST['nickname'] 41 | msg.text = request.POST['text'] 42 | if 'image' in request.files: 43 | upload = request.files['image'] 44 | if not upload.filename.lower().endswith( 45 | ('.jpg', '.jpeg', '.png', '.bmp', '.gif')): 46 | redirect('/') 47 | mime = mimetypes.guess_type(upload.filename)[0] 48 | msg.image_filename = upload.filename 49 | # Save fullsize image 50 | msg.image.put(upload.file, content_type=mime) 51 | # Create and save thumbnail 52 | image = Image.open(msg.image) 53 | image.thumbnail((80, 60), Image.ANTIALIAS) 54 | data = StringIO.StringIO() 55 | image.save(data, image.format) 56 | data.seek(0) 57 | msg.thumb.put(data, content_type=mime) 58 | msg.save() 59 | redirect('/') 60 | 61 | @get('/:image_type#(image|thumb)#/:docid') 62 | def get_image(image_type, docid): 63 | ''' Send image or thumbnail from file stored in the database. ''' 64 | f = Message.objects.with_id(ObjectId(docid))[image_type] 65 | response.content_type = f.content_type 66 | return HTTPResponse(f) 67 | 68 | @get('/static/:filename#.+#') 69 | def get_static_file(filename): 70 | ''' Send static files from ./static folder. ''' 71 | return static_file(filename, root='./static') 72 | -------------------------------------------------------------------------------- /README.asciidoc: -------------------------------------------------------------------------------- 1 | // Use this source for both GitHub README and blogpost. 2 | :blogpost-title: Bottle + MongoDB Example 3 | :blogpost-status: published 4 | :blogpost-doctype: article 5 | :blogpost-posttype: post 6 | :blogpost-categories: Bottle, MongoDB, Python 7 | 8 | = {blogpost-title} 9 | 10 | ifdef::blogpost[] 11 | *Published*: 2012-03-18 12 | endif::blogpost[] 13 | 14 | A didactic Web application written in Python to illustrate how to use 15 | the http://bottlepy.org/[Bottle] web-framework with the 16 | http://www.mongodb.org[MongoDB] database. 17 | 18 | It's a port I made of Mike Dirolf's 19 | https://github.com/mdirolf/djanMon[DjanMon] application (how to use 20 | Django with MongoDB). 21 | 22 | ifdef::blogpost[] 23 | // Wordpress processing instruction. 24 | pass::[] 25 | endif::blogpost[] 26 | 27 | http://bottlepy.org/[Bottle] is a wonderful micro web-framework, it's 28 | beautifully written and documented and has that ``just right'' feel 29 | about it. Bottle has support for multiple webservers and template 30 | engines -- I'm really enjoying it after working with Django. 31 | 32 | http://www.mongodb.org[MongoDB] is a schemaless document-oriented 33 | database designed for the Web. For me, working with MongoDB has been 34 | a revelation. Database creation, administration and migration (long 35 | the bane of database developers) is trivial in comparison with 36 | traditional SQL databases. Document storage management is baked into 37 | MongoDB and is used in many web applications (document storage has 38 | always been awkward and slow with relational databases). 39 | 40 | ifndef::blogpost[] 41 | This document is also published as a http://srackham.wordpress.com/2011/03/17/bottle-mongodb-example/[blogpost]. 42 | 43 | Here's a https://github.com/srackham/bottle-mongodb-example/blob/master/screenshot.png[screenshot]. 44 | endif::blogpost[] 45 | 46 | ifdef::blogpost[] 47 | You can find the source on https://github.com/srackham/bottle-mongodb-example[GitHub]. 48 | 49 | Here's a screenshot: 50 | 51 | image::screenshot.png[] 52 | endif::blogpost[] 53 | 54 | To run the application install the <> then: 55 | 56 | git clone https://github.com/srackham/bottle-mongodb-example.git 57 | cd bottle-mongodb-example 58 | python bottle-mongodb-example.py 59 | 60 | The application creates and displays a paged list of user 61 | created messages which are stored in a MongoDB database along with 62 | uploaded images (no file system management required it's all in the 63 | database) -- not bad for 93 lines of Python code and a 79 line 64 | HTML template. The really neat thing is that you don't have to create 65 | the database, tables or schema -- it all happens automatically. 66 | 67 | 68 | [[X1]] 69 | == Prerequisites 70 | - Python. 71 | - MongoDB (see the http://www.mongodb.org/[MongoDB] website for 72 | install instructions). 73 | - PyMongo, the Python MongoDB driver: 74 | 75 | sudo easy_install pymongo 76 | 77 | - The Bottle web-framework: 78 | 79 | git clone https://github.com/defnull/bottle.git 80 | cd bottle 81 | python setup.py install 82 | 83 | I'm running in this environment -- it works for me but your mileage 84 | may vary: 85 | 86 | - Xubuntu 10.04 87 | - Bottle 0.9dev 88 | - Python 2.6.5 89 | - MongoDB 1.6.5 90 | - PyMongo 1.9 91 | --------------------------------------------------------------------------------