0002-add-support-for-file-validation-8402.patch
wcs/fields.py | ||
---|---|---|
30 | 30 |
from qommon.strftime import strftime |
31 | 31 | |
32 | 32 |
import data_sources |
33 |
import file_validation |
|
33 | 34 | |
34 | 35 | |
35 | 36 |
class PrefillSelectionWidget(CompositeWidget): |
... | ... | |
668 | 669 |
class FileField(WidgetField): |
669 | 670 |
key = 'file' |
670 | 671 |
description = N_('File Upload') |
671 |
file_type = []
|
|
672 |
document_type = None
|
|
672 | 673 |
max_file_size = None |
673 | 674 | |
674 | 675 |
widget_class = FileWithPreviewWidget |
675 | 676 |
extra_attributes = ['file_type', 'max_file_size'] |
676 | 677 | |
678 |
@property |
|
679 |
def file_type(self): |
|
680 |
if self.document_type: |
|
681 |
return self.document_type.get('mimetypes') |
|
682 | ||
677 | 683 |
def fill_admin_form(self, form): |
678 | 684 |
WidgetField.fill_admin_form(self, form) |
679 |
file_types = [ |
|
680 |
('audio/*', _('Sound files')), |
|
681 |
('video/*', _('Video files')), |
|
682 |
('image/*', _('Image files'))] |
|
683 |
filetypes_cfg = get_cfg('filetypes', {}) |
|
684 |
if filetypes_cfg: |
|
685 |
for file_type in filetypes_cfg.values(): |
|
686 |
file_types.append(( |
|
687 |
','.join(file_type['mimetypes']), file_type['label'])) |
|
688 |
if self.file_type: |
|
689 |
known_file_types = [x[0] for x in file_types] |
|
690 |
for file_type in self.file_type: |
|
691 |
if not file_type in known_file_types: |
|
692 |
file_types.append((file_type, file_type)) |
|
693 |
form.add(CheckboxesWidget, 'file_type', title=_('File type suggestion'), |
|
694 |
value=self.file_type, elements=file_types, inline=True, |
|
695 |
advanced=not(self.file_type)) |
|
685 |
document_types, value = self.get_document_types_and_value() |
|
686 |
options = [(None, '---', '')] |
|
687 |
options += [(doc_type, doc_type['label'], key) for key, doc_type in document_types.iteritems()] |
|
688 |
form.add(SingleSelectWidget, 'document_type', title=_('File type suggestion'), |
|
689 |
value=value, options=options, advanced=not(self.document_type)) |
|
696 | 690 |
form.add(FileSizeWidget, 'max_file_size', title=('Max file size'), |
697 | 691 |
value=self.max_file_size, |
698 | 692 |
advanced=not(self.max_file_size)) |
699 | 693 | |
700 | 694 |
def get_admin_attributes(self): |
701 |
return WidgetField.get_admin_attributes(self) + ['file_type',
|
|
695 |
l = WidgetField.get_admin_attributes(self) + ['document_type',
|
|
702 | 696 |
'max_file_size'] |
697 |
return l |
|
703 | 698 | |
704 | 699 |
def get_view_value(self, value): |
705 | 700 |
return htmltext('<a download="%s" href="[download]?f=%s">%s</a>') % ( |
... | ... | |
726 | 721 |
if value and hasattr(value, 'token'): |
727 | 722 |
get_request().form[self.field_key + '$token'] = value.token |
728 | 723 | |
724 |
def get_default_document_types(self): |
|
725 |
return { |
|
726 |
'_audio': { |
|
727 |
'label': _('Sound files'), |
|
728 |
'mimetypes': 'audio/*', |
|
729 |
}, |
|
730 |
'_video': { |
|
731 |
'label': _('Video files'), |
|
732 |
'mimetypes': 'video/*', |
|
733 |
}, |
|
734 |
'_image': { |
|
735 |
'label': _('Image files'), |
|
736 |
'mimetypes': 'image/*', |
|
737 |
} |
|
738 |
} |
|
739 | ||
740 |
def get_document_types_and_value(self): |
|
741 |
document_types = self.get_default_document_types() |
|
742 |
# Local document types |
|
743 |
document_types.update(get_cfg('filetypes', {})) |
|
744 |
# Remote documents types |
|
745 |
document_types.update(file_validation.get_document_types()) |
|
746 |
value = self.document_type |
|
747 |
if isinstance(value, str): |
|
748 |
value = None |
|
749 |
for key, document_type in document_types.iteritems(): |
|
750 |
document_type['id'] = key |
|
751 |
# normalize current value against known file types |
|
752 |
if value and value['id'] == key: |
|
753 |
value = document_type |
|
754 |
# add current file type if it does not exist anymore in the settings |
|
755 |
if value and value['id'] not in document_types: |
|
756 |
document_types[value['id']] = value |
|
757 |
return document_types, value |
|
758 | ||
759 |
def migrate(self): |
|
760 |
if 'file_type' in self.__dict__: |
|
761 |
for key, value in self.get_default_document_types().iteritems(): |
|
762 |
if self.file_type == value['mimetypes']: |
|
763 |
self.document_type = value.copy() |
|
764 |
self.document_type['id'] = key |
|
765 |
del self.__dict__['file_type'] |
|
766 |
return True |
|
767 |
return False |
|
768 | ||
729 | 769 |
register_field_class(FileField) |
730 | 770 | |
731 | 771 |
wcs/file_validation.py | ||
---|---|---|
1 |
import json |
|
2 |
import urlparse |
|
3 |
import hashlib |
|
4 |
import urllib |
|
5 | ||
6 |
from qommon.misc import http_get_page |
|
7 |
from quixote import get_publisher, get_response |
|
8 |
from quixote.html import htmltext |
|
9 | ||
10 | ||
11 |
def json_encode_helper(d, charset): |
|
12 |
'''Encode a JSON structure into local charset''' |
|
13 |
if isinstance(d, unicode): |
|
14 |
return d.encode(charset) |
|
15 |
elif isinstance(d, list): |
|
16 |
return [json_encode_helper(e, charset) for e in d] |
|
17 |
elif isinstance(d, dict): |
|
18 |
new_d = {} |
|
19 |
for k, v in d.iteritems(): |
|
20 |
new_d[json_encode_helper(k, charset)] = json_encode_helper(v, charset) |
|
21 |
return new_d |
|
22 |
else: |
|
23 |
return d |
|
24 | ||
25 | ||
26 |
def json_encode(d, charset=None): |
|
27 |
return json_encode_helper(d, charset or get_publisher().site_charset) |
|
28 | ||
29 | ||
30 |
def has_file_validation(): |
|
31 |
return get_publisher().get_site_option('fargo_url') is not None |
|
32 | ||
33 | ||
34 |
def fargo_get(path): |
|
35 |
fargo_url = get_publisher().get_site_option('fargo_url') |
|
36 |
url = urlparse.urljoin(fargo_url, path) |
|
37 |
response, status, data, auth_header = http_get_page(url) |
|
38 |
if status == 200: |
|
39 |
return json_encode(json.loads(data)) |
|
40 | ||
41 | ||
42 |
def sha256_of_upload(upload): |
|
43 |
return hashlib.sha256(upload.get_content()).hexdigest() |
|
44 | ||
45 | ||
46 |
def get_document_types(): |
|
47 |
if not has_file_validation(): |
|
48 |
return {} |
|
49 |
response = fargo_get('/document-types/') |
|
50 |
publisher = get_publisher() |
|
51 |
if response.get('err') == 0: |
|
52 |
result = {} |
|
53 |
for schema in response['data']: |
|
54 |
d = { |
|
55 |
'id': schema['name'], |
|
56 |
'label': schema['label'], |
|
57 |
'fargo': True, |
|
58 |
} |
|
59 |
if 'mimetypes' in schema: |
|
60 |
d['mimetypes'] = shema['mimetypes'] |
|
61 |
result[d['id']] = d |
|
62 | ||
63 |
return result |
|
64 |
return {} |
|
65 | ||
66 |
def validation_path(filled, field, upload): |
|
67 |
user = filled.get_user() |
|
68 |
if not user: |
|
69 |
return |
|
70 |
if not user.name_identifiers: |
|
71 |
return |
|
72 |
if not field.document_type or not field.document_type.get('fargo'): |
|
73 |
return |
|
74 |
name_id = user.name_identifiers[0] |
|
75 |
sha_256 = sha256_of_upload(upload) |
|
76 |
document_type = field.document_type |
|
77 |
path = '%s/%s/%s/' % ( |
|
78 |
urllib.quote(name_id), |
|
79 |
urllib.quote(sha_256), |
|
80 |
urllib.quote(document_type), |
|
81 |
) |
|
82 |
return path |
|
83 | ||
84 |
def validate_upload(filled, field, upload): |
|
85 |
path = validation_path(filled, field, upload) |
|
86 |
if not path: |
|
87 |
return |
|
88 |
response = fargo_get('metadata/' + path) |
|
89 |
if response is None: |
|
90 |
return |
|
91 |
if response['err'] == 1: |
|
92 |
return False |
|
93 |
return response['data'] |
|
94 | ||
95 |
def get_document_type_label(document_type): |
|
96 |
return get_document_types().get(document_type, {}).get('label') |
|
97 | ||
98 |
def validation_link(filled, field, upload): |
|
99 |
path = validation_path(filled, field, upload) |
|
100 |
if not path: |
|
101 |
return |
|
102 |
get_response().add_css_include('../js/smoothness/jquery-ui-1.10.0.custom.min.css') |
|
103 |
get_response().add_javascript(['jquery-ui.js', 'jquery.js', 'fargo.js']) |
|
104 |
label = get_document_type_label(field.document_type) |
|
105 |
fargo_url = get_publisher().get_site_option('fargo_url') |
|
106 |
url = urlparse.urljoin(fargo_url, 'validation/' + path) |
|
107 |
url += '?next=%s' % urllib.quote(get_publisher().get_frontoffice_url() + '/reload-top') |
|
108 |
return htmltext(_('<a data-title="Validate as a %(document_type_label)s" data-width="800" data-height="500" href="%(url)s">Validate as a %(document_type_label)s</a>')) % { |
|
109 |
'url': url, |
|
110 |
'document_type_label': label, |
|
111 |
} |
wcs/forms/common.py | ||
---|---|---|
15 | 15 |
# along with this program; if not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 | 17 |
import sys |
18 |
import hashlib |
|
19 |
import urlparse |
|
18 | 20 | |
19 | 21 |
from quixote import get_publisher, get_request, get_response, get_session, redirect |
20 | 22 |
from quixote.directory import Directory |
21 | 23 |
from quixote.html import TemplateIO, htmltext |
22 | 24 | |
23 |
from wcs.fields import WidgetField |
|
25 |
from wcs.fields import WidgetField, FileField |
|
26 |
from wcs import file_validation |
|
24 | 27 | |
25 | 28 |
from qommon import template |
26 | 29 |
from qommon import get_logger |
... | ... | |
399 | 402 |
s = f.get_view_value(value) |
400 | 403 |
s = s.replace(str('[download]'), str('%sdownload' % form_url)) |
401 | 404 |
r += s |
405 |
if isinstance(f, FileField): |
|
406 |
s = self.file_validation_status(f, value) |
|
407 |
if s: |
|
408 |
r += htmltext(str(s)) |
|
402 | 409 |
r += htmltext('</div></div>') |
403 | 410 | |
404 | 411 | |
... | ... | |
529 | 536 |
else: |
530 | 537 |
return redirect('files/%s/' % fn) |
531 | 538 | |
539 |
def file_validation_status(self, field, value): |
|
540 |
status = file_validation.validate_upload(self.filled, field, value) |
|
541 |
if status is None: |
|
542 |
return |
|
543 |
r = TemplateIO(html=True) |
|
544 |
r += htmltext('<div class="file-validation">') |
|
545 |
if status is False: |
|
546 |
r += file_validation.validation_link(self.filled, field, value) |
|
547 |
else: |
|
548 |
r += htmltext(_('<p>%s validated by %s on %s</p>')) % ( |
|
549 |
status['label'], status['creator'], status['created']) |
|
550 |
r += htmltext('<ul>') |
|
551 |
for meta in status['metadata']: |
|
552 |
r += htmltext(_('<li>%(label)s: %(value)s</li>')) % { |
|
553 |
'label': meta['label'], |
|
554 |
'value': meta['value'] |
|
555 |
} |
|
556 |
r += htmltext('</ul>') |
|
557 |
r += htmltext('</div>') |
|
558 |
return str(r) |
|
559 | ||
532 | 560 |
def _q_lookup(self, component): |
533 | 561 |
if component == 'files': |
534 | 562 |
self.check_receiver() |
wcs/qommon/static/js/fargo.js | ||
---|---|---|
1 | ||
2 |
$(function() { |
|
3 |
var iframe = $('<iframe frameborder="0" marginwidth="0" marginheight="0" allowfullscreen></iframe>'); |
|
4 |
var dialog = $("<div></div>").append(iframe).appendTo("body").dialog({ |
|
5 |
autoOpen: false, |
|
6 |
modal: true, |
|
7 |
resizable: false, |
|
8 |
width: "auto", |
|
9 |
height: "auto", |
|
10 |
close: function () { |
|
11 |
iframe.attr("src", ""); |
|
12 |
} |
|
13 |
}); |
|
14 |
$('.file-validation a').click(function (e) { |
|
15 |
e.preventDefault(); |
|
16 |
var src = $(e.target).attr('href'); |
|
17 |
var title = $(e.target).data("title"); |
|
18 |
var width = $(e.target).data("width"); |
|
19 |
var height = $(e.target).data("height"); |
|
20 |
iframe.attr({ |
|
21 |
width: parseInt(width), |
|
22 |
height: parseInt(height), |
|
23 |
src: src |
|
24 |
}); |
|
25 |
dialog.dialog("option", "title", title); |
|
26 |
dialog.dialog("open"); |
|
27 |
}); |
|
28 |
$('p.use-file-from-fargo span').click(function(e) { |
|
29 |
e.preventDefault(); |
|
30 |
var base_widget = $(this).parents('.file-upload-widget'); |
|
31 |
document.fargo_set_token = function (token, title) { |
|
32 |
if (token) { |
|
33 |
$(base_widget).find('.filename').text(title); |
|
34 |
$(base_widget).find('.fileinfo').show(); |
|
35 |
$(base_widget).find('input[type=hidden]').val(token); |
|
36 |
$(base_widget).find('input[type=file]').hide(); |
|
37 |
} |
|
38 |
document.fargo_close_dialog(); |
|
39 |
} |
|
40 |
document.fargo_close_dialog = function () { |
|
41 |
document.fargo_set_token = undefined; |
|
42 |
dialog.dialog('close'); |
|
43 |
} |
|
44 |
var src = $(this).data('src'); |
|
45 |
var title = $(this).data("title"); |
|
46 |
var width = $(this).data("width"); |
|
47 |
var height = $(this).data("height"); |
|
48 |
iframe.attr({ |
|
49 |
width: parseInt(width), |
|
50 |
height: parseInt(height), |
|
51 |
src: src |
|
52 |
}); |
|
53 |
dialog.dialog("option", "title", title); |
|
54 |
dialog.dialog("open"); |
|
55 |
}); |
|
56 |
}); |
wcs/root.py | ||
---|---|---|
192 | 192 |
_q_exports = ['admin', 'backoffice', 'forms', 'login', 'logout', 'saml', |
193 | 193 |
'ident', 'register', 'afterjobs', 'themes', 'myspace', 'user', 'roles', |
194 | 194 |
'pages', ('tmp-upload', 'tmp_upload'), 'api', '__version__', |
195 |
'tryauth', 'auth', 'preview'] |
|
195 |
'tryauth', 'auth', 'preview', ('reload-top', 'reload_top')]
|
|
196 | 196 | |
197 | 197 |
api = ApiDirectory() |
198 | 198 |
themes = template.ThemesDirectory() |
... | ... | |
308 | 308 |
# or a form ? |
309 | 309 |
return forms.root.RootDirectory()._q_lookup(component) |
310 | 310 | |
311 |
def reload_top(self): |
|
312 |
r = TemplateIO(html=True) |
|
313 |
r += htmltext('<script>window.top.document.location.reload();</script>') |
|
314 |
return r.getvalue() |
|
315 | ||
311 | 316 |
admin = None |
312 | 317 |
backoffice = None |
313 | 318 | |
314 |
- |