[ADD] handling of binary fields in list views
authorXavier Morel <xmo@openerp.com>
Tue, 10 Jan 2012 15:51:06 +0000 (16:51 +0100)
committerXavier Morel <xmo@openerp.com>
Tue, 10 Jan 2012 15:51:06 +0000 (16:51 +0100)
lp bug: https://launchpad.net/bugs/914267 fixed

bzr revid: xmo@openerp.com-20120110155106-p3g85wbh5g4wlbdi

addons/web/controllers/main.py
addons/web/static/src/js/formats.js
addons/web/static/src/js/view_form.js
addons/web/static/src/js/view_list.js
addons/web/static/src/xml/base.xml

index 852d513..b640587 100644 (file)
@@ -1184,23 +1184,40 @@ class Binary(openerpweb.Controller):
         return open(os.path.join(addons_path, 'web', 'static', 'src', 'img', 'placeholder.png'), 'rb').read()
 
     @openerpweb.httprequest
-    def saveas(self, req, model, id, field, fieldname, **kw):
+    def saveas(self, req, model, field, id=None, filename_field=None, **kw):
+        """ Download link for files stored as binary fields.
+
+        If the ``id`` parameter is omitted, fetches the default value for the
+        binary field (via ``default_get``), otherwise fetches the field for
+        that precise record.
+
+        :param req: OpenERP request
+        :type req: :class:`web.common.http.HttpRequest`
+        :param str model: name of the model to fetch the binary from
+        :param str field: binary field
+        :param str id: id of the record from which to fetch the binary
+        :param str filename_field: field holding the file's name, if any
+        :returns: :class:`werkzeug.wrappers.Response`
+        """
         Model = req.session.model(model)
         context = req.session.eval_context(req.context)
+        fields = [field]
+        if filename_field:
+            fields.append(filename_field)
         if id:
-            res = Model.read([int(id)], [field, fieldname], context)[0]
+            res = Model.read([int(id)], fields, context)[0]
         else:
-            res = Model.default_get([field, fieldname], context)
+            res = Model.default_get(fields, context)
         filecontent = base64.b64decode(res.get(field, ''))
         if not filecontent:
             return req.not_found()
         else:
             filename = '%s_%s' % (model.replace('.', '_'), id)
-            if fieldname:
-                filename = res.get(fieldname, '') or filename
+            if filename_field:
+                filename = res.get(filename_field, '') or filename
             return req.make_response(filecontent,
                 [('Content-Type', 'application/octet-stream'),
-                 ('Content-Disposition', 'attachment; filename=' +  filename)])
+                 ('Content-Disposition', 'attachment; filename="%s"' % filename)])
 
     @openerpweb.httprequest
     def upload(self, req, callback, ufile):
index 2418439..819301f 100644 (file)
@@ -237,7 +237,15 @@ openerp.web.auto_date_to_str = function(value, type) {
 };
 
 /**
- * Formats a provided cell based on its field type
+ * Formats a provided cell based on its field type. Most of the field types
+ * return a correctly formatted value, but some tags and fields are
+ * special-cased in their handling:
+ *
+ * * buttons will return an actual ``<button>`` tag with a bunch of error handling
+ *
+ * * boolean fields will return a checkbox input, potentially disabled
+ *
+ * * binary fields will return a link to download the binary data as a file
  *
  * @param {Object} row_data record whose values should be displayed in the cell
  * @param {Object} column column descriptor
@@ -245,21 +253,21 @@ openerp.web.auto_date_to_str = function(value, type) {
  * @param {String} column.type widget type for a field control
  * @param {String} [column.string] button label
  * @param {String} [column.icon] button icon
- * @param {String} [value_if_empty=''] what to display if the field's value is ``false``
- * @param {Boolean} [process_modifiers=true] should the modifiers be computed ?
+ * @param {Object} [options]
+ * @param {String} [options.value_if_empty=''] what to display if the field's value is ``false``
+ * @param {Boolean} [options.process_modifiers=true] should the modifiers be computed ?
+ * @param {String} [options.model] current record's model
+ * @param {Number} [options.id] current record's id
+ *
  */
-openerp.web.format_cell = function (row_data, column, value_if_empty, process_modifiers) {
+openerp.web.format_cell = function (row_data, column, options) {
+    options = options || {};
     var attrs = {};
-    if (process_modifiers !== false) {
+    if (options.process_modifiers !== false) {
         attrs = column.modifiers_for(row_data);
     }
     if (attrs.invisible) { return ''; }
 
-    if (column.type === "boolean") {
-        return _.str.sprintf('<input type="checkbox" %s disabled="disabled"/>',
-                 row_data[column.id].value ? 'checked="checked"' : '');
-    }
-
     if (column.tag === 'button') {
         return _.template('<button type="button" title="<%-title%>" <%=additional_attributes%> >' +
             '<img src="<%-prefix%>/web/static/src/img/icons/<%-icon%>.png" alt="<%-alt%>"/>' +
@@ -269,14 +277,36 @@ openerp.web.format_cell = function (row_data, column, value_if_empty, process_mo
                     'disabled="disabled" class="oe-listview-button-disabled"' : '',
                 prefix: openerp.connection.prefix,
                 icon: column.icon,
-                alt: column.string || '',
+                alt: column.string || ''
             });
     }
     if (!row_data[column.id]) {
-        return value_if_empty === undefined ? '' : value_if_empty;
+        return options.value_if_empty === undefined ? '' : options.value_if_empty;
     }
+
+    switch (column.type) {
+    case "boolean":
+        return _.str.sprintf('<input type="checkbox" %s disabled="disabled"/>',
+                 row_data[column.id].value ? 'checked="checked"' : '');
+    case "binary":
+        var text = _t("Download"),
+            download_url = _.str.sprintf('/web/binary/saveas?session_id=%s&model=%s&field=%s&id=%d', openerp.connection.session_id, options.model, column.id, options.id);
+        if (column.filename) {
+            download_url += '&filename_field=' + column.filename;
+            if (row_data[column.filename]) {
+                text = _.str.sprintf(_t("Download \"%s\""), openerp.web.format_value(
+                        row_data[column.filename].value, {type: 'char'}));
+            }
+        }
+        return _.str.sprintf('<a href="%(href)s">%(text)s</a> (%(size)s)', {
+            text: text,
+            href: download_url,
+            size: row_data[column.id].value
+        });
+    }
+
     return openerp.web.format_value(
-            row_data[column.id].value, column, value_if_empty);
+            row_data[column.id].value, column, options.value_if_empty);
 }
 
 };
index 622f9bb..89b3a97 100644 (file)
@@ -3009,7 +3009,7 @@ openerp.web.form.FieldBinary = openerp.web.form.Field.extend({
     on_save_as: function() {
         var url = '/web/binary/saveas?session_id=' + this.session.session_id + '&model=' +
             this.view.dataset.model +'&id=' + (this.view.datarecord.id || '') + '&field=' + this.name +
-            '&fieldname=' + (this.node.attrs.filename || '') + '&t=' + (new Date().getTime());
+            '&filename_field=' + (this.node.attrs.filename || '') + '&t=' + (new Date().getTime());
         window.open(url);
     },
     on_clear: function() {
index 1c49119..70ae337 100644 (file)
@@ -685,6 +685,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
         this.display_aggregates(aggregates);
     },
     display_aggregates: function (aggregation) {
+        var self = this;
         var $footer_cells = this.$element.find('.oe-list-footer');
         _(this.aggregate_columns).each(function (column) {
             if (!column['function']) {
@@ -692,7 +693,10 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
             }
 
             $footer_cells.filter(_.str.sprintf('[data-field=%s]', column.id))
-                .html(openerp.web.format_cell(aggregation, column, undefined, false));
+                .html(openerp.web.format_cell(aggregation, column, {
+                    process_modifiers: false,
+                    model: self.dataset.model
+            }));
         });
     },
     get_selected_ids: function() {
@@ -846,6 +850,9 @@ openerp.web.ListView.List = openerp.web.Class.extend( /** @lends openerp.web.Lis
                     return self.reload_record(self.records.get(record_id));
                 }]);
             })
+            .delegate('a', 'click', function (e) {
+                e.stopPropagation();
+            })
             .delegate('tr', 'click', function (e) {
                 e.stopPropagation();
                 var row_id = self.row_id(e.currentTarget);
@@ -905,7 +912,10 @@ openerp.web.ListView.List = openerp.web.Class.extend( /** @lends openerp.web.Lis
                 });
             }
         }
-        return openerp.web.format_cell(record.toForm().data, column);
+        return openerp.web.format_cell(record.toForm().data, column, {
+            model: this.dataset.model,
+            id: record.get('id')
+        });
     },
     render: function () {
         if (this.$current) {
@@ -1211,7 +1221,11 @@ openerp.web.ListView.Groups = openerp.web.Class.extend( /** @lends openerp.web.L
                     return column.id === group.grouped_on; });
                 try {
                     $group_column.html(openerp.web.format_cell(
-                        row_data, group_column, _t("Undefined"), false));
+                        row_data, group_column, {
+                            value_if_empty: _t("Undefined"),
+                            process_modifiers: false,
+                            model: self.dataset.model
+                        }));
                 } catch (e) {
                     $group_column.html(row_data[group_column.id].value);
                 }
@@ -1289,7 +1303,7 @@ openerp.web.ListView.Groups = openerp.web.Class.extend( /** @lends openerp.web.L
             page = this.datagroup.openable ? this.page : view.page;
 
         var fields = _.pluck(_.select(this.columns, function(x) {return x.tag == "field"}), 'name');
-        var options = { offset: page * limit, limit: limit };
+        var options = { offset: page * limit, limit: limit, context: {bin_size: true} };
         //TODO xmo: investigate why we need to put the setTimeout
         $.async_when().then(function() {dataset.read_slice(fields, options , function (records) {
             // FIXME: ignominious hacks, parents (aka form view) should not send two ListView#reload_content concurrently
index d4bb512..642f1f8 100644 (file)
         <li t-foreach="attachments" t-as="attachment">
             <t t-if="attachment.type == 'binary'" t-set="attachment.url" t-value="_s + '/web/binary/saveas?session_id='
                 + session.session_id + '&amp;model=ir.attachment&amp;id=' + attachment.id
-                + '&amp;field=datas&amp;fieldname=name&amp;t=' + (new Date().getTime())"/>
+                + '&amp;field=datas&amp;filename_field=name&amp;t=' + (new Date().getTime())"/>
             <a class="oe-sidebar-attachments-link" t-att-href="attachment.url" target="_blank">
                 <t t-esc="attachment.name"/>
             </a>