diff --git a/.directory b/.directory deleted file mode 100644 index 280ecee9..00000000 --- a/.directory +++ /dev/null @@ -1,3 +0,0 @@ -[Dolphin] -Timestamp=2022,1,7,19,23,46.14 -Version=4 diff --git a/.env.template b/.env.template index e825cbbf..8a9ccc48 100644 --- a/.env.template +++ b/.env.template @@ -152,6 +152,6 @@ REVERSE_PROXY_AUTH=0 # Disabled by default, uncomment to enable # ENABLE_PDF_EXPORT=1 -# Duration to keep the cached export file (in seconds) -EXPORT_FILE_CACHE_DURATION=300 +# Recipe exports are cached for a certain time by default, adjust time if needed +# EXPORT_FILE_CACHE_DURATION=600 diff --git a/cookbook/migrations/0165_exportlog_cache_duration.py b/cookbook/migrations/0165_exportlog_cache_duration.py deleted file mode 100644 index 8005a4b2..00000000 --- a/cookbook/migrations/0165_exportlog_cache_duration.py +++ /dev/null @@ -1,18 +0,0 @@ -# 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 deleted file mode 100644 index 15e19e13..00000000 --- a/cookbook/migrations/0166_exportlog_possibly_not_expired.py +++ /dev/null @@ -1,18 +0,0 @@ -# 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/migrations/0164_exportlog.py b/cookbook/migrations/0169_exportlog.py similarity index 83% rename from cookbook/migrations/0164_exportlog.py rename to cookbook/migrations/0169_exportlog.py index 952c9372..0fa6c20e 100644 --- a/cookbook/migrations/0164_exportlog.py +++ b/cookbook/migrations/0169_exportlog.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.11 on 2022-01-07 20:29 +# Generated by Django 3.2.11 on 2022-02-03 15:03 import cookbook.models from django.conf import settings @@ -10,7 +10,7 @@ class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('cookbook', '0163_auto_20220105_0758'), + ('cookbook', '0168_add_unit_searchfields'), ] operations = [ @@ -23,6 +23,8 @@ class Migration(migrations.Migration): ('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(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')), diff --git a/cookbook/tests/other/test_export.py b/cookbook/tests/other/test_export.py new file mode 100644 index 00000000..995c0cce --- /dev/null +++ b/cookbook/tests/other/test_export.py @@ -0,0 +1,25 @@ +import pytest +from django.contrib import auth +from django.urls import reverse + +from cookbook.forms import ImportExportBase +from cookbook.helper.ingredient_parser import IngredientParser +from cookbook.models import ExportLog + + +@pytest.fixture +def obj_1(space_1, u1_s1): + return ExportLog.objects.create(type=ImportExportBase.DEFAULT, running=False, created_by=auth.get_user(u1_s1), space=space_1, exported_recipes=10, total_recipes=10) + + +@pytest.mark.parametrize("arg", [ + ['a_u', 302], + ['g1_s1', 302], + ['u1_s1', 200], + ['a1_s1', 200], + ['u1_s2', 404], + ['a1_s2', 404], +]) +def test_export_file_cache(arg, request, obj_1): + c = request.getfixturevalue(arg[0]) + assert c.get(reverse('view_export_file', args=[obj_1.pk])).status_code == arg[1] diff --git a/cookbook/urls.py b/cookbook/urls.py index ac7dd838..bd384989 100644 --- a/cookbook/urls.py +++ b/cookbook/urls.py @@ -73,7 +73,7 @@ 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'), diff --git a/cookbook/views/import_export.py b/cookbook/views/import_export.py index 9d8fec20..1974c18d 100644 --- a/cookbook/views/import_export.py +++ b/cookbook/views/import_export.py @@ -5,7 +5,7 @@ from django.core.cache import cache from django.contrib import messages from django.http import HttpResponse, HttpResponseRedirect, JsonResponse -from django.shortcuts import render +from django.shortcuts import render, get_object_or_404 from django.urls import reverse from django.utils.translation import gettext as _ @@ -125,7 +125,10 @@ def export_recipe(request): recipes = Recipe.objects.filter(space=request.space, internal=True).all() integration = get_integration(request, form.cleaned_data['type']) - + + if form.cleaned_data['type'] == ImportExportBase.PDF and not settings.ENABLE_PDF_EXPORT: + return JsonResponse({'error': _('The PDF Exporter is not enabled on this instance as it is still in an experimental state.')}) + 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]) @@ -141,7 +144,6 @@ def export_recipe(request): }, status=400 ) - else: pk = '' recipe = request.GET.get('r') @@ -156,6 +158,7 @@ def export_recipe(request): 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}) @@ -163,16 +166,15 @@ def export_response(request, pk): @group_required('user') def export_file(request, pk): - - cacheData = cache.get('export_file_'+str(pk)) + el = get_object_or_404(ExportLog, pk=pk, space=request.space) + + cacheData = cache.get(f'export_file_{el.pk}') if cacheData is None: - el = ExportLog.objects.get(pk=pk) - el.possibly_not_expired = False; + 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']+'"' + response['Content-Disposition'] = 'attachment; filename="' + cacheData['filename'] + '"' return response - diff --git a/docs/features/import_export.md b/docs/features/import_export.md index d551cced..98424158 100644 --- a/docs/features/import_export.md +++ b/docs/features/import_export.md @@ -21,26 +21,26 @@ 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 | ✔️ | ❌ | ✔️ | -| PDF (experimental) | ⌚️ | ✔ | ✔️ | +|--------------------| ------ | -- | ------ | +| 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 +✔️ = implemented, ❌ = not implemented and not possible/planned, ⌚ = not yet implemented ## Default The default integration is the built in (and preferred) way to import and export recipes. diff --git a/vue/src/apps/ExportView/ExportView.vue b/vue/src/apps/ExportView/ExportView.vue index 5db26da2..ca55b720 100644 --- a/vue/src/apps/ExportView/ExportView.vue +++ b/vue/src/apps/ExportView/ExportView.vue @@ -9,24 +9,9 @@
@@ -53,8 +38,6 @@ @search-change="searchRecipes"> - -
@@ -164,14 +147,17 @@ export default { axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'; axios.post(resolveDjangoUrl('view_export',), formData).then((response) => { - - window.location.href = resolveDjangoUrl('view_export_response', response.data['export_id']) + if (response.data['error'] !== undefined){ + makeToast(this.$t("Error"), response.data['error'],"warning") + }else{ + window.location.href = resolveDjangoUrl('view_export_response', response.data['export_id']) + } }).catch((err) => { this.error = err.data this.loading = false console.log(err) - makeToast(this.$t("Error"), this.$t("There was an error loading a resource!"), "danger") + makeToast(this.$t("Error"), this.$t("There was an error loading a resource!"), "warning") }) },