Browse Source

Naive and incomplete theme import implementation

rafalp 6 years ago
parent
commit
b7497f4289

+ 1 - 0
misago/templates/misago/admin/themes/import.html

@@ -27,6 +27,7 @@ class="form-horizontal" enctype="multipart/form-data"
 <div class="form-body no-fieldsets">
   {% with label_class="col-md-3" field_class="col-md-9" %}
     {% form_row form.name label_class field_class %}
+    {% form_row form.parent label_class field_class %}
     {% form_row form.upload label_class field_class %}
   {% endwith %}
 </div>

+ 1 - 2
misago/themes/admin/exporter.py

@@ -97,8 +97,7 @@ def copy_asset_file(export_dir, asset_file):
     with open(dst_path, "wb") as fp:
         for chunk in asset_file.chunks():
             fp.write(chunk)
-    dirname = os.path.basename(export_dir)
-    return os.path.join(dirname, filename)
+    return filename
 
 
 def write_theme_manifest(export_dir, manifest):

+ 3 - 2
misago/themes/admin/forms.py

@@ -53,14 +53,15 @@ class ImportForm(forms.Form):
         max_length=255,
         required=False,
     )
+    parent = ThemeChoiceField(label=_("Parent"), required=False)
     upload = forms.FileField(
         label=_("Theme file"),
-        help_text=_("Theme file should be a zip file."),
+        help_text=_("Theme file should be a ZIP file."),
     )
 
     def clean_upload(self):
         data = self.cleaned_data["upload"]
-        error_message = gettext("Uploaded file is not a valid Zip file.")
+        error_message = gettext("Uploaded file is not a valid ZIP file.")
         if not data.name.lower().endswith(".zip"):
             raise forms.ValidationError(error_message)
         if data.content_type not in ("application/zip", "application/octet-stream"):

+ 61 - 2
misago/themes/admin/importer.py

@@ -1,13 +1,72 @@
+import json
+import os
 from tempfile import TemporaryDirectory
+from zipfile import BadZipFile, ZipFile
 
 from django.utils.translation import gettext as _
 
+from ..models import Theme
+
 
 class ThemeImportError(BaseException):
     pass
 
 
-def import_theme(name, zipfile):
+def import_theme(name, parent, zipfile):
     with TemporaryDirectory() as tmp_dir:
-        pass
+        extract_zipfile_to_tmp_dir(zipfile, tmp_dir)
+        validate_zipfile_contains_single_directory(tmp_dir)
+        theme_dir = os.path.join(tmp_dir, os.listdir(tmp_dir)[0])
+        clean_data = clean_theme_contents(theme_dir)
+
+        # TODO: finish import
+        return Theme.objects.create(
+            name=name or clean_data["name"],
+            parent=parent,
+            version=clean_data["version"],
+            author=clean_data["author"],
+            url=clean_data["url"],
+        )
     
+
+def extract_zipfile_to_tmp_dir(zipfile, tmp_dir):
+    try:
+        ZipFile(zipfile).extractall(tmp_dir)
+    except BadZipFile:
+        raise ThemeImportError(_("Uploaded ZIP file could not be extracted."))
+
+
+def validate_zipfile_contains_single_directory(tmp_dir):
+    dir_contents = os.listdir(tmp_dir)
+    if not len(dir_contents):
+        raise ThemeImportError(_("Uploaded ZIP file is empty."))
+    if len(dir_contents) > 1:
+        raise ThemeImportError(_("Uploaded ZIP file should contain single directory."))
+    if not os.path.isdir(os.path.join(tmp_dir, dir_contents[0])):
+        raise ThemeImportError(_("Uploaded ZIP file didn't contain a directory."))
+
+
+def clean_theme_contents(theme_dir):
+    return read_manifest(theme_dir)
+
+
+def read_manifest(theme_dir):
+    try:
+        with open(os.path.join(theme_dir, "manifest.json")) as fp:
+            manifest = json.load(fp)
+        if not isinstance(manifest, dict):
+            message = _(
+                '"manifest.json" contained by ZIP file is not a valid '
+                'theme manifest file.'
+            )
+            raise ThemeImportError(message)
+    except FileNotFoundError:
+        raise ThemeImportError(
+            _('Uploaded ZIP file didn\'t contain a "manifest.json".')
+        )
+    except json.decoder.JSONDecodeError:
+        raise ThemeImportError(
+            _('"manifest.json" contained by ZIP file is not a valid JSON file.')
+        )
+    else:
+        return manifest

+ 2 - 2
misago/themes/admin/views.py

@@ -107,8 +107,8 @@ class ImportTheme(ThemeAdmin, generic.FormView):
             form.add_error("upload", str(e))
             return self.render(request, {"form": form})
 
-    def import_theme(self, request, *_, name, upload):
-        theme = import_theme(name, upload)
+    def import_theme(self, request, *_, name, parent, upload):
+        theme = import_theme(name, parent, upload)
         message = gettext('Theme "%(name)s" has been imported.')
         messages.success(request, message % {"name": theme})