-
-
Notifications
You must be signed in to change notification settings - Fork 858
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor label/report template copying (#6582)
* [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 * add s3 support * storages * more adaptions * use s3 switch to set storage backend * fix reqs * cleanup default_storage * init in storage_backend * move to storage classes everywhere * fix call * remove more S3 references * move storage init * fix startup error * alsways use url * ignore FileExistsError * move s3 required url in * remove S3 for now * use Djangos defaults * fix old import * remove default_storage calls * make labels/reports more similar * expand functions out * refactor to use refs where possible * refactor copy section to be similar * unify db lookup * move shared code to generic section * move ready out * docstrings * move even more functions out * move references inline of the classes * clean up refs * fix init * fix ensure dir * remove unneeded tries * cleanup diff * more cleanup * fix tests * use SUBDIR
- Loading branch information
Showing
9 changed files
with
293 additions
and
367 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
"""Shared templating code.""" | ||
|
||
import logging | ||
import os | ||
import warnings | ||
from pathlib import Path | ||
|
||
from django.conf import settings | ||
from django.core.exceptions import AppRegistryNotReady | ||
from django.core.files.storage import default_storage | ||
from django.db.utils import IntegrityError, OperationalError, ProgrammingError | ||
|
||
from maintenance_mode.core import maintenance_mode_on, set_maintenance_mode | ||
|
||
import InvenTree.helpers | ||
from InvenTree.config import ensure_dir | ||
|
||
logger = logging.getLogger('inventree') | ||
|
||
|
||
MEDIA_STORAGE_DIR = Path(settings.MEDIA_ROOT) | ||
|
||
|
||
class TemplatingMixin: | ||
"""Mixin that contains shared templating code.""" | ||
|
||
name: str = '' | ||
db: str = '' | ||
|
||
def __init__(self, *args, **kwargs): | ||
"""Ensure that the required properties are set.""" | ||
super().__init__(*args, **kwargs) | ||
if self.name == '': | ||
raise NotImplementedError('ref must be set') | ||
if self.db == '': | ||
raise NotImplementedError('db must be set') | ||
|
||
def create_defaults(self): | ||
"""Function that creates all default templates for the app.""" | ||
raise NotImplementedError('create_defaults must be implemented') | ||
|
||
def get_src_dir(self, ref_name): | ||
"""Get the source directory for the default templates.""" | ||
raise NotImplementedError('get_src_dir must be implemented') | ||
|
||
def get_new_obj_data(self, data, filename): | ||
"""Get the data for a new template db object.""" | ||
raise NotImplementedError('get_new_obj_data must be implemented') | ||
|
||
# Standardized code | ||
def ready(self): | ||
"""This function is called whenever the app is loaded.""" | ||
import InvenTree.ready | ||
|
||
# skip loading if plugin registry is not loaded or we run in a background thread | ||
if ( | ||
not InvenTree.ready.isPluginRegistryLoaded() | ||
or not InvenTree.ready.isInMainThread() | ||
): | ||
return | ||
|
||
if not InvenTree.ready.canAppAccessDatabase(allow_test=False): | ||
return # pragma: no cover | ||
|
||
with maintenance_mode_on(): | ||
try: | ||
self.create_defaults() | ||
except ( | ||
AppRegistryNotReady, | ||
IntegrityError, | ||
OperationalError, | ||
ProgrammingError, | ||
): | ||
# Database might not yet be ready | ||
warnings.warn( | ||
f'Database was not ready for creating {self.name}s', stacklevel=2 | ||
) | ||
|
||
set_maintenance_mode(False) | ||
|
||
def create_template_dir(self, model, data): | ||
"""Create folder and database entries for the default templates, if they do not already exist.""" | ||
ref_name = model.getSubdir() | ||
|
||
# Create root dir for templates | ||
src_dir = self.get_src_dir(ref_name) | ||
dst_dir = MEDIA_STORAGE_DIR.joinpath(self.name, 'inventree', ref_name) | ||
ensure_dir(dst_dir, default_storage) | ||
|
||
# Copy each template across (if required) | ||
for entry in data: | ||
self.create_template_file(model, src_dir, entry, ref_name) | ||
|
||
def create_template_file(self, model, src_dir, data, ref_name): | ||
"""Ensure a label template is in place.""" | ||
# Destination filename | ||
filename = os.path.join(self.name, 'inventree', ref_name, data['file']) | ||
|
||
src_file = src_dir.joinpath(data['file']) | ||
dst_file = MEDIA_STORAGE_DIR.joinpath(filename) | ||
|
||
do_copy = False | ||
|
||
if not dst_file.exists(): | ||
logger.info("%s template '%s' is not present", self.name, filename) | ||
do_copy = True | ||
else: | ||
# Check if the file contents are different | ||
src_hash = InvenTree.helpers.hash_file(src_file) | ||
dst_hash = InvenTree.helpers.hash_file(dst_file) | ||
|
||
if src_hash != dst_hash: | ||
logger.info("Hash differs for '%s'", filename) | ||
do_copy = True | ||
|
||
if do_copy: | ||
logger.info("Copying %s template '%s'", self.name, dst_file) | ||
# Ensure destination dir exists | ||
dst_file.parent.mkdir(parents=True, exist_ok=True) | ||
|
||
# Copy file | ||
default_storage.save(filename, src_file.open('rb')) | ||
|
||
# Check if a file matching the template already exists | ||
try: | ||
if model.objects.filter(**{self.db: filename}).exists(): | ||
return # pragma: no cover | ||
except Exception: | ||
logger.exception( | ||
"Failed to query %s for '%s' - you should run 'invoke update' first!", | ||
self.name, | ||
filename, | ||
) | ||
|
||
logger.info("Creating entry for %s '%s'", model, data.get('name')) | ||
|
||
try: | ||
model.objects.create(**self.get_new_obj_data(data, filename)) | ||
except Exception: | ||
logger.warning("Failed to create %s '%s'", self.name, data['name']) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.