mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Move media operations to storages backend (#6478)
* [BUG] Inventree fiddles with files directly rather than using Django Storage api Fixes #2585 * PEP fix * clean diff * move template discovery into central location * more moving file operations * fix paths * and another path fixing * more fixes * fix typing * switch config back to local * revert locale stats * remove dependency for template copy step * fix typing * log more * log data * more logging * pass filenames as strings * clean diff
This commit is contained in:
parent
7169b5de26
commit
cfe00aaa0f
8
InvenTree/InvenTree/files.py
Normal file
8
InvenTree/InvenTree/files.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
"""Helpers for file handling in InvenTree."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
TEMPLATES_DIR = Path(__file__).parent.parent
|
||||||
|
MEDIA_STORAGE_DIR = settings.MEDIA_ROOT
|
@ -9,14 +9,15 @@ import os
|
|||||||
import os.path
|
import os.path
|
||||||
import re
|
import re
|
||||||
from decimal import Decimal, InvalidOperation
|
from decimal import Decimal, InvalidOperation
|
||||||
from typing import TypeVar
|
from pathlib import Path
|
||||||
|
from typing import TypeVar, Union
|
||||||
from wsgiref.util import FileWrapper
|
from wsgiref.util import FileWrapper
|
||||||
|
|
||||||
import django.utils.timezone as timezone
|
import django.utils.timezone as timezone
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.staticfiles.storage import StaticFilesStorage
|
from django.contrib.staticfiles.storage import StaticFilesStorage
|
||||||
from django.core.exceptions import FieldError, ValidationError
|
from django.core.exceptions import FieldError, ValidationError
|
||||||
from django.core.files.storage import default_storage
|
from django.core.files.storage import Storage, default_storage
|
||||||
from django.http import StreamingHttpResponse
|
from django.http import StreamingHttpResponse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
@ -861,9 +862,14 @@ def hash_barcode(barcode_data):
|
|||||||
return str(hash.hexdigest())
|
return str(hash.hexdigest())
|
||||||
|
|
||||||
|
|
||||||
def hash_file(filename: str):
|
def hash_file(filename: Union[str, Path], storage: Union[Storage, None] = None):
|
||||||
"""Return the MD5 hash of a file."""
|
"""Return the MD5 hash of a file."""
|
||||||
return hashlib.md5(open(filename, 'rb').read()).hexdigest()
|
content = (
|
||||||
|
open(filename, 'rb').read()
|
||||||
|
if storage is None
|
||||||
|
else storage.open(str(filename), 'rb').read()
|
||||||
|
)
|
||||||
|
return hashlib.md5(content).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
def server_timezone() -> str:
|
def server_timezone() -> str:
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
"""Shared templating code."""
|
"""Shared templating code."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
import warnings
|
import warnings
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.core.exceptions import AppRegistryNotReady
|
from django.core.exceptions import AppRegistryNotReady
|
||||||
from django.core.files.storage import default_storage
|
from django.core.files.storage import default_storage
|
||||||
from django.db.utils import IntegrityError, OperationalError, ProgrammingError
|
from django.db.utils import IntegrityError, OperationalError, ProgrammingError
|
||||||
@ -18,9 +16,6 @@ from InvenTree.config import ensure_dir
|
|||||||
logger = logging.getLogger('inventree')
|
logger = logging.getLogger('inventree')
|
||||||
|
|
||||||
|
|
||||||
MEDIA_STORAGE_DIR = Path(settings.MEDIA_ROOT)
|
|
||||||
|
|
||||||
|
|
||||||
class TemplatingMixin:
|
class TemplatingMixin:
|
||||||
"""Mixin that contains shared templating code."""
|
"""Mixin that contains shared templating code."""
|
||||||
|
|
||||||
@ -84,8 +79,7 @@ class TemplatingMixin:
|
|||||||
|
|
||||||
# Create root dir for templates
|
# Create root dir for templates
|
||||||
src_dir = self.get_src_dir(ref_name)
|
src_dir = self.get_src_dir(ref_name)
|
||||||
dst_dir = MEDIA_STORAGE_DIR.joinpath(self.name, 'inventree', ref_name)
|
ensure_dir(Path(self.name, 'inventree', ref_name), default_storage)
|
||||||
ensure_dir(dst_dir, default_storage)
|
|
||||||
|
|
||||||
# Copy each template across (if required)
|
# Copy each template across (if required)
|
||||||
for entry in data:
|
for entry in data:
|
||||||
@ -94,29 +88,27 @@ class TemplatingMixin:
|
|||||||
def create_template_file(self, model, src_dir, data, ref_name):
|
def create_template_file(self, model, src_dir, data, ref_name):
|
||||||
"""Ensure a label template is in place."""
|
"""Ensure a label template is in place."""
|
||||||
# Destination filename
|
# Destination filename
|
||||||
filename = os.path.join(self.name, 'inventree', ref_name, data['file'])
|
filename = Path(self.name, 'inventree', ref_name, data['file'])
|
||||||
|
|
||||||
src_file = src_dir.joinpath(data['file'])
|
src_file = src_dir.joinpath(data['file'])
|
||||||
dst_file = MEDIA_STORAGE_DIR.joinpath(filename)
|
|
||||||
|
|
||||||
do_copy = False
|
do_copy = False
|
||||||
|
|
||||||
if not dst_file.exists():
|
if not default_storage.exists(filename):
|
||||||
logger.info("%s template '%s' is not present", self.name, filename)
|
logger.info("%s template '%s' is not present", self.name, filename)
|
||||||
do_copy = True
|
do_copy = True
|
||||||
else:
|
else:
|
||||||
# Check if the file contents are different
|
# Check if the file contents are different
|
||||||
src_hash = InvenTree.helpers.hash_file(src_file)
|
src_hash = InvenTree.helpers.hash_file(src_file)
|
||||||
dst_hash = InvenTree.helpers.hash_file(dst_file)
|
dst_hash = InvenTree.helpers.hash_file(filename, default_storage)
|
||||||
|
|
||||||
if src_hash != dst_hash:
|
if src_hash != dst_hash:
|
||||||
logger.info("Hash differs for '%s'", filename)
|
logger.info("Hash differs for '%s'", filename)
|
||||||
do_copy = True
|
do_copy = True
|
||||||
|
|
||||||
if do_copy:
|
if do_copy:
|
||||||
logger.info("Copying %s template '%s'", self.name, dst_file)
|
logger.info("Copying %s template '%s'", self.name, filename)
|
||||||
# Ensure destination dir exists
|
# Ensure destination dir exists
|
||||||
dst_file.parent.mkdir(parents=True, exist_ok=True)
|
ensure_dir(filename.parent, default_storage)
|
||||||
|
|
||||||
# Copy file
|
# Copy file
|
||||||
default_storage.save(filename, src_file.open('rb'))
|
default_storage.save(filename, src_file.open('rb'))
|
||||||
@ -135,6 +127,8 @@ class TemplatingMixin:
|
|||||||
logger.info("Creating entry for %s '%s'", model, data.get('name'))
|
logger.info("Creating entry for %s '%s'", model, data.get('name'))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
model.objects.create(**self.get_new_obj_data(data, filename))
|
model.objects.create(**self.get_new_obj_data(data, str(filename)))
|
||||||
except Exception:
|
except Exception as _e:
|
||||||
logger.warning("Failed to create %s '%s'", self.name, data['name'])
|
logger.warning(
|
||||||
|
"Failed to create %s '%s' with error '%s'", self.name, data['name'], _e
|
||||||
|
)
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
@ -19,6 +18,7 @@ from PIL import Image
|
|||||||
import report.models as report_models
|
import report.models as report_models
|
||||||
from build.models import Build
|
from build.models import Build
|
||||||
from common.models import InvenTreeSetting, InvenTreeUserSetting
|
from common.models import InvenTreeSetting, InvenTreeUserSetting
|
||||||
|
from InvenTree.files import MEDIA_STORAGE_DIR, TEMPLATES_DIR
|
||||||
from InvenTree.unit_test import InvenTreeAPITestCase
|
from InvenTree.unit_test import InvenTreeAPITestCase
|
||||||
from report.templatetags import barcode as barcode_tags
|
from report.templatetags import barcode as barcode_tags
|
||||||
from report.templatetags import report as report_tags
|
from report.templatetags import report as report_tags
|
||||||
@ -249,11 +249,9 @@ class ReportTest(InvenTreeAPITestCase):
|
|||||||
|
|
||||||
def copyReportTemplate(self, filename, description):
|
def copyReportTemplate(self, filename, description):
|
||||||
"""Copy the provided report template into the required media directory."""
|
"""Copy the provided report template into the required media directory."""
|
||||||
src_dir = Path(__file__).parent.joinpath('templates', 'report')
|
src_dir = TEMPLATES_DIR.joinpath('report', 'templates', 'report')
|
||||||
|
|
||||||
template_dir = os.path.join('report', 'inventree', self.model.getSubdir())
|
template_dir = os.path.join('report', 'inventree', self.model.getSubdir())
|
||||||
|
dst_dir = MEDIA_STORAGE_DIR.joinpath(template_dir)
|
||||||
dst_dir = settings.MEDIA_ROOT.joinpath(template_dir)
|
|
||||||
|
|
||||||
if not dst_dir.exists(): # pragma: no cover
|
if not dst_dir.exists(): # pragma: no cover
|
||||||
dst_dir.mkdir(parents=True, exist_ok=True)
|
dst_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
Loading…
Reference in New Issue
Block a user