summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Geubelle <mge@odoo.com>2017-04-21 13:18:24 +0200
committerMartin Geubelle <mge@odoo.com>2017-04-24 15:28:57 +0200
commit9def2009ee2233283e72d0aeaea1ba63aec7f827 (patch)
treea2698a2f93bd34261c3fcb118ea582f38c036420
parenteafa14d3bc16e7212000d0c9c30a3ed922395574 (diff)
[FIX] web: reintroduce many2many_binary field widget
The field has been adapated to the new views. This commit also introduces some tests.
-rw-r--r--addons/web/static/src/js/fields/field_registry.js1
-rw-r--r--addons/web/static/src/js/fields/relational_fields.js190
-rw-r--r--addons/web/static/src/xml/base.xml41
-rw-r--r--addons/web/static/tests/fields/relational_fields_tests.js80
4 files changed, 291 insertions, 21 deletions
diff --git a/addons/web/static/src/js/fields/field_registry.js b/addons/web/static/src/js/fields/field_registry.js
index db2f547c469..41889fcfa85 100644
--- a/addons/web/static/src/js/fields/field_registry.js
+++ b/addons/web/static/src/js/fields/field_registry.js
@@ -57,6 +57,7 @@ registry
.add('list.many2one', relational_fields.ListFieldMany2One)
.add('kanban.many2one', relational_fields.KanbanFieldMany2One)
.add('many2many', relational_fields.FieldMany2Many)
+ .add('many2many_binary', relational_fields.FieldMany2ManyBinaryMultiFiles)
.add('many2many_tags', relational_fields.FieldMany2ManyTags)
.add('form.many2many_tags', relational_fields.FormFieldMany2ManyTags)
.add('kanban.many2many_tags', relational_fields.KanbanFieldMany2ManyTags)
diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js
index 995d8792cf4..ca5c584c47b 100644
--- a/addons/web/static/src/js/fields/relational_fields.js
+++ b/addons/web/static/src/js/fields/relational_fields.js
@@ -943,6 +943,195 @@ var FieldMany2Many = FieldX2Many.extend({
},
});
+/**
+ * Widget to upload or delete one or more files at the same time.
+ */
+var FieldMany2ManyBinaryMultiFiles = AbstractField.extend({
+ template: "FieldBinaryFileUploader",
+ supportedFieldTypes: ['many2many'],
+ fieldsToFetch: {
+ name: {type: 'char'},
+ datas_fname: {type: 'char'},
+ mimetype: {type: 'char'},
+ },
+ events: {
+ 'click .o_attach': '_onAttach',
+ 'click .oe_delete': '_onDelete',
+ 'change .o_form_input_file': '_onFileChanged',
+ },
+ /**
+ * @constructor
+ */
+ init: function () {
+ this._super.apply(this, arguments);
+
+ if (this.field.type !== 'many2many' || this.field.relation !== 'ir.attachment') {
+ var msg = _t("The type of the field '%s' must be a many2many field with a relation to 'ir.attachment' model.");
+ throw _.str.sprintf(msg, this.field.string);
+ }
+
+ this.uploadingFiles = [];
+ this.fileupload_id = _.uniqueId('oe_fileupload_temp');
+ $(window).on(this.fileupload_id, this._onFileLoaded.bind(this));
+
+ this.metadata = {};
+ this._generatedMetadata();
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * Compute the URL of an attachment.
+ *
+ * @private
+ * @param {Object} attachment
+ * @returns {string} URL of the attachment
+ */
+ _getFileUrl: function (attachment) {
+ return '/web/content/' + attachment.id + '?download=true';
+ },
+ /**
+ * Process the field data to add some information (url, etc.).
+ *
+ * @private
+ */
+ _generatedMetadata: function () {
+ var self = this;
+ _.each(this.value.data, function (record) {
+ // attachments are tagged `allowUnlink` because only new attachments
+ // will be unlinked after deletion
+ self.metadata[record.id] = {
+ allowUnlink: false,
+ url: self._getFileUrl(record.data),
+ };
+ });
+ },
+ /**
+ * @private
+ * @override
+ */
+ _render: function () {
+ // render the attachments ; as the attachments will changes after each
+ // _setValue, we put the rendering here to ensure they will be updated
+ this.$('.oe_placeholder_files, .oe_attachments')
+ .replaceWith($(qweb.render('FieldBinaryFileUploader.files', {
+ widget: this,
+ })));
+ this.$('.oe_fileupload').show();
+
+ // display image thumbnail
+ this.$('.o_image[data-mimetype^="image"]').each(function () {
+ var $img = $(this);
+ if (/gif|jpe|jpg|png/.test($img.data('mimetype')) && $img.data('src')) {
+ $img.css('background-image', "url('" + $img.data('src') + "')");
+ }
+ });
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ */
+ _onAttach: function () {
+ // This widget uses a hidden form to upload files. Clicking on 'Attach'
+ // will simulate a click on the related input.
+ this.$('.o_form_input_file').click();
+ },
+ /**
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onDelete: function (ev) {
+ ev.preventDefault();
+ ev.stopPropagation();
+
+ var fileID = $(ev.currentTarget).data('id');
+ var record = _.findWhere(this.value.data, {res_id: fileID});
+ if (record) {
+ this._setValue({
+ operation: 'REMOVE',
+ ids: [record.id],
+ });
+ var metadata = this.metadata[record.id];
+ if (!metadata || metadata.allowUnlink) {
+ this._rpc({
+ model: 'ir.attachment',
+ method: 'unlink',
+ args: [record.res_id],
+ });
+ }
+ }
+ },
+ /**
+ * @private
+ * @param {Event} ev
+ */
+ _onFileChanged: function (ev) {
+ var self = this;
+ ev.stopPropagation();
+
+ var files = ev.target.files;
+ var attachment_ids = this.value.res_ids;
+
+ _.each(files, function (file) {
+ var record = _.find(self.value.data, function (attachment) {
+ return attachment.data.name === file.name;
+ });
+ if (record) {
+ var metadata = self.metadata[record.id];
+ if (!metadata || metadata.allowUnlink) {
+ // there is a existing attachment with the same name so we
+ // replace it
+ attachment_ids = _.without(attachment_ids, record.res_id);
+ self._rpc({
+ model: 'ir.attachment',
+ method: 'unlink',
+ args: [record.res_id],
+ });
+ }
+ }
+ self.uploadingFiles.push(file);
+ });
+
+ this._setValue({
+ operation: 'REPLACE_WITH',
+ ids: attachment_ids,
+ });
+
+ this.$('form.o_form_binary_form').submit();
+ this.$('.oe_fileupload').hide();
+ },
+ /**
+ * @private
+ */
+ _onFileLoaded: function () {
+ var self = this;
+ // the first argument isn't a file but the jQuery.Event
+ var files = Array.prototype.slice.call(arguments, 1);
+ // files has been uploaded, clear uploading
+ this.uploadingFiles = [];
+
+ var attachment_ids = this.value.res_ids;
+ _.each(files, function (file) {
+ if (file.error) {
+ self.do_warn(_t('Uploading Error'), file.error);
+ } else {
+ attachment_ids.push(file.id);
+ }
+ });
+
+ this._setValue({
+ operation: 'REPLACE_WITH',
+ ids: attachment_ids,
+ });
+ },
+});
+
var FieldMany2ManyTags = AbstractField.extend({
tag_template: "FieldMany2ManyTag",
className: "o_form_field o_form_field_many2manytags",
@@ -1526,6 +1715,7 @@ return {
FieldOne2Many: FieldOne2Many,
FieldMany2Many: FieldMany2Many,
+ FieldMany2ManyBinaryMultiFiles: FieldMany2ManyBinaryMultiFiles,
FieldMany2ManyCheckBoxes: FieldMany2ManyCheckBoxes,
FieldMany2ManyTags: FieldMany2ManyTags,
FormFieldMany2ManyTags: FormFieldMany2ManyTags,
diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml
index ce4e8eeefd3..c044002a039 100644
--- a/addons/web/static/src/xml/base.xml
+++ b/addons/web/static/src/xml/base.xml
@@ -984,6 +984,7 @@
<span class="o_form_binary_progress">Uploading...</span>
<t t-call="HiddenInputFile">
+ <t t-set="image_only" t-value="true"/>
<t t-set="fileupload_id" t-value="widget.fileupload_id"/>
</t>
</div>
@@ -1031,49 +1032,47 @@
<input type="hidden" name="csrf_token" t-att-value="csrf_token"/>
<input type="hidden" name="session_id" value="" t-if="widget.getSession().override_session"/>
<input type="hidden" name="callback" t-att-value="fileupload_id"/>
- <input t-if="widget.widget!='image'" type="file" class="o_form_input_file" name="ufile" t-att="{'multiple': multi_upload ? 'multiple' : null}"/>
- <input t-if="widget.widget=='image'" type="file" class="o_form_input_file" name="ufile" accept="image/*"/>
+ <input t-if="widget.image_only" type="file" class="o_form_input_file" name="ufile" accept="image/*"/>
+ <input t-else="1" type="file" class="o_form_input_file" name="ufile" t-att="{'multiple': multi_upload ? 'multiple' : null}"/>
<t t-raw="0"/>
</form>
<iframe t-att-id="fileupload_id" t-att-name="fileupload_id" style="display: none"/>
</div>
</t>
-<div t-name="FieldBinaryFileUploader" t-attf-class="oe_fileupload #{widget.node.attrs.class ? widget.node.attrs.class :''}">
+<div t-name="FieldBinaryFileUploader" t-attf-class="oe_fileupload #{widget.attrs.class ? widget.attrs.class :''}">
<div class="oe_placeholder_files"/>
- <div t-if="!widget.get('effective_readonly')" class="oe_add">
+ <div t-if="widget.mode !== 'readonly'" class="oe_add">
<button class="btn btn-default o_attach"><span class="fa fa-paperclip"/> <t t-esc="widget.string"/></button>
<t t-call="HiddenInputFile">
<t t-set="fileupload_id" t-value="widget.fileupload_id"/>
<t t-set="fileupload_action" t-translation="off">/web/binary/upload_attachment</t>
<t t-set="multi_upload" t-value="true"/>
- <input type="hidden" name="model" t-att-value="widget.view.model"/>
+ <input type="hidden" name="model" t-att-value="widget.model"/>
<input type="hidden" name="id" value="0"/>
- <input t-if="widget.getSession().override_session" type="hidden" name="session_id" t-att-value="widget.session.session_id"/>
+ <input t-if="widget.getSession().override_session" type="hidden" name="session_id" t-att-value="widget.getSession().session_id"/>
</t>
</div>
</div>
<div t-name="FieldBinaryFileUploader.files" class="oe_attachments">
- <t t-if="widget.get('effective_readonly')">
- <div t-foreach="widget.get('value')" t-as="id">
- <t t-set="file" t-value="widget.data[id]"/>
- <a target="_blank" t-att-href="file.url" t-attf-title="#{(file.name || file.filename) + (file.date?' \n('+file.date+')':'' )}"><t t-raw="file.name || file.filename"/></a>
+ <t t-if="widget.mode === 'readonly'">
+ <div t-foreach="widget.value.data" t-as="file">
+ <a target="_blank" t-att-href="widget.metadata[file.id].url" t-attf-title="#{(file.data.name || file.data.filename) + (file.data.date?' \n('+file.data.date+')':'' )}"><t t-raw="file.data.name || file.data.filename"/></a>
</div>
</t>
- <t t-if="!widget.get('effective_readonly')">
- <div t-foreach="values" t-as="id" class="oe_attachment">
- <t t-set="file" t-value="widget.data[id]"/>
- <t t-if="!file.upload">
+ <t t-else="1">
+ <div t-foreach="widget.value.data" t-as="file" class="oe_attachment">
+ <t t-if="!file.data.upload">
<div>
- <a href="#" class="fa fa-times pull-right oe_delete" title="Delete this file" t-att-data-id="file.id"/>
- <t t-raw="file.name || file.filename"/>
+ <a href="#" class="fa fa-times pull-right oe_delete" title="Delete this file" t-att-data-id="file.data.id"/>
+ <t t-raw="file.data.name || file.data.filename"/>
</div>
- <a class="o_image" target="_blank" t-att-href="file.url"
- t-att-title="file.name"
- t-att-data-mimetype="file.mimetype"
- t-attf-data-src="/web/image/#{file.id}/100x80"/>
+ <a class="o_image" target="_blank" t-att-href="widget.metadata[file.id] ? widget.metadata[file.id].url : false"
+ t-att-title="file.data.name"
+ t-att-data-mimetype="file.data.mimetype"
+ t-attf-data-src="/web/image/#{file.data.id}/100x80"/>
</t>
</div>
- <div t-foreach="widget.files_uploading" t-as="file" class="oe_attachment">
+ <div t-foreach="widget.uploadingFiles" t-as="file" class="oe_attachment">
<div>Uploading...</div>
<a t-att-title="file.name" t-att-name="file.name" class="o_image">
<i class="fa fa-spinner fa-spin fa-5x fa-fw"></i>
diff --git a/addons/web/static/tests/fields/relational_fields_tests.js b/addons/web/static/tests/fields/relational_fields_tests.js
index 11433bff92c..47043fda90e 100644
--- a/addons/web/static/tests/fields/relational_fields_tests.js
+++ b/addons/web/static/tests/fields/relational_fields_tests.js
@@ -3954,6 +3954,86 @@ QUnit.module('relational_fields', {
form.destroy();
});
+
+ QUnit.module('FieldMany2ManyBinaryMultiFiles');
+
+ QUnit.test('widget many2many_binary', function (assert) {
+ assert.expect(14);
+ this.data['ir.attachment'] = {
+ fields: {
+ name: {string:"Name", type: "char"},
+ mimetype: {string: "Mimetype", type: "char"},
+ },
+ records: [{
+ id: 17,
+ name: 'Marley&Me.jpg',
+ mimetype: 'jpg',
+ }],
+ };
+ this.data.turtle.fields.picture_ids = {
+ string: "Pictures",
+ type: "many2many",
+ relation: 'ir.attachment',
+ };
+ this.data.turtle.records[0].picture_ids = [17];
+
+ var form = createView({
+ View: FormView,
+ model: 'turtle',
+ data: this.data,
+ arch:'<form string="Turtles">' +
+ '<group><field name="picture_ids" widget="many2many_binary"/></group>' +
+ '</form>',
+ archs: {
+ 'ir.attachment,false,list': '<tree string="Pictures"><field name="name"/></tree>',
+ },
+ res_id: 1,
+ mockRPC: function (route, args) {
+ assert.step(route);
+ if (route === '/web/dataset/call_kw/ir.attachment/read') {
+ assert.deepEqual(args.args[1], ['name', 'datas_fname', 'mimetype']);
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ assert.strictEqual(form.$('div.o_form_field.oe_fileupload').length, 1,
+ "there should be the attachment widget");
+ assert.strictEqual(form.$('div.o_form_field.oe_fileupload .oe_attachments').children().length, 1,
+ "there should be no attachment");
+ assert.strictEqual(form.$('div.o_form_field.oe_fileupload .o_attach').length, 0,
+ "there should not be an Add button (readonly)");
+ assert.strictEqual(form.$('div.o_form_field.oe_fileupload .oe_attachment .oe_delete').length, 0,
+ "there should not be a Delete button (readonly)");
+
+ // to edit mode
+ form.$buttons.find('.o_form_button_edit').click();
+ assert.strictEqual(form.$('div.o_form_field.oe_fileupload .o_attach').length, 1,
+ "there should be an Add button");
+ assert.strictEqual(form.$('div.o_form_field.oe_fileupload .o_attach').text().trim(), "Pictures",
+ "the button should be correctly named");
+ assert.strictEqual(form.$('div.o_form_field.oe_fileupload .o_hidden_input_file form').length, 1,
+ "there should be a hidden form to upload attachments");
+
+ // TODO: add an attachment
+ // no idea how to test this
+
+ // delete the attachment
+ form.$('div.o_form_field.oe_fileupload .oe_attachment .oe_delete').click();
+
+
+ assert.verifySteps([
+ '/web/dataset/call_kw/turtle/read',
+ '/web/dataset/call_kw/ir.attachment/read',
+ ]);
+
+ form.$buttons.find('.o_form_button_save').click();
+
+ assert.strictEqual(form.$('div.o_form_field.oe_fileupload .oe_attachments').children().length, 0,
+ "there should be no attachment");
+
+ form.destroy();
+ });
});
});
});