From 9c873127a58d0fde1b658cef42dee64d267ad2bb Mon Sep 17 00:00:00 2001 From: Tiago Rascazzi Date: Sat, 8 Jan 2022 11:07:33 -0500 Subject: [PATCH 1/6] Added loading page --- .directory | 3 + .env.template | 5 +- cookbook/forms.py | 2 + cookbook/integration/default.py | 9 +- cookbook/integration/integration.py | 75 ++- cookbook/integration/pdfexport.py | 32 +- cookbook/integration/recipesage.py | 6 +- cookbook/integration/saffron.py | 6 +- cookbook/migrations/0164_exportlog.py | 32 + .../0165_exportlog_cache_duration.py | 18 + .../0166_exportlog_possibly_not_expired.py | 18 + cookbook/models.py | 19 + cookbook/serializer.py | 16 +- cookbook/static/css/app.min.css | 7 + cookbook/templates/export.html | 42 +- cookbook/templates/export_response.html | 32 + cookbook/urls.py | 5 +- cookbook/views/api.py | 15 +- cookbook/views/import_export.py | 46 +- recipes/settings.py | 2 + .../ExportResponseView/ExportResponseView.vue | 145 +++++ vue/src/apps/ExportResponseView/main.js | 18 + vue/src/apps/ExportView/ExportView.vue | 167 +++++ vue/src/apps/ExportView/main.js | 18 + vue/src/utils/openapi/api.ts | 575 ++++++++++++++++-- vue/vue.config.js | 8 + 26 files changed, 1212 insertions(+), 109 deletions(-) create mode 100644 .directory create mode 100644 cookbook/migrations/0164_exportlog.py create mode 100644 cookbook/migrations/0165_exportlog_cache_duration.py create mode 100644 cookbook/migrations/0166_exportlog_possibly_not_expired.py create mode 100644 cookbook/templates/export_response.html create mode 100644 vue/src/apps/ExportResponseView/ExportResponseView.vue create mode 100644 vue/src/apps/ExportResponseView/main.js create mode 100644 vue/src/apps/ExportView/ExportView.vue create mode 100644 vue/src/apps/ExportView/main.js diff --git a/.directory b/.directory new file mode 100644 index 00000000..280ecee9 --- /dev/null +++ b/.directory @@ -0,0 +1,3 @@ +[Dolphin] +Timestamp=2022,1,7,19,23,46.14 +Version=4 diff --git a/.env.template b/.env.template index aa13a7a9..01835438 100644 --- a/.env.template +++ b/.env.template @@ -148,4 +148,7 @@ REVERSE_PROXY_AUTH=0 # Enables exporting PDF (see export docs) # Disabled by default, uncomment to enable -# ENABLE_PDF_EXPORT=1 \ No newline at end of file +# ENABLE_PDF_EXPORT=1 + +# Duration to keep the cached export file +EXPORT_FILE_CACHE_DURATION=600 \ No newline at end of file diff --git a/cookbook/forms.py b/cookbook/forms.py index c7a03706..31f3850f 100644 --- a/cookbook/forms.py +++ b/cookbook/forms.py @@ -167,6 +167,7 @@ class ImportExportBase(forms.Form): )) +#TODO deprecated class ImportForm(ImportExportBase): files = forms.FileField(required=True, widget=forms.ClearableFileInput(attrs={'multiple': True})) duplicates = forms.BooleanField(help_text=_( @@ -174,6 +175,7 @@ class ImportForm(ImportExportBase): required=False) +#TODO deprecated class ExportForm(ImportExportBase): recipes = forms.ModelMultipleChoiceField(widget=MultiSelectWidget, queryset=Recipe.objects.none(), required=False) all = forms.BooleanField(required=False) diff --git a/cookbook/integration/default.py b/cookbook/integration/default.py index 39c0bc66..6a2b3410 100644 --- a/cookbook/integration/default.py +++ b/cookbook/integration/default.py @@ -32,11 +32,12 @@ class Default(Integration): return None def get_file_from_recipe(self, recipe): + export = RecipeExportSerializer(recipe).data return 'recipe.json', JSONRenderer().render(export).decode("utf-8") - def get_files_from_recipes(self, recipes, cookie): + def get_files_from_recipes(self, recipes, el, cookie): export_zip_stream = BytesIO() export_zip_obj = ZipFile(export_zip_stream, 'w') @@ -50,6 +51,7 @@ class Default(Integration): recipe_stream.write(data) recipe_zip_obj.writestr(filename, recipe_stream.getvalue()) recipe_stream.close() + try: recipe_zip_obj.writestr(f'image{get_filetype(r.image.file.name)}', r.image.file.read()) except ValueError: @@ -57,6 +59,11 @@ class Default(Integration): recipe_zip_obj.close() export_zip_obj.writestr(str(r.pk) + '.zip', recipe_zip_stream.getvalue()) + + el.exported_recipes += 1 + el.msg += self.recipe_processed_msg(r) + el.save() + export_zip_obj.close() return [[ 'export.zip', export_zip_stream.getvalue() ]] \ No newline at end of file diff --git a/cookbook/integration/integration.py b/cookbook/integration/integration.py index 3b8034e3..11524a38 100644 --- a/cookbook/integration/integration.py +++ b/cookbook/integration/integration.py @@ -1,9 +1,12 @@ +import time import datetime import json import traceback import uuid from io import BytesIO, StringIO from zipfile import BadZipFile, ZipFile +from django.core.cache import cache + from bs4 import Tag from django.core.exceptions import ObjectDoesNotExist @@ -18,6 +21,7 @@ from cookbook.forms import ImportExportBase from cookbook.helper.image_processing import get_filetype, handle_image from cookbook.models import Keyword, Recipe from recipes.settings import DEBUG +from recipes.settings import EXPORT_FILE_CACHE_DURATION class Integration: @@ -61,35 +65,37 @@ class Integration: space=request.space ) - def do_export(self, recipes): - """ - Perform the export based on a list of recipes - :param recipes: list of recipe objects - :return: HttpResponse with the file of the requested export format that is directly downloaded (When that format involve multiple files they are zipped together) - """ - - files = self.get_files_from_recipes(recipes, self.request.COOKIES) - - if len(files) == 1: - filename, file = files[0] - export_filename = filename - export_file = file - - else: - export_filename = "export.zip" - export_stream = BytesIO() - export_obj = ZipFile(export_stream, 'w') - - for filename, file in files: - export_obj.writestr(filename, file) - - export_obj.close() - export_file = export_stream.getvalue() - response = HttpResponse(export_file, content_type='application/force-download') - response['Content-Disposition'] = 'attachment; filename="'+export_filename+'"' - return response + def do_export(self, recipes, el): + + with scope(space=self.request.space): + el.total_recipes = len(recipes) + el.cache_duration = EXPORT_FILE_CACHE_DURATION + el.save() + + files = self.get_files_from_recipes(recipes, el, self.request.COOKIES) + + if len(files) == 1: + filename, file = files[0] + export_filename = filename + export_file = file + + else: + export_filename = "export.zip" + export_stream = BytesIO() + export_obj = ZipFile(export_stream, 'w') + + for filename, file in files: + export_obj.writestr(filename, file) + + export_obj.close() + export_file = export_stream.getvalue() + + + cache.set('export_file_'+str(el.pk), {'filename': export_filename, 'file': export_file}, EXPORT_FILE_CACHE_DURATION) + el.running = False + el.save() def import_file_name_filter(self, zip_info_object): @@ -128,7 +134,7 @@ class Integration: for d in data_list: recipe = self.get_recipe_from_file(d) recipe.keywords.add(self.keyword) - il.msg += f'{recipe.pk} - {recipe.name} \n' + il.msg += self.recipe_processed_msg(recipe) self.handle_duplicates(recipe, import_duplicates) il.imported_recipes += 1 il.save() @@ -153,7 +159,7 @@ class Integration: else: recipe = self.get_recipe_from_file(BytesIO(import_zip.read(z.filename))) recipe.keywords.add(self.keyword) - il.msg += f'{recipe.pk} - {recipe.name} \n' + il.msg += self.recipe_processed_msg(recipe) self.handle_duplicates(recipe, import_duplicates) il.imported_recipes += 1 il.save() @@ -168,7 +174,7 @@ class Integration: try: recipe = self.get_recipe_from_file(d) recipe.keywords.add(self.keyword) - il.msg += f'{recipe.pk} - {recipe.name} \n' + il.msg += self.recipe_processed_msg(recipe) self.handle_duplicates(recipe, import_duplicates) il.imported_recipes += 1 il.save() @@ -185,7 +191,7 @@ class Integration: try: recipe = self.get_recipe_from_file(d) recipe.keywords.add(self.keyword) - il.msg += f'{recipe.pk} - {recipe.name} \n' + il.msg += self.recipe_processed_msg(recipe) self.handle_duplicates(recipe, import_duplicates) il.imported_recipes += 1 il.save() @@ -195,7 +201,7 @@ class Integration: else: recipe = self.get_recipe_from_file(f['file']) recipe.keywords.add(self.keyword) - il.msg += f'{recipe.pk} - {recipe.name} \n' + il.msg += self.recipe_processed_msg(recipe) self.handle_duplicates(recipe, import_duplicates) except BadZipFile: il.msg += 'ERROR ' + _( @@ -263,7 +269,7 @@ class Integration: raise NotImplementedError('Method not implemented in integration') - def get_files_from_recipes(self, recipes, cookie): + def get_files_from_recipes(self, recipes, el, cookie): """ Takes a list of recipe object and converts it to a array containing each file. Each file is represented as an array [filename, data] where data is a string of the content of the file. @@ -282,3 +288,6 @@ class Integration: log.msg += exception.msg if DEBUG: traceback.print_exc() + + def recipe_processed_msg(self, recipe): + return f'{recipe.pk} - {recipe.name} \n' diff --git a/cookbook/integration/pdfexport.py b/cookbook/integration/pdfexport.py index b982f24d..9f500480 100644 --- a/cookbook/integration/pdfexport.py +++ b/cookbook/integration/pdfexport.py @@ -11,22 +11,25 @@ from cookbook.helper.image_processing import get_filetype from cookbook.integration.integration import Integration from cookbook.serializer import RecipeExportSerializer -import django.core.management.commands.runserver as runserver +from cookbook.models import ExportLog +from asgiref.sync import sync_to_async +import django.core.management.commands.runserver as runserver +import logging 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): + async def get_files_from_recipes_async(self, recipes, el, cookie): cmd = runserver.Command() browser = await launch( handleSIGINT=False, handleSIGTERM=False, handleSIGHUP=False, - ignoreHTTPSErrors=True + ignoreHTTPSErrors=True, ) cookies = {'domain': cmd.default_addr, 'name': 'sessionid', 'value': cookie['sessionid'], } @@ -39,17 +42,28 @@ class PDFexport(Integration): } } - page = await browser.newPage() - await page.emulateMedia('print') - await page.setCookie(cookies) files = [] for recipe in recipes: - await page.goto('http://' + cmd.default_addr + ':' + cmd.default_port + '/view/recipe/' + str(recipe.id), {'waitUntil': 'networkidle0', }) + + page = await browser.newPage() + await page.emulateMedia('print') + await page.setCookie(cookies) + + await page.goto('http://'+cmd.default_addr+':'+cmd.default_port+'/view/recipe/'+str(recipe.id), {'waitUntil': 'domcontentloaded'}) + await page.waitForSelector('h3'); + files.append([recipe.name + '.pdf', await page.pdf(options)]) + await page.close(); + + el.exported_recipes += 1 + el.msg += self.recipe_processed_msg(recipe) + await sync_to_async(el.save, thread_sensitive=True)() + await browser.close() return files - def get_files_from_recipes(self, recipes, cookie): - return asyncio.run(self.get_files_from_recipes_async(recipes, cookie)) + + def get_files_from_recipes(self, recipes, el, cookie): + return asyncio.run(self.get_files_from_recipes_async(recipes, el, cookie)) diff --git a/cookbook/integration/recipesage.py b/cookbook/integration/recipesage.py index 0ca32194..93dee885 100644 --- a/cookbook/integration/recipesage.py +++ b/cookbook/integration/recipesage.py @@ -88,11 +88,15 @@ class RecipeSage(Integration): return data - def get_files_from_recipes(self, recipes, cookie): + def get_files_from_recipes(self, recipes, el, cookie): json_list = [] for r in recipes: json_list.append(self.get_file_from_recipe(r)) + el.exported_recipes += 1 + el.msg += self.recipe_processed_msg(r) + el.save() + return [['export.json', json.dumps(json_list)]] def split_recipe_file(self, file): diff --git a/cookbook/integration/saffron.py b/cookbook/integration/saffron.py index 16a93a0c..63342b2b 100644 --- a/cookbook/integration/saffron.py +++ b/cookbook/integration/saffron.py @@ -87,10 +87,14 @@ class Saffron(Integration): return recipe.name+'.txt', data - def get_files_from_recipes(self, recipes, cookie): + def get_files_from_recipes(self, recipes, el, cookie): files = [] for r in recipes: filename, data = self.get_file_from_recipe(r) files.append([ filename, data ]) + el.exported_recipes += 1 + el.msg += self.recipe_processed_msg(r) + el.save() + return files \ No newline at end of file diff --git a/cookbook/migrations/0164_exportlog.py b/cookbook/migrations/0164_exportlog.py new file mode 100644 index 00000000..952c9372 --- /dev/null +++ b/cookbook/migrations/0164_exportlog.py @@ -0,0 +1,32 @@ +# Generated by Django 3.2.11 on 2022-01-07 20:29 + +import cookbook.models +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('cookbook', '0163_auto_20220105_0758'), + ] + + operations = [ + migrations.CreateModel( + name='ExportLog', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('type', models.CharField(max_length=32)), + ('running', models.BooleanField(default=True)), + ('msg', models.TextField(default='')), + ('total_recipes', models.IntegerField(default=0)), + ('exported_recipes', models.IntegerField(default=0)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')), + ], + bases=(models.Model, cookbook.models.PermissionModelMixin), + ), + ] diff --git a/cookbook/migrations/0165_exportlog_cache_duration.py b/cookbook/migrations/0165_exportlog_cache_duration.py new file mode 100644 index 00000000..8005a4b2 --- /dev/null +++ b/cookbook/migrations/0165_exportlog_cache_duration.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.11 on 2022-01-08 00:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('cookbook', '0164_exportlog'), + ] + + operations = [ + migrations.AddField( + model_name='exportlog', + name='cache_duration', + field=models.IntegerField(default=0), + ), + ] diff --git a/cookbook/migrations/0166_exportlog_possibly_not_expired.py b/cookbook/migrations/0166_exportlog_possibly_not_expired.py new file mode 100644 index 00000000..15e19e13 --- /dev/null +++ b/cookbook/migrations/0166_exportlog_possibly_not_expired.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.11 on 2022-01-08 00:43 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('cookbook', '0165_exportlog_cache_duration'), + ] + + operations = [ + migrations.AddField( + model_name='exportlog', + name='possibly_not_expired', + field=models.BooleanField(default=True), + ), + ] diff --git a/cookbook/models.py b/cookbook/models.py index cfe4ab81..df9880a8 100644 --- a/cookbook/models.py +++ b/cookbook/models.py @@ -992,6 +992,25 @@ class ImportLog(models.Model, PermissionModelMixin): def __str__(self): return f"{self.created_at}:{self.type}" +class ExportLog(models.Model, PermissionModelMixin): + type = models.CharField(max_length=32) + running = models.BooleanField(default=True) + msg = models.TextField(default="") + + total_recipes = models.IntegerField(default=0) + exported_recipes = models.IntegerField(default=0) + cache_duration = models.IntegerField(default=0) + possibly_not_expired = models.BooleanField(default=True) + + created_at = models.DateTimeField(auto_now_add=True) + created_by = models.ForeignKey(User, on_delete=models.CASCADE) + + objects = ScopedManager(space='space') + space = models.ForeignKey(Space, on_delete=models.CASCADE) + + def __str__(self): + return f"{self.created_at}:{self.type}" + class BookmarkletImport(ExportModelOperationsMixin('bookmarklet_import'), models.Model, PermissionModelMixin): html = models.TextField() diff --git a/cookbook/serializer.py b/cookbook/serializer.py index ac623868..4d980087 100644 --- a/cookbook/serializer.py +++ b/cookbook/serializer.py @@ -14,7 +14,7 @@ from rest_framework.fields import empty from cookbook.helper.shopping_helper import list_from_recipe from cookbook.models import (Automation, BookmarkletImport, Comment, CookLog, Food, - FoodInheritField, ImportLog, Ingredient, Keyword, MealPlan, MealType, + FoodInheritField, ImportLog, ExportLog, Ingredient, Keyword, MealPlan, MealType, NutritionInformation, Recipe, RecipeBook, RecipeBookEntry, RecipeImport, ShareLink, ShoppingList, ShoppingListEntry, ShoppingListRecipe, Step, Storage, Supermarket, SupermarketCategory, @@ -841,6 +841,20 @@ class ImportLogSerializer(serializers.ModelSerializer): read_only_fields = ('created_by',) +class ExportLogSerializer(serializers.ModelSerializer): + + def create(self, validated_data): + validated_data['created_by'] = self.context['request'].user + validated_data['space'] = self.context['request'].space + return super().create(validated_data) + + class Meta: + model = ExportLog + fields = ('id', 'type', 'msg', 'running', 'total_recipes', 'exported_recipes', 'cache_duration', 'possibly_not_expired', 'created_by', 'created_at') + read_only_fields = ('created_by',) + + + class AutomationSerializer(serializers.ModelSerializer): def create(self, validated_data): diff --git a/cookbook/static/css/app.min.css b/cookbook/static/css/app.min.css index 7c680a78..a100e2c2 100644 --- a/cookbook/static/css/app.min.css +++ b/cookbook/static/css/app.min.css @@ -1140,3 +1140,10 @@ min-width: 28rem; } } + +@media print{ + #switcher{ + display: none; + } + +} \ No newline at end of file diff --git a/cookbook/templates/export.html b/cookbook/templates/export.html index 4133da93..ca514823 100644 --- a/cookbook/templates/export.html +++ b/cookbook/templates/export.html @@ -1,25 +1,33 @@ {% extends "base.html" %} -{% load crispy_forms_filters %} -{% load i18n %} +{% load render_bundle from webpack_loader %} {% load static %} +{% load i18n %} +{% load l10n %} + {% block title %}{% trans 'Export Recipes' %}{% endblock %} -{% block extra_head %} - {{ form.media }} + +{% block content %} +
+ +
+{% endblock %} + + +{% block script %} + {% if debug %} + + {% else %} + + {% endif %} + + + + {% render_bundle 'export_view' %} {% endblock %} -{% block content %} -

{% trans 'Export' %}

-
-
-
- {% csrf_token %} - {{ form|crispy }} - -
-
-
-{% endblock %} \ No newline at end of file diff --git a/cookbook/templates/export_response.html b/cookbook/templates/export_response.html new file mode 100644 index 00000000..1f438632 --- /dev/null +++ b/cookbook/templates/export_response.html @@ -0,0 +1,32 @@ +{% extends "base.html" %} +{% load render_bundle from webpack_loader %} +{% load static %} +{% load i18n %} +{% load l10n %} + +{% block title %}{% trans 'Export' %}{% endblock %} + +{% block content %} + +
+ +
+ + +{% endblock %} + + +{% block script %} + {% if debug %} + + {% else %} + + {% endif %} + + + + {% render_bundle 'export_response_view' %} +{% endblock %} \ No newline at end of file diff --git a/cookbook/urls.py b/cookbook/urls.py index 68ac160b..ac7dd838 100644 --- a/cookbook/urls.py +++ b/cookbook/urls.py @@ -21,6 +21,7 @@ router.register(r'cook-log', api.CookLogViewSet) router.register(r'food', api.FoodViewSet) router.register(r'food-inherit-field', api.FoodInheritFieldViewSet) router.register(r'import-log', api.ImportLogViewSet) +router.register(r'export-log', api.ExportLogViewSet) router.register(r'ingredient', api.IngredientViewSet) router.register(r'keyword', api.KeywordViewSet) router.register(r'meal-plan', api.MealPlanViewSet) @@ -72,8 +73,10 @@ urlpatterns = [ path('abuse/', views.report_share_abuse, name='view_report_share_abuse'), path('import/', import_export.import_recipe, name='view_import'), - path('import-response//', import_export.import_response, name='view_import_response'), + path('import-response//', import_export.import_response, name='view_import_response'),\ path('export/', import_export.export_recipe, name='view_export'), + path('export-response//', import_export.export_response, name='view_export_response'), + path('export-file//', import_export.export_file, name='view_export_file'), path('view/recipe/', views.recipe_view, name='view_recipe'), path('view/recipe//', views.recipe_view, name='view_recipe'), diff --git a/cookbook/views/api.py b/cookbook/views/api.py index 86f97660..3ef9db87 100644 --- a/cookbook/views/api.py +++ b/cookbook/views/api.py @@ -40,7 +40,7 @@ from cookbook.helper.recipe_search import get_facet, old_search, search_recipes from cookbook.helper.recipe_url_import import get_from_scraper from cookbook.helper.shopping_helper import list_from_recipe, shopping_helper from cookbook.models import (Automation, BookmarkletImport, CookLog, Food, FoodInheritField, - ImportLog, Ingredient, Keyword, MealPlan, MealType, Recipe, RecipeBook, + ImportLog, ExportLog, Ingredient, Keyword, MealPlan, MealType, Recipe, RecipeBook, RecipeBookEntry, ShareLink, ShoppingList, ShoppingListEntry, ShoppingListRecipe, Step, Storage, Supermarket, SupermarketCategory, SupermarketCategoryRelation, Sync, SyncLog, Unit, UserFile, @@ -51,7 +51,7 @@ from cookbook.provider.nextcloud import Nextcloud from cookbook.schemas import FilterSchema, QueryParam, QueryParamAutoSchema, TreeSchema from cookbook.serializer import (AutomationSerializer, BookmarkletImportSerializer, CookLogSerializer, FoodInheritFieldSerializer, FoodSerializer, - FoodShoppingUpdateSerializer, ImportLogSerializer, + FoodShoppingUpdateSerializer, ImportLogSerializer, ExportLogSerializer, IngredientSerializer, KeywordSerializer, MealPlanSerializer, MealTypeSerializer, RecipeBookEntrySerializer, RecipeBookSerializer, RecipeImageSerializer, @@ -799,6 +799,17 @@ class ImportLogViewSet(viewsets.ModelViewSet): return self.queryset.filter(space=self.request.space) +class ExportLogViewSet(viewsets.ModelViewSet): + queryset = ExportLog.objects + serializer_class = ExportLogSerializer + permission_classes = [CustomIsUser] + pagination_class = DefaultPagination + + def get_queryset(self): + return self.queryset.filter(space=self.request.space) + + + class BookmarkletImportViewSet(viewsets.ModelViewSet): queryset = BookmarkletImport.objects serializer_class = BookmarkletImportSerializer diff --git a/cookbook/views/import_export.py b/cookbook/views/import_export.py index 9fd810c1..c1530b00 100644 --- a/cookbook/views/import_export.py +++ b/cookbook/views/import_export.py @@ -1,9 +1,10 @@ import re import threading from io import BytesIO +from django.core.cache import cache from django.contrib import messages -from django.http import HttpResponseRedirect, JsonResponse +from django.http import HttpResponse, HttpResponseRedirect, JsonResponse from django.shortcuts import render from django.urls import reverse from django.utils.translation import gettext as _ @@ -29,7 +30,7 @@ from cookbook.integration.recipesage import RecipeSage 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 cookbook.models import Recipe, ImportLog, ExportLog, UserPreference from recipes import settings @@ -123,13 +124,23 @@ def export_recipe(request): 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) + + el = ExportLog.objects.create(type=form.cleaned_data['type'], created_by=request.user, space=request.space) + + t = threading.Thread(target=integration.do_export, args=[recipes, el]) + t.setDaemon(True) + t.start() + + return JsonResponse({'export_id': el.pk}) except NotImplementedError: - messages.add_message(request, messages.ERROR, _('Exporting is not implemented for this provider')) + return JsonResponse( + { + 'error': True, + 'msg': _('Importing is not implemented for this provider') + }, + status=400 + ) else: form = ExportForm(space=request.space) @@ -145,3 +156,24 @@ def export_recipe(request): @group_required('user') def import_response(request, pk): return render(request, 'import_response.html', {'pk': pk}) + +@group_required('user') +def export_response(request, pk): + return render(request, 'export_response.html', {'pk': pk}) + + +@group_required('user') +def export_file(request, pk): + + cacheData = cache.get('export_file_'+str(pk)) + + if cacheData is None: + el = ExportLog.objects.get(pk=pk) + el.possibly_not_expired = False; + el.save() + return render(request, 'export_response.html', {'pk': pk}) + + response = HttpResponse(cacheData['file'], content_type='application/force-download') + response['Content-Disposition'] = 'attachment; filename="'+cacheData['filename']+'"' + return response + diff --git a/recipes/settings.py b/recipes/settings.py index 9b9dacbb..9f091533 100644 --- a/recipes/settings.py +++ b/recipes/settings.py @@ -138,6 +138,7 @@ 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))) +EXPORT_FILE_CACHE_DURATION = int(os.getenv('EXPORT_FILE_CACHE_DURATION', 600)) MIDDLEWARE = [ 'corsheaders.middleware.CorsMiddleware', @@ -425,3 +426,4 @@ EMAIL_USE_TLS = bool(int(os.getenv('EMAIL_USE_TLS', False))) EMAIL_USE_SSL = bool(int(os.getenv('EMAIL_USE_SSL', False))) DEFAULT_FROM_EMAIL = os.getenv('DEFAULT_FROM_EMAIL', 'webmaster@localhost') ACCOUNT_EMAIL_SUBJECT_PREFIX = os.getenv('ACCOUNT_EMAIL_SUBJECT_PREFIX', '[Tandoor Recipes] ') # allauth sender prefix + diff --git a/vue/src/apps/ExportResponseView/ExportResponseView.vue b/vue/src/apps/ExportResponseView/ExportResponseView.vue new file mode 100644 index 00000000..913f4366 --- /dev/null +++ b/vue/src/apps/ExportResponseView/ExportResponseView.vue @@ -0,0 +1,145 @@ + + + + + diff --git a/vue/src/apps/ExportResponseView/main.js b/vue/src/apps/ExportResponseView/main.js new file mode 100644 index 00000000..220ac3be --- /dev/null +++ b/vue/src/apps/ExportResponseView/main.js @@ -0,0 +1,18 @@ +import Vue from 'vue' +import App from './ExportResponseView.vue' +import i18n from '@/i18n' + +Vue.config.productionTip = false + +// TODO move this and other default stuff to centralized JS file (verify nothing breaks) +let publicPath = localStorage.STATIC_URL + 'vue/' +if (process.env.NODE_ENV === 'development') { + publicPath = 'http://localhost:8080/' +} +export default __webpack_public_path__ = publicPath // eslint-disable-line + + +new Vue({ + i18n, + render: h => h(App), +}).$mount('#app') diff --git a/vue/src/apps/ExportView/ExportView.vue b/vue/src/apps/ExportView/ExportView.vue new file mode 100644 index 00000000..a9bbaceb --- /dev/null +++ b/vue/src/apps/ExportView/ExportView.vue @@ -0,0 +1,167 @@ + + + + + + + diff --git a/vue/src/apps/ExportView/main.js b/vue/src/apps/ExportView/main.js new file mode 100644 index 00000000..8c8af8e5 --- /dev/null +++ b/vue/src/apps/ExportView/main.js @@ -0,0 +1,18 @@ +import Vue from 'vue' +import App from './ExportView.vue' +import i18n from '@/i18n' + +Vue.config.productionTip = false + +// TODO move this and other default stuff to centralized JS file (verify nothing breaks) +let publicPath = localStorage.STATIC_URL + 'vue/' +if (process.env.NODE_ENV === 'development') { + publicPath = 'http://localhost:8080/' +} +export default __webpack_public_path__ = publicPath // eslint-disable-line + + +new Vue({ + i18n, + render: h => h(App), +}).$mount('#app') diff --git a/vue/src/utils/openapi/api.ts b/vue/src/utils/openapi/api.ts index 8a8c1cff..0466eeb7 100644 --- a/vue/src/utils/openapi/api.ts +++ b/vue/src/utils/openapi/api.ts @@ -173,6 +173,73 @@ export interface CookLog { */ created_at?: string; } +/** + * + * @export + * @interface ExportLog + */ +export interface ExportLog { + /** + * + * @type {number} + * @memberof ExportLog + */ + id?: number; + /** + * + * @type {string} + * @memberof ExportLog + */ + type: string; + /** + * + * @type {string} + * @memberof ExportLog + */ + msg?: string; + /** + * + * @type {boolean} + * @memberof ExportLog + */ + running?: boolean; + /** + * + * @type {number} + * @memberof ExportLog + */ + total_recipes?: number; + /** + * + * @type {number} + * @memberof ExportLog + */ + exported_recipes?: number; + /** + * + * @type {number} + * @memberof ExportLog + */ + cache_duration?: number; + /** + * + * @type {boolean} + * @memberof ExportLog + */ + possibly_not_expired?: boolean; + /** + * + * @type {string} + * @memberof ExportLog + */ + created_by?: string; + /** + * + * @type {string} + * @memberof ExportLog + */ + created_at?: string; +} /** * * @export @@ -211,10 +278,10 @@ export interface Food { recipe?: FoodRecipe | null; /** * - * @type {boolean} + * @type {string} * @memberof Food */ - food_onhand?: boolean; + food_onhand?: string | null; /** * * @type {FoodSupermarketCategory} @@ -607,10 +674,10 @@ export interface IngredientFood { recipe?: FoodRecipe | null; /** * - * @type {boolean} + * @type {string} * @memberof IngredientFood */ - food_onhand?: boolean; + food_onhand?: string | null; /** * * @type {FoodSupermarketCategory} @@ -704,6 +771,37 @@ export interface InlineResponse2001 { */ results?: Array; } +/** + * + * @export + * @interface InlineResponse20010 + */ +export interface InlineResponse20010 { + /** + * + * @type {number} + * @memberof InlineResponse20010 + */ + count?: number; + /** + * + * @type {string} + * @memberof InlineResponse20010 + */ + next?: string | null; + /** + * + * @type {string} + * @memberof InlineResponse20010 + */ + previous?: string | null; + /** + * + * @type {Array} + * @memberof InlineResponse20010 + */ + results?: Array; +} /** * * @export @@ -761,10 +859,10 @@ export interface InlineResponse2003 { previous?: string | null; /** * - * @type {Array} + * @type {Array} * @memberof InlineResponse2003 */ - results?: Array; + results?: Array; } /** * @@ -792,10 +890,10 @@ export interface InlineResponse2004 { previous?: string | null; /** * - * @type {Array} + * @type {Array} * @memberof InlineResponse2004 */ - results?: Array; + results?: Array; } /** * @@ -823,10 +921,10 @@ export interface InlineResponse2005 { previous?: string | null; /** * - * @type {Array} + * @type {Array} * @memberof InlineResponse2005 */ - results?: Array; + results?: Array; } /** * @@ -854,10 +952,10 @@ export interface InlineResponse2006 { previous?: string | null; /** * - * @type {Array} + * @type {Array} * @memberof InlineResponse2006 */ - results?: Array; + results?: Array; } /** * @@ -885,10 +983,10 @@ export interface InlineResponse2007 { previous?: string | null; /** * - * @type {Array} + * @type {Array} * @memberof InlineResponse2007 */ - results?: Array; + results?: Array; } /** * @@ -916,10 +1014,10 @@ export interface InlineResponse2008 { previous?: string | null; /** * - * @type {Array} + * @type {Array} * @memberof InlineResponse2008 */ - results?: Array; + results?: Array; } /** * @@ -947,10 +1045,10 @@ export interface InlineResponse2009 { previous?: string | null; /** * - * @type {Array} + * @type {Array} * @memberof InlineResponse2009 */ - results?: Array; + results?: Array; } /** * @@ -1182,7 +1280,7 @@ export interface MealPlanRecipe { * @type {any} * @memberof MealPlanRecipe */ - image?: any; + image?: any | null; /** * * @type {Array} @@ -1372,7 +1470,7 @@ export interface Recipe { * @type {any} * @memberof Recipe */ - image?: any; + image?: any | null; /** * * @type {Array} @@ -1770,7 +1868,7 @@ export interface RecipeOverview { * @type {any} * @memberof RecipeOverview */ - image?: any; + image?: any | null; /** * * @type {Array} @@ -3054,6 +3152,12 @@ export interface UserPreference { * @memberof UserPreference */ filter_to_supermarket?: boolean; + /** + * + * @type {boolean} + * @memberof UserPreference + */ + shopping_add_onhand?: boolean; } /** @@ -3237,6 +3341,39 @@ export const ApiApiAxiosParamCreator = function (configuration?: Configuration) options: localVarRequestOptions, }; }, + /** + * + * @param {ExportLog} [exportLog] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + createExportLog: async (exportLog?: ExportLog, options: any = {}): Promise => { + const localVarPath = `/api/export-log/`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(exportLog, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * * @param {Food} [food] @@ -4075,6 +4212,39 @@ export const ApiApiAxiosParamCreator = function (configuration?: Configuration) + setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {string} id A unique integer value identifying this export log. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + destroyExportLog: async (id: string, options: any = {}): Promise => { + // verify required parameter 'id' is not null or undefined + assertParamExists('destroyExportLog', 'id', id) + const localVarPath = `/api/export-log/{id}/` + .replace(`{${"id"}}`, encodeURIComponent(String(id))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'DELETE', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; @@ -4940,6 +5110,45 @@ export const ApiApiAxiosParamCreator = function (configuration?: Configuration) + setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {number} [page] A page number within the paginated result set. + * @param {number} [pageSize] Number of results to return per page. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + listExportLogs: async (page?: number, pageSize?: number, options: any = {}): Promise => { + const localVarPath = `/api/export-log/`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + if (page !== undefined) { + localVarQueryParameter['page'] = page; + } + + if (pageSize !== undefined) { + localVarQueryParameter['page_size'] = pageSize; + } + + + setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; @@ -6200,6 +6409,43 @@ export const ApiApiAxiosParamCreator = function (configuration?: Configuration) options: localVarRequestOptions, }; }, + /** + * + * @param {string} id A unique integer value identifying this export log. + * @param {ExportLog} [exportLog] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + partialUpdateExportLog: async (id: string, exportLog?: ExportLog, options: any = {}): Promise => { + // verify required parameter 'id' is not null or undefined + assertParamExists('partialUpdateExportLog', 'id', id) + const localVarPath = `/api/export-log/{id}/` + .replace(`{${"id"}}`, encodeURIComponent(String(id))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'PATCH', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(exportLog, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * * @param {string} id A unique integer value identifying this food. @@ -7159,6 +7405,39 @@ export const ApiApiAxiosParamCreator = function (configuration?: Configuration) + setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {string} id A unique integer value identifying this export log. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + retrieveExportLog: async (id: string, options: any = {}): Promise => { + // verify required parameter 'id' is not null or undefined + assertParamExists('retrieveExportLog', 'id', id) + const localVarPath = `/api/export-log/{id}/` + .replace(`{${"id"}}`, encodeURIComponent(String(id))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; @@ -8178,6 +8457,43 @@ export const ApiApiAxiosParamCreator = function (configuration?: Configuration) options: localVarRequestOptions, }; }, + /** + * + * @param {string} id A unique integer value identifying this export log. + * @param {ExportLog} [exportLog] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + updateExportLog: async (id: string, exportLog?: ExportLog, options: any = {}): Promise => { + // verify required parameter 'id' is not null or undefined + assertParamExists('updateExportLog', 'id', id) + const localVarPath = `/api/export-log/{id}/` + .replace(`{${"id"}}`, encodeURIComponent(String(id))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'PUT', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(exportLog, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * * @param {string} id A unique integer value identifying this food. @@ -9054,6 +9370,16 @@ export const ApiApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.createCookLog(cookLog, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @param {ExportLog} [exportLog] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async createExportLog(exportLog?: ExportLog, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.createExportLog(exportLog, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * * @param {Food} [food] @@ -9307,6 +9633,16 @@ export const ApiApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.destroyCookLog(id, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @param {string} id A unique integer value identifying this export log. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async destroyExportLog(id: string, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.destroyExportLog(id, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * * @param {string} id A unique integer value identifying this food. @@ -9567,6 +9903,17 @@ export const ApiApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.listCookLogs(page, pageSize, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @param {number} [page] A page number within the paginated result set. + * @param {number} [pageSize] Number of results to return per page. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async listExportLogs(page?: number, pageSize?: number, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.listExportLogs(page, pageSize, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * * @param {*} [options] Override http request option. @@ -9620,7 +9967,7 @@ export const ApiApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async listKeywords(query?: string, root?: number, tree?: number, page?: number, pageSize?: number, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + async listKeywords(query?: string, root?: number, tree?: number, page?: number, pageSize?: number, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { const localVarAxiosArgs = await localVarAxiosParamCreator.listKeywords(query, root, tree, page, pageSize, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, @@ -9679,7 +10026,7 @@ export const ApiApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async listRecipes(query?: string, keywords?: number, foods?: number, units?: number, rating?: number, books?: string, keywordsOr?: string, foodsOr?: string, booksOr?: string, internal?: string, random?: string, _new?: string, page?: number, pageSize?: number, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + async listRecipes(query?: string, keywords?: number, foods?: number, units?: number, rating?: number, books?: string, keywordsOr?: string, foodsOr?: string, booksOr?: string, internal?: string, random?: string, _new?: string, page?: number, pageSize?: number, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { const localVarAxiosArgs = await localVarAxiosParamCreator.listRecipes(query, keywords, foods, units, rating, books, keywordsOr, foodsOr, booksOr, internal, random, _new, page, pageSize, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, @@ -9722,7 +10069,7 @@ export const ApiApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async listSteps(recipe?: number, query?: string, page?: number, pageSize?: number, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + async listSteps(recipe?: number, query?: string, page?: number, pageSize?: number, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { const localVarAxiosArgs = await localVarAxiosParamCreator.listSteps(recipe, query, page, pageSize, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, @@ -9742,7 +10089,7 @@ export const ApiApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async listSupermarketCategoryRelations(page?: number, pageSize?: number, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + async listSupermarketCategoryRelations(page?: number, pageSize?: number, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { const localVarAxiosArgs = await localVarAxiosParamCreator.listSupermarketCategoryRelations(page, pageSize, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, @@ -9771,7 +10118,7 @@ export const ApiApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async listSyncLogs(page?: number, pageSize?: number, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + async listSyncLogs(page?: number, pageSize?: number, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { const localVarAxiosArgs = await localVarAxiosParamCreator.listSyncLogs(page, pageSize, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, @@ -9792,7 +10139,7 @@ export const ApiApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async listUnits(query?: string, page?: number, pageSize?: number, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + async listUnits(query?: string, page?: number, pageSize?: number, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { const localVarAxiosArgs = await localVarAxiosParamCreator.listUnits(query, page, pageSize, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, @@ -9830,7 +10177,7 @@ export const ApiApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async listViewLogs(page?: number, pageSize?: number, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + async listViewLogs(page?: number, pageSize?: number, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { const localVarAxiosArgs = await localVarAxiosParamCreator.listViewLogs(page, pageSize, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, @@ -9927,6 +10274,17 @@ export const ApiApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.partialUpdateCookLog(id, cookLog, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @param {string} id A unique integer value identifying this export log. + * @param {ExportLog} [exportLog] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async partialUpdateExportLog(id: string, exportLog?: ExportLog, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.partialUpdateExportLog(id, exportLog, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * * @param {string} id A unique integer value identifying this food. @@ -10212,6 +10570,16 @@ export const ApiApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.retrieveCookLog(id, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @param {string} id A unique integer value identifying this export log. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async retrieveExportLog(id: string, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.retrieveExportLog(id, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * * @param {string} id A unique integer value identifying this food. @@ -10517,6 +10885,17 @@ export const ApiApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.updateCookLog(id, cookLog, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @param {string} id A unique integer value identifying this export log. + * @param {ExportLog} [exportLog] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async updateExportLog(id: string, exportLog?: ExportLog, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.updateExportLog(id, exportLog, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * * @param {string} id A unique integer value identifying this food. @@ -10799,6 +11178,15 @@ export const ApiApiFactory = function (configuration?: Configuration, basePath?: createCookLog(cookLog?: CookLog, options?: any): AxiosPromise { return localVarFp.createCookLog(cookLog, options).then((request) => request(axios, basePath)); }, + /** + * + * @param {ExportLog} [exportLog] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + createExportLog(exportLog?: ExportLog, options?: any): AxiosPromise { + return localVarFp.createExportLog(exportLog, options).then((request) => request(axios, basePath)); + }, /** * * @param {Food} [food] @@ -11027,6 +11415,15 @@ export const ApiApiFactory = function (configuration?: Configuration, basePath?: destroyCookLog(id: string, options?: any): AxiosPromise { return localVarFp.destroyCookLog(id, options).then((request) => request(axios, basePath)); }, + /** + * + * @param {string} id A unique integer value identifying this export log. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + destroyExportLog(id: string, options?: any): AxiosPromise { + return localVarFp.destroyExportLog(id, options).then((request) => request(axios, basePath)); + }, /** * * @param {string} id A unique integer value identifying this food. @@ -11261,6 +11658,16 @@ export const ApiApiFactory = function (configuration?: Configuration, basePath?: listCookLogs(page?: number, pageSize?: number, options?: any): AxiosPromise { return localVarFp.listCookLogs(page, pageSize, options).then((request) => request(axios, basePath)); }, + /** + * + * @param {number} [page] A page number within the paginated result set. + * @param {number} [pageSize] Number of results to return per page. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + listExportLogs(page?: number, pageSize?: number, options?: any): AxiosPromise { + return localVarFp.listExportLogs(page, pageSize, options).then((request) => request(axios, basePath)); + }, /** * * @param {*} [options] Override http request option. @@ -11310,7 +11717,7 @@ export const ApiApiFactory = function (configuration?: Configuration, basePath?: * @param {*} [options] Override http request option. * @throws {RequiredError} */ - listKeywords(query?: string, root?: number, tree?: number, page?: number, pageSize?: number, options?: any): AxiosPromise { + listKeywords(query?: string, root?: number, tree?: number, page?: number, pageSize?: number, options?: any): AxiosPromise { return localVarFp.listKeywords(query, root, tree, page, pageSize, options).then((request) => request(axios, basePath)); }, /** @@ -11364,7 +11771,7 @@ export const ApiApiFactory = function (configuration?: Configuration, basePath?: * @param {*} [options] Override http request option. * @throws {RequiredError} */ - listRecipes(query?: string, keywords?: number, foods?: number, units?: number, rating?: number, books?: string, keywordsOr?: string, foodsOr?: string, booksOr?: string, internal?: string, random?: string, _new?: string, page?: number, pageSize?: number, options?: any): AxiosPromise { + listRecipes(query?: string, keywords?: number, foods?: number, units?: number, rating?: number, books?: string, keywordsOr?: string, foodsOr?: string, booksOr?: string, internal?: string, random?: string, _new?: string, page?: number, pageSize?: number, options?: any): AxiosPromise { return localVarFp.listRecipes(query, keywords, foods, units, rating, books, keywordsOr, foodsOr, booksOr, internal, random, _new, page, pageSize, options).then((request) => request(axios, basePath)); }, /** @@ -11403,7 +11810,7 @@ export const ApiApiFactory = function (configuration?: Configuration, basePath?: * @param {*} [options] Override http request option. * @throws {RequiredError} */ - listSteps(recipe?: number, query?: string, page?: number, pageSize?: number, options?: any): AxiosPromise { + listSteps(recipe?: number, query?: string, page?: number, pageSize?: number, options?: any): AxiosPromise { return localVarFp.listSteps(recipe, query, page, pageSize, options).then((request) => request(axios, basePath)); }, /** @@ -11421,7 +11828,7 @@ export const ApiApiFactory = function (configuration?: Configuration, basePath?: * @param {*} [options] Override http request option. * @throws {RequiredError} */ - listSupermarketCategoryRelations(page?: number, pageSize?: number, options?: any): AxiosPromise { + listSupermarketCategoryRelations(page?: number, pageSize?: number, options?: any): AxiosPromise { return localVarFp.listSupermarketCategoryRelations(page, pageSize, options).then((request) => request(axios, basePath)); }, /** @@ -11447,7 +11854,7 @@ export const ApiApiFactory = function (configuration?: Configuration, basePath?: * @param {*} [options] Override http request option. * @throws {RequiredError} */ - listSyncLogs(page?: number, pageSize?: number, options?: any): AxiosPromise { + listSyncLogs(page?: number, pageSize?: number, options?: any): AxiosPromise { return localVarFp.listSyncLogs(page, pageSize, options).then((request) => request(axios, basePath)); }, /** @@ -11466,7 +11873,7 @@ export const ApiApiFactory = function (configuration?: Configuration, basePath?: * @param {*} [options] Override http request option. * @throws {RequiredError} */ - listUnits(query?: string, page?: number, pageSize?: number, options?: any): AxiosPromise { + listUnits(query?: string, page?: number, pageSize?: number, options?: any): AxiosPromise { return localVarFp.listUnits(query, page, pageSize, options).then((request) => request(axios, basePath)); }, /** @@ -11500,7 +11907,7 @@ export const ApiApiFactory = function (configuration?: Configuration, basePath?: * @param {*} [options] Override http request option. * @throws {RequiredError} */ - listViewLogs(page?: number, pageSize?: number, options?: any): AxiosPromise { + listViewLogs(page?: number, pageSize?: number, options?: any): AxiosPromise { return localVarFp.listViewLogs(page, pageSize, options).then((request) => request(axios, basePath)); }, /** @@ -11588,6 +11995,16 @@ export const ApiApiFactory = function (configuration?: Configuration, basePath?: partialUpdateCookLog(id: string, cookLog?: CookLog, options?: any): AxiosPromise { return localVarFp.partialUpdateCookLog(id, cookLog, options).then((request) => request(axios, basePath)); }, + /** + * + * @param {string} id A unique integer value identifying this export log. + * @param {ExportLog} [exportLog] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + partialUpdateExportLog(id: string, exportLog?: ExportLog, options?: any): AxiosPromise { + return localVarFp.partialUpdateExportLog(id, exportLog, options).then((request) => request(axios, basePath)); + }, /** * * @param {string} id A unique integer value identifying this food. @@ -11847,6 +12264,15 @@ export const ApiApiFactory = function (configuration?: Configuration, basePath?: retrieveCookLog(id: string, options?: any): AxiosPromise { return localVarFp.retrieveCookLog(id, options).then((request) => request(axios, basePath)); }, + /** + * + * @param {string} id A unique integer value identifying this export log. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + retrieveExportLog(id: string, options?: any): AxiosPromise { + return localVarFp.retrieveExportLog(id, options).then((request) => request(axios, basePath)); + }, /** * * @param {string} id A unique integer value identifying this food. @@ -12122,6 +12548,16 @@ export const ApiApiFactory = function (configuration?: Configuration, basePath?: updateCookLog(id: string, cookLog?: CookLog, options?: any): AxiosPromise { return localVarFp.updateCookLog(id, cookLog, options).then((request) => request(axios, basePath)); }, + /** + * + * @param {string} id A unique integer value identifying this export log. + * @param {ExportLog} [exportLog] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + updateExportLog(id: string, exportLog?: ExportLog, options?: any): AxiosPromise { + return localVarFp.updateExportLog(id, exportLog, options).then((request) => request(axios, basePath)); + }, /** * * @param {string} id A unique integer value identifying this food. @@ -12388,6 +12824,17 @@ export class ApiApi extends BaseAPI { return ApiApiFp(this.configuration).createCookLog(cookLog, options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @param {ExportLog} [exportLog] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof ApiApi + */ + public createExportLog(exportLog?: ExportLog, options?: any) { + return ApiApiFp(this.configuration).createExportLog(exportLog, options).then((request) => request(this.axios, this.basePath)); + } + /** * * @param {Food} [food] @@ -12666,6 +13113,17 @@ export class ApiApi extends BaseAPI { return ApiApiFp(this.configuration).destroyCookLog(id, options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @param {string} id A unique integer value identifying this export log. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof ApiApi + */ + public destroyExportLog(id: string, options?: any) { + return ApiApiFp(this.configuration).destroyExportLog(id, options).then((request) => request(this.axios, this.basePath)); + } + /** * * @param {string} id A unique integer value identifying this food. @@ -12952,6 +13410,18 @@ export class ApiApi extends BaseAPI { return ApiApiFp(this.configuration).listCookLogs(page, pageSize, options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @param {number} [page] A page number within the paginated result set. + * @param {number} [pageSize] Number of results to return per page. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof ApiApi + */ + public listExportLogs(page?: number, pageSize?: number, options?: any) { + return ApiApiFp(this.configuration).listExportLogs(page, pageSize, options).then((request) => request(this.axios, this.basePath)); + } + /** * * @param {*} [options] Override http request option. @@ -13345,6 +13815,18 @@ export class ApiApi extends BaseAPI { return ApiApiFp(this.configuration).partialUpdateCookLog(id, cookLog, options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @param {string} id A unique integer value identifying this export log. + * @param {ExportLog} [exportLog] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof ApiApi + */ + public partialUpdateExportLog(id: string, exportLog?: ExportLog, options?: any) { + return ApiApiFp(this.configuration).partialUpdateExportLog(id, exportLog, options).then((request) => request(this.axios, this.basePath)); + } + /** * * @param {string} id A unique integer value identifying this food. @@ -13656,6 +14138,17 @@ export class ApiApi extends BaseAPI { return ApiApiFp(this.configuration).retrieveCookLog(id, options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @param {string} id A unique integer value identifying this export log. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof ApiApi + */ + public retrieveExportLog(id: string, options?: any) { + return ApiApiFp(this.configuration).retrieveExportLog(id, options).then((request) => request(this.axios, this.basePath)); + } + /** * * @param {string} id A unique integer value identifying this food. @@ -13991,6 +14484,18 @@ export class ApiApi extends BaseAPI { return ApiApiFp(this.configuration).updateCookLog(id, cookLog, options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @param {string} id A unique integer value identifying this export log. + * @param {ExportLog} [exportLog] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof ApiApi + */ + public updateExportLog(id: string, exportLog?: ExportLog, options?: any) { + return ApiApiFp(this.configuration).updateExportLog(id, exportLog, options).then((request) => request(this.axios, this.basePath)); + } + /** * * @param {string} id A unique integer value identifying this food. diff --git a/vue/vue.config.js b/vue/vue.config.js index 01460e04..a94390ce 100644 --- a/vue/vue.config.js +++ b/vue/vue.config.js @@ -17,6 +17,14 @@ const pages = { entry: "./src/apps/ImportResponseView/main.js", chunks: ["chunk-vendors"], }, + export_response_view: { + entry: "./src/apps/ExportResponseView/main.js", + chunks: ["chunk-vendors"], + }, + export_view: { + entry: "./src/apps/ExportView/main.js", + chunks: ["chunk-vendors"], + }, supermarket_view: { entry: "./src/apps/SupermarketView/main.js", chunks: ["chunk-vendors"], From 08e6833c129544e15b5c244c354057b82fa3aae8 Mon Sep 17 00:00:00 2001 From: TiagoRascazzi <47132532+TiagoRascazzi@users.noreply.github.com> Date: Sat, 8 Jan 2022 11:10:48 -0500 Subject: [PATCH 2/6] Removed comment --- cookbook/forms.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/cookbook/forms.py b/cookbook/forms.py index 31f3850f..c7a03706 100644 --- a/cookbook/forms.py +++ b/cookbook/forms.py @@ -167,7 +167,6 @@ class ImportExportBase(forms.Form): )) -#TODO deprecated class ImportForm(ImportExportBase): files = forms.FileField(required=True, widget=forms.ClearableFileInput(attrs={'multiple': True})) duplicates = forms.BooleanField(help_text=_( @@ -175,7 +174,6 @@ class ImportForm(ImportExportBase): required=False) -#TODO deprecated class ExportForm(ImportExportBase): recipes = forms.ModelMultipleChoiceField(widget=MultiSelectWidget, queryset=Recipe.objects.none(), required=False) all = forms.BooleanField(required=False) From 33d1022a738c5b1843cee1e00807dc63e5288fef Mon Sep 17 00:00:00 2001 From: Tiago Rascazzi Date: Sat, 8 Jan 2022 12:30:42 -0500 Subject: [PATCH 3/6] Increase number of result for multiselect in export --- vue/src/apps/ExportView/ExportView.vue | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vue/src/apps/ExportView/ExportView.vue b/vue/src/apps/ExportView/ExportView.vue index a9bbaceb..160cbe9e 100644 --- a/vue/src/apps/ExportView/ExportView.vue +++ b/vue/src/apps/ExportView/ExportView.vue @@ -115,7 +115,9 @@ export default { let apiFactory = new ApiApiFactory() this.recipes_loading = true - apiFactory.listRecipes(query).then((response) => { + + let maxResultLenght = 1000 + apiFactory.listRecipes(query, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, 1, maxResultLenght).then((response) => { this.recipes = response.data.results; this.recipes_loading = false }).catch((err) => { From 7c48c13dce07d8590e64bb1ba92570c64658a2ab Mon Sep 17 00:00:00 2001 From: Tiago Rascazzi Date: Sat, 8 Jan 2022 13:37:26 -0500 Subject: [PATCH 4/6] Added export from url args --- cookbook/templates/export.html | 2 +- cookbook/views/import_export.py | 6 ++---- vue/src/apps/ExportView/ExportView.vue | 24 +++++++++++++++++++++--- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/cookbook/templates/export.html b/cookbook/templates/export.html index ca514823..787fc81c 100644 --- a/cookbook/templates/export.html +++ b/cookbook/templates/export.html @@ -24,7 +24,7 @@ {% render_bundle 'export_view' %} diff --git a/cookbook/views/import_export.py b/cookbook/views/import_export.py index c1530b00..6e0a7b3e 100644 --- a/cookbook/views/import_export.py +++ b/cookbook/views/import_export.py @@ -143,14 +143,12 @@ def export_recipe(request): ) else: - form = ExportForm(space=request.space) recipe = request.GET.get('r') if recipe: if re.match(r'^([0-9])+$', recipe): - if recipe := Recipe.objects.filter(pk=int(recipe), space=request.space).first(): - form = ExportForm(initial={'recipes': recipe}, space=request.space) + recipe = Recipe.objects.filter(pk=int(recipe), space=request.space).first() - return render(request, 'export.html', {'form': form}) + return render(request, 'export.html', {'pk': recipe.pk}) @group_required('user') diff --git a/vue/src/apps/ExportView/ExportView.vue b/vue/src/apps/ExportView/ExportView.vue index 160cbe9e..efe0538b 100644 --- a/vue/src/apps/ExportView/ExportView.vue +++ b/vue/src/apps/ExportView/ExportView.vue @@ -93,8 +93,7 @@ export default { components: {Multiselect}, data() { return { - import_id: window.EXPORT_ID, - import_info: undefined, + export_id: window.EXPORT_ID, loading: false, disabled_multiselect: false, @@ -107,11 +106,29 @@ export default { }, mounted() { - this.searchRecipes('') + this.insertRequested() + }, methods: { + insertRequested: function(){ + + let apiFactory = new ApiApiFactory() + + this.recipes_loading = true + + apiFactory.retrieveRecipe(this.export_id).then((response) => { + this.recipes_loading = false + this.recipe_list.push(response.data) + + }).catch((err) => { + console.log(err) + StandardToasts.makeStandardToast(StandardToasts.FAIL_FETCH) + }).then(e => this.searchRecipes('')) + }, + searchRecipes: function (query) { + let apiFactory = new ApiApiFactory() this.recipes_loading = true @@ -120,6 +137,7 @@ export default { apiFactory.listRecipes(query, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, 1, maxResultLenght).then((response) => { this.recipes = response.data.results; this.recipes_loading = false + }).catch((err) => { console.log(err) StandardToasts.makeStandardToast(StandardToasts.FAIL_FETCH) From 16b357e11e512b1e55e119fb7117bef357db0e8c Mon Sep 17 00:00:00 2001 From: Tiago Rascazzi Date: Sat, 8 Jan 2022 14:44:28 -0500 Subject: [PATCH 5/6] Added printReady selector --- cookbook/integration/pdfexport.py | 2 +- cookbook/views/import_export.py | 5 +++-- vue/src/apps/ExportView/ExportView.vue | 7 ++++--- vue/src/apps/RecipeView/RecipeView.vue | 13 ++++++++++++- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/cookbook/integration/pdfexport.py b/cookbook/integration/pdfexport.py index 9f500480..20c948a3 100644 --- a/cookbook/integration/pdfexport.py +++ b/cookbook/integration/pdfexport.py @@ -51,7 +51,7 @@ class PDFexport(Integration): await page.setCookie(cookies) await page.goto('http://'+cmd.default_addr+':'+cmd.default_port+'/view/recipe/'+str(recipe.id), {'waitUntil': 'domcontentloaded'}) - await page.waitForSelector('h3'); + await page.waitForSelector('#printReady'); files.append([recipe.name + '.pdf', await page.pdf(options)]) await page.close(); diff --git a/cookbook/views/import_export.py b/cookbook/views/import_export.py index 6e0a7b3e..9d8fec20 100644 --- a/cookbook/views/import_export.py +++ b/cookbook/views/import_export.py @@ -143,12 +143,13 @@ def export_recipe(request): ) else: + pk = '' recipe = request.GET.get('r') if recipe: if re.match(r'^([0-9])+$', recipe): - recipe = Recipe.objects.filter(pk=int(recipe), space=request.space).first() + pk = Recipe.objects.filter(pk=int(recipe), space=request.space).first().pk - return render(request, 'export.html', {'pk': recipe.pk}) + return render(request, 'export.html', {'pk': pk}) @group_required('user') diff --git a/vue/src/apps/ExportView/ExportView.vue b/vue/src/apps/ExportView/ExportView.vue index efe0538b..5db26da2 100644 --- a/vue/src/apps/ExportView/ExportView.vue +++ b/vue/src/apps/ExportView/ExportView.vue @@ -105,9 +105,10 @@ export default { } }, mounted() { - - this.insertRequested() - + if(this.export_id) + this.insertRequested() + else + this.searchRecipes('') }, methods: { diff --git a/vue/src/apps/RecipeView/RecipeView.vue b/vue/src/apps/RecipeView/RecipeView.vue index 7cf1932a..f4c25cd3 100644 --- a/vue/src/apps/RecipeView/RecipeView.vue +++ b/vue/src/apps/RecipeView/RecipeView.vue @@ -107,7 +107,7 @@
- +
@@ -246,6 +246,9 @@ export default { this.start_time = moment().format("yyyy-MM-DDTHH:mm") } + + if(recipe.image === null) this.printReady() + this.recipe = this.rootrecipe = recipe this.servings = this.servings_cache[this.rootrecipe.id] = recipe.servings this.loading = false @@ -272,6 +275,14 @@ export default { this.servings = this.servings_cache?.[e.id] ?? e.servings } }, + printReady: function(){ + const template = document.createElement("template"); + template.id = "printReady"; + document.body.appendChild(template); + }, + onImgLoad: function(){ + this.printReady() + }, }, } From fef9bcb1e115dbd9b8b53e2f115ef53d2a07d285 Mon Sep 17 00:00:00 2001 From: Tiago Rascazzi Date: Tue, 11 Jan 2022 15:44:10 -0500 Subject: [PATCH 6/6] Added date to filename --- cookbook/integration/default.py | 4 ++-- cookbook/integration/integration.py | 21 +++++++++++++-------- cookbook/integration/pdfexport.py | 2 +- cookbook/integration/recipesage.py | 4 ++-- cookbook/integration/saffron.py | 2 +- 5 files changed, 19 insertions(+), 14 deletions(-) diff --git a/cookbook/integration/default.py b/cookbook/integration/default.py index 6a2b3410..da72dd71 100644 --- a/cookbook/integration/default.py +++ b/cookbook/integration/default.py @@ -61,9 +61,9 @@ class Default(Integration): export_zip_obj.writestr(str(r.pk) + '.zip', recipe_zip_stream.getvalue()) el.exported_recipes += 1 - el.msg += self.recipe_processed_msg(r) + el.msg += self.get_recipe_processed_msg(r) el.save() export_zip_obj.close() - return [[ 'export.zip', export_zip_stream.getvalue() ]] \ No newline at end of file + return [[ self.get_export_file_name(), export_zip_stream.getvalue() ]] \ No newline at end of file diff --git a/cookbook/integration/integration.py b/cookbook/integration/integration.py index 11524a38..9048f413 100644 --- a/cookbook/integration/integration.py +++ b/cookbook/integration/integration.py @@ -6,7 +6,7 @@ import uuid from io import BytesIO, StringIO from zipfile import BadZipFile, ZipFile from django.core.cache import cache - +import datetime from bs4 import Tag from django.core.exceptions import ObjectDoesNotExist @@ -82,7 +82,8 @@ class Integration: export_file = file else: - export_filename = "export.zip" + #zip the files if there is more then one file + export_filename = self.get_export_file_name() export_stream = BytesIO() export_obj = ZipFile(export_stream, 'w') @@ -134,7 +135,7 @@ class Integration: for d in data_list: recipe = self.get_recipe_from_file(d) recipe.keywords.add(self.keyword) - il.msg += self.recipe_processed_msg(recipe) + il.msg += self.get_recipe_processed_msg(recipe) self.handle_duplicates(recipe, import_duplicates) il.imported_recipes += 1 il.save() @@ -159,7 +160,7 @@ class Integration: else: recipe = self.get_recipe_from_file(BytesIO(import_zip.read(z.filename))) recipe.keywords.add(self.keyword) - il.msg += self.recipe_processed_msg(recipe) + il.msg += self.get_recipe_processed_msg(recipe) self.handle_duplicates(recipe, import_duplicates) il.imported_recipes += 1 il.save() @@ -174,7 +175,7 @@ class Integration: try: recipe = self.get_recipe_from_file(d) recipe.keywords.add(self.keyword) - il.msg += self.recipe_processed_msg(recipe) + il.msg += self.get_recipe_processed_msg(recipe) self.handle_duplicates(recipe, import_duplicates) il.imported_recipes += 1 il.save() @@ -191,7 +192,7 @@ class Integration: try: recipe = self.get_recipe_from_file(d) recipe.keywords.add(self.keyword) - il.msg += self.recipe_processed_msg(recipe) + il.msg += self.get_recipe_processed_msg(recipe) self.handle_duplicates(recipe, import_duplicates) il.imported_recipes += 1 il.save() @@ -201,7 +202,7 @@ class Integration: else: recipe = self.get_recipe_from_file(f['file']) recipe.keywords.add(self.keyword) - il.msg += self.recipe_processed_msg(recipe) + il.msg += self.get_recipe_processed_msg(recipe) self.handle_duplicates(recipe, import_duplicates) except BadZipFile: il.msg += 'ERROR ' + _( @@ -289,5 +290,9 @@ class Integration: if DEBUG: traceback.print_exc() - def recipe_processed_msg(self, recipe): + + def get_export_file_name(self, format='zip'): + return "export_{}.{}".format(datetime.datetime.now().strftime("%Y-%m-%d"), format) + + def get_recipe_processed_msg(self, recipe): return f'{recipe.pk} - {recipe.name} \n' diff --git a/cookbook/integration/pdfexport.py b/cookbook/integration/pdfexport.py index 20c948a3..fca78247 100644 --- a/cookbook/integration/pdfexport.py +++ b/cookbook/integration/pdfexport.py @@ -57,7 +57,7 @@ class PDFexport(Integration): await page.close(); el.exported_recipes += 1 - el.msg += self.recipe_processed_msg(recipe) + el.msg += self.get_recipe_processed_msg(recipe) await sync_to_async(el.save, thread_sensitive=True)() diff --git a/cookbook/integration/recipesage.py b/cookbook/integration/recipesage.py index 93dee885..0bc6704b 100644 --- a/cookbook/integration/recipesage.py +++ b/cookbook/integration/recipesage.py @@ -94,10 +94,10 @@ class RecipeSage(Integration): json_list.append(self.get_file_from_recipe(r)) el.exported_recipes += 1 - el.msg += self.recipe_processed_msg(r) + el.msg += self.get_recipe_processed_msg(r) el.save() - return [['export.json', json.dumps(json_list)]] + return [[self.get_export_file_name('json'), json.dumps(json_list)]] def split_recipe_file(self, file): return json.loads(file.read().decode("utf-8")) diff --git a/cookbook/integration/saffron.py b/cookbook/integration/saffron.py index 63342b2b..058f2a8f 100644 --- a/cookbook/integration/saffron.py +++ b/cookbook/integration/saffron.py @@ -94,7 +94,7 @@ class Saffron(Integration): files.append([ filename, data ]) el.exported_recipes += 1 - el.msg += self.recipe_processed_msg(r) + el.msg += self.get_recipe_processed_msg(r) el.save() return files \ No newline at end of file