diff --git a/cookbook/integration/chowdown.py b/cookbook/integration/chowdown.py index 8ce741895..b00663271 100644 --- a/cookbook/integration/chowdown.py +++ b/cookbook/integration/chowdown.py @@ -67,8 +67,8 @@ class Chowdown(Integration): recipe.steps.add(step) for f in self.files: - if '.zip' in f.name: - import_zip = ZipFile(f.file) + if '.zip' in f['name']: + import_zip = ZipFile(f['file']) for z in import_zip.filelist: if re.match(f'^images/{image}$', z.filename): self.import_recipe_image(recipe, BytesIO(import_zip.read(z.filename))) diff --git a/cookbook/integration/integration.py b/cookbook/integration/integration.py index 2cbbf02ae..db543ac1c 100644 --- a/cookbook/integration/integration.py +++ b/cookbook/integration/integration.py @@ -10,6 +10,8 @@ from django.http import HttpResponseRedirect, HttpResponse from django.urls import reverse from django.utils.formats import date_format from django.utils.translation import gettext as _ +from django_scopes import scope + from cookbook.models import Keyword, Recipe @@ -81,37 +83,38 @@ class Integration: :param files: List of in memory files :return: HttpResponseRedirect to the recipe search showing all imported recipes """ - ignored_recipes = [] - try: - self.files = files - for f in files: - if '.zip' in f.name or '.paprikarecipes' in f.name: - import_zip = ZipFile(f.file) - for z in import_zip.filelist: - if self.import_file_name_filter(z): - recipe = self.get_recipe_from_file(BytesIO(import_zip.read(z.filename))) - recipe.keywords.add(self.keyword) - if duplicate := self.is_duplicate(recipe): - ignored_recipes.append(duplicate) - import_zip.close() - else: - recipe = self.get_recipe_from_file(f.file) - recipe.keywords.add(self.keyword) - if duplicate := self.is_duplicate(recipe): - ignored_recipes.append(duplicate) - except BadZipFile: - messages.add_message(self.request, messages.ERROR, _('Importer expected a .zip file. Did you choose the correct importer type for your data ?')) + with scope(space=self.request.space): + ignored_recipes = [] + try: + self.files = files + for f in files: + if '.zip' in f['name'] or '.paprikarecipes' in f['name']: + import_zip = ZipFile(f['file']) + for z in import_zip.filelist: + if self.import_file_name_filter(z): + recipe = self.get_recipe_from_file(BytesIO(import_zip.read(z.filename))) + recipe.keywords.add(self.keyword) + if duplicate := self.is_duplicate(recipe): + ignored_recipes.append(duplicate) + import_zip.close() + else: + recipe = self.get_recipe_from_file(f['file']) + recipe.keywords.add(self.keyword) + if duplicate := self.is_duplicate(recipe): + ignored_recipes.append(duplicate) + except BadZipFile: + messages.add_message(self.request, messages.ERROR, _('Importer expected a .zip file. Did you choose the correct importer type for your data ?')) - if len(ignored_recipes) > 0: - messages.add_message(self.request, messages.WARNING, _('The following recipes were ignored because they already existed:') + ' ' + ', '.join(ignored_recipes)) - return HttpResponseRedirect(reverse('view_search') + '?keywords=' + str(self.keyword.pk)) + if len(ignored_recipes) > 0: + messages.add_message(self.request, messages.WARNING, _('The following recipes were ignored because they already existed:') + ' ' + ', '.join(ignored_recipes)) + return HttpResponseRedirect(reverse('view_search') + '?keywords=' + str(self.keyword.pk)) def is_duplicate(self, recipe): """ Checks if a recipe is already present, if so deletes it :param recipe: Recipe object """ - if Recipe.objects.filter(space=self.request.space, name=recipe.name).exists(): + if Recipe.objects.filter(space=self.request.space, name=recipe.name).count() > 1: recipe.delete() return recipe.name else: diff --git a/cookbook/integration/mealie.py b/cookbook/integration/mealie.py index 2cce31632..207e130a2 100644 --- a/cookbook/integration/mealie.py +++ b/cookbook/integration/mealie.py @@ -40,8 +40,8 @@ class Mealie(Integration): recipe.steps.add(step) for f in self.files: - if '.zip' in f.name: - import_zip = ZipFile(f.file) + if '.zip' in f['name']: + import_zip = ZipFile(f['file']) for z in import_zip.filelist: if re.match(f'^images/{recipe_json["slug"]}.jpg$', z.filename): self.import_recipe_image(recipe, BytesIO(import_zip.read(z.filename))) diff --git a/cookbook/integration/nextcloud_cookbook.py b/cookbook/integration/nextcloud_cookbook.py index 8d43dc9d5..24d1d998e 100644 --- a/cookbook/integration/nextcloud_cookbook.py +++ b/cookbook/integration/nextcloud_cookbook.py @@ -42,8 +42,8 @@ class NextcloudCookbook(Integration): recipe.steps.add(step) for f in self.files: - if '.zip' in f.name: - import_zip = ZipFile(f.file) + if '.zip' in f['name']: + import_zip = ZipFile(f['file']) for z in import_zip.filelist: if re.match(f'^Recipes/{recipe.name}/full.jpg$', z.filename): self.import_recipe_image(recipe, BytesIO(import_zip.read(z.filename))) diff --git a/cookbook/models.py b/cookbook/models.py index 20bd0f75a..ba97c5601 100644 --- a/cookbook/models.py +++ b/cookbook/models.py @@ -241,6 +241,20 @@ class SyncLog(models.Model, PermissionModelMixin): return f"{self.created_at}:{self.sync} - {self.status}" +class ImportLog(models.Model, PermissionModelMixin): + type = models.CharField(max_length=32) + running = models.BooleanField(default=True) + msg = models.TextField(default="") + 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} - {self.msg}" + + class Keyword(models.Model, PermissionModelMixin): name = models.CharField(max_length=64) icon = models.CharField(max_length=16, blank=True, null=True) diff --git a/cookbook/views/import_export.py b/cookbook/views/import_export.py index bf3f7c761..d70b2d07d 100644 --- a/cookbook/views/import_export.py +++ b/cookbook/views/import_export.py @@ -1,4 +1,6 @@ import re +import threading +from io import BytesIO from django.contrib import messages from django.shortcuts import render @@ -37,7 +39,15 @@ def import_recipe(request): if form.is_valid(): try: integration = get_integration(request, form.cleaned_data['type']) - return integration.do_import(request.FILES.getlist('files')) + files = [] + for f in request.FILES.getlist('files'): + files.append({'file': BytesIO(f.read()), 'name': f.name}) + t = threading.Thread(target=integration.do_import, args=[files]) + t.setDaemon(True) + t.start() + + messages.add_message(request, messages.SUCCESS, 'Import started') + return render(request, 'import.html', {'form': form}) except NotImplementedError: messages.add_message(request, messages.ERROR, _('Importing is not implemented for this provider')) else: