mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-08-20 22:40:31 -04:00
Extract Spreadsheet model from app/utils.py
`app/utils.py` is a bit of a dumping ground for things we don’t have a better place for. We now have a place and structure for storing ‘model’ code (‘model’ in the model, view, controller (MVC) sense of the word). This commit moves the spreadsheet model to that place.
This commit is contained in:
105
app/models/spreadsheet.py
Normal file
105
app/models/spreadsheet.py
Normal file
@@ -0,0 +1,105 @@
|
||||
import csv
|
||||
from io import BytesIO, StringIO
|
||||
from os import path
|
||||
|
||||
import pyexcel
|
||||
import pyexcel_xlsx
|
||||
|
||||
|
||||
class Spreadsheet():
|
||||
|
||||
ALLOWED_FILE_EXTENSIONS = ('csv', 'xlsx', 'xls', 'ods', 'xlsm', 'tsv')
|
||||
|
||||
def __init__(self, csv_data=None, rows=None, filename=''):
|
||||
|
||||
self.filename = filename
|
||||
|
||||
if csv_data and rows:
|
||||
raise TypeError('Spreadsheet must be created from either rows or CSV data')
|
||||
|
||||
self._csv_data = csv_data or ''
|
||||
self._rows = rows or []
|
||||
|
||||
@property
|
||||
def as_dict(self):
|
||||
return {
|
||||
'file_name': self.filename,
|
||||
'data': self.as_csv_data
|
||||
}
|
||||
|
||||
@property
|
||||
def as_csv_data(self):
|
||||
if not self._csv_data:
|
||||
with StringIO() as converted:
|
||||
output = csv.writer(converted)
|
||||
for row in self._rows:
|
||||
output.writerow(row)
|
||||
self._csv_data = converted.getvalue()
|
||||
return self._csv_data
|
||||
|
||||
@classmethod
|
||||
def can_handle(cls, filename):
|
||||
return cls.get_extension(filename) in cls.ALLOWED_FILE_EXTENSIONS
|
||||
|
||||
@staticmethod
|
||||
def get_extension(filename):
|
||||
return path.splitext(filename)[1].lower().lstrip('.')
|
||||
|
||||
@staticmethod
|
||||
def normalise_newlines(file_content):
|
||||
return '\r\n'.join(file_content.read().decode('utf-8').splitlines())
|
||||
|
||||
@classmethod
|
||||
def from_rows(cls, rows, filename=''):
|
||||
return cls(rows=rows, filename=filename)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dictionary, filename=''):
|
||||
return cls.from_rows(
|
||||
zip(
|
||||
*sorted(dictionary.items(), key=lambda pair: pair[0])
|
||||
),
|
||||
filename=filename,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_file(cls, file_content, filename=''):
|
||||
extension = cls.get_extension(filename)
|
||||
|
||||
if extension == 'csv':
|
||||
return cls(csv_data=Spreadsheet.normalise_newlines(file_content), filename=filename)
|
||||
|
||||
if extension == 'tsv':
|
||||
file_content = StringIO(
|
||||
Spreadsheet.normalise_newlines(file_content))
|
||||
|
||||
instance = cls.from_rows(
|
||||
pyexcel.iget_array(
|
||||
file_type=extension,
|
||||
file_stream=file_content),
|
||||
filename)
|
||||
pyexcel.free_resources()
|
||||
return instance
|
||||
|
||||
@classmethod
|
||||
def from_file_form(cls, form):
|
||||
return cls.from_file(
|
||||
form.file.data,
|
||||
filename=form.file.data.filename,
|
||||
)
|
||||
|
||||
@property
|
||||
def as_rows(self):
|
||||
if not self._rows:
|
||||
self._rows = list(csv.reader(
|
||||
StringIO(self._csv_data),
|
||||
quoting=csv.QUOTE_MINIMAL,
|
||||
skipinitialspace=True,
|
||||
))
|
||||
return self._rows
|
||||
|
||||
@property
|
||||
def as_excel_file(self):
|
||||
io = BytesIO()
|
||||
pyexcel_xlsx.save_data(io, {'Sheet 1': self.as_rows})
|
||||
return io.getvalue()
|
||||
Reference in New Issue
Block a user