diff --git a/Dockerfile b/Dockerfile index 4c6c4bc9..cbf07b59 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,7 +9,7 @@ WORKDIR /opt/recipes COPY . ./ RUN chmod +x boot.sh -RUN apk add --no-cache --virtual .build-deps gcc musl-dev postgresql-dev zlib-dev jpeg-dev libressl-dev libffi-dev && \ +RUN apk add --no-cache --virtual .build-deps gcc musl-dev postgresql-dev zlib-dev jpeg-dev libressl-dev libffi-dev openssl-dev && \ python -m venv venv && \ venv/bin/pip install -r requirements.txt --no-cache-dir &&\ apk --purge del .build-deps diff --git a/cookbook/forms.py b/cookbook/forms.py index 17d92865..cc8c45fa 100644 --- a/cookbook/forms.py +++ b/cookbook/forms.py @@ -131,39 +131,17 @@ class ShoppingForm(forms.Form): ) -class ExportForm(forms.Form): - recipe = forms.ModelChoiceField( - queryset=Recipe.objects.filter(internal=True).all(), - widget=SelectWidget - ) - image = forms.BooleanField( - help_text=_('Export Base64 encoded image?'), - required=False - ) - download = forms.BooleanField( - help_text=_('Download export directly or show on page?'), - required=False - ) - - -class ImportForm(forms.Form): - recipe = forms.CharField( - widget=forms.Textarea, - help_text=_('Simply paste a JSON export into this textarea and click import.') # noqa: E501 - ) - - class ImportExportBase(forms.Form): DEFAULT = 'Default' type = forms.ChoiceField(choices=((DEFAULT, _('Default')),)) -class NewImportForm(ImportExportBase): +class ImportForm(ImportExportBase): files = forms.FileField(required=True, widget=forms.ClearableFileInput(attrs={'multiple': True})) -class NewExportForm(ImportExportBase): +class ExportForm(ImportExportBase): recipes = forms.ModelMultipleChoiceField(queryset=Recipe.objects.filter(internal=True).all(), widget=MultiSelectWidget) diff --git a/cookbook/helper/dal.py b/cookbook/helper/dal.py index 03d8ed3c..6a109a6d 100644 --- a/cookbook/helper/dal.py +++ b/cookbook/helper/dal.py @@ -13,7 +13,7 @@ class BaseAutocomplete(autocomplete.Select2QuerySetView): qs = self.model.objects.all() if self.q: - qs = qs.filter(name__istartswith=self.q) + qs = qs.filter(name__icontains=self.q) return qs diff --git a/cookbook/templates/export.html b/cookbook/templates/export.html index cac74ae3..4133da93 100644 --- a/cookbook/templates/export.html +++ b/cookbook/templates/export.html @@ -1,6 +1,6 @@ {% extends "base.html" %} -{% load i18n %} {% load crispy_forms_filters %} +{% load i18n %} {% load static %} {% block title %}{% trans 'Export Recipes' %}{% endblock %} @@ -9,8 +9,9 @@ {{ form.media }} {% endblock %} -{% block content %} +{% block content %} +

{% trans 'Export' %}

@@ -21,50 +22,4 @@
- - {% if export %} - -
-
-
- - -
-
- -
-
-
- -
-
- - - {% endif %} {% endblock %} \ No newline at end of file diff --git a/cookbook/templates/import.html b/cookbook/templates/import.html index 96a7be70..d36a8adc 100644 --- a/cookbook/templates/import.html +++ b/cookbook/templates/import.html @@ -1,14 +1,20 @@ {% extends "base.html" %} +{% load crispy_forms_filters %} {% load i18n %} -{% load crispy_forms_tags %} {% load static %} {% block title %}{% trans 'Import Recipes' %}{% endblock %} +{% block extra_head %} + {{ form.media }} +{% endblock %} + + {% block content %} +

{% trans 'Import' %}

-
+ {% csrf_token %} {{ form|crispy }}
+ {% endblock %} \ No newline at end of file diff --git a/cookbook/views/import_export.py b/cookbook/views/import_export.py index 2d61e03b..e99d4af7 100644 --- a/cookbook/views/import_export.py +++ b/cookbook/views/import_export.py @@ -1,76 +1,24 @@ -import base64 -import json import re -from json import JSONDecodeError -from django.contrib import messages -from django.core.files.base import ContentFile -from django.http import HttpResponse, HttpResponseRedirect from django.shortcuts import render -from django.urls import reverse_lazy -from django.utils.translation import gettext as _ -from rest_framework.renderers import JSONRenderer -from cookbook.forms import ExportForm, ImportForm +from cookbook.forms import ExportForm, ExportForm, ImportForm from cookbook.helper.permission_helper import group_required +from cookbook.integration.default import Default from cookbook.models import Recipe -from cookbook.serializer import RecipeSerializer, RecipeExportSerializer + + +def get_integration(request, export_type): + return Default(request) @group_required('user') def import_recipe(request): if request.method == "POST": - form = ImportForm(request.POST) + form = ImportForm(request.POST, request.FILES) if form.is_valid(): - try: - data = json.loads( - re.sub(r'"id":([0-9]+),', '', re.sub(r',(\s)*"recipe":([0-9]+)', '', form.cleaned_data['recipe'])) - ) - - sr = RecipeExportSerializer(data=data, context={'request': request}) - if sr.is_valid(): - sr.validated_data['created_by'] = request.user - recipe = sr.save() - - if data['image']: - try: - fmt, img = data['image'].split(';base64,') - ext = fmt.split('/')[-1] - # TODO possible security risk, - # maybe some checks needed - recipe.image = (ContentFile( - base64.b64decode(img), - name=f'{recipe.pk}.{ext}') - ) - recipe.save() - except ValueError: - pass - - messages.add_message( - request, - messages.SUCCESS, - _('Recipe imported successfully!') - ) - return HttpResponseRedirect( - reverse_lazy('view_recipe', args=[recipe.pk]) - ) - else: - messages.add_message( - request, - messages.ERROR, - _('Something went wrong during the import!') - ) - messages.add_message( - request, messages.WARNING, sr.errors - ) - except JSONDecodeError as e: - print(e) - messages.add_message( - request, - messages.ERROR, - _('Could not parse the supplied JSON!') - ) - + integration = Default(request) + return integration.do_import(request.FILES.getlist('files')) else: form = ImportForm() @@ -79,41 +27,17 @@ def import_recipe(request): @group_required('user') def export_recipe(request): - context = {} if request.method == "POST": form = ExportForm(request.POST) if form.is_valid(): - recipe = form.cleaned_data['recipe'] - if recipe.internal: - export = RecipeExportSerializer(recipe).data - - if recipe.image and form.cleaned_data['image']: - with open(recipe.image.path, 'rb') as img_f: - export['image'] = f'data:image/png;base64,{base64.b64encode(img_f.read()).decode("utf-8")}' # noqa: E501 - - json_string = JSONRenderer().render(export).decode("utf-8") - - if form.cleaned_data['download']: - response = HttpResponse( - json_string, content_type='text/plain' - ) - response['Content-Disposition'] = f'attachment; filename={recipe.name}.json' # noqa: E501 - return response - - context['export'] = re.sub(r'"id":([0-9])+,', '', json_string) - else: - form.add_error( - 'recipe', - _('External recipes cannot be exported, please share the file directly or select an internal recipe.') # noqa: E501 - ) + integration = Default(request) + return integration.do_export(form.cleaned_data['recipes']) else: form = ExportForm() recipe = request.GET.get('r') if recipe: if re.match(r'^([0-9])+$', recipe): if recipe := Recipe.objects.filter(pk=int(recipe)).first(): - form = ExportForm(initial={'recipe': recipe}) + form = ExportForm(initial={'recipes': recipe}) - context['form'] = form - - return render(request, 'export.html', context) + return render(request, 'export.html', {'form': form}) diff --git a/cookbook/views/views.py b/cookbook/views/views.py index 69f9efe6..693979d3 100644 --- a/cookbook/views/views.py +++ b/cookbook/views/views.py @@ -22,7 +22,7 @@ from rest_framework.authtoken.models import Token from cookbook.filters import RecipeFilter from cookbook.forms import (CommentForm, Recipe, RecipeBookEntryForm, User, UserCreateForm, UserNameForm, UserPreference, - UserPreferenceForm, ImportForm, NewImportForm, NewExportForm) + UserPreferenceForm, ImportForm, ImportForm, ExportForm) from cookbook.helper.permission_helper import group_required, share_link_valid, has_group_permission from cookbook.integration.default import Default from cookbook.models import (Comment, CookLog, InviteLink, MealPlan, @@ -490,27 +490,7 @@ def test(request): if not settings.DEBUG: return HttpResponseRedirect(reverse('index')) - if request.method == "POST": - form = NewImportForm(request.POST, request.FILES) - if form.is_valid(): - integration = Default(request) - return integration.do_import(request.FILES.getlist('files')) - else: - form = NewImportForm() - - return render(request, 'test.html', {'form': form}) - def test2(request): if not settings.DEBUG: return HttpResponseRedirect(reverse('index')) - - if request.method == "POST": - form = NewExportForm(request.POST) - if form.is_valid(): - integration = Default(request) - return integration.do_export(form.cleaned_data['recipes']) - else: - form = NewExportForm() - - return render(request, 'test2.html', {'form': form})