using new import/export module

This commit is contained in:
vabene1111 2021-02-08 11:38:38 +01:00
parent d2783429a1
commit 086570ce90
7 changed files with 30 additions and 186 deletions

View File

@ -9,7 +9,7 @@ WORKDIR /opt/recipes
COPY . ./ COPY . ./
RUN chmod +x boot.sh 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 && \ python -m venv venv && \
venv/bin/pip install -r requirements.txt --no-cache-dir &&\ venv/bin/pip install -r requirements.txt --no-cache-dir &&\
apk --purge del .build-deps apk --purge del .build-deps

View File

@ -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): class ImportExportBase(forms.Form):
DEFAULT = 'Default' DEFAULT = 'Default'
type = forms.ChoiceField(choices=((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})) 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) recipes = forms.ModelMultipleChoiceField(queryset=Recipe.objects.filter(internal=True).all(), widget=MultiSelectWidget)

View File

@ -13,7 +13,7 @@ class BaseAutocomplete(autocomplete.Select2QuerySetView):
qs = self.model.objects.all() qs = self.model.objects.all()
if self.q: if self.q:
qs = qs.filter(name__istartswith=self.q) qs = qs.filter(name__icontains=self.q)
return qs return qs

View File

@ -1,6 +1,6 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load i18n %}
{% load crispy_forms_filters %} {% load crispy_forms_filters %}
{% load i18n %}
{% load static %} {% load static %}
{% block title %}{% trans 'Export Recipes' %}{% endblock %} {% block title %}{% trans 'Export Recipes' %}{% endblock %}
@ -9,8 +9,9 @@
{{ form.media }} {{ form.media }}
{% endblock %} {% endblock %}
{% block content %}
{% block content %}
<h2>{% trans 'Export' %}</h2>
<div class="row"> <div class="row">
<div class="col col-md-12"> <div class="col col-md-12">
<form action="." method="post"> <form action="." method="post">
@ -21,50 +22,4 @@
</form> </form>
</div> </div>
</div> </div>
{% if export %}
<br/>
<div class="row">
<div class="col col-md-12">
<label for="id_export">
{% trans 'Exported Recipe' %}</label>
<textarea id="id_export" class="form-control" rows="12">
{{ export }}
</textarea>
</div>
</div>
<br/>
<div class="row">
<div class="col col-md-12 text-center">
<button class="btn btn-success" onclick="copy()" style="width: 15vw" data-toggle="tooltip"
data-placement="right" title="{% trans 'Copy to clipboard' %}" id="id_btn_copy"
onmouseout="resetTooltip()"><i
class="far fa-copy"></i></button>
</div>
</div>
<script type="text/javascript">
function copy() {
let json = $('#id_export');
json.select();
$('#id_btn_copy').attr('data-original-title', '{% trans 'Copied!' %}').tooltip('show');
document.execCommand("copy");
}
function resetTooltip() {
setTimeout(function () {
$('#id_btn_copy').attr('data-original-title', '{% trans 'Copy list to clipboard' %}');
}, 300);
}
$(function () {
$('[data-toggle="tooltip"]').tooltip()
})
</script>
{% endif %}
{% endblock %} {% endblock %}

View File

@ -1,14 +1,20 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load crispy_forms_filters %}
{% load i18n %} {% load i18n %}
{% load crispy_forms_tags %}
{% load static %} {% load static %}
{% block title %}{% trans 'Import Recipes' %}{% endblock %} {% block title %}{% trans 'Import Recipes' %}{% endblock %}
{% block extra_head %}
{{ form.media }}
{% endblock %}
{% block content %} {% block content %}
<h2>{% trans 'Import' %}</h2>
<div class="row"> <div class="row">
<div class="col col-md-12"> <div class="col col-md-12">
<form action="." method="post"> <form action="." method="post" enctype="multipart/form-data">
{% csrf_token %} {% csrf_token %}
{{ form|crispy }} {{ form|crispy }}
<button class="btn btn-success" type="submit"><i class="fas fa-file-import"></i> {% trans 'Import' %} <button class="btn btn-success" type="submit"><i class="fas fa-file-import"></i> {% trans 'Import' %}
@ -16,4 +22,5 @@
</form> </form>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -1,76 +1,24 @@
import base64
import json
import re 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.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.helper.permission_helper import group_required
from cookbook.integration.default import Default
from cookbook.models import Recipe from cookbook.models import Recipe
from cookbook.serializer import RecipeSerializer, RecipeExportSerializer
def get_integration(request, export_type):
return Default(request)
@group_required('user') @group_required('user')
def import_recipe(request): def import_recipe(request):
if request.method == "POST": if request.method == "POST":
form = ImportForm(request.POST) form = ImportForm(request.POST, request.FILES)
if form.is_valid(): if form.is_valid():
try: integration = Default(request)
data = json.loads( return integration.do_import(request.FILES.getlist('files'))
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!')
)
else: else:
form = ImportForm() form = ImportForm()
@ -79,41 +27,17 @@ def import_recipe(request):
@group_required('user') @group_required('user')
def export_recipe(request): def export_recipe(request):
context = {}
if request.method == "POST": if request.method == "POST":
form = ExportForm(request.POST) form = ExportForm(request.POST)
if form.is_valid(): if form.is_valid():
recipe = form.cleaned_data['recipe'] integration = Default(request)
if recipe.internal: return integration.do_export(form.cleaned_data['recipes'])
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
)
else: else:
form = ExportForm() form = ExportForm()
recipe = request.GET.get('r') recipe = request.GET.get('r')
if recipe: if recipe:
if re.match(r'^([0-9])+$', recipe): if re.match(r'^([0-9])+$', recipe):
if recipe := Recipe.objects.filter(pk=int(recipe)).first(): 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', {'form': form})
return render(request, 'export.html', context)

View File

@ -22,7 +22,7 @@ from rest_framework.authtoken.models import Token
from cookbook.filters import RecipeFilter from cookbook.filters import RecipeFilter
from cookbook.forms import (CommentForm, Recipe, RecipeBookEntryForm, User, from cookbook.forms import (CommentForm, Recipe, RecipeBookEntryForm, User,
UserCreateForm, UserNameForm, UserPreference, 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.helper.permission_helper import group_required, share_link_valid, has_group_permission
from cookbook.integration.default import Default from cookbook.integration.default import Default
from cookbook.models import (Comment, CookLog, InviteLink, MealPlan, from cookbook.models import (Comment, CookLog, InviteLink, MealPlan,
@ -490,27 +490,7 @@ def test(request):
if not settings.DEBUG: if not settings.DEBUG:
return HttpResponseRedirect(reverse('index')) 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): def test2(request):
if not settings.DEBUG: if not settings.DEBUG:
return HttpResponseRedirect(reverse('index')) 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})