summaryrefslogtreecommitdiff
path: root/addons/web/static/src
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 /addons/web/static/src
parenteafa14d3bc16e7212000d0c9c30a3ed922395574 (diff)
[FIX] web: reintroduce many2many_binary field widget
The field has been adapated to the new views. This commit also introduces some tests.
Diffstat (limited to 'addons/web/static/src')
-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
3 files changed, 211 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>