Browse Source

Stop using tablib & use openpyxl

Remove copyright notice, the code has been replaced
Jaap Roes 2 years ago
parent
commit
07adb156d1

+ 1 - 3
setup.py

@@ -33,8 +33,7 @@ install_requires = [
     "Willow>=1.4,<1.5",
     "requests>=2.11.1,<3.0",
     "l18n>=2018.5",
-    "xlsxwriter>=1.2.8,<4.0",
-    "tablib[xlsx]>=0.14.0",
+    "openpyxl>=3.0.10,<4.0",
     "anyascii>=0.1.5",
     "telepath>=0.1.1,<1",
 ]
@@ -48,7 +47,6 @@ testing_extras = [
     "Jinja2>=3.0,<3.2",
     "boto3>=1.16,<1.17",
     "freezegun>=0.3.8",
-    "openpyxl>=2.6.4",
     "azure-mgmt-cdn>=5.1,<6.0",
     "azure-mgmt-frontdoor>=0.3,<0.4",
     "django-pattern-library>=0.7,<0.8",

+ 49 - 47
wagtail/contrib/redirects/base_formats.py

@@ -1,33 +1,36 @@
-"""
-Copyright (c) Bojan Mihelac and individual contributors.
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification,
-are permitted provided that the following conditions are met:
-
-    1. Redistributions of source code must retain the above copyright notice,
-       this list of conditions and the following disclaimer.
-
-    2. Redistributions in binary form must reproduce the above copyright
-       notice, this list of conditions and the following disclaimer in the
-       documentation and/or other materials provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
-ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
-ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-"""
-# https://raw.githubusercontent.com/django-import-export/django-import-export/main/import_export/formats/base_formats.py
-from io import BytesIO
+import csv
+from io import BytesIO, StringIO
 
 import openpyxl
-import tablib
+
+
+class Dataset(list):
+    def __init__(self, rows=(), headers=None):
+        super().__init__(rows)
+        self.headers = headers or (self.pop(0) if len(self) > 0 else [])
+
+    def __str__(self):
+        """Print a table"""
+        result = [[]]
+        widths = []
+
+        for col in self.headers:
+            value = str(col) if col is not None else ""
+            result[0].append(value)
+            widths.append(len(value) + 1)
+
+        for row in self:
+            result.append([])
+            for idx, col in enumerate(row):
+                value = str(col) if col is not None else ""
+                result[-1].append(value)
+                widths[idx] = max(widths[idx], len(value) + 1)
+
+        row_formatter = (
+            "| ".join(f"{{{idx}:{width}}}" for idx, width in enumerate(widths))
+        ).format
+
+        return "\n".join(row_formatter(*row) for row in result)
 
 
 class CSV:
@@ -37,13 +40,19 @@ class CSV:
     def get_read_mode(self):
         return "r"
 
-    def create_dataset(self, in_stream):
-        return tablib.import_set(in_stream, format="csv")
+    def create_dataset(self, data, delimiter=","):
+        """
+        Create dataset from csv data.
+        """
+        return Dataset(csv.reader(StringIO(data), delimiter=delimiter))
 
 
 class TSV(CSV):
-    def create_dataset(self, in_stream):
-        return tablib.import_set(in_stream, format="tsv")
+    def create_dataset(self, data):
+        """
+        Create dataset from tsv data.
+        """
+        return super().create_dataset(data, delimiter="\t")
 
 
 class XLSX:
@@ -53,23 +62,16 @@ class XLSX:
     def get_read_mode(self):
         return "rb"
 
-    def create_dataset(self, in_stream):
+    def create_dataset(self, data):
         """
-        Create dataset from first sheet.
+        Create dataset from the first sheet of a xlsx workbook.
         """
-        xlsx_book = openpyxl.load_workbook(BytesIO(in_stream), read_only=True)
-
-        dataset = tablib.Dataset()
-        sheet = xlsx_book.active
-
-        # obtain generator
-        rows = sheet.rows
-        dataset.headers = [cell.value for cell in next(rows)]
-
-        for row in rows:
-            row_values = [cell.value for cell in row]
-            dataset.append(row_values)
-        return dataset
+        workbook = openpyxl.load_workbook(BytesIO(data), read_only=True, data_only=True)
+        sheet = workbook.worksheets[0]
+        try:
+            return Dataset(tuple(cell.value for cell in row) for row in sheet.rows)
+        finally:
+            workbook.close()
 
 
 DEFAULT_FORMATS = [

+ 9 - 14
wagtail/contrib/redirects/management/commands/import_redirects.py

@@ -1,8 +1,8 @@
 import os
 
-import tablib
 from django.core.management.base import BaseCommand
 
+from wagtail.contrib.redirects.base_formats import Dataset
 from wagtail.contrib.redirects.forms import RedirectForm
 from wagtail.contrib.redirects.utils import (
     get_format_cls_by_extension,
@@ -106,8 +106,10 @@ class Command(BaseCommand):
         if not format_:
             format_ = extension
 
-        if not get_format_cls_by_extension(format_):
+        import_format_cls = get_format_cls_by_extension(format_)
+        if import_format_cls is None:
             raise Exception("Invalid format '{0}'".format(extension))
+        input_format = import_format_cls()
 
         if extension in ["xls", "xlsx"]:
             mode = "rb"
@@ -115,18 +117,11 @@ class Command(BaseCommand):
             mode = "r"
 
         with open(src, mode) as fh:
-            imported_data = tablib.Dataset().load(fh.read(), format=format_)
-
-            sample_data = tablib.Dataset(
-                *imported_data[: min(len(imported_data), 4)],
-                headers=imported_data.headers,
-            )
-
-            try:
-                self.stdout.write("Sample data:")
-                self.stdout.write(str(sample_data))
-            except Exception:
-                self.stdout.write("Warning: Cannot display sample data")
+            imported_data = input_format.create_dataset(fh.read())
+            sample_data = Dataset(imported_data[:4], imported_data.headers)
+
+            self.stdout.write("Sample data:")
+            self.stdout.write(str(sample_data))
 
             self.stdout.write("--------------")