@@ -309,7 +309,7 @@ def test_company_image_migrated(self):
309309 self .assertTrue (
310310 inv_img .primary , f'Image for company { pk } not marked primary'
311311 )
312- # NEW: Check that image file actually exists
312+ # Check that image file actually exists
313313 self .assertTrue (inv_img .image , f'Image field is empty for company { pk } ' )
314314 self .assertIsNotNone (
315315 inv_img .image .name , f'Image name is None for company { pk } '
@@ -372,11 +372,6 @@ def test_image_file_integrity(self):
372372 content = f .read ()
373373 self .assertGreater (len (content ), 0 , 'Image file is empty' )
374374
375- def test_multiple_companies_same_image (self ):
376- """Test deduplication when multiple companies share the same image filename."""
377- # This would require modifying prepare() to create this scenario
378- # or creating a separate test class
379-
380375 def test_content_type_exists (self ):
381376 """Verify that content types are properly set up."""
382377 ContentType = self .new_state .apps .get_model ('contenttypes' , 'contenttype' )
@@ -422,6 +417,150 @@ def test_image_metadata_preserved(self):
422417 )
423418
424419
420+ class TestLegacyImageMigrationReverse (MigratorTestCase ):
421+ """Test that InvenTreeImage values are correctly migrated back to Company.image and Part.image."""
422+
423+ # Start from the new state (after migration) and migrate back
424+ migrate_from = [('common' , '0042_migrate_part_images' )]
425+ migrate_to = [('common' , '0040_inventreeimage' )]
426+
427+ @classmethod
428+ def setUpClass (cls ):
429+ """Set up the test class."""
430+ super ().setUpClass ()
431+ cls ._temp_media = tempfile .mkdtemp (prefix = 'test_media_reverse_' )
432+ cls ._override = override_settings (MEDIA_ROOT = cls ._temp_media )
433+ cls ._override .enable ()
434+
435+ @classmethod
436+ def tearDownClass (cls ):
437+ """Clean up after the test class."""
438+ super ().tearDownClass ()
439+ cls ._override .disable ()
440+ shutil .rmtree (cls ._temp_media , ignore_errors = True )
441+
442+ def prepare (self ):
443+ """Populate the 'new' database state (with InvenTreeImage model)."""
444+ # Get models from the NEW state (after forward migration)
445+ Company = self .old_state .apps .get_model ('company' , 'company' )
446+ Part = self .old_state .apps .get_model ('part' , 'part' )
447+ InvenTreeImage = self .old_state .apps .get_model ('common' , 'inventreeimage' )
448+ ContentType = self .old_state .apps .get_model ('contenttypes' , 'contenttype' )
449+
450+ # --- COMPANY SETUP ---
451+ ct_company = ContentType .objects .get (app_label = 'company' , model = 'company' )
452+
453+ self .company_data = [
454+ {'name' : 'CoOne' , 'file_name' : 'test01.png' },
455+ {'name' : 'CoTwo' , 'file_name' : 'test02.png' },
456+ ]
457+ self .co_pks = []
458+
459+ for entry in self .company_data :
460+ # Create company without image
461+ co = Company .objects .create (name = entry ['name' ])
462+ self .co_pks .append (co .pk )
463+
464+ # Create InvenTreeImage for this company
465+ img = generate_image (filename = entry ['file_name' ])
466+ InvenTreeImage .objects .create (
467+ content_type = ct_company , object_id = co .pk , image = img , primary = True
468+ )
469+
470+ # Company with no image
471+ no_image_co = Company .objects .create (
472+ name = 'NoImageCo' , description = 'No image here'
473+ )
474+ self .no_image_co_pk = no_image_co .pk
475+
476+ # --- PART SETUP ---
477+ ct_part = ContentType .objects .get (app_label = 'part' , model = 'part' )
478+
479+ # Dummy MPPT data
480+ tree = {'tree_id' : 0 , 'level' : 0 , 'lft' : 0 , 'rght' : 0 }
481+
482+ self .part_data = [
483+ {'name' : 'PartOne' , 'file_name' : 'part01.png' },
484+ {'name' : 'PartTwo' , 'file_name' : 'part02.png' },
485+ ]
486+ self .part_pks = []
487+
488+ for entry in self .part_data :
489+ # Create part without image
490+ part = Part .objects .create (
491+ name = entry ['name' ],
492+ description = 'Test Part Description' ,
493+ active = True ,
494+ assembly = True ,
495+ purchaseable = True ,
496+ ** tree ,
497+ )
498+ self .part_pks .append (part .pk )
499+
500+ # Create InvenTreeImage for this part
501+ img = generate_image (filename = entry ['file_name' ])
502+ InvenTreeImage .objects .create (
503+ content_type = ct_part , object_id = part .pk , image = img , primary = True
504+ )
505+
506+ # Part with no image
507+ no_image_part = Part .objects .create (
508+ name = 'NoImagePart' ,
509+ description = 'Test NoImagePart Description' ,
510+ active = True ,
511+ assembly = True ,
512+ purchaseable = True ,
513+ ** tree ,
514+ )
515+ self .no_image_part_pk = no_image_part .pk
516+
517+ def test_company_image_reverse_migrated (self ):
518+ """After reverse migration, Company.image should be restored."""
519+ Company = self .new_state .apps .get_model ('company' , 'company' )
520+
521+ # Check each company has its image restored
522+ for idx , _data in enumerate (self .company_data ):
523+ pk = self .co_pks [idx ]
524+ co = Company .objects .get (pk = pk )
525+
526+ # Verify image field is populated
527+ self .assertTrue (co .image , f'Image not restored for company { pk } ' )
528+ self .assertIsNotNone (co .image .name , f'Image name is None for company { pk } ' )
529+
530+ # Verify image file exists
531+ self .assertTrue (
532+ co .image .storage .exists (co .image .name ),
533+ f'Image file does not exist for company { pk } ' ,
534+ )
535+
536+ # Verify company with no image still has no image
537+ no_img_co = Company .objects .get (pk = self .no_image_co_pk )
538+ self .assertFalse (no_img_co .image , 'Company should not have image' )
539+
540+ def test_part_image_reverse_migrated (self ):
541+ """After reverse migration, Part.image should be restored."""
542+ Part = self .new_state .apps .get_model ('part' , 'part' )
543+
544+ # Check each part has its image restored
545+ for idx , _data in enumerate (self .part_data ):
546+ pk = self .part_pks [idx ]
547+ part = Part .objects .get (pk = pk )
548+
549+ # Verify image field is populated
550+ self .assertTrue (part .image , f'Image not restored for part { pk } ' )
551+ self .assertIsNotNone (part .image .name , f'Image name is None for part { pk } ' )
552+
553+ # Verify image file exists
554+ self .assertTrue (
555+ part .image .storage .exists (part .image .name ),
556+ f'Image file does not exist for part { pk } ' ,
557+ )
558+
559+ # Verify part with no image still has no image
560+ no_img_part = Part .objects .get (pk = self .no_image_part_pk )
561+ self .assertFalse (no_img_part .image , 'Part should not have image' )
562+
563+
425564def prep_currency_migration (self , vals : str ):
426565 """Prepare the environment for the currency migration tests."""
427566 # Set keys
0 commit comments