diff --git a/InvenTree/InvenTree/management/commands/rebuild_thumbnails.py b/InvenTree/InvenTree/management/commands/rebuild_thumbnails.py new file mode 100644 index 0000000000..243d609863 --- /dev/null +++ b/InvenTree/InvenTree/management/commands/rebuild_thumbnails.py @@ -0,0 +1,70 @@ +""" +Custom management command to rebuild thumbnail images + +- May be required after importing a new dataset, for example +""" + +import os +import logging + +from PIL import UnidentifiedImageError + +from django.core.management.base import BaseCommand +from django.conf import settings +from django.db.utils import OperationalError, ProgrammingError + +from company.models import Company +from part.models import Part + + +logger = logging.getLogger("inventree-thumbnails") + + +class Command(BaseCommand): + """ + Rebuild all thumbnail images + """ + + def rebuild_thumbnail(self, model): + """ + Rebuild the thumbnail specified by the "image" field of the provided model + """ + + if not model.image: + return + + img = model.image + url = img.thumbnail.name + loc = os.path.join(settings.MEDIA_ROOT, url) + + if not os.path.exists(loc): + logger.info(f"Generating thumbnail image for '{img}'") + + try: + model.image.render_variations(replace=False) + except FileNotFoundError: + logger.error(f"ERROR: Image file '{img}' is missing") + except UnidentifiedImageError: + logger.error(f"ERROR: Image file '{img}' is not a valid image") + + def handle(self, *args, **kwargs): + + logger.setLevel(logging.INFO) + + logger.info("Rebuilding Part thumbnails") + + for part in Part.objects.exclude(image=None): + try: + self.rebuild_thumbnail(part) + except (OperationalError, ProgrammingError): + logger.error("ERROR: Database read error.") + break + + logger.info("Rebuilding Company thumbnails") + + for company in Company.objects.exclude(image=None): + try: + self.rebuild_thumbnail(company) + except (OperationalError, ProgrammingError): + logger.error("ERROR: abase read error.") + break diff --git a/InvenTree/company/apps.py b/InvenTree/company/apps.py index 76798c5ad4..497193237d 100644 --- a/InvenTree/company/apps.py +++ b/InvenTree/company/apps.py @@ -23,29 +23,4 @@ class CompanyConfig(AppConfig): This function is called whenever the Company app is loaded. """ - if canAppAccessDatabase(): - self.generate_company_thumbs() - - def generate_company_thumbs(self): - - from .models import Company - - logger.debug("Checking Company image thumbnails") - - try: - for company in Company.objects.all(): - if company.image: - url = company.image.thumbnail.name - loc = os.path.join(settings.MEDIA_ROOT, url) - - if not os.path.exists(loc): - logger.info("InvenTree: Generating thumbnail for Company '{c}'".format(c=company.name)) - try: - company.image.render_variations(replace=False) - except FileNotFoundError: - logger.warning(f"Image file '{company.image}' missing") - except UnidentifiedImageError: - logger.warning(f"Image file '{company.image}' is invalid") - except (OperationalError, ProgrammingError): - # Getting here probably meant the database was in test mode - pass + pass diff --git a/InvenTree/part/apps.py b/InvenTree/part/apps.py index 0c57e2c1ab..ed423da9bd 100644 --- a/InvenTree/part/apps.py +++ b/InvenTree/part/apps.py @@ -24,40 +24,8 @@ class PartConfig(AppConfig): """ if canAppAccessDatabase(): - self.generate_part_thumbnails() self.update_trackable_status() - def generate_part_thumbnails(self): - """ - Generate thumbnail images for any Part that does not have one. - This function exists mainly for legacy support, - as any *new* image uploaded will have a thumbnail generated automatically. - """ - - from .models import Part - - logger.debug("InvenTree: Checking Part image thumbnails") - - try: - # Only check parts which have images - for part in Part.objects.exclude(image=None): - if part.image: - url = part.image.thumbnail.name - loc = os.path.join(settings.MEDIA_ROOT, url) - - if not os.path.exists(loc): - logger.info("InvenTree: Generating thumbnail for Part '{p}'".format(p=part.name)) - try: - part.image.render_variations(replace=False) - except FileNotFoundError: - logger.warning(f"Image file '{part.image}' missing") - pass - except UnidentifiedImageError: - logger.warning(f"Image file '{part.image}' is invalid") - except (OperationalError, ProgrammingError): - # Exception if the database has not been migrated yet - pass - def update_trackable_status(self): """ Check for any instances where a trackable part is used in the BOM diff --git a/tasks.py b/tasks.py index 1abbf23bc6..59fa83e56b 100644 --- a/tasks.py +++ b/tasks.py @@ -127,13 +127,20 @@ def worker(c): @task -def rebuild(c): +def rebuild_models(c): """ Rebuild database models with MPTT structures """ - manage(c, "rebuild_models") + manage(c, "rebuild_models", pty=True) +@task +def rebuild_thumbnails(c): + """ + Rebuild missing image thumbnails + """ + + manage(c, "rebuild_thumbnails", pty=True) @task def clean_settings(c): @@ -143,7 +150,7 @@ def clean_settings(c): manage(c, "clean_settings") -@task(post=[rebuild]) +@task(post=[rebuild_models, rebuild_thumbnails]) def migrate(c): """ Performs database migrations. @@ -341,7 +348,7 @@ def export_records(c, filename='data.json'): print("Data export completed") -@task(help={'filename': 'Input filename'}, post=[rebuild]) +@task(help={'filename': 'Input filename'}, post=[rebuild_models, rebuild_thumbnails]) def import_records(c, filename='data.json'): """ Import database records from a file @@ -399,7 +406,7 @@ def delete_data(c, force=False): manage(c, 'flush') -@task(post=[rebuild]) +@task(post=[rebuild_models, rebuild_thumbnails]) def import_fixtures(c): """ Import fixture data into the database.