Repository: django-oscar/django-oscar

Issue:
"Please copy/symlink the 'missing image'..." exception when MEDIA_ROOT folders are not created 
I get the exception 
`Please copy/symlink the 'missing image' image at ... into your MEDIA_ROOT at .... This exception was raised because Oscar was unable to symlink it for you."
`
whenever I create a fresh environment. 

When I just create folders structure of my MEDIA_ROOT the symlink is created.
Possible solution - add folder creation code before symlink creation.



Discussion:
The missing image is always creating issues, we should do better. Ideas and/or PR's welcome
Why don't we just link to ` django-oscar/src/oscar/static/oscar/img/image_not_found.jpg ` directly instead of attempting (and often failing!) to copy it into the media root? If projects want to customise/change the image they they can just provide their own image at the same path with higher priority, or set `OSCAR_MISSING_IMAGE_URL`.
Yeah agreed. I'm not sure why was chosen for the current solution, but going the proper Django way sounds like a good plan

This is why it was done that way: https://github.com/django-oscar/django-oscar/pull/734 . The image has to be inside `MEDIA_ROOT` for sorl-thumbnail to work on it. Any other ideas?
Ah yeah, had to be a good reason for this. Perhaps a management command which does this automatically? Like ``./manage.py oscar_init_media`` ?

Resolving patch:
diff --git a/src/oscar/apps/catalogue/abstract_models.py b/src/oscar/apps/catalogue/abstract_models.py
--- a/src/oscar/apps/catalogue/abstract_models.py
+++ b/src/oscar/apps/catalogue/abstract_models.py
@@ -1119,6 +1119,11 @@ def symlink_missing_image(self, media_file_path):
         static_file_path = find('oscar/img/%s' % self.name)
         if static_file_path is not None:
             try:
+                # Check that the target directory exists, and attempt to
+                # create it if it doesn't.
+                media_file_dir = os.path.dirname(media_file_path)
+                if not os.path.exists(media_file_dir):
+                    os.makedirs(media_file_dir)
                 os.symlink(static_file_path, media_file_path)
             except OSError:
                 raise ImproperlyConfigured((


Test patch:
diff --git a/tests/integration/catalogue/test_product_image.py b/tests/integration/catalogue/test_product_image.py
--- a/tests/integration/catalogue/test_product_image.py
+++ b/tests/integration/catalogue/test_product_image.py
@@ -1,5 +1,11 @@
-from django.test import TestCase
+import mock
+import os
+import tempfile
 
+from django.contrib.staticfiles.testing import StaticLiveServerTestCase
+from django.test import TestCase, override_settings
+
+from oscar.apps.catalogue.abstract_models import MissingProductImage
 from oscar.test import factories
 
 
@@ -14,3 +20,34 @@ def test_images_are_in_consecutive_order(self):
 
         for idx, im in enumerate(product.images.all()):
             self.assertEqual(im.display_order, idx)
+
+
+class TestMissingProductImage(StaticLiveServerTestCase):
+
+    TEMP_MEDIA_ROOT = tempfile.mkdtemp()
+
+    @override_settings(MEDIA_ROOT=TEMP_MEDIA_ROOT)
+    @mock.patch('oscar.apps.catalogue.abstract_models.find')
+    def test_symlink_creates_directories(self, mock_find):
+        # Create a fake empty file to symlink
+        img = tempfile.NamedTemporaryFile(delete=False)
+        img.close()
+
+        mock_find.return_value = img.name
+        # Initialise the class with a nested path
+        path = 'image/path.jpg'
+        MissingProductImage(path)
+        # Check that the directory exists
+        image_path = os.path.join(self.TEMP_MEDIA_ROOT, path)
+        self.assertTrue(os.path.exists(image_path))
+
+        # Clean up
+        for f in [image_path, img.name]:
+            os.unlink(f)
+        for d in [os.path.join(self.TEMP_MEDIA_ROOT, 'image'), self.TEMP_MEDIA_ROOT]:
+            os.rmdir(d)
+
+    @mock.patch('oscar.apps.catalogue.abstract_models.MissingProductImage.symlink_missing_image')
+    def test_no_symlink_when_no_media_root(self, mock_symlink):
+        MissingProductImage()
+        self.assertEqual(mock_symlink.call_count, 0)