-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Flask-python and RethinkDB based recipes app
- Loading branch information
0 parents
commit 8e5f99d
Showing
7 changed files
with
434 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
*.pyc | ||
.vagrant |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# -*- mode: ruby -*- | ||
# vi: set ft=ruby : | ||
|
||
# Vagrantfile API/syntax version. Don't touch unless you know what you're doing! | ||
VAGRANTFILE_API_VERSION = "2" | ||
|
||
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| | ||
config.vm.box = "hashicorp/precise64" | ||
|
||
dirname = File.basename(Dir.getwd) | ||
config.vm.hostname = dirname | ||
|
||
config.vm.network "forwarded_port", guest: 5000, host: 5005 | ||
config.vm.network "forwarded_port", guest: 8080, host: 8085 | ||
|
||
$script = <<SCRIPT | ||
echo "Provisioning Flask, RethinkDB, Python imaging libraries" | ||
sudo apt-get update | ||
sudo apt-get -y install python avahi-daemon python-pip git curl upstart python-dev build-essential libjpeg-dev libfreetype6-dev zlib1g-dev libpng12-dev | ||
sudo ln -s /usr/lib/x86_64-linux-gnu/libjpeg.so /usr/lib | ||
sudo ln -s /usr/lib/x86_64-linux-gnu/libfreetype.so /usr/lib | ||
sudo ln -s /usr/lib/x86_64-linux-gnu/libz.so /usr/lib | ||
source /etc/lsb-release && echo "deb http://download.rethinkdb.com/apt $DISTRIB_CODENAME main" | sudo tee /etc/apt/sources.list.d/rethinkdb.list | ||
wget -qO- http://download.rethinkdb.com/apt/pubkey.gpg | sudo apt-key add - | ||
sudo apt-get update | ||
sudo apt-get -y install rethinkdb | ||
sudo pip install PIL --upgrade | ||
sudo pip install -r /vagrant/requirements.txt | ||
sudo initctl emit vagrant-ready | ||
SCRIPT | ||
|
||
config.vm.provision "shell", inline: $script | ||
|
||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
import requests |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
from collections import namedtuple | ||
from math import sqrt | ||
import random | ||
try: | ||
import Image | ||
except ImportError: | ||
from PIL import Image | ||
import json | ||
import colorsys | ||
import colorific | ||
import urllib2 | ||
import urllib, cStringIO | ||
import re | ||
import translitcodec | ||
|
||
|
||
|
||
def get_gimages(query): | ||
answer = [] | ||
for i in range(0,8,4): | ||
url = 'https://ajax.googleapis.com/ajax/services/search/images?v=1.0&q=%s&start=%d&imgsz=large&imgtype=photo' % (query,i) | ||
r = urllib2.urlopen(url) | ||
for k in json.loads(r.read())['responseData']['results']: | ||
answer.append(k['tbUrl']) | ||
return answer | ||
|
||
def chunks(l, n): | ||
""" Yield successive n-sized chunks from l. | ||
""" | ||
for i in xrange(0, len(l), n): | ||
yield l[i:i+n] | ||
|
||
## Slug functions ## | ||
|
||
_punct_re = re.compile(r'[\t !"#$%&\()*\-/<=>?@\[\\\]^_`{|},.]+') | ||
|
||
def slugify(text, delim=u'-'): | ||
"""Generates an ASCII-only slug.""" | ||
result = [] | ||
for word in _punct_re.split(text.lower()): | ||
word = word.replace('\'', '') #Don't replace apostrophes with a dash, just strip them | ||
word = word.encode('translit/long') | ||
if word: | ||
result.append(word) | ||
return unicode(delim.join(result)) | ||
|
||
from flask.ext.wtf import Form | ||
from wtforms.fields import TextField | ||
from wtforms.validators import Required | ||
from wtforms.fields import StringField | ||
from wtforms.widgets import TextArea | ||
|
||
class RecipeForm(Form): | ||
title = TextField('title', validators = [Required()]) | ||
ingredients = StringField(u'Ingredients', widget=TextArea(),validators = [Required()]) | ||
directions = StringField(u'directions', widget=TextArea(), validators = [Required()]) | ||
|
||
|
||
from flask import * | ||
app = Flask(__name__) | ||
app.secret_key = 'correcthorsebatterystaples' | ||
|
||
# rethink imports | ||
import rethinkdb as r | ||
from rethinkdb.errors import RqlRuntimeError, RqlDriverError | ||
|
||
# rethink config | ||
RDB_HOST = 'localhost' | ||
RDB_PORT = 28015 | ||
TODO_DB = 'recipes' | ||
|
||
# db setup; only run once | ||
def dbSetup(): | ||
connection = r.connect(host=RDB_HOST, port=RDB_PORT) | ||
try: | ||
r.db_create(TODO_DB).run(connection) | ||
r.db(TODO_DB).table_create('recipes').run(connection) | ||
print 'Database setup completed' | ||
except RqlRuntimeError: | ||
print 'Database already exists.' | ||
finally: | ||
connection.close() | ||
dbSetup() | ||
|
||
# open connection before each request | ||
@app.before_request | ||
def before_request(): | ||
try: | ||
g.rdb_conn = r.connect(host=RDB_HOST, port=RDB_PORT, db=TODO_DB) | ||
except RqlDriverError: | ||
abort(503, "Database connection could be established.") | ||
|
||
# close the connection after each request | ||
@app.teardown_request | ||
def teardown_request(exception): | ||
try: | ||
g.rdb_conn.close() | ||
except AttributeError: | ||
pass | ||
|
||
|
||
@app.route('/favicon.ico') | ||
def favicon(): | ||
abort(404) | ||
|
||
@app.route('/add', methods = ['GET', 'POST']) | ||
def add(): | ||
form = RecipeForm() | ||
#form.ingredients.data = "1c ingredient\nenter ingredients here " | ||
|
||
if request.method == 'POST' and form.validate(): | ||
|
||
lightness = [] | ||
rainbow = [] | ||
resp=[] | ||
urls = get_gimages("+".join(form.title.data.split())) | ||
for url in urls: | ||
i = cStringIO.StringIO(urllib.urlopen(url).read()) | ||
quiche = colorific.extract_colors(i, max_colors=5) | ||
resp.extend([each.value for each in quiche.colors]) | ||
resp = [(m,sqrt(0.299 * m[0]**2 + 0.587 * m[1]**2 + 0.114 * m[2]**2)) for m in resp] | ||
lightness = sorted(resp,key=lambda x: x[1]) | ||
lightness = [i[0] for i in lightness] | ||
lightness.sort(key=lambda tup: colorsys.rgb_to_hsv(tup[0],tup[1],tup[2])[2]) | ||
for each in chunks(lightness,10): | ||
avg = tuple(map(lambda y: sum(y) / len(y), zip(*each))) | ||
rainbow.append(avg) | ||
|
||
slug = slugify(form.title.data) | ||
|
||
recipe = { 'title': form.title.data, 'ingredients': [{'amount': " ".join(ingredient.split()[0:2]), 'what': " ".join(ingredient.split()[2:])} for ingredient in form.ingredients.data.split('\r\n')], 'directions': form.directions.data.split('\r\n'), 'urls': urls, 'slug': slug, 'avgcolors': [list(i) for i in rainbow]} | ||
|
||
|
||
|
||
recipe['ingredients'] = [i for i in recipe['ingredients'] if not i['what']==''] | ||
recipe['directions'] = [i for i in recipe['directions'] if not i==''] | ||
|
||
r.table('recipes').insert(recipe).run(g.rdb_conn) | ||
|
||
return redirect(url_for('index', query=slug)) | ||
|
||
|
||
#r.table('recipes').get('de670bed-791d-41d0-97cb-1ceaf9ba54ac').update({'time_updated': r.now(), 'slug': "broccoli-cheese-soup", 'urls': urls, 'avgcolors': [list(i) for i in rainbow]}).run(g.rdb_conn) | ||
|
||
return render_template("add.html", form=form) | ||
|
||
|
||
@app.route('/<query>') | ||
def index(query): | ||
recipe = list(r.table('recipes').filter({'slug': query}).run(g.rdb_conn)) | ||
|
||
if recipe: | ||
recipe = recipe[0] | ||
else: | ||
abort(404) | ||
|
||
steps = [] | ||
for i in recipe['directions']: | ||
for j in [k['what'].split(",")[0] for k in recipe['ingredients']]: | ||
i = i.replace(j, ("<kbd>" + j + "</kbd>")) | ||
steps.append(Markup(i)) | ||
|
||
rainbow = [tuple(l) for l in recipe['avgcolors']] | ||
|
||
return render_template("index.html", urls = recipe['urls'], avg=rainbow, ingredients = recipe['ingredients'], steps = steps, title=recipe['title']) | ||
|
||
|
||
if __name__ == '__main__': | ||
app.run(debug = True, host='0.0.0.0') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
flask | ||
rethinkdb | ||
colorific | ||
translitcodec | ||
flask-wtf |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<meta charset="utf-8"> | ||
<meta http-equiv="X-UA-Compatible" content="IE=edge"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1"> | ||
<title>Add a Recipe</title> | ||
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.js"></script> | ||
<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css"></style> | ||
<script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script> | ||
|
||
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css"> | ||
|
||
|
||
<link rel="stylesheet" type="text/css" href="http://fonts.googleapis.com/css?family=Ubuntu"> | ||
|
||
<style> | ||
body { | ||
font-family: 'Ubuntu', serif; | ||
|
||
} | ||
textarea { | ||
-webkit-box-sizing: border-box; | ||
-moz-box-sizing: border-box; | ||
box-sizing: border-box; | ||
width: 100%; | ||
height:100%; | ||
} | ||
</style> | ||
</head> | ||
<body> | ||
<div class="container"> | ||
|
||
|
||
<form action="" role="form" method="POST" name="add_task"> | ||
{{form.hidden_tag()}} | ||
|
||
<div class="row"> | ||
<div class="col-md-12"> | ||
<h1>Add a Recipe</h1> | ||
|
||
<div class="panel panel-primary"> | ||
<div class="panel-heading"> | ||
<h3 class="panel-title"></h3>Title | ||
</div> | ||
<div class="panel-body"> | ||
{{form.title}} | ||
</div> | ||
</div> | ||
</div> | ||
|
||
</div> | ||
<div class="row"> | ||
|
||
<div class="col-md-3"> | ||
<div class="panel panel-primary"> | ||
<div class="panel-heading"> | ||
<h3 class="panel-title"></h3>Ingredients | ||
</div> | ||
<div class="panel-body" > | ||
{{ form.ingredients(rows="10") }} | ||
</div> | ||
</div> | ||
</div> | ||
<div class="col-md-9"> | ||
<div class="panel panel-primary"> | ||
<div class="panel-heading"> | ||
<h3 class="panel-title"></h3> Directions </h3> | ||
</div> | ||
<div class="panel-body"> | ||
{{ form.directions(rows="10") }} | ||
</div> | ||
</div> | ||
</div> | ||
|
||
<input type="submit" value="Add Recipe" class="btn btn-default btn-md"> | ||
{% for error in form.errors.label %} | ||
<span style="color: red;">{{ error }}</span> | ||
{% endfor %} | ||
</div> | ||
|
||
</form> | ||
</div> | ||
|
||
|
||
|
||
</body> | ||
</html> |
Oops, something went wrong.