diff --git a/InvenTree/report/api.py b/InvenTree/report/api.py index af88f4799f..c7d2b15e4d 100644 --- a/InvenTree/report/api.py +++ b/InvenTree/report/api.py @@ -225,13 +225,14 @@ class ReportPrintMixin: outputs.append(report.render_as_string(request)) else: outputs.append(report.render(request)) - except TemplateDoesNotExist: - - filename = report.template + except TemplateDoesNotExist as e: + template = str(e) + if not template: + template = report.template return Response( { - 'error': _(f"Template file '{filename}' is missing or does not exist"), + 'error': _(f"Template file '{template}' is missing or does not exist"), }, status=400, ) @@ -269,13 +270,16 @@ class ReportPrintMixin: else: pdf = outputs[0].get_document().write_pdf() - except TemplateDoesNotExist: + except TemplateDoesNotExist as e: - filename = report.template + template = str(e) + + if not template: + template = report.template return Response( { - 'error': _(f"Template file '{filename}' is missing or does not exist"), + 'error': _(f"Template file '{template}' is missing or does not exist"), }, status=400, ) diff --git a/InvenTree/report/models.py b/InvenTree/report/models.py index be8d803edf..3ee19bd5e6 100644 --- a/InvenTree/report/models.py +++ b/InvenTree/report/models.py @@ -14,12 +14,12 @@ import datetime from django.urls import reverse from django.db import models from django.conf import settings +from django.core.cache import cache from django.core.exceptions import ValidationError, FieldError from django.template.loader import render_to_string from django.template import Template, Context -from django.core.files.storage import FileSystemStorage from django.core.validators import FileExtensionValidator import build.models @@ -43,32 +43,12 @@ except OSError as err: # pragma: no cover logger = logging.getLogger("inventree") -class ReportFileUpload(FileSystemStorage): - """ - Custom implementation of FileSystemStorage class. - - When uploading a report (or a snippet / asset / etc), - it is often important to ensure the filename is not arbitrarily *changed*, - if the name of the uploaded file is identical to the currently stored file. - - For example, a snippet or asset file is referenced in a template by filename, - and we do not want that filename to change when we upload a new *version* - of the snippet or asset file. - - This uploader class performs the following pseudo-code function: - - - If the model is *new*, proceed as normal - - If the model is being updated: - a) If the new filename is *different* from the existing filename, proceed as normal - b) If the new filename is *identical* to the existing filename, we want to overwrite the existing file - """ - - def get_available_name(self, name, max_length=None): - - return super().get_available_name(name, max_length) - - def rename_template(instance, filename): + """ + Helper function for 'renaming' uploaded report files. + Pass responsibility back to the calling class, + to ensure that files are uploaded to the correct directory. + """ return instance.rename_file(filename) @@ -155,7 +135,23 @@ class ReportBase(models.Model): filename = os.path.basename(filename) - return os.path.join('report', 'report_template', self.getSubdir(), filename) + path = os.path.join('report', 'report_template', self.getSubdir(), filename) + + fullpath = os.path.join(settings.MEDIA_ROOT, path) + fullpath = os.path.abspath(fullpath) + + # If the report file is the *same* filename as the one being uploaded, + # remove the original one from the media directory + if str(filename) == str(self.template): + + if os.path.exists(fullpath): + logger.info(f"Deleting existing report template: '{filename}'") + os.remove(fullpath) + + # Ensure that the cache is cleared for this template! + cache.delete(fullpath) + + return path @property def extension(self): @@ -522,16 +518,20 @@ def rename_snippet(instance, filename): path = os.path.join('report', 'snippets', filename) + fullpath = os.path.join(settings.MEDIA_ROOT, path) + fullpath = os.path.abspath(fullpath) + # If the snippet file is the *same* filename as the one being uploaded, # delete the original one from the media directory if str(filename) == str(instance.snippet): - fullpath = os.path.join(settings.MEDIA_ROOT, path) - fullpath = os.path.abspath(fullpath) if os.path.exists(fullpath): logger.info(f"Deleting existing snippet file: '{filename}'") os.remove(fullpath) + # Ensure that the cache is deleted for this snippet + cache.delete(fullpath) + return path