Repository: zestedesavoir/zds-site

Issue:
Les images n'apparaissent pas dans les ePUBs
**Description du bug**

Les images n'apparaissent pas dans les exports des contenus en ePUBs.

Le problème semble être différent suivant l'URL utilisée pour inclure l'image dans le Markdown :
- si on utilise une URL relative, comme lorsqu'on insère une image par glissé-deposé dans le nouvel éditeur, une erreur survient à la génération du markdown, par exemple :

        ERROR:zds.utils.templatetags.emarkdown:Markdown errors ["ENOENT: no such file or directory, open 'OPS/images/b39cdac3-957b-450d-a30c-e4046a4aa095.png'", "ENOENT: no such file or directory, open 'OPS/images/76b74b82-6431-4f98-bda1-6a4932605681.png'", "ENOENT: no such file or directory, open 'OPS/images/c4bba782-b92f-4ac3-b477-5310b96b5851.png'", "ENOENT: no such file or directory, open 'OPS/images/34cb492b-22a5-41dc-8f21-7e4247810345.png'"]

En effet, l'image n'est pas dans le dossier qui contient les images pour construire l'ePUB.
- si on utilise une URL absolue, l'image est bien récupérée, mais elle n'apparaît pas dans l'ePUB généré.





Discussion:

Resolving patch:
diff --git a/zds/tutorialv2/epub_utils.py b/zds/tutorialv2/epub_utils.py
--- a/zds/tutorialv2/epub_utils.py
+++ b/zds/tutorialv2/epub_utils.py
@@ -22,10 +22,12 @@ def __build_mime_type_conf():
     return {"filename": "mimetype", "content": "application/epub+zip"}
 
 
-def __traverse_and_identify_images(image_dir):
+def __traverse_and_identify_images(root_image_dir, current_dir=None):
     """
-    :param image_dir:
-    :type image_dir: pathlib.Path
+    :param root_image_dir: Root folder of the images
+    :type root_image_dir: pathlib.Path
+    :param current_dir:  Folder currently explored
+    :type current_dir: pathlib.Path
     :return:
     """
     media_type_map = {
@@ -36,13 +38,16 @@ def __traverse_and_identify_images(image_dir):
         ".svg": "image/svg",
     }
 
-    for image_file_path in image_dir.iterdir():
+    if current_dir is None:
+        current_dir = root_image_dir
+
+    for image_file_path in current_dir.iterdir():
         if image_file_path.is_dir():
-            yield from __traverse_and_identify_images(image_file_path)
+            yield from __traverse_and_identify_images(root_image_dir, image_file_path)
             continue
         ext = path.splitext(image_file_path.name)[1]
-        identifier = f"image_{image_file_path.name}".lower().replace(".", "-").replace("@", "-")
-        ebook_image_path = Path("images", image_file_path.name)
+        ebook_image_path = Path("images", image_file_path.relative_to(root_image_dir))
+        identifier = "image_" + str(ebook_image_path)[7:].lower().replace(".", "-").replace("@", "-").replace("/", "-")
         yield ebook_image_path, identifier, media_type_map.get(ext.lower(), "image/png")
 
 
@@ -206,7 +211,14 @@ def handle_image_path_with_good_img_dir_path(html_code):
                 if self.url_scheme_matcher.search(image_url):
                     splitted = parse.urlsplit(image_url)
                     final_path = splitted.path
-                elif image_url.startswith(settings.MEDIA_URL):
+                elif (not (Path(settings.MEDIA_URL).is_dir() and Path(image_url).exists())) and image_url.startswith(
+                    settings.MEDIA_URL
+                ):
+                    # do not go there if image_url is the path on the system
+                    # and not a portion of web URL
+                    # (image_url.startswith(settings.MEDIA_URL) can be True if
+                    # zds-site is in a directory under /media (the default
+                    # value of settings.MEDIA_URL))
                     final_path = Path(image_url).name
                 elif Path(image_url).is_absolute() and "images" in image_url:
                     root = Path(image_url)
@@ -217,7 +229,7 @@ def handle_image_path_with_good_img_dir_path(html_code):
                     final_path = Path(image_url).name
                 image_path_in_ebook = relative_path + "/images/" + str(final_path).replace("%20", "_")
                 image["src"] = str(image_path_in_ebook)
-                self.names.add(Path(image_path_in_ebook).name)
+                self.names.add(final_path)
             ids = {}
             for element in soup_parser.find_all(name=None, attrs={"id": (lambda s: True)}):
                 while element.get("id", None) and element["id"] in ids:
@@ -229,8 +241,13 @@ def handle_image_path_with_good_img_dir_path(html_code):
         return handle_image_path_with_good_img_dir_path
 
     def remove_unused_image(self, image_path: Path, imglist):
-        for image in image_path.iterdir():
-            if image.name not in self.names and not image.is_dir():
+        # Remove unused images:
+        for image in image_path.rglob("*"):
+            if str(Path(image).relative_to(image_path)) not in self.names and not image.is_dir():
                 os.remove(str(image))
                 imglist = [i for i in imglist if i[0].name.replace("%20", "_") != image.name]
+        # Remove empty folders:
+        for item in image_path.iterdir():
+            if item.is_dir() and len(list(item.iterdir())) == 0:
+                os.rmdir(str(item))
         return imglist
diff --git a/zds/tutorialv2/publish_container.py b/zds/tutorialv2/publish_container.py
--- a/zds/tutorialv2/publish_container.py
+++ b/zds/tutorialv2/publish_container.py
@@ -165,8 +165,8 @@ def publish_container(
         makedirs(current_dir)
 
     img_relative_path = ".." if ctx["relative"] == "." else "../" + ctx["relative"]
+    wrapped_image_callback = image_callback(img_relative_path) if image_callback else None
     if container.has_extracts():  # the container can be rendered in one template
-        wrapped_image_callback = image_callback(img_relative_path) if image_callback else image_callback
         args = {"container": container, "is_js": is_js}
         args.update(ctx)
         args["relative"] = img_relative_path
@@ -184,9 +184,7 @@ def publish_container(
 
         container.introduction = None
         container.conclusion = None
-
     else:  # separate render of introduction and conclusion
-        wrapped_image_callback_intro_ccl = image_callback(img_relative_path) if image_callback else image_callback
         # create subdirectory
         if not path.isdir(current_dir):
             makedirs(current_dir)
@@ -201,9 +199,7 @@ def publish_container(
             else:
                 parsed = emarkdown(container.get_introduction(), db_object.js_support)
             container.introduction = str(part_path)
-            write_chapter_file(
-                base_dir, container, part_path, parsed, path_to_title_dict, wrapped_image_callback_intro_ccl
-            )
+            write_chapter_file(base_dir, container, part_path, parsed, path_to_title_dict, wrapped_image_callback)
         children = copy.copy(container.children)
         container.children = []
         container.children_dict = {}
@@ -234,9 +230,7 @@ def publish_container(
             else:
                 parsed = emarkdown(container.get_conclusion(), db_object.js_support)
             container.conclusion = str(part_path)
-            write_chapter_file(
-                base_dir, container, part_path, parsed, path_to_title_dict, wrapped_image_callback_intro_ccl
-            )
+            write_chapter_file(base_dir, container, part_path, parsed, path_to_title_dict, wrapped_image_callback)
 
     return path_to_title_dict
 
diff --git a/zds/utils/templatetags/emarkdown.py b/zds/utils/templatetags/emarkdown.py
--- a/zds/utils/templatetags/emarkdown.py
+++ b/zds/utils/templatetags/emarkdown.py
@@ -158,7 +158,6 @@ def epub_markdown(md_input, image_directory):
             md_input,
             output_format="epub",
             images_download_dir=image_directory.absolute,
-            local_url_to_local_path=[settings.MEDIA_URL + "galleries/[0-9]+", image_directory.relative],
         )
         .replace('src"/', f'src="{media_root}')
         .replace(f'src="{media_root}{replaced_media_url}', f'src="{media_root}')


Test patch: