From 3fa2cdd7087249485cdc71bfd148f742eded62f3 Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Mon, 28 Sep 2015 17:16:21 +0200 Subject: [PATCH] add support for file validation --- wcs/fields.py | 12 ++++- wcs/file_validation.py | 107 ++++++++++++++++++++++++++++++++++++++++++ wcs/forms/common.py | 30 +++++++++++- wcs/qommon/static/js/fargo.js | 56 ++++++++++++++++++++++ wcs/root.py | 7 ++- 5 files changed, 209 insertions(+), 3 deletions(-) create mode 100644 wcs/file_validation.py create mode 100644 wcs/qommon/static/js/fargo.js diff --git a/wcs/fields.py b/wcs/fields.py index fbde7d3..944fd13 100644 --- a/wcs/fields.py +++ b/wcs/fields.py @@ -30,6 +30,7 @@ from qommon import get_cfg, get_logger from qommon.strftime import strftime import data_sources +import file_validation class PrefillSelectionWidget(CompositeWidget): @@ -666,6 +667,7 @@ class FileField(WidgetField): key = 'file' description = N_('File Upload') file_type = [] + document_type = None max_file_size = None widget_class = FileWithPreviewWidget @@ -693,10 +695,18 @@ class FileField(WidgetField): form.add(FileSizeWidget, 'max_file_size', title=('Max file size'), value=self.max_file_size, advanced=not(self.max_file_size)) + document_types = file_validation.get_document_types() + if document_types: + document_types = [(None, '---')] + document_types + form.add(SingleSelectWidget, 'document_type', title=_('Document type'), + value=self.document_type, options=document_types) def get_admin_attributes(self): - return WidgetField.get_admin_attributes(self) + ['file_type', + l = WidgetField.get_admin_attributes(self) + ['file_type', 'max_file_size'] + if file_validation.get_document_types(): + l += ['document_type'] + return l def get_view_value(self, value): return htmltext('%s') % ( diff --git a/wcs/file_validation.py b/wcs/file_validation.py new file mode 100644 index 0000000..6145532 --- /dev/null +++ b/wcs/file_validation.py @@ -0,0 +1,107 @@ +import json +import urlparse +import hashlib +import urllib + +from qommon.misc import http_get_page +from quixote import get_publisher, get_response +from quixote.html import htmltext + + +def json_encode_helper(d, charset): + '''Encode a JSON structure into local charset''' + if isinstance(d, unicode): + return d.encode(charset) + elif isinstance(d, list): + return [json_encode_helper(e, charset) for e in d] + elif isinstance(d, dict): + new_d = {} + for k, v in d.iteritems(): + new_d[json_encode_helper(k, charset)] = json_encode_helper(v, charset) + return new_d + else: + return d + + +def json_encode(d, charset=None): + return json_encode_helper(d, charset or get_publisher().site_charset) + + +def has_file_validation(): + return get_publisher().get_site_option('fargo_url') is not None + + +def fargo_get(path): + fargo_url = get_publisher().get_site_option('fargo_url') + url = urlparse.urljoin(fargo_url, path) + response, status, data, auth_header = http_get_page(url) + if status == 200: + return json_encode(json.loads(data)) + + +def sha256_of_upload(upload): + return hashlib.sha256(upload.get_content()).hexdigest() + + +def get_document_types(): + if not has_file_validation(): + return + response = fargo_get('/document-types/') + publisher = get_publisher() + if response.get('err') == 0: + result = [] + for schema in response['data']: + result.append( + ( + str(schema['name']), + schema['label'].encode(publisher.site_charset) + ) + ) + return result + +def validation_path(filled, field, upload): + user = filled.get_user() + if not user: + return + if not user.name_identifiers: + return + if not field.document_type: + return + name_id = user.name_identifiers[0] + sha_256 = sha256_of_upload(upload) + document_type = field.document_type + path = '%s/%s/%s/' % ( + urllib.quote(name_id), + urllib.quote(sha_256), + urllib.quote(document_type), + ) + return path + +def validate_upload(filled, field, upload): + path = validation_path(filled, field, upload) + if not path: + return + response = fargo_get('metadata/' + path) + if response is None: + return + if response['err'] == 1: + return False + return response['data'] + +def get_document_type_label(document_type): + return dict(get_document_types()).get(document_type, {}) + +def validation_link(filled, field, upload): + path = validation_path(filled, field, upload) + if not path: + return + get_response().add_css_include('../js/smoothness/jquery-ui-1.10.0.custom.min.css') + get_response().add_javascript(['jquery-ui.js', 'jquery.js', 'fargo.js']) + label = get_document_type_label(field.document_type) + fargo_url = get_publisher().get_site_option('fargo_url') + url = urlparse.urljoin(fargo_url, 'validation/' + path) + url += '?next=%s' % urllib.quote(get_publisher().get_frontoffice_url() + '/reload-top') + return htmltext(_('Validate as a %(document_type_label)s')) % { + 'url': url, + 'document_type_label': label, + } diff --git a/wcs/forms/common.py b/wcs/forms/common.py index 06f0b7e..d883c66 100644 --- a/wcs/forms/common.py +++ b/wcs/forms/common.py @@ -15,12 +15,15 @@ # along with this program; if not, see . import sys +import hashlib +import urlparse from quixote import get_publisher, get_request, get_response, get_session, redirect from quixote.directory import Directory from quixote.html import TemplateIO, htmltext -from wcs.fields import WidgetField +from wcs.fields import WidgetField, FileField +from wcs import file_validation from qommon import template from qommon import get_logger @@ -399,6 +402,10 @@ class FormStatusPage(Directory): s = f.get_view_value(value) s = s.replace(str('[download]'), str('%sdownload' % form_url)) r += s + if isinstance(f, FileField): + s = self.file_validation_status(f, value) + if s: + r += htmltext(str(s)) r += htmltext('') @@ -529,6 +536,27 @@ class FormStatusPage(Directory): else: return redirect('files/%s/' % fn) + def file_validation_status(self, field, value): + status = file_validation.validate_upload(self.filled, field, value) + if status is None: + return + r = TemplateIO(html=True) + r += htmltext('
') + if status is False: + r += file_validation.validation_link(self.filled, field, value) + else: + r += htmltext(_('

%s validated by %s on %s

')) % ( + status['label'], status['creator'], status['created']) + r += htmltext('
    ') + for meta in status['metadata']: + r += htmltext(_('
  • %(label)s: %(value)s
  • ')) % { + 'label': meta['label'], + 'value': meta['value'] + } + r += htmltext('
') + r += htmltext('
') + return str(r) + def _q_lookup(self, component): if component == 'files': self.check_receiver() diff --git a/wcs/qommon/static/js/fargo.js b/wcs/qommon/static/js/fargo.js new file mode 100644 index 0000000..af79ead --- /dev/null +++ b/wcs/qommon/static/js/fargo.js @@ -0,0 +1,56 @@ + +$(function() { + var iframe = $(''); + var dialog = $("
").append(iframe).appendTo("body").dialog({ + autoOpen: false, + modal: true, + resizable: false, + width: "auto", + height: "auto", + close: function () { + iframe.attr("src", ""); + } + }); + $('.file-validation a').click(function (e) { + e.preventDefault(); + var src = $(e.target).attr('href'); + var title = $(e.target).data("title"); + var width = $(e.target).data("width"); + var height = $(e.target).data("height"); + iframe.attr({ + width: parseInt(width), + height: parseInt(height), + src: src + }); + dialog.dialog("option", "title", title); + dialog.dialog("open"); + }); + $('p.use-file-from-fargo span').click(function(e) { + e.preventDefault(); + var base_widget = $(this).parents('.file-upload-widget'); + document.fargo_set_token = function (token, title) { + if (token) { + $(base_widget).find('.filename').text(title); + $(base_widget).find('.fileinfo').show(); + $(base_widget).find('input[type=hidden]').val(token); + $(base_widget).find('input[type=file]').hide(); + } + document.fargo_close_dialog(); + } + document.fargo_close_dialog = function () { + document.fargo_set_token = undefined; + dialog.dialog('close'); + } + var src = $(this).data('src'); + var title = $(this).data("title"); + var width = $(this).data("width"); + var height = $(this).data("height"); + iframe.attr({ + width: parseInt(width), + height: parseInt(height), + src: src + }); + dialog.dialog("option", "title", title); + dialog.dialog("open"); + }); +}); diff --git a/wcs/root.py b/wcs/root.py index 854f172..8eb6ee3 100644 --- a/wcs/root.py +++ b/wcs/root.py @@ -192,7 +192,7 @@ class RootDirectory(Directory): _q_exports = ['admin', 'backoffice', 'forms', 'login', 'logout', 'saml', 'ident', 'register', 'afterjobs', 'themes', 'myspace', 'user', 'roles', 'pages', ('tmp-upload', 'tmp_upload'), 'api', '__version__', - 'tryauth', 'auth', 'preview'] + 'tryauth', 'auth', 'preview', ('reload-top', 'reload_top')] api = ApiDirectory() themes = template.ThemesDirectory() @@ -308,6 +308,11 @@ class RootDirectory(Directory): # or a form ? return forms.root.RootDirectory()._q_lookup(component) + def reload_top(self): + r = TemplateIO(html=True) + r += htmltext('') + return r.getvalue() + admin = None backoffice = None -- 2.1.4