diff --git a/.env.template b/.env.template index 635e079c..aa13a7a9 100644 --- a/.env.template +++ b/.env.template @@ -145,3 +145,7 @@ REVERSE_PROXY_AUTH=0 #AUTH_LDAP_BIND_DN= #AUTH_LDAP_BIND_PASSWORD= #AUTH_LDAP_USER_SEARCH_BASE_DN= + +# Enables exporting PDF (see export docs) +# Disabled by default, uncomment to enable +# ENABLE_PDF_EXPORT=1 \ No newline at end of file diff --git a/cookbook/forms.py b/cookbook/forms.py index b5fb9866..a5717c97 100644 --- a/cookbook/forms.py +++ b/cookbook/forms.py @@ -47,7 +47,7 @@ class UserPreferenceForm(forms.ModelForm): fields = ( 'default_unit', 'use_fractions', 'use_kj', 'theme', 'nav_color', 'sticky_navbar', 'default_page', 'show_recent', 'search_style', - 'plan_share', 'ingredient_decimals', 'comments', + 'plan_share', 'ingredient_decimals', 'comments', ) labels = { @@ -496,7 +496,7 @@ class ShoppingPreferenceForm(forms.ModelForm): 'mealplan_autoexclude_onhand': _('When adding a meal plan to the shopping list (manually or automatically), exclude ingredients that are on hand.'), 'default_delay': _('Default number of hours to delay a shopping list entry.'), 'filter_to_supermarket': _('Filter shopping list to only include supermarket categories.'), - 'shopping_recent_days': _('Days of recent shopping list entries to display.'), + 'shopping_recent_days': _('Days of recent shopping list entries to display.'), 'shopping_add_onhand': _("Mark food 'On Hand' when checked off shopping list."), 'csv_delim': _('Delimiter to use for CSV exports.'), 'csv_prefix': _('Prefix to add when copying list to the clipboard.'), diff --git a/cookbook/integration/pdfexport.py b/cookbook/integration/pdfexport.py index 61571da0..b982f24d 100644 --- a/cookbook/integration/pdfexport.py +++ b/cookbook/integration/pdfexport.py @@ -19,10 +19,6 @@ class PDFexport(Integration): def get_recipe_from_file(self, file): raise NotImplementedError('Method not implemented in storage integration') - - - - async def get_files_from_recipes_async(self, recipes, cookie): cmd = runserver.Command() @@ -33,15 +29,15 @@ class PDFexport(Integration): ignoreHTTPSErrors=True ) - cookies = {'domain': cmd.default_addr, 'name': 'sessionid', 'value': cookie['sessionid'],} - options = { 'format': 'letter', - 'margin': { - 'top': '0.75in', - 'bottom': '0.75in', - 'left': '0.75in', - 'right': '0.75in', - } - } + cookies = {'domain': cmd.default_addr, 'name': 'sessionid', 'value': cookie['sessionid'], } + options = {'format': 'letter', + 'margin': { + 'top': '0.75in', + 'bottom': '0.75in', + 'left': '0.75in', + 'right': '0.75in', + } + } page = await browser.newPage() await page.emulateMedia('print') @@ -49,14 +45,11 @@ class PDFexport(Integration): files = [] for recipe in recipes: - await page.goto('http://'+cmd.default_addr+':'+cmd.default_port+'/view/recipe/'+str(recipe.id), {'waitUntil': 'networkidle0',}) - files.append([ recipe.name+'.pdf', await page.pdf(options) ]) - + await page.goto('http://' + cmd.default_addr + ':' + cmd.default_port + '/view/recipe/' + str(recipe.id), {'waitUntil': 'networkidle0', }) + files.append([recipe.name + '.pdf', await page.pdf(options)]) await browser.close() return files - - def get_files_from_recipes(self, recipes, cookie): return asyncio.run(self.get_files_from_recipes_async(recipes, cookie)) diff --git a/cookbook/views/import_export.py b/cookbook/views/import_export.py index 83a9e7c9..9fd810c1 100644 --- a/cookbook/views/import_export.py +++ b/cookbook/views/import_export.py @@ -30,6 +30,7 @@ from cookbook.integration.rezkonv import RezKonv from cookbook.integration.saffron import Saffron from cookbook.integration.pdfexport import PDFexport from cookbook.models import Recipe, ImportLog, UserPreference +from recipes import settings def get_integration(request, export_type): @@ -121,6 +122,10 @@ def export_recipe(request): recipes = form.cleaned_data['recipes'] if form.cleaned_data['all']: recipes = Recipe.objects.filter(space=request.space, internal=True).all() + + if form.cleaned_data['type'] == ImportExportBase.PDF and not settings.ENABLE_PDF_EXPORT: + messages.add_message(request, messages.ERROR, _('The PDF Exporter is not enabled on this instance as it is still in an experimental state.')) + return render(request, 'export.html', {'form': form}) integration = get_integration(request, form.cleaned_data['type']) return integration.do_export(recipes) except NotImplementedError: diff --git a/docs/features/import_export.md b/docs/features/import_export.md index 9db829ec..d551cced 100644 --- a/docs/features/import_export.md +++ b/docs/features/import_export.md @@ -20,24 +20,25 @@ if your favorite one is missing. Overview of the capabilities of the different integrations. -| Integration | Import | Export | Images | -| ----------- | ------ | ------ | ------ | -| Default | ✔️ | ✔️ | ✔️ | -| Nextcloud | ✔️ | ⌚ | ✔️ | -| Mealie | ✔️ | ⌚ | ✔️ | -| Chowdown | ✔️ | ⌚ | ✔️ | -| Safron | ✔️ | ⌚ | ❌ | -| Paprika | ✔️ | ⌚ | ✔️ | -| ChefTap | ✔️ | ❌ | ❌ | -| Pepperplate | ✔️ | ⌚ | ❌ | -| RecipeSage | ✔️ | ✔️ | ✔️ | -| Domestica | ✔️ | ⌚ | ✔️ | -| MealMaster | ✔️ | ❌ | ❌ | -| RezKonv | ✔️ | ❌ | ❌ | -| OpenEats | ✔️ | ❌ | ⌚ | -| Plantoeat | ✔️ | ❌ | ✔ | -| CookBookApp | ✔️ | ⌚ | ✔️ | -| CopyMeThat | ✔️ | ❌ | ✔️ | +| Integration | Import | Export | Images | +|--------------------| ------ | ------ | ------ | +| Default | ✔️ | ✔️ | ✔️ | +| Nextcloud | ✔️ | ⌚ | ✔️ | +| Mealie | ✔️ | ⌚ | ✔️ | +| Chowdown | ✔️ | ⌚ | ✔️ | +| Safron | ✔️ | ✔ | ❌ | +| Paprika | ✔️ | ⌚ | ✔️ | +| ChefTap | ✔️ | ❌ | ❌ | +| Pepperplate | ✔️ | ⌚ | ❌ | +| RecipeSage | ✔️ | ✔️ | ✔️ | +| Domestica | ✔️ | ⌚ | ✔️ | +| MealMaster | ✔️ | ❌ | ❌ | +| RezKonv | ✔️ | ❌ | ❌ | +| OpenEats | ✔️ | ❌ | ⌚ | +| Plantoeat | ✔️ | ❌ | ✔ | +| CookBookApp | ✔️ | ⌚ | ✔️ | +| CopyMeThat | ✔️ | ❌ | ✔️ | +| PDF (experimental) | ⌚️ | ✔ | ✔️ | ✔ = implemented, ❌ = not implemented and not possible/planned, ⌚ = not yet implemented @@ -222,4 +223,14 @@ CookBookApp can export .zip files containing .html files. Upload the entire ZIP ## CopyMeThat -CopyMeThat can export .zip files containing an `.html` file as well as a folder containing all the images. Upload the entire ZIP to Tandoor to import all included recipes. \ No newline at end of file +CopyMeThat can export .zip files containing an `.html` file as well as a folder containing all the images. Upload the entire ZIP to Tandoor to import all included recipes. + +## PDF + +The PDF Exporter is an experimental feature that uses the puppeteer browser renderer to render each recipe and export it to PDF. +For that to work it downloads a chromium binary of about 140 MB to your server and then renders the PDF files using that. + +Since that is something some server administrators might not want there the PDF exporter is disabled by default and can be enabled with `ENABLE_PDF_EXPORT=1` in `.env`. + +See [this issue](https://github.com/TandoorRecipes/recipes/pull/1211) for more discussion on this and +[this issue](https://github.com/TandoorRecipes/recipes/issues/781) for the future plans to support server side rendering. \ No newline at end of file diff --git a/recipes/settings.py b/recipes/settings.py index 0e79da2e..9b9dacbb 100644 --- a/recipes/settings.py +++ b/recipes/settings.py @@ -137,6 +137,8 @@ ENABLE_SIGNUP = bool(int(os.getenv('ENABLE_SIGNUP', False))) ENABLE_METRICS = bool(int(os.getenv('ENABLE_METRICS', False))) +ENABLE_PDF_EXPORT = bool(int(os.getenv('ENABLE_PDF_EXPORT', False))) + MIDDLEWARE = [ 'corsheaders.middleware.CorsMiddleware', 'django.middleware.security.SecurityMiddleware',