Projet

Général

Profil

0002-add-support-for-file-validation-8402.patch

Benjamin Dauvergne, 02 octobre 2015 17:02

Télécharger (13,8 ko)

Voir les différences:

Subject: [PATCH 2/2] add support for file validation (#8402)

 wcs/fields.py                 |  78 +++++++++++++++++++++--------
 wcs/file_validation.py        | 111 ++++++++++++++++++++++++++++++++++++++++++
 wcs/forms/common.py           |  30 +++++++++++-
 wcs/qommon/static/js/fargo.js |  56 +++++++++++++++++++++
 wcs/root.py                   |   7 ++-
 5 files changed, 261 insertions(+), 21 deletions(-)
 create mode 100644 wcs/file_validation.py
 create mode 100644 wcs/qommon/static/js/fargo.js
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
-