added export support for recipe sage
This commit is contained in:
parent
ba1e18410a
commit
c1287407a3
@ -120,16 +120,16 @@ class ImportExportBase(forms.Form):
|
|||||||
(MEALIE, 'Mealie'), (CHOWDOWN, 'Chowdown'), (SAFRON, 'Safron'), (CHEFTAP, 'ChefTap'),
|
(MEALIE, 'Mealie'), (CHOWDOWN, 'Chowdown'), (SAFRON, 'Safron'), (CHEFTAP, 'ChefTap'),
|
||||||
(PEPPERPLATE, 'Pepperplate'), (RECIPESAGE, 'Recipe Sage'), (DOMESTICA, 'Domestica'),
|
(PEPPERPLATE, 'Pepperplate'), (RECIPESAGE, 'Recipe Sage'), (DOMESTICA, 'Domestica'),
|
||||||
))
|
))
|
||||||
duplicates = forms.BooleanField(help_text=_('To prevent duplicates recipes with the same name as existing ones are ignored. Check this box to import everything.'), required=False)
|
|
||||||
|
|
||||||
|
|
||||||
class ImportForm(ImportExportBase):
|
class ImportForm(ImportExportBase):
|
||||||
files = forms.FileField(required=True, widget=forms.ClearableFileInput(attrs={'multiple': True}))
|
files = forms.FileField(required=True, widget=forms.ClearableFileInput(attrs={'multiple': True}))
|
||||||
|
duplicates = forms.BooleanField(help_text=_('To prevent duplicates recipes with the same name as existing ones are ignored. Check this box to import everything.'), required=False)
|
||||||
|
|
||||||
|
|
||||||
class ExportForm(ImportExportBase):
|
class ExportForm(ImportExportBase):
|
||||||
recipes = forms.ModelMultipleChoiceField(widget=MultiSelectWidget, queryset=Recipe.objects.none())
|
recipes = forms.ModelMultipleChoiceField(widget=MultiSelectWidget, queryset=Recipe.objects.none())
|
||||||
all = forms.BooleanField()
|
all = forms.BooleanField(required=False)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
space = kwargs.pop('space')
|
space = kwargs.pop('space')
|
||||||
|
@ -1,18 +1,16 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from io import BytesIO, StringIO
|
from io import BytesIO, StringIO
|
||||||
from zipfile import ZipFile, BadZipFile
|
from zipfile import ZipFile, BadZipFile
|
||||||
|
|
||||||
from django.contrib import messages
|
|
||||||
from django.core.files import File
|
from django.core.files import File
|
||||||
from django.http import HttpResponseRedirect, HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.urls import reverse
|
|
||||||
from django.utils.formats import date_format
|
from django.utils.formats import date_format
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from django_scopes import scope
|
from django_scopes import scope
|
||||||
|
|
||||||
|
from cookbook.forms import ImportExportBase
|
||||||
from cookbook.models import Keyword, Recipe
|
from cookbook.models import Keyword, Recipe
|
||||||
|
|
||||||
|
|
||||||
@ -20,6 +18,7 @@ class Integration:
|
|||||||
request = None
|
request = None
|
||||||
keyword = None
|
keyword = None
|
||||||
files = None
|
files = None
|
||||||
|
export_type = None
|
||||||
ignored_recipes = []
|
ignored_recipes = []
|
||||||
|
|
||||||
def __init__(self, request, export_type):
|
def __init__(self, request, export_type):
|
||||||
@ -28,6 +27,7 @@ class Integration:
|
|||||||
:param request: request context of import session (used to link user to created objects)
|
:param request: request context of import session (used to link user to created objects)
|
||||||
"""
|
"""
|
||||||
self.request = request
|
self.request = request
|
||||||
|
self.export_type = export_type
|
||||||
self.keyword = Keyword.objects.create(
|
self.keyword = Keyword.objects.create(
|
||||||
name=f'Import {export_type} {date_format(datetime.datetime.now(), "DATETIME_FORMAT")}.{datetime.datetime.now().strftime("%S")}',
|
name=f'Import {export_type} {date_format(datetime.datetime.now(), "DATETIME_FORMAT")}.{datetime.datetime.now().strftime("%S")}',
|
||||||
description=f'Imported by {request.user.get_user_name()} at {date_format(datetime.datetime.now(), "DATETIME_FORMAT")}. Type: {export_type}',
|
description=f'Imported by {request.user.get_user_name()} at {date_format(datetime.datetime.now(), "DATETIME_FORMAT")}. Type: {export_type}',
|
||||||
@ -41,6 +41,9 @@ class Integration:
|
|||||||
:param recipes: list of recipe objects
|
:param recipes: list of recipe objects
|
||||||
:return: HttpResponse with a ZIP file that is directly downloaded
|
:return: HttpResponse with a ZIP file that is directly downloaded
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
#TODO this is temporary, find a better solution for different export formats when doing other exporters
|
||||||
|
if self.export_type != ImportExportBase.RECIPESAGE:
|
||||||
export_zip_stream = BytesIO()
|
export_zip_stream = BytesIO()
|
||||||
export_zip_obj = ZipFile(export_zip_stream, 'w')
|
export_zip_obj = ZipFile(export_zip_stream, 'w')
|
||||||
|
|
||||||
@ -68,6 +71,14 @@ class Integration:
|
|||||||
response = HttpResponse(export_zip_stream.getvalue(), content_type='application/force-download')
|
response = HttpResponse(export_zip_stream.getvalue(), content_type='application/force-download')
|
||||||
response['Content-Disposition'] = 'attachment; filename="export.zip"'
|
response['Content-Disposition'] = 'attachment; filename="export.zip"'
|
||||||
return response
|
return response
|
||||||
|
else:
|
||||||
|
json_list = []
|
||||||
|
for r in recipes:
|
||||||
|
json_list.append(self.get_file_from_recipe(r))
|
||||||
|
|
||||||
|
response = HttpResponse(json.dumps(json_list), content_type='application/force-download')
|
||||||
|
response['Content-Disposition'] = 'attachment; filename="recipes.json"'
|
||||||
|
return response
|
||||||
|
|
||||||
def import_file_name_filter(self, zip_info_object):
|
def import_file_name_filter(self, zip_info_object):
|
||||||
"""
|
"""
|
||||||
|
@ -2,6 +2,7 @@ import base64
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
from rest_framework.renderers import JSONRenderer
|
||||||
|
|
||||||
from cookbook.helper.ingredient_parser import parse, get_food, get_unit
|
from cookbook.helper.ingredient_parser import parse, get_food, get_unit
|
||||||
from cookbook.integration.integration import Integration
|
from cookbook.integration.integration import Integration
|
||||||
@ -58,4 +59,31 @@ class RecipeSage(Integration):
|
|||||||
return recipe
|
return recipe
|
||||||
|
|
||||||
def get_file_from_recipe(self, recipe):
|
def get_file_from_recipe(self, recipe):
|
||||||
raise NotImplementedError('Method not implemented in storage integration')
|
data = {
|
||||||
|
'@context': 'http://schema.org',
|
||||||
|
'@type': 'Recipe',
|
||||||
|
'creditText': '',
|
||||||
|
'isBasedOn': '',
|
||||||
|
'name': recipe.name,
|
||||||
|
'description': recipe.description,
|
||||||
|
'prepTime': str(recipe.working_time),
|
||||||
|
'totalTime': str(recipe.waiting_time + recipe.working_time),
|
||||||
|
'recipeYield': str(recipe.servings),
|
||||||
|
'image': [],
|
||||||
|
'recipeCategory': [],
|
||||||
|
'comment': [],
|
||||||
|
'recipeIngredient': [],
|
||||||
|
'recipeInstructions': [],
|
||||||
|
}
|
||||||
|
|
||||||
|
for s in recipe.steps.all():
|
||||||
|
if s.type != Step.TIME:
|
||||||
|
data['recipeInstructions'].append({
|
||||||
|
'@type': 'HowToStep',
|
||||||
|
'text': s.instruction
|
||||||
|
})
|
||||||
|
|
||||||
|
for i in s.ingredients.all():
|
||||||
|
data['recipeIngredient'].append(f'{float(i.amount)} {i.unit} {i.food}')
|
||||||
|
|
||||||
|
return data
|
||||||
|
@ -78,7 +78,7 @@ def export_recipe(request):
|
|||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
try:
|
try:
|
||||||
recipes = form.cleaned_data['recipes']
|
recipes = form.cleaned_data['recipes']
|
||||||
if form['all']:
|
if form.cleaned_data['all']:
|
||||||
recipes = Recipe.objects.filter(space=request.space, internal=True).all()
|
recipes = Recipe.objects.filter(space=request.space, internal=True).all()
|
||||||
integration = get_integration(request, form.cleaned_data['type'])
|
integration = get_integration(request, form.cleaned_data['type'])
|
||||||
return integration.do_export(recipes)
|
return integration.do_export(recipes)
|
||||||
|
@ -30,7 +30,7 @@ Overview of the capabilities of the different integrations.
|
|||||||
| Paprika | ✔️ | ⌚ | ✔️ |
|
| Paprika | ✔️ | ⌚ | ✔️ |
|
||||||
| ChefTap | ✔️ | ❌ | ❌️ |
|
| ChefTap | ✔️ | ❌ | ❌️ |
|
||||||
| Pepperplate | ✔️ | ⌚ | ❌️ |
|
| Pepperplate | ✔️ | ⌚ | ❌️ |
|
||||||
| RecipeSage | ✔️ | ⌚ | ✔️ |
|
| RecipeSage | ✔️ | ✔️ | ✔️ |
|
||||||
| Domestica | ✔️ | ⌚ | ✔️ |
|
| Domestica | ✔️ | ⌚ | ✔️ |
|
||||||
|
|
||||||
✔ = implemented, ❌ = not implemented and not possible/planned, ⌚ = not yet implemented
|
✔ = implemented, ❌ = not implemented and not possible/planned, ⌚ = not yet implemented
|
||||||
@ -46,6 +46,9 @@ easy machine readability if you want to use the data for any other purpose.
|
|||||||
Go to Settings > Export Recipe Data and select `EXPORT AS JSON-LD (BEST)`. Then simply upload the exported file
|
Go to Settings > Export Recipe Data and select `EXPORT AS JSON-LD (BEST)`. Then simply upload the exported file
|
||||||
to Tandoor.
|
to Tandoor.
|
||||||
|
|
||||||
|
The RecipeSage integration also allows exporting. To migrate from Tandoor to RecipeSage simply export with Recipe Sage
|
||||||
|
selected and import the json file in RecipeSage. Images are currently not supported for exporting.
|
||||||
|
|
||||||
## Domestica
|
## Domestica
|
||||||
Go to Import/Export and select `Export Recipes`. Then simply upload the exported file
|
Go to Import/Export and select `Export Recipes`. Then simply upload the exported file
|
||||||
to Tandoor.
|
to Tandoor.
|
||||||
|
Loading…
Reference in New Issue
Block a user