Better doc
This commit is contained in:
parent
c8ad274fc5
commit
e7659166be
19
LICENSE.txt
Normal file
19
LICENSE.txt
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright 2018 - Lucas Verney (Phyks)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
64
README.md
Normal file
64
README.md
Normal file
@ -0,0 +1,64 @@
|
||||
Cuizin
|
||||
======
|
||||
|
||||
Cuizin is a tool wrapping around [Web Outside Of Browsers](http://weboob.org/)
|
||||
to help you sort and organize recipes you find online. You can also manually
|
||||
add new recipes.
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
$ git clone … # Clone this repository
|
||||
$ pip install -r requirements.txt # Install Python dependencies
|
||||
$ npm install # Install JS dependencies for the frontend
|
||||
$ npm run build # Build the JS dependencies
|
||||
```
|
||||
|
||||
Ideally, Python dependencies should be installed in a virtual environment.
|
||||
|
||||
TODO:
|
||||
- `npm run build` should handle prefix
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
Run `python -m cuizin` to run the built-in webserver. Conversely, you can use
|
||||
WSGI to serve the application directly (`application` variable is exported in
|
||||
`cuizin/__main__.py`).
|
||||
|
||||
You can customize the behavior of the app by passing environment variables:
|
||||
* `HOST` to set the host on which the webserver should listen to (defaults to
|
||||
`localhost` only). Use `HOST=0.0.0.0` to make it world-accessible.
|
||||
* `PORT` to set the port on which the webserver should listen. Defaults to
|
||||
`8080`.
|
||||
* `DEBUG` to enable or disable the debug from Bottle (defaults to `False`).
|
||||
* `WEBOOB_MODULES_PATH` to set the path to the local clone of the Weboob
|
||||
modules. Default to using the `weboob-modules` package installed by `pip`
|
||||
(and which you should regularly update with `pip install --upgrade
|
||||
weboob-modules`).
|
||||
|
||||
If you serve the app with a reverse proxy, you should serve the content of
|
||||
`cuizin/dist` (static frontend files) directly, without passing it down to the
|
||||
Bottle webserver.
|
||||
|
||||
|
||||
## Contributing
|
||||
|
||||
All contributions are welcome, feel free to open a MR. Just in case, if you
|
||||
plan on working on some major new feature, please open an issue before to get
|
||||
some feedbacks.
|
||||
|
||||
All the code lies under `cuizin` directory. Frontend code lies under
|
||||
`cuizin/js_src`. `build` and `config` folders at the root of this repository
|
||||
are used for the frontend build.
|
||||
|
||||
Additionnally, you can use `make dev` to spawn a development webserver to
|
||||
serve the JS frontend and auto-update/auto-reload when you make changes. The
|
||||
spawned JS server will be set up at `localhost:8081` and you should start the
|
||||
backend Python server at `localhost:8080` with `python -m cuizin` along it.
|
||||
|
||||
|
||||
## License
|
||||
|
||||
This software is released under an MIT License.
|
@ -14,7 +14,7 @@ module.exports = {
|
||||
|
||||
// Various Dev Server settings
|
||||
host: 'localhost', // can be overwritten by process.env.HOST
|
||||
port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
|
||||
port: 8081, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
|
||||
autoOpenBrowser: false,
|
||||
errorOverlay: true,
|
||||
notifyOnErrors: true,
|
||||
|
@ -1,41 +1,3 @@
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
import json
|
||||
import sys
|
||||
|
||||
from weboob.core.ouiboube import WebNip
|
||||
from weboob.tools.json import WeboobEncoder
|
||||
|
||||
from cuizin import db
|
||||
|
||||
BACKENDS = ['750g', 'allrecipes', 'cuisineaz', 'marmiton', 'supertoinette']
|
||||
|
||||
|
||||
def add_recipe(url, modules_path=None):
|
||||
webnip = WebNip(modules_path=modules_path)
|
||||
|
||||
backends = [
|
||||
webnip.load_backend(
|
||||
module,
|
||||
module,
|
||||
params={}
|
||||
)
|
||||
for module in BACKENDS
|
||||
]
|
||||
|
||||
recipe = None
|
||||
for backend in backends:
|
||||
browser = backend.browser
|
||||
if url.startswith(browser.BASEURL):
|
||||
browser.location(url)
|
||||
recipe = db.Recipe.from_weboob(browser.page.get_recipe())
|
||||
# Ensure URL is set
|
||||
recipe.url = url
|
||||
break
|
||||
|
||||
if not recipe:
|
||||
# TODO
|
||||
recipe = db.Recipe()
|
||||
recipe.url = url
|
||||
|
||||
recipe.save()
|
||||
return recipe
|
||||
"""
|
||||
Cuizin is a lightweight tool to help you manage your recipes.
|
||||
"""
|
||||
|
@ -1,3 +1,6 @@
|
||||
"""
|
||||
Main entry point for Cuizin.
|
||||
"""
|
||||
import os
|
||||
|
||||
import peewee
|
||||
@ -11,7 +14,7 @@ app = application = web.app
|
||||
if __name__ == '__main__':
|
||||
HOST = os.environ.get('CUIZIN_HOST', 'localhost')
|
||||
PORT = os.environ.get('CUIZIN_PORT', '8080')
|
||||
DEBUG = os.environ.get('CUIZIN_DEBUG', False)
|
||||
DEBUG = bool(os.environ.get('CUIZIN_DEBUG', False))
|
||||
|
||||
try:
|
||||
db.database.create_tables([db.Recipe])
|
||||
|
22
cuizin/db.py
22
cuizin/db.py
@ -1,3 +1,6 @@
|
||||
"""
|
||||
Database definition
|
||||
"""
|
||||
import base64
|
||||
import json
|
||||
|
||||
@ -15,6 +18,9 @@ database.connect()
|
||||
|
||||
|
||||
class JSONField(TextField):
|
||||
"""
|
||||
A Peewee database field with transparent JSON dump/load.
|
||||
"""
|
||||
def db_value(self, value):
|
||||
return json.dumps(value)
|
||||
|
||||
@ -24,6 +30,9 @@ class JSONField(TextField):
|
||||
|
||||
|
||||
class Recipe(Model):
|
||||
"""
|
||||
Our base model for a recipe.
|
||||
"""
|
||||
title = CharField()
|
||||
url = CharField(null=True, unique=True)
|
||||
author = CharField(null=True)
|
||||
@ -41,25 +50,32 @@ class Recipe(Model):
|
||||
@staticmethod
|
||||
def from_weboob(obj):
|
||||
recipe = Recipe()
|
||||
# Set fields
|
||||
for field in ['title', 'url', 'author', 'picture_url',
|
||||
'short_description', 'preparation_time', 'cooking_time',
|
||||
'ingredients', 'instructions']:
|
||||
value = getattr(obj, field)
|
||||
if value:
|
||||
setattr(recipe, field, value)
|
||||
|
||||
# Serialize number of person
|
||||
recipe.nb_person = '-'.join(str(num) for num in obj.nb_person)
|
||||
# Download picture and save it as a blob
|
||||
recipe.picture = requests.get(obj.picture_url).content
|
||||
return recipe
|
||||
|
||||
def to_dict(self):
|
||||
"""
|
||||
Dict conversion function, for serialization in the API.
|
||||
"""
|
||||
serialized = model_to_dict(self)
|
||||
prepend_info = (
|
||||
# Dump picture as a base64 string, compatible with HTML `src` attribute
|
||||
# for images.
|
||||
picture_mime = (
|
||||
'data:%s;base64' % magic.from_buffer(serialized['picture'],
|
||||
mime=True)
|
||||
)
|
||||
serialized['picture'] = '%s,%s' % (
|
||||
prepend_info,
|
||||
picture_mime,
|
||||
base64.b64encode(serialized['picture']).decode('utf-8')
|
||||
)
|
||||
return serialized
|
||||
|
@ -1,2 +1,2 @@
|
||||
export const API_URL = 'http://localhost:8080';
|
||||
export const API_URL = 'http://localhost:8080'; // TODO: Should be coming from an env variable
|
||||
export const FOOBAR = 'TODO'; // TODO
|
||||
|
56
cuizin/scraping.py
Normal file
56
cuizin/scraping.py
Normal file
@ -0,0 +1,56 @@
|
||||
"""
|
||||
Scraping code, to fetch and add a recipe.
|
||||
|
||||
This code wraps around [Web Outside Of Browsers](http://weboob.org/).
|
||||
"""
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import os
|
||||
|
||||
from weboob.core.ouiboube import WebNip
|
||||
|
||||
from cuizin import db
|
||||
|
||||
# List of backends with recipe abilities in Weboob
|
||||
BACKENDS = ['750g', 'allrecipes', 'cuisineaz', 'marmiton', 'supertoinette']
|
||||
|
||||
|
||||
def add_recipe(url):
|
||||
"""
|
||||
Add a recipe, trying to scrape from a given URL.
|
||||
|
||||
:param url: URL of the recipe.
|
||||
:return: A ``cuizin.db.Recipe`` model.
|
||||
"""
|
||||
# Eventually load modules from a local clone
|
||||
MODULES_PATH = os.environ.get('WEBOOB_MODULES_PATH', None)
|
||||
|
||||
# Get all backends with recipe abilities
|
||||
webnip = WebNip(modules_path=MODULES_PATH)
|
||||
backends = [
|
||||
webnip.load_backend(
|
||||
module,
|
||||
module,
|
||||
params={}
|
||||
)
|
||||
for module in BACKENDS
|
||||
]
|
||||
|
||||
# Try to fetch the recipe with a Weboob backend
|
||||
recipe = None
|
||||
for backend in backends:
|
||||
browser = backend.browser
|
||||
if url.startswith(browser.BASEURL):
|
||||
browser.location(url)
|
||||
recipe = db.Recipe.from_weboob(browser.page.get_recipe())
|
||||
# Ensure URL is set
|
||||
recipe.url = url
|
||||
break
|
||||
|
||||
if not recipe:
|
||||
# TODO
|
||||
recipe = db.Recipe()
|
||||
recipe.url = url
|
||||
|
||||
recipe.save()
|
||||
return recipe
|
@ -1,9 +1,12 @@
|
||||
import json
|
||||
import os
|
||||
|
||||
import bottle
|
||||
|
||||
from cuizin import add_recipe
|
||||
from cuizin import db
|
||||
from cuizin.scraping import add_recipe
|
||||
|
||||
MODULE_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
|
||||
app = bottle.Bottle()
|
||||
@ -26,13 +29,20 @@ def enable_cors():
|
||||
|
||||
@app.route('/api/v1', ['GET', 'OPTIONS'])
|
||||
def api_v1_index():
|
||||
"""
|
||||
API index route
|
||||
"""
|
||||
return {
|
||||
'recipes': '/api/v1/recipes'
|
||||
'recipes': '/api/v1/recipes',
|
||||
'recipe': '/api/v1/recipe/:id'
|
||||
}
|
||||
|
||||
|
||||
@app.route('/api/v1/recipes', ['GET', 'OPTIONS'])
|
||||
def api_v1_recipes():
|
||||
"""
|
||||
List all recipes
|
||||
"""
|
||||
# CORS
|
||||
if bottle.request.method == 'OPTIONS':
|
||||
return ''
|
||||
@ -46,6 +56,9 @@ def api_v1_recipes():
|
||||
|
||||
@app.post('/api/v1/recipes')
|
||||
def api_v1_recipes_post():
|
||||
"""
|
||||
Create a new recipe from URL
|
||||
"""
|
||||
data = json.load(bottle.request.body)
|
||||
if 'url' not in data:
|
||||
return {
|
||||
@ -69,6 +82,9 @@ def api_v1_recipes_post():
|
||||
|
||||
@app.route('/api/v1/recipe/:id', ['GET', 'OPTIONS'])
|
||||
def api_v1_recipe(id):
|
||||
"""
|
||||
Get a given recipe from db
|
||||
"""
|
||||
# CORS
|
||||
if bottle.request.method == 'OPTIONS':
|
||||
return ''
|
||||
@ -84,6 +100,9 @@ def api_v1_recipe(id):
|
||||
|
||||
@app.delete('/api/v1/recipe/:id', ['DELETE', 'OPTIONS'])
|
||||
def api_v1_recipe_delete(id):
|
||||
"""
|
||||
Delete a given recipe from db
|
||||
"""
|
||||
# CORS
|
||||
if bottle.request.method == 'OPTIONS':
|
||||
return ''
|
||||
@ -97,7 +116,19 @@ def api_v1_recipe_delete(id):
|
||||
}
|
||||
|
||||
|
||||
@app.get('/')
|
||||
def index():
|
||||
"""
|
||||
Return built index.html file
|
||||
"""
|
||||
return bottle.static_file('index.html',
|
||||
root=os.path.join(MODULE_DIR, 'dist'))
|
||||
|
||||
|
||||
@app.get('/static/<filename:path>')
|
||||
def get_static_files(filename):
|
||||
"""Get Static files"""
|
||||
return bottle.static_file(filename) # TODO: root=
|
||||
"""
|
||||
Get Static files
|
||||
"""
|
||||
return bottle.static_file(filename,
|
||||
root=os.path.join(MODULE_DIR, 'dist', 'static'))
|
||||
|
Loading…
Reference in New Issue
Block a user