wip on new import/export system
This commit is contained in:
@ -153,6 +153,20 @@ class ImportForm(forms.Form):
|
||||
)
|
||||
|
||||
|
||||
class ImportExportBase(forms.Form):
|
||||
DEFAULT = 'Default'
|
||||
|
||||
type = forms.ChoiceField(choices=((DEFAULT, _('Default')),))
|
||||
|
||||
|
||||
class NewImportForm(ImportExportBase):
|
||||
files = forms.FileField(required=True, widget=forms.ClearableFileInput(attrs={'multiple': True}))
|
||||
|
||||
|
||||
class NewExportForm(ImportExportBase):
|
||||
recipes = forms.ModelMultipleChoiceField(queryset=Recipe.objects.filter(internal=True).all(), widget=MultiSelectWidget)
|
||||
|
||||
|
||||
class UnitMergeForm(forms.Form):
|
||||
prefix = 'unit'
|
||||
|
||||
|
43
cookbook/integration/default.py
Normal file
43
cookbook/integration/default.py
Normal file
@ -0,0 +1,43 @@
|
||||
import json
|
||||
import os
|
||||
from zipfile import ZipFile
|
||||
|
||||
from rest_framework.renderers import JSONRenderer
|
||||
|
||||
from cookbook.integration.integration import Integration
|
||||
from cookbook.serializer import RecipeExportSerializer
|
||||
|
||||
|
||||
class Default(Integration):
|
||||
|
||||
def do_export(self, recipes):
|
||||
path = self.get_tmp_dir_path()
|
||||
export_zip_obj = ZipFile(os.path.join(path, 'export.zip'), 'w')
|
||||
|
||||
for r in recipes:
|
||||
if r.internal:
|
||||
base_path = os.path.join(path, str(r.pk))
|
||||
os.makedirs(base_path, exist_ok=True)
|
||||
recipe_zip_obj = ZipFile(base_path + '.zip', 'w')
|
||||
|
||||
f = open(os.path.join(path, str(r.pk), 'recipe.json'), "w", encoding="utf-8")
|
||||
f.write(self.get_export(r))
|
||||
recipe_zip_obj.write(f.name)
|
||||
recipe_zip_obj.write(r.image.path)
|
||||
f.close()
|
||||
|
||||
recipe_zip_obj.close()
|
||||
export_zip_obj.write(recipe_zip_obj.filename)
|
||||
|
||||
export_zip_obj.close()
|
||||
return export_zip_obj.filename
|
||||
|
||||
def get_recipe(self, string):
|
||||
data = json.loads(string)
|
||||
|
||||
return RecipeExportSerializer(data=data, context={'request': self.request})
|
||||
|
||||
def get_export(self, recipe):
|
||||
export = RecipeExportSerializer(recipe).data
|
||||
|
||||
return JSONRenderer().render(export).decode("utf-8")
|
@ -1,8 +1,36 @@
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
|
||||
class Integration:
|
||||
@staticmethod
|
||||
def get_recipe(string):
|
||||
request = None
|
||||
|
||||
def __init__(self, request):
|
||||
self.request = request
|
||||
|
||||
def do_export(self, recipes):
|
||||
raise Exception('Method not implemented in storage integration')
|
||||
|
||||
@staticmethod
|
||||
def get_export(recipe):
|
||||
def do_import(self):
|
||||
raise Exception('Method not implemented in storage integration')
|
||||
|
||||
def get_recipe(self, string):
|
||||
raise Exception('Method not implemented in storage integration')
|
||||
|
||||
def get_export(self, recipe):
|
||||
raise Exception('Method not implemented in storage integration')
|
||||
|
||||
def get_export_file(self, recipe):
|
||||
try:
|
||||
with open(recipe.image.path, 'rb') as img_f:
|
||||
return img_f
|
||||
except:
|
||||
return None
|
||||
|
||||
def get_tmp_dir_path(self):
|
||||
path = os.path.join(tempfile.gettempdir(), 'recipe_io', str(self.request.user.pk))
|
||||
os.makedirs(path, exist_ok=True)
|
||||
return path
|
||||
|
||||
def delete_temp_dir_path(self):
|
||||
os.remove(self.get_tmp_dir_path())
|
||||
|
@ -113,7 +113,7 @@ class KeywordSerializer(UniqueFieldsMixin, serializers.ModelSerializer):
|
||||
def create(self, validated_data):
|
||||
# since multi select tags dont have id's
|
||||
# duplicate names might be routed to create
|
||||
obj, created = Keyword.objects.get_or_create(**validated_data)
|
||||
obj, created = Keyword.objects.get_or_create(name=validated_data['name'])
|
||||
return obj
|
||||
|
||||
class Meta:
|
||||
@ -131,7 +131,7 @@ class UnitSerializer(UniqueFieldsMixin, serializers.ModelSerializer):
|
||||
def create(self, validated_data):
|
||||
# since multi select tags dont have id's
|
||||
# duplicate names might be routed to create
|
||||
obj, created = Unit.objects.get_or_create(**validated_data)
|
||||
obj, created = Unit.objects.get_or_create(name=validated_data['name'])
|
||||
return obj
|
||||
|
||||
class Meta:
|
||||
@ -145,7 +145,7 @@ class SupermarketCategorySerializer(UniqueFieldsMixin, WritableNestedModelSerial
|
||||
def create(self, validated_data):
|
||||
# since multi select tags dont have id's
|
||||
# duplicate names might be routed to create
|
||||
obj, created = SupermarketCategory.objects.get_or_create(**validated_data)
|
||||
obj, created = SupermarketCategory.objects.get_or_create(name=validated_data['name'])
|
||||
return obj
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
@ -221,17 +221,6 @@ class StepSerializer(WritableNestedModelSerializer):
|
||||
)
|
||||
|
||||
|
||||
# used for the import export. temporary workaround until that module is finally fixed
|
||||
class StepExportSerializer(WritableNestedModelSerializer):
|
||||
ingredients = IngredientSerializer(many=True)
|
||||
|
||||
class Meta:
|
||||
model = Step
|
||||
fields = (
|
||||
'id', 'name', 'type', 'instruction', 'ingredients', 'time', 'order', 'show_as_header'
|
||||
)
|
||||
|
||||
|
||||
class NutritionInformationSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = NutritionInformation
|
||||
@ -270,11 +259,6 @@ class RecipeSerializer(WritableNestedModelSerializer):
|
||||
return super().create(validated_data)
|
||||
|
||||
|
||||
# used for the import export. temporary workaround until that module is finally fixed
|
||||
class RecipeExportSerializer(RecipeSerializer):
|
||||
steps = StepExportSerializer(many=True)
|
||||
|
||||
|
||||
class RecipeImageSerializer(WritableNestedModelSerializer):
|
||||
class Meta:
|
||||
model = Recipe
|
||||
@ -403,3 +387,72 @@ class ViewLogSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = ViewLog
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
# Export/Import Serializers
|
||||
|
||||
class KeywordExportSerializer(KeywordSerializer):
|
||||
class Meta:
|
||||
model = Keyword
|
||||
fields = ('name', 'icon', 'description', 'created_at', 'updated_at')
|
||||
|
||||
|
||||
class NutritionInformationExportSerializer(NutritionInformationSerializer):
|
||||
class Meta:
|
||||
model = NutritionInformation
|
||||
fields = ('carbohydrates', 'fats', 'proteins', 'calories', 'source')
|
||||
|
||||
|
||||
class SupermarketCategoryExportSerializer(SupermarketCategorySerializer):
|
||||
class Meta:
|
||||
model = SupermarketCategory
|
||||
fields = ('name',)
|
||||
|
||||
|
||||
class UnitExportSerializer(UnitSerializer):
|
||||
class Meta:
|
||||
model = Unit
|
||||
fields = ('name', 'description')
|
||||
|
||||
|
||||
class FoodExportSerializer(FoodSerializer):
|
||||
supermarket_category = SupermarketCategoryExportSerializer(allow_null=True, required=False)
|
||||
|
||||
class Meta:
|
||||
model = Food
|
||||
fields = ('name', 'ignore_shopping', 'supermarket_category')
|
||||
|
||||
|
||||
class IngredientExportSerializer(WritableNestedModelSerializer):
|
||||
food = FoodExportSerializer(allow_null=True)
|
||||
unit = UnitExportSerializer(allow_null=True)
|
||||
amount = CustomDecimalField()
|
||||
|
||||
class Meta:
|
||||
model = Ingredient
|
||||
fields = ('food', 'unit', 'amount', 'note', 'order', 'is_header', 'no_amount')
|
||||
|
||||
|
||||
class StepExportSerializer(WritableNestedModelSerializer):
|
||||
ingredients = IngredientExportSerializer(many=True)
|
||||
|
||||
class Meta:
|
||||
model = Step
|
||||
fields = ('name', 'type', 'instruction', 'ingredients', 'time', 'order', 'show_as_header')
|
||||
|
||||
|
||||
class RecipeExportSerializer(WritableNestedModelSerializer):
|
||||
nutrition = NutritionInformationSerializer(allow_null=True, required=False)
|
||||
steps = StepExportSerializer(many=True)
|
||||
keywords = KeywordExportSerializer(many=True)
|
||||
|
||||
class Meta:
|
||||
model = Recipe
|
||||
fields = (
|
||||
'name', 'description', 'keywords', 'steps', 'working_time',
|
||||
'waiting_time', 'internal', 'nutrition', 'servings', 'servings_text',
|
||||
)
|
||||
|
||||
def create(self, validated_data):
|
||||
validated_data['created_by'] = self.context['request'].user
|
||||
return super().create(validated_data)
|
||||
|
@ -1,11 +1,26 @@
|
||||
<script type="application/javascript">
|
||||
window.addEventListener("load", () => {
|
||||
if ("serviceWorker" in navigator) {
|
||||
navigator.serviceWorker.register("{% url 'service_worker' %}", {scope: '/'}).then(function (reg) {
|
||||
console.warn('Successfully registered service worker', err);
|
||||
}).catch(function (err) {
|
||||
console.warn('Error whilst registering service worker', err);
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% extends "base.html" %}
|
||||
{% load crispy_forms_filters %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}{% trans 'Import Recipes' %}{% endblock %}
|
||||
|
||||
{% block extra_head %}
|
||||
{{ form.media }}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<h2>{% trans 'Import' %}</h2>
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
<form action="." method="post">
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
<button class="btn btn-success" type="submit"><i class="fas fa-file-import"></i> {% trans 'Import' %}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
25
cookbook/templates/test2.html
Normal file
25
cookbook/templates/test2.html
Normal file
@ -0,0 +1,25 @@
|
||||
{% extends "base.html" %}
|
||||
{% load crispy_forms_filters %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}{% trans 'Export Recipes' %}{% endblock %}
|
||||
|
||||
{% block extra_head %}
|
||||
{{ form.media }}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<h2>{% trans 'Export' %}</h2>
|
||||
<div class="row">
|
||||
<div class="col col-md-12">
|
||||
<form action="." method="post">
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
<button class="btn btn-success" type="submit"><i class="fas fa-file-export"></i> {% trans 'Export' %}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -51,7 +51,8 @@ urlpatterns = [
|
||||
path('shopping/latest/', views.latest_shopping_list, name='view_shopping_latest'),
|
||||
path('settings/', views.user_settings, name='view_settings'),
|
||||
path('history/', views.history, name='view_history'),
|
||||
path('test/<int:pk>', views.test, name='view_test'),
|
||||
path('test/', views.test, name='view_test'),
|
||||
path('test2/', views.test2, name='view_test2'),
|
||||
|
||||
path('import/', import_export.import_recipe, name='view_import'),
|
||||
path('export/', import_export.export_recipe, name='view_export'),
|
||||
|
@ -11,7 +11,7 @@ from django.contrib.auth.password_validation import validate_password
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import IntegrityError
|
||||
from django.db.models import Avg, Q
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.http import HttpResponseRedirect, FileResponse, HttpResponse
|
||||
from django.shortcuts import get_object_or_404, render, redirect
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.utils import timezone
|
||||
@ -22,8 +22,9 @@ from rest_framework.authtoken.models import Token
|
||||
from cookbook.filters import RecipeFilter
|
||||
from cookbook.forms import (CommentForm, Recipe, RecipeBookEntryForm, User,
|
||||
UserCreateForm, UserNameForm, UserPreference,
|
||||
UserPreferenceForm)
|
||||
UserPreferenceForm, ImportForm, NewImportForm, NewExportForm)
|
||||
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,
|
||||
RecipeBook, RecipeBookEntry, ViewLog, ShoppingList)
|
||||
from cookbook.tables import (CookLogTable, RecipeTable, RecipeTableSmall,
|
||||
@ -485,10 +486,29 @@ def offline(request):
|
||||
return render(request, 'offline.html', {})
|
||||
|
||||
|
||||
def test(request, pk):
|
||||
def test(request):
|
||||
if not settings.DEBUG:
|
||||
return HttpResponseRedirect(reverse('index'))
|
||||
|
||||
recipe = Recipe.objects.get(pk=pk)
|
||||
if request.method == "POST":
|
||||
form = NewImportForm(request.POST)
|
||||
else:
|
||||
form = NewImportForm()
|
||||
|
||||
return render(request, 'test.html', {'recipe': recipe})
|
||||
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)
|
||||
integration.do_export(form.cleaned_data['recipes'])
|
||||
return render(request, 'test2.html', {'form': form})
|
||||
else:
|
||||
form = NewExportForm()
|
||||
|
||||
return render(request, 'test2.html', {'form': form})
|
||||
|
Reference in New Issue
Block a user