Update UI
This commit is contained in:
parent
1170bb39c0
commit
746f683cab
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,6 +1,8 @@
|
|||||||
*.swp
|
*.swp
|
||||||
|
*.pyc
|
||||||
package-lock.json
|
package-lock.json
|
||||||
yarn.lock
|
yarn.lock
|
||||||
|
*.db
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
node_modules/
|
node_modules/
|
||||||
|
@ -11,9 +11,6 @@ BACKENDS = ['750g', 'allrecipes', 'cuisineaz', 'marmiton', 'supertoinette']
|
|||||||
|
|
||||||
|
|
||||||
def add_recipe(url, modules_path=None):
|
def add_recipe(url, modules_path=None):
|
||||||
db.database.connect()
|
|
||||||
db.database.create_tables([db.Recipe])
|
|
||||||
|
|
||||||
webnip = WebNip(modules_path=modules_path)
|
webnip = WebNip(modules_path=modules_path)
|
||||||
|
|
||||||
backends = [
|
backends = [
|
||||||
@ -25,9 +22,20 @@ def add_recipe(url, modules_path=None):
|
|||||||
for module in BACKENDS
|
for module in BACKENDS
|
||||||
]
|
]
|
||||||
|
|
||||||
|
recipe = None
|
||||||
for backend in backends:
|
for backend in backends:
|
||||||
browser = backend.browser
|
browser = backend.browser
|
||||||
if url.startswith(browser.BASEURL):
|
if url.startswith(browser.BASEURL):
|
||||||
browser.location(url)
|
browser.location(url)
|
||||||
db.Recipe.from_weboob(browser.page.get_recipe()).save()
|
recipe = db.Recipe.from_weboob(browser.page.get_recipe())
|
||||||
|
# Ensure URL is set
|
||||||
|
recipe.url = url
|
||||||
break
|
break
|
||||||
|
|
||||||
|
if not recipe:
|
||||||
|
# TODO
|
||||||
|
recipe = db.Recipe()
|
||||||
|
recipe.url = url
|
||||||
|
|
||||||
|
recipe.save()
|
||||||
|
return recipe
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
|
import peewee
|
||||||
|
|
||||||
|
from cuizin import db
|
||||||
from cuizin import web
|
from cuizin import web
|
||||||
|
|
||||||
|
|
||||||
@ -9,4 +12,10 @@ if __name__ == '__main__':
|
|||||||
HOST = os.environ.get('CUIZIN_HOST', 'localhost')
|
HOST = os.environ.get('CUIZIN_HOST', 'localhost')
|
||||||
PORT = os.environ.get('CUIZIN_PORT', '8080')
|
PORT = os.environ.get('CUIZIN_PORT', '8080')
|
||||||
DEBUG = os.environ.get('CUIZIN_DEBUG', False)
|
DEBUG = os.environ.get('CUIZIN_DEBUG', False)
|
||||||
|
|
||||||
|
try:
|
||||||
|
db.database.create_tables([db.Recipe])
|
||||||
|
except peewee.OperationalError:
|
||||||
|
pass
|
||||||
|
|
||||||
app.run(host=HOST, port=PORT, debug=DEBUG)
|
app.run(host=HOST, port=PORT, debug=DEBUG)
|
||||||
|
17
cuizin/db.py
17
cuizin/db.py
@ -1,4 +1,5 @@
|
|||||||
import base64
|
import base64
|
||||||
|
import mimetypes
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from peewee import (
|
from peewee import (
|
||||||
@ -8,11 +9,13 @@ from peewee import (
|
|||||||
from playhouse.shortcuts import model_to_dict
|
from playhouse.shortcuts import model_to_dict
|
||||||
|
|
||||||
|
|
||||||
database = SqliteDatabase('recipes.db')
|
database = SqliteDatabase('recipes.db', threadlocals=True)
|
||||||
|
database.connect()
|
||||||
|
|
||||||
|
|
||||||
class Recipe(Model):
|
class Recipe(Model):
|
||||||
title = CharField()
|
title = CharField()
|
||||||
|
url = CharField(null=True, unique=True)
|
||||||
author = CharField(null=True)
|
author = CharField(null=True)
|
||||||
picture = BlobField(null=True)
|
picture = BlobField(null=True)
|
||||||
short_description = TextField(null=True)
|
short_description = TextField(null=True)
|
||||||
@ -29,7 +32,7 @@ class Recipe(Model):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def from_weboob(obj):
|
def from_weboob(obj):
|
||||||
recipe = Recipe()
|
recipe = Recipe()
|
||||||
for field in ['title', 'author', 'picture_url', 'short_description',
|
for field in ['title', 'url', 'author', 'picture_url', 'short_description',
|
||||||
'preparation_time', 'cooking_time', 'instructions']:
|
'preparation_time', 'cooking_time', 'instructions']:
|
||||||
value = getattr(obj, field)
|
value = getattr(obj, field)
|
||||||
if value:
|
if value:
|
||||||
@ -39,7 +42,11 @@ class Recipe(Model):
|
|||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
serialized = model_to_dict(self)
|
serialized = model_to_dict(self)
|
||||||
serialized['picture'] = base64.b64encode(
|
prepend_info = (
|
||||||
serialized['picture']
|
'data:%s;base64' % mimetypes.guess_type(serialized['picture'])[0]
|
||||||
).decode('utf-8')
|
)
|
||||||
|
serialized['picture'] = '%s,%s' % (
|
||||||
|
prepend_info,
|
||||||
|
base64.b64encode(serialized['picture']).decode('utf-8')
|
||||||
|
)
|
||||||
return serialized
|
return serialized
|
||||||
|
@ -1,91 +1,31 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-app>
|
<v-app>
|
||||||
<v-navigation-drawer
|
<v-toolbar app>
|
||||||
persistent
|
<v-toolbar-title class="toolbar-title">
|
||||||
:mini-variant="miniVariant"
|
<router-link to="/">Cuizin</router-link>
|
||||||
:clipped="clipped"
|
</v-toolbar-title>
|
||||||
v-model="drawer"
|
|
||||||
enable-resize-watcher
|
|
||||||
fixed
|
|
||||||
app
|
|
||||||
>
|
|
||||||
<v-list>
|
|
||||||
<v-list-tile
|
|
||||||
value="true"
|
|
||||||
v-for="(item, i) in items"
|
|
||||||
:key="i"
|
|
||||||
>
|
|
||||||
<v-list-tile-action>
|
|
||||||
<v-icon v-html="item.icon"></v-icon>
|
|
||||||
</v-list-tile-action>
|
|
||||||
<v-list-tile-content>
|
|
||||||
<v-list-tile-title v-text="item.title"></v-list-tile-title>
|
|
||||||
</v-list-tile-content>
|
|
||||||
</v-list-tile>
|
|
||||||
</v-list>
|
|
||||||
</v-navigation-drawer>
|
|
||||||
<v-toolbar
|
|
||||||
app
|
|
||||||
:clipped-left="clipped"
|
|
||||||
>
|
|
||||||
<v-toolbar-side-icon @click.stop="drawer = !drawer"></v-toolbar-side-icon>
|
|
||||||
<v-btn icon @click.stop="miniVariant = !miniVariant">
|
|
||||||
<v-icon v-html="miniVariant ? 'chevron_right' : 'chevron_left'"></v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn icon @click.stop="clipped = !clipped">
|
|
||||||
<v-icon>web</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn icon @click.stop="fixed = !fixed">
|
|
||||||
<v-icon>remove</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-toolbar-title v-text="title"></v-toolbar-title>
|
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<v-btn icon @click.stop="rightDrawer = !rightDrawer">
|
<router-link to="/new">
|
||||||
<v-icon>menu</v-icon>
|
<v-btn icon>
|
||||||
</v-btn>
|
<v-icon>add</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</router-link>
|
||||||
</v-toolbar>
|
</v-toolbar>
|
||||||
<v-content>
|
<v-content>
|
||||||
<router-view/>
|
<router-view/>
|
||||||
</v-content>
|
</v-content>
|
||||||
<v-navigation-drawer
|
|
||||||
temporary
|
|
||||||
:right="right"
|
|
||||||
v-model="rightDrawer"
|
|
||||||
fixed
|
|
||||||
app
|
|
||||||
>
|
|
||||||
<v-list>
|
|
||||||
<v-list-tile @click="right = !right">
|
|
||||||
<v-list-tile-action>
|
|
||||||
<v-icon>compare_arrows</v-icon>
|
|
||||||
</v-list-tile-action>
|
|
||||||
<v-list-tile-title>Switch drawer (click me)</v-list-tile-title>
|
|
||||||
</v-list-tile>
|
|
||||||
</v-list>
|
|
||||||
</v-navigation-drawer>
|
|
||||||
<v-footer :fixed="fixed" app>
|
|
||||||
<span>© 2017</span>
|
|
||||||
</v-footer>
|
|
||||||
</v-app>
|
</v-app>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
clipped: false,
|
|
||||||
drawer: true,
|
|
||||||
fixed: false,
|
|
||||||
items: [{
|
|
||||||
icon: 'bubble_chart',
|
|
||||||
title: 'Inspire',
|
|
||||||
}],
|
|
||||||
miniVariant: false,
|
|
||||||
right: true,
|
|
||||||
rightDrawer: false,
|
|
||||||
title: 'Vuetify.js',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
name: 'App',
|
name: 'App',
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.toolbar-title a {
|
||||||
|
color: rgba(0,0,0,.87);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
<template>
|
|
||||||
<v-container fluid>
|
|
||||||
<v-slide-y-transition mode="out-in">
|
|
||||||
<v-layout column align-center>
|
|
||||||
<img src="@/assets/logo.png" alt="Vuetify.js" class="mb-5">
|
|
||||||
<blockquote>
|
|
||||||
“First, solve the problem. Then, write the code.”
|
|
||||||
<footer>
|
|
||||||
<small>
|
|
||||||
<em>—John Johnson</em>
|
|
||||||
</small>
|
|
||||||
</footer>
|
|
||||||
</blockquote>
|
|
||||||
</v-layout>
|
|
||||||
</v-slide-y-transition>
|
|
||||||
</v-container>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
|
||||||
<style scoped>
|
|
||||||
h1, h2 {
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
ul {
|
|
||||||
list-style-type: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
li {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0 10px;
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
color: #42b983;
|
|
||||||
}
|
|
||||||
</style>
|
|
61
cuizin/js_src/components/Home.vue
Normal file
61
cuizin/js_src/components/Home.vue
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<template>
|
||||||
|
<v-container fluid grid-list-md>
|
||||||
|
<v-layout row wrap>
|
||||||
|
<v-flex
|
||||||
|
v-for="recipe in recipes"
|
||||||
|
:key="recipe.title"
|
||||||
|
xs3
|
||||||
|
>
|
||||||
|
<v-card :to="{name: 'Recipe', params: { recipeId: recipe.id }}">
|
||||||
|
<v-card-media :src="recipe.picture" height="200px"></v-card-media>
|
||||||
|
<v-card-title primary-title>
|
||||||
|
<div>
|
||||||
|
<h3 class="headline mb-0">{{ recipe.title }}</h3>
|
||||||
|
</div>
|
||||||
|
</v-card-title>
|
||||||
|
<p>{{ recipe.short_description }}</p>
|
||||||
|
<v-layout row text-xs-center>
|
||||||
|
<v-flex xs6>
|
||||||
|
<p><v-icon>timelapse</v-icon> {{ recipe.preparation_time }} mins</p>
|
||||||
|
</v-flex>
|
||||||
|
<v-flex xs6>
|
||||||
|
<p><v-icon>whatshot</v-icon> {{ recipe.cooking_time }} mins</p>
|
||||||
|
</v-flex>
|
||||||
|
</v-layout>
|
||||||
|
</v-card>
|
||||||
|
</v-flex>
|
||||||
|
</v-layout>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import * as constants from '@/constants';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isLoading: false,
|
||||||
|
recipes: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fetchRecipes();
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
// call again the method if the route changes
|
||||||
|
$route: 'fetchRecipes',
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetchRecipes() {
|
||||||
|
this.isLoading = true;
|
||||||
|
|
||||||
|
fetch(`${constants.API_URL}/api/v1/recipes`)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then((response) => {
|
||||||
|
this.recipes = response.recipes;
|
||||||
|
this.isLoading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
144
cuizin/js_src/components/New.vue
Normal file
144
cuizin/js_src/components/New.vue
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
<template>
|
||||||
|
<v-container text-xs-center>
|
||||||
|
<v-layout row wrap>
|
||||||
|
<v-flex xs12>
|
||||||
|
<h2>Import from URL</h2>
|
||||||
|
<v-form v-model="validImport">
|
||||||
|
<v-text-field
|
||||||
|
label="URL"
|
||||||
|
v-model="url"
|
||||||
|
required
|
||||||
|
:rules="urlRules"
|
||||||
|
></v-text-field>
|
||||||
|
<v-btn
|
||||||
|
@click="submitImport"
|
||||||
|
:disabled="!validImport || disabledImport"
|
||||||
|
>
|
||||||
|
Import
|
||||||
|
</v-btn>
|
||||||
|
</v-form>
|
||||||
|
</v-flex>
|
||||||
|
</v-layout>
|
||||||
|
<v-layout row wrap mt-5>
|
||||||
|
<v-flex xs12>
|
||||||
|
<h2>Add manually</h2>
|
||||||
|
<v-form v-model="validAdd">
|
||||||
|
<v-text-field
|
||||||
|
label="Title"
|
||||||
|
v-model="title"
|
||||||
|
required
|
||||||
|
></v-text-field>
|
||||||
|
<v-text-field
|
||||||
|
label="Picture"
|
||||||
|
v-model="picture"
|
||||||
|
></v-text-field>
|
||||||
|
<v-text-field
|
||||||
|
label="Short description"
|
||||||
|
v-model="short_description"
|
||||||
|
textarea
|
||||||
|
></v-text-field>
|
||||||
|
<v-layout row>
|
||||||
|
<v-flex xs4 mr-3>
|
||||||
|
<v-text-field
|
||||||
|
label="Number of persons"
|
||||||
|
v-model="nb_person"
|
||||||
|
type="number"
|
||||||
|
></v-text-field>
|
||||||
|
</v-flex>
|
||||||
|
<v-flex xs4 mx-3>
|
||||||
|
<v-text-field
|
||||||
|
label="Preparation time"
|
||||||
|
v-model="preparation_time"
|
||||||
|
type="number"
|
||||||
|
suffix="mins"
|
||||||
|
></v-text-field>
|
||||||
|
</v-flex>
|
||||||
|
<v-flex xs4 ml-3>
|
||||||
|
<v-text-field
|
||||||
|
label="Cooking time"
|
||||||
|
v-model="cooking_time"
|
||||||
|
type="number"
|
||||||
|
suffix="mins"
|
||||||
|
></v-text-field>
|
||||||
|
</v-flex>
|
||||||
|
</v-layout>
|
||||||
|
<v-text-field
|
||||||
|
label="Ingredients"
|
||||||
|
v-model="ingredients"
|
||||||
|
textarea
|
||||||
|
></v-text-field>
|
||||||
|
<v-text-field
|
||||||
|
label="Instructions"
|
||||||
|
v-model="instructions"
|
||||||
|
textarea
|
||||||
|
required
|
||||||
|
></v-text-field>
|
||||||
|
|
||||||
|
<v-btn
|
||||||
|
@click="submitAdd"
|
||||||
|
:disabled="!validAdd"
|
||||||
|
>
|
||||||
|
Add
|
||||||
|
</v-btn>
|
||||||
|
</v-form>
|
||||||
|
</v-flex>
|
||||||
|
</v-layout>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import * as constants from '@/constants';
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
url: null,
|
||||||
|
validImport: false,
|
||||||
|
disabledImport: false,
|
||||||
|
validAdd: false,
|
||||||
|
title: null,
|
||||||
|
picture: null,
|
||||||
|
short_description: null,
|
||||||
|
nb_person: null,
|
||||||
|
preparation_time: null,
|
||||||
|
cooking_time: null,
|
||||||
|
ingredients: null,
|
||||||
|
instructions: null,
|
||||||
|
urlRules: [
|
||||||
|
v => !!v || 'URL is required',
|
||||||
|
(v) => {
|
||||||
|
try {
|
||||||
|
new URL(v); // eslint-disable-line no-new
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return 'URL must be valid';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
submitImport() {
|
||||||
|
this.disabledImport = true;
|
||||||
|
fetch(`${constants.API_URL}/api/v1/recipes`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
url: this.url,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(response => response.recipes[0])
|
||||||
|
.then(recipe => this.$router.push({
|
||||||
|
name: 'Recipe',
|
||||||
|
params: {
|
||||||
|
recipeId: recipe.id,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
submitAdd() {
|
||||||
|
// TODO
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
45
cuizin/js_src/components/Recipe.vue
Normal file
45
cuizin/js_src/components/Recipe.vue
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<template>
|
||||||
|
<v-container grid-list-md>
|
||||||
|
<v-layout row wrap>
|
||||||
|
<v-flex xs12 v-if="this.recipe">
|
||||||
|
<h1>{{ this.recipe.title }}</h1>
|
||||||
|
<p><img :src="this.recipe.picture" height="300px" /></p>
|
||||||
|
<p>{{ this.recipe.short_description }}</p>
|
||||||
|
<p>{{ this.recipe.ingredients }}</p>
|
||||||
|
<p>{{ this.recipe.instructions }}</p>
|
||||||
|
</v-flex>
|
||||||
|
</v-layout>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import * as constants from '@/constants';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isLoading: false,
|
||||||
|
recipe: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fetchRecipe();
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
// call again the method if the route changes
|
||||||
|
$route: 'fetchRecipe',
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetchRecipe() {
|
||||||
|
this.isLoading = true;
|
||||||
|
|
||||||
|
fetch(`${constants.API_URL}/api/v1/recipe/${this.$route.params.recipeId}`)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then((response) => {
|
||||||
|
this.recipe = response.recipes[0];
|
||||||
|
this.isLoading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
2
cuizin/js_src/constants.js
Normal file
2
cuizin/js_src/constants.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export const API_URL = 'http://localhost:8080';
|
||||||
|
export const FOOBAR = 'TODO'; // TODO
|
@ -7,6 +7,10 @@ import 'vuetify/dist/vuetify.min.css';
|
|||||||
import App from './App';
|
import App from './App';
|
||||||
import router from './router';
|
import router from './router';
|
||||||
|
|
||||||
|
// Isomorphic fetch
|
||||||
|
require('es6-promise').polyfill();
|
||||||
|
require('isomorphic-fetch');
|
||||||
|
|
||||||
Vue.use(Vuetify);
|
Vue.use(Vuetify);
|
||||||
|
|
||||||
Vue.config.productionTip = false;
|
Vue.config.productionTip = false;
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import Router from 'vue-router';
|
import Router from 'vue-router';
|
||||||
import HelloWorld from '@/components/HelloWorld';
|
import Home from '@/components/Home';
|
||||||
|
import New from '@/components/New';
|
||||||
|
import Recipe from '@/components/Recipe';
|
||||||
|
|
||||||
Vue.use(Router);
|
Vue.use(Router);
|
||||||
|
|
||||||
@ -8,8 +10,18 @@ export default new Router({
|
|||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
name: 'HelloWorld',
|
name: 'Home',
|
||||||
component: HelloWorld,
|
component: Home,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/new',
|
||||||
|
name: 'New',
|
||||||
|
component: New,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/recipe/:recipeId',
|
||||||
|
name: 'Recipe',
|
||||||
|
component: Recipe,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
@ -1,20 +1,43 @@
|
|||||||
import bottle
|
import json
|
||||||
|
|
||||||
|
import bottle
|
||||||
|
import peewee
|
||||||
|
|
||||||
|
from cuizin import add_recipe
|
||||||
from cuizin import db
|
from cuizin import db
|
||||||
|
|
||||||
|
|
||||||
bottle.Bottle()
|
app = bottle.Bottle()
|
||||||
|
|
||||||
|
@app.hook('after_request')
|
||||||
|
def enable_cors():
|
||||||
|
"""
|
||||||
|
Add CORS headers at each request.
|
||||||
|
"""
|
||||||
|
# The str() call is required as we import unicode_literal and WSGI
|
||||||
|
# headers list should have plain str type.
|
||||||
|
bottle.response.headers[str('Access-Control-Allow-Origin')] = str('*')
|
||||||
|
bottle.response.headers[str('Access-Control-Allow-Methods')] = str(
|
||||||
|
'PUT, GET, POST, DELETE, OPTIONS, PATCH'
|
||||||
|
)
|
||||||
|
bottle.response.headers[str('Access-Control-Allow-Headers')] = str(
|
||||||
|
'Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.get('/api/v1')
|
@app.route('/api/v1', ['GET', 'OPTIONS'])
|
||||||
def api_v1_index():
|
def api_v1_index():
|
||||||
return {
|
return {
|
||||||
'recipes': '/api/v1/recipes'
|
'recipes': '/api/v1/recipes'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@app.get('/api/v1/recipes')
|
@app.route('/api/v1/recipes', ['GET', 'OPTIONS'])
|
||||||
def api_v1_recipes():
|
def api_v1_recipes():
|
||||||
|
# CORS
|
||||||
|
if bottle.request.method == 'OPTIONS':
|
||||||
|
return ''
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'recipes': [
|
'recipes': [
|
||||||
recipe.to_dict() for recipe in db.Recipe.select()
|
recipe.to_dict() for recipe in db.Recipe.select()
|
||||||
@ -22,8 +45,33 @@ def api_v1_recipes():
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@app.get('/api/v1/recipe/:id')
|
@app.post('/api/v1/recipes')
|
||||||
|
def api_v1_recipes_post():
|
||||||
|
data = json.load(bottle.request.body)
|
||||||
|
if 'url' not in data:
|
||||||
|
return {
|
||||||
|
'error': 'No URL provided'
|
||||||
|
}
|
||||||
|
|
||||||
|
recipes = []
|
||||||
|
try:
|
||||||
|
recipes = [add_recipe(data['url']).to_dict()]
|
||||||
|
except peewee.IntegrityError:
|
||||||
|
recipes = [db.Recipe.select().where(
|
||||||
|
db.Recipe.url == data['url']
|
||||||
|
).first().to_dict()]
|
||||||
|
|
||||||
|
return {
|
||||||
|
'recipes': recipes
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/v1/recipe/:id', ['GET', 'OPTIONS'])
|
||||||
def api_v1_recipe(id):
|
def api_v1_recipe(id):
|
||||||
|
# CORS
|
||||||
|
if bottle.request.method == 'OPTIONS':
|
||||||
|
return ''
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'recipes': [
|
'recipes': [
|
||||||
recipe.to_dict() for recipe in db.Recipe.select().where(
|
recipe.to_dict() for recipe in db.Recipe.select().where(
|
||||||
@ -36,4 +84,4 @@ def api_v1_recipe(id):
|
|||||||
@app.get('/static/<filename:path>')
|
@app.get('/static/<filename:path>')
|
||||||
def get_static_files(filename):
|
def get_static_files(filename):
|
||||||
"""Get Static files"""
|
"""Get Static files"""
|
||||||
return bottle.static_file(filename, root=)
|
return bottle.static_file(filename) # TODO: root=
|
||||||
|
@ -11,6 +11,8 @@
|
|||||||
"build": "node build/build.js"
|
"build": "node build/build.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"es6-promise": "^4.2.4",
|
||||||
|
"isomorphic-fetch": "^2.2.1",
|
||||||
"vue": "^2.5.2",
|
"vue": "^2.5.2",
|
||||||
"vue-router": "^3.0.1",
|
"vue-router": "^3.0.1",
|
||||||
"vuetify": "^1.0.0"
|
"vuetify": "^1.0.0"
|
||||||
|
BIN
recipes.db
BIN
recipes.db
Binary file not shown.
Loading…
Reference in New Issue
Block a user