bjoern@0: # -*- coding: utf-8 -*-
bjoern@0:
bjoern@29: import logging
bjoern@0: import tempfile
bjoern@0:
bjoern@16: from flask import request, Response, json, render_template
bjoern@0: from flask.views import MethodView
bjoern@0:
bjoern@4: from py3o.template import Template
bjoern@4:
bjoern@19: from PyPDF2 import PdfFileMerger
bjoern@61: from PyPDF2.utils import PyPdfError
bjoern@19:
bjoern@47: from werkzeug.utils import escape
bjoern@47:
bjoern@29: log = logging.getLogger(__name__)
bjoern@19:
bjoern@0: ALLOWED_FORMATS = ["pdf", "doc", "docx", "odt"]
bjoern@0:
bjoern@19: PDF_MIMETYPE = "application/pdf"
bjoern@47: JSON_MIMETYPE = "application/json"
bjoern@47: HTML_MIMETYPE = "text/html"
bjoern@19:
bjoern@0: MIMETYPES = {
bjoern@0: "odt": "application/vnd.oasis.opendocument.text",
bjoern@0: "doc": "application/msword",
bjoern@0: "docx": "application/vnd.openxmlformats-officedocument"
bjoern@0: ".wordprocessingml.document",
bjoern@19: "pdf": PDF_MIMETYPE,
bjoern@0: }
bjoern@0:
bjoern@0: DEFAULT_MIMETYPE = "application/octet-stream"
bjoern@0:
bjoern@0:
bjoern@47: class ErrorResponse(Response):
bjoern@42:
bjoern@47: BAD_REQUEST_ERROR_CODE = 400
bjoern@42:
bjoern@47: def __init__(self, title, error_code, details,
bh@92: http_error_code=BAD_REQUEST_ERROR_CODE):
bjoern@47: data, mime_type = self.get_response_data(title, error_code, details)
bjoern@47: super(ErrorResponse, self).__init__(response=data, mimetype=mime_type,
bh@92: status=http_error_code)
bjoern@47:
bjoern@47: def json(self, title, error_code, details):
bjoern@47: return json.dumps({
bjoern@47: "error": title,
bjoern@47: "error_code": error_code,
bjoern@47: "details": details,
bjoern@47: }), JSON_MIMETYPE
bjoern@47:
bjoern@47: def html(self, title, error_code, details):
bjoern@59: data = (
bjoern@47: u'\n'
bjoern@47: u'
%(code)s %(name)s\n'
bjoern@47: u'%(name)s
\n'
bjoern@47: u'%(details)s\n'
bjoern@47: ) % {
bjoern@47: "code": error_code,
bjoern@47: "name": escape(title),
bjoern@47: "details": escape(details),
bjoern@47: }
bjoern@47: return data, HTML_MIMETYPE
bjoern@47:
bjoern@47: def get_response_data(self, title, error_code, details):
bjoern@42: if self.is_wants_json():
bjoern@47: return self.json(title, error_code, details)
bjoern@47: return self.html(title, error_code, details)
bjoern@42:
bjoern@42: def is_wants_json(self):
bjoern@47: best = request.accept_mimetypes.best_match([JSON_MIMETYPE,
bjoern@47: HTML_MIMETYPE])
bjoern@47: return best == JSON_MIMETYPE and \
bjoern@42: request.accept_mimetypes[best] > \
bjoern@47: request.accept_mimetypes[HTML_MIMETYPE]
bjoern@47:
bjoern@47:
bjoern@47: class TemplateErrorResponse(ErrorResponse):
bjoern@47:
bjoern@47: TEMPLATE_ERROR_CODE = 100
bjoern@47:
bjoern@47: def __init__(self, details, error_code=TEMPLATE_ERROR_CODE):
bjoern@47: super(TemplateErrorResponse, self).__init__(
bjoern@47: title="TemplateError", error_code=error_code, details=details,
bh@92: http_error_code=500)
bjoern@47:
bjoern@47:
bjoern@47: class ConversionErrorResponse(ErrorResponse):
bjoern@47:
bjoern@47: CONVERSION_ERROR_CODE = 200
bjoern@47:
bjoern@47: def __init__(self, details, error_code=CONVERSION_ERROR_CODE):
bjoern@54: super(ConversionErrorResponse, self).__init__(
bjoern@47: title="ConversionError", error_code=error_code, details=details,
bh@92: http_error_code=500)
bjoern@42:
bjoern@42:
bjoern@60: class MergeErrorResponse(ErrorResponse):
bjoern@60:
bjoern@60: MERGE_ERROR_CODE = 300
bjoern@60:
bh@93: def __init__(self, details, error_code=MERGE_ERROR_CODE,
bh@93: http_error_code=500):
bjoern@60: super(MergeErrorResponse, self).__init__(
bjoern@60: title="MergeError", error_code=error_code, details=details,
bh@93: http_error_code=http_error_code)
bjoern@60:
bjoern@60:
bjoern@0: class ConvertView(MethodView):
bjoern@0:
bjoern@0: def __init__(self, pyuno_driver_name="", hostname="localhost", port=2001):
bjoern@0: driver_module = self._load_driver_module(pyuno_driver_name)
bjoern@0: self.convertor = driver_module.Convertor(hostname, port)
bjoern@0:
bjoern@0: def _load_driver_module(self, pyuno_driver_name):
bjoern@0: return __import__(pyuno_driver_name, globals(), locals(),
bjoern@0: ["Convertor"])
bjoern@0:
bjoern@3: def is_format_supported(self, fformat):
bjoern@3: return fformat and fformat.lower() in ALLOWED_FORMATS
bjoern@0:
bjoern@3: def post(self):
frank@84: log.debug("Converting document")
bjoern@3: ffile = request.files['file']
bjoern@29: if not ffile.filename:
bjoern@47: return ErrorResponse(
bjoern@47: "Upload file missing", error_code=101,
bjoern@47: details="Please upload a file for conversion",
bh@92: http_error_code=400)
bjoern@29:
bjoern@3: fformat = request.form['format']
bjoern@3: if not self.is_format_supported(fformat):
bjoern@47: return ErrorResponse(
bjoern@47: "Invalid format", error_code=102,
bjoern@47: details="Format %s not allowed" % fformat,
bh@92: http_error_code=400)
bjoern@3:
bjoern@30: datadict = self.get_datadict()
bjoern@30:
frank@84: if datadict:
frank@84: log.debug(" with datadict")
frank@84:
bjoern@29: mimetype = self.get_mimetype_for_format(fformat)
bjoern@29:
frank@84: log.debug(" to %s" % fformat)
frank@84:
bjoern@30: outfile = self.save_form_file(ffile)
bjoern@30:
bjoern@30: if datadict:
bjoern@31: try:
bjoern@31: tfile = tempfile.NamedTemporaryFile()
bjoern@42: t = Template(outfile, tfile, ignore_undefined_variables=True)
bjoern@31: t.render(datadict)
bjoern@31: outfile.close()
bjoern@31: outfile = tfile
bjoern@42: except Exception, e:
bjoern@41: log.exception("Template error")
bjoern@47: return TemplateErrorResponse(details=str(e))
bjoern@30:
bjoern@32: if fformat != "odt":
bjoern@30: try:
bjoern@30: outfile = self.convert(outfile, fformat)
bjoern@42: except Exception, e:
bjoern@30: log.exception("Conversion error")
bjoern@47: return ConversionErrorResponse(details=str(e))
bjoern@29:
frank@84: log.debug("Document converted")
bjoern@0: return Response(outfile, mimetype=mimetype)
bjoern@0:
bjoern@16: def get(self):
bjoern@16: return render_template("convert.html")
bjoern@16:
bjoern@3: def save_form_file(self, infile):
bjoern@33: outfile = tempfile.NamedTemporaryFile()
bjoern@30: infile.save(outfile)
bjoern@30: infile.close()
bjoern@33: outfile.seek(0)
bjoern@3: return outfile
bjoern@3:
bjoern@3: def convert(self, infile, fformat):
bjoern@0: outfile = tempfile.NamedTemporaryFile()
bjoern@0:
bjoern@3: self.convertor.convert(infile.name, outfile.name, fformat)
bjoern@0:
bjoern@0: infile.close()
bjoern@0: return outfile
bjoern@0:
bjoern@3: def get_mimetype_for_format(self, fformat):
bjoern@3: return MIMETYPES.get(fformat, DEFAULT_MIMETYPE)
bjoern@4:
bjoern@4: def get_datadict(self):
bjoern@34: vars = request.form.get('datadict')
bjoern@30: if not vars:
bjoern@30: return None
bjoern@4: return json.loads(vars)
bjoern@18:
bjoern@19:
bjoern@19: class MergeView(MethodView):
bjoern@19:
bjoern@19: def get(self):
bjoern@19: return render_template("merge.html")
bjoern@19:
bjoern@19: def post(self):
bjoern@57: log.debug("Merging PDF documents")
bjoern@57:
frank@87: merger = PdfFileMerger(strict=False)
bjoern@67:
bjoern@67: ffiles = []
bjoern@67:
bjoern@67: # allow files to have arbitray form names
bjoern@67: # order files by their form names
bjoern@67: for key, value in sorted(request.files.iterlists(),
bjoern@67: key=lambda x: x[0].lower()):
bjoern@67: ffiles.extend(value)
bjoern@19:
bjoern@62: for ffile in ffiles:
bjoern@62: try:
frank@85: merger.append(ffile, import_bookmarks=False)
bjoern@62: except Exception, e:
bjoern@62: log.exception("Error merging file %s" % ffile)
bjoern@62: if self.is_ignore_file_errors():
bjoern@62: continue
bjoern@62: else:
bjoern@62: return MergeErrorResponse(details=str(e))
bjoern@19:
bjoern@62: outfile = tempfile.NamedTemporaryFile()
bjoern@19:
bjoern@62: try:
bjoern@61: merger.write(outfile)
bjoern@61: merger.close()
bjoern@61: outfile.seek(0)
bjoern@61: except PyPdfError, e:
bjoern@61: log.exception("Merge error")
bjoern@61: return MergeErrorResponse(details=str(e))
bjoern@19:
bjoern@57: log.debug("PDF documents merged")
bjoern@19: return Response(outfile, mimetype=PDF_MIMETYPE)
bjoern@22:
bjoern@62: def is_ignore_file_errors(self):
bjoern@62: return request.args.get("ignore_file_errors", False) or \
bjoern@62: request.form.get("ignore_file_errors", False)
bjoern@62:
bernhard@73: class CheckView(MethodView):
bernhard@73:
bernhard@73: def get(self):
bernhard@73: return render_template("check.html")
bernhard@73:
bernhard@73: def post(self):
bh@94: """Check that the attached PDF file is ready for merging.
bh@94: If it is not ready a MergeErrorResponse is returned with
bh@94: http_error_code=422. The default error code of 500 is not really
bh@94: sensible because it is not an internal server error if the
bh@94: attachment cannot be merged. The code 422 is used in WEB-DAV
bh@94: with the meaning "Unprocessable Entity" which fits relatively
bh@94: well.
bh@94: """
bernhard@73: log.debug("Checking a PDF document's readiness for merging")
bernhard@73:
bernhard@73: ffile = request.files['file']
bernhard@73: if not ffile.filename:
bernhard@73: return ErrorResponse(
bernhard@73: "Upload file missing", error_code=101,
bernhard@73: details="Please upload a file for conversion",
bh@92: http_error_code=400)
bernhard@73:
bh@91: with tempfile.TemporaryFile() as outfile:
bh@91: merger = PdfFileMerger(strict=False)
bh@91: try:
bh@91: merger.append(ffile, import_bookmarks=False)
bh@91: except Exception, e:
bh@91: log.exception("Error testing merger.append of %s" % ffile)
bh@94: return MergeErrorResponse(details=str(e), http_error_code=422)
bernhard@73:
bh@91: try:
bh@91: merger.write(outfile)
bh@91: except Exception, e:
bh@91: log.exception("Error testing merger.write of merged %s" % ffile)
bh@94: return MergeErrorResponse(details=str(e), http_error_code=422)
bh@91:
bh@91: merger.close()
bernhard@73:
bernhard@73: log.debug("PDF document %s checked." % ffile)
bernhard@73: return Response("Okay.")
bernhard@73:
bjoern@22:
bjoern@22: class TemplateView(MethodView):
bjoern@22:
bjoern@22: template_name = ""
bjoern@22:
bjoern@22: def __init__(self, template_name=None):
bjoern@22: if template_name:
bjoern@22: self.template_name = template_name
bjoern@22:
bjoern@22: def get_template_name(self):
bjoern@22: return self.template_name
bjoern@22:
bjoern@22: def get(self):
bjoern@22: return render_template(self.get_template_name())