added chowdown importer
This commit is contained in:
parent
58c5b2c301
commit
6ba1ff4505
@ -2,6 +2,7 @@
|
|||||||
<dictionary name="vabene1111-PC">
|
<dictionary name="vabene1111-PC">
|
||||||
<words>
|
<words>
|
||||||
<w>autosync</w>
|
<w>autosync</w>
|
||||||
|
<w>chowdown</w>
|
||||||
<w>csrftoken</w>
|
<w>csrftoken</w>
|
||||||
<w>gunicorn</w>
|
<w>gunicorn</w>
|
||||||
<w>ical</w>
|
<w>ical</w>
|
||||||
|
@ -136,8 +136,9 @@ class ImportExportBase(forms.Form):
|
|||||||
PAPRIKA = 'PAPRIKA'
|
PAPRIKA = 'PAPRIKA'
|
||||||
NEXTCLOUD = 'NEXTCLOUD'
|
NEXTCLOUD = 'NEXTCLOUD'
|
||||||
MEALIE = 'MEALIE'
|
MEALIE = 'MEALIE'
|
||||||
|
CHOWDOWN = 'CHOWDOWN'
|
||||||
|
|
||||||
type = forms.ChoiceField(choices=((DEFAULT, _('Default')), (PAPRIKA, _('Paprika')), (NEXTCLOUD, _('Nextcloud Cookbook')), (MEALIE, _('Mealie')),))
|
type = forms.ChoiceField(choices=((DEFAULT, _('Default')), (PAPRIKA, _('Paprika')), (NEXTCLOUD, _('Nextcloud Cookbook')), (MEALIE, _('Mealie')), (CHOWDOWN, _('Chowdown')),))
|
||||||
|
|
||||||
|
|
||||||
class ImportForm(ImportExportBase):
|
class ImportForm(ImportExportBase):
|
||||||
|
80
cookbook/integration/chowdown.py
Normal file
80
cookbook/integration/chowdown.py
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import json
|
||||||
|
import re
|
||||||
|
from io import BytesIO
|
||||||
|
from zipfile import ZipFile
|
||||||
|
|
||||||
|
from cookbook.helper.ingredient_parser import parse
|
||||||
|
from cookbook.integration.integration import Integration
|
||||||
|
from cookbook.models import Recipe, Step, Food, Unit, Ingredient, Keyword
|
||||||
|
|
||||||
|
|
||||||
|
class Chowdown(Integration):
|
||||||
|
|
||||||
|
def import_file_name_filter(self, zip_info_object):
|
||||||
|
print("testing", zip_info_object.filename)
|
||||||
|
return re.match(r'^_recipes/([A-Za-z\d\s-])+.md$', zip_info_object.filename)
|
||||||
|
|
||||||
|
def get_recipe_from_file(self, file):
|
||||||
|
ingredient_mode = False
|
||||||
|
direction_mode = False
|
||||||
|
description_mode = False
|
||||||
|
|
||||||
|
ingredients = []
|
||||||
|
directions = []
|
||||||
|
descriptions = []
|
||||||
|
for fl in file.readlines():
|
||||||
|
line = fl.decode("utf-8")
|
||||||
|
if 'title:' in line:
|
||||||
|
title = line.replace('title:', '').replace('"', '').strip()
|
||||||
|
if 'image:' in line:
|
||||||
|
image = line.replace('image:', '').strip()
|
||||||
|
if 'tags:' in line:
|
||||||
|
tags = line.replace('tags:', '').strip()
|
||||||
|
if ingredient_mode:
|
||||||
|
if len(line) > 2 and 'directions:' not in line:
|
||||||
|
ingredients.append(line[2:])
|
||||||
|
if '---' in line and direction_mode:
|
||||||
|
direction_mode = False
|
||||||
|
description_mode = True
|
||||||
|
if direction_mode:
|
||||||
|
if len(line) > 2:
|
||||||
|
directions.append(line[2:])
|
||||||
|
if 'ingredients:' in line:
|
||||||
|
ingredient_mode = True
|
||||||
|
if 'directions:' in line:
|
||||||
|
ingredient_mode = False
|
||||||
|
direction_mode = True
|
||||||
|
if description_mode and len(line) > 3 and '---' not in line:
|
||||||
|
descriptions.append(line)
|
||||||
|
|
||||||
|
recipe = Recipe.objects.create(name=title, created_by=self.request.user, internal=True, )
|
||||||
|
|
||||||
|
for k in tags.split(','):
|
||||||
|
keyword, created = Keyword.objects.get_or_create(name=k.strip())
|
||||||
|
recipe.keywords.add(keyword)
|
||||||
|
|
||||||
|
step = Step.objects.create(
|
||||||
|
instruction='\n'.join(directions) + '\n\n' + '\n'.join(descriptions)
|
||||||
|
)
|
||||||
|
|
||||||
|
for ingredient in ingredients:
|
||||||
|
amount, unit, ingredient, note = parse(ingredient)
|
||||||
|
f, created = Food.objects.get_or_create(name=ingredient)
|
||||||
|
u, created = Unit.objects.get_or_create(name=unit)
|
||||||
|
step.ingredients.add(Ingredient.objects.create(
|
||||||
|
food=f, unit=u, amount=amount, note=note
|
||||||
|
))
|
||||||
|
recipe.steps.add(step)
|
||||||
|
|
||||||
|
for f in self.files:
|
||||||
|
if '.zip' in f.name:
|
||||||
|
import_zip = ZipFile(f.file)
|
||||||
|
for z in import_zip.filelist:
|
||||||
|
if re.match(f'^images/{image}$', z.filename):
|
||||||
|
self.import_recipe_image(recipe, BytesIO(import_zip.read(z.filename)))
|
||||||
|
|
||||||
|
return recipe
|
||||||
|
|
||||||
|
|
||||||
|
def get_file_from_recipe(self, recipe):
|
||||||
|
raise NotImplementedError('Method not implemented in storage integration')
|
@ -22,6 +22,7 @@ class NextcloudCookbook(Integration):
|
|||||||
servings=recipe_json['recipeYield'])
|
servings=recipe_json['recipeYield'])
|
||||||
|
|
||||||
# TODO parse times (given in PT2H3M )
|
# TODO parse times (given in PT2H3M )
|
||||||
|
# TODO parse keywords
|
||||||
|
|
||||||
ingredients_added = False
|
ingredients_added = False
|
||||||
for s in recipe_json['recipeInstructions']:
|
for s in recipe_json['recipeInstructions']:
|
||||||
|
@ -6,6 +6,7 @@ from django.utils.translation import gettext as _
|
|||||||
|
|
||||||
from cookbook.forms import ExportForm, ImportForm, ImportExportBase
|
from cookbook.forms import ExportForm, ImportForm, ImportExportBase
|
||||||
from cookbook.helper.permission_helper import group_required
|
from cookbook.helper.permission_helper import group_required
|
||||||
|
from cookbook.integration.chowdown import Chowdown
|
||||||
from cookbook.integration.default import Default
|
from cookbook.integration.default import Default
|
||||||
from cookbook.integration.mealie import Mealie
|
from cookbook.integration.mealie import Mealie
|
||||||
from cookbook.integration.nextcloud_cookbook import NextcloudCookbook
|
from cookbook.integration.nextcloud_cookbook import NextcloudCookbook
|
||||||
@ -22,6 +23,8 @@ def get_integration(request, export_type):
|
|||||||
return NextcloudCookbook(request)
|
return NextcloudCookbook(request)
|
||||||
if export_type == ImportExportBase.MEALIE:
|
if export_type == ImportExportBase.MEALIE:
|
||||||
return Mealie(request)
|
return Mealie(request)
|
||||||
|
if export_type == ImportExportBase.CHOWDOWN:
|
||||||
|
return Chowdown(request)
|
||||||
|
|
||||||
|
|
||||||
@group_required('user')
|
@group_required('user')
|
||||||
|
@ -7,12 +7,12 @@ integrations to allow you to both import and export your recipes into whatever f
|
|||||||
Feel like there is an important integration missing ? Just take a look at the [integration issues](https://github.com/vabene1111/recipes/issues?q=is%3Aissue+is%3Aopen+label%3Aintegration) or open a new one
|
Feel like there is an important integration missing ? Just take a look at the [integration issues](https://github.com/vabene1111/recipes/issues?q=is%3Aissue+is%3Aopen+label%3Aintegration) or open a new one
|
||||||
if your favorite one is missing.
|
if your favorite one is missing.
|
||||||
|
|
||||||
!!! warning "WIP"
|
!!! info "Export"
|
||||||
Please note that this feature is relatively new and many integrations are missing.
|
I strongly believe in everyone's right to use their data as they please and therefore want to give you
|
||||||
Additionally, many recipe applications provide formats that are not structured in an easily machine-readable way
|
the most possible flexibility with your recipes.
|
||||||
and thus require a lot of work to integrate even tough the module is very versatile.
|
That said for most of the people getting this application running with their recipes is the biggest priority.
|
||||||
If you are good at writing parsers feel free to add new integrations for your favorite services.
|
Because of this importing as many formats as possible is prioritized over exporting.
|
||||||
|
Exporter for the different formats will follow over time.
|
||||||
|
|
||||||
## Default
|
## Default
|
||||||
The default integration is the build in (and preferred) way to import and export recipes.
|
The default integration is the build in (and preferred) way to import and export recipes.
|
||||||
@ -36,7 +36,7 @@ You will get a `Recipes.zip` file. Simply upload the file and choose the Nextclo
|
|||||||
|
|
||||||
!!! warning "Folder Structure"
|
!!! warning "Folder Structure"
|
||||||
Importing only works if the folder structure is correct. If you do not use the standard path or create the
|
Importing only works if the folder structure is correct. If you do not use the standard path or create the
|
||||||
zip file in any other way make sure the strucutre is as follows
|
zip file in any other way make sure the structure is as follows
|
||||||
```
|
```
|
||||||
Recipes.zip/
|
Recipes.zip/
|
||||||
└── Recipes/
|
└── Recipes/
|
||||||
@ -57,6 +57,24 @@ To migrate your recipes
|
|||||||
2. Download the backup by clicking on it and pressing download (this wasn't working for me, so I had to manually pull it from the server)
|
2. Download the backup by clicking on it and pressing download (this wasn't working for me, so I had to manually pull it from the server)
|
||||||
3. Upload the entire `.zip` file to the importer page and import everything
|
3. Upload the entire `.zip` file to the importer page and import everything
|
||||||
|
|
||||||
|
## Chowdown
|
||||||
|
Chowdown stores all your recipes in plain text markdown files in a directory called `_recipes`.
|
||||||
|
Images are saved in a directory called `images`.
|
||||||
|
|
||||||
|
In order to import your Chowdown recipes simply create a `.zip` file from those two folders and import them.
|
||||||
|
The folder structure should look as follows
|
||||||
|
```
|
||||||
|
Recipes.zip/
|
||||||
|
├── _recipes/
|
||||||
|
│ ├── recipe one.md
|
||||||
|
│ ├── recipe two.md
|
||||||
|
│ └── ...
|
||||||
|
└── images/
|
||||||
|
├── image-name.jpg
|
||||||
|
├── second-image-name.jpg
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
## Paprika
|
## Paprika
|
||||||
Paprika can create two types of export. The first is a proprietary `.paprikarecipes` file in some kind of binarized format.
|
Paprika can create two types of export. The first is a proprietary `.paprikarecipes` file in some kind of binarized format.
|
||||||
The second one is HTML files containing at least a bit of microdata.
|
The second one is HTML files containing at least a bit of microdata.
|
||||||
|
Loading…
Reference in New Issue
Block a user