diff --git a/cmis_field/controllers/main.py b/cmis_field/controllers/main.py index 14495c44..483c2703 100644 --- a/cmis_field/controllers/main.py +++ b/cmis_field/controllers/main.py @@ -27,6 +27,18 @@ def create_document_field_value(self, model_name, res_id, field_name, documents) return {'value': value} return {'value': None} + @http.route( + '/web/cmis/field/cmis_document/get_document_parent', + type='json', + methods=['POST'], + auth="user" + ) + def get_document_parent(self, model_name, res_id, backend_id, field_name): + env = http.request.env + record = env[model_name].browse(int(res_id)) + backend = env["cmis.backend"].browse(int(backend_id)) + return record._fields[field_name].get_create_parents(record, backend)[record.id] + def _decode_files(self, documents): for doc in documents: file_ = doc.get("data") diff --git a/cmis_field/fields/cmis_document.py b/cmis_field/fields/cmis_document.py index 1555d10c..bc06009a 100644 --- a/cmis_field/fields/cmis_document.py +++ b/cmis_field/fields/cmis_document.py @@ -61,7 +61,8 @@ def _description_backend(self, env): else: msg = _('No backend found. Please check your configuration.') return {'backend_error': msg} - return backend.get_web_description()[backend.id] + result = backend.get_web_description()[backend.id] + return result def get_cmis_object(self, record): """Returns an instance of @@ -135,10 +136,9 @@ def clean_up_document(cmis_object_id, backend_id, dbname): pass # remove created resource in case of rollback - test_mode = getattr(threading.currentThread(), 'testing', False) + test_mode = getattr(threading.current_thread(), 'testing', False) if not test_mode: - record.env.cr.after( - 'rollback', + record.env.cr.postrollback.add( partial( clean_up_document, object_id, diff --git a/cmis_web/__manifest__.py b/cmis_web/__manifest__.py index f1f8e161..9faaae1c 100644 --- a/cmis_web/__manifest__.py +++ b/cmis_web/__manifest__.py @@ -18,6 +18,7 @@ "web.assets_backend": [ "/cmis_web/static/lib/cmisjs/superagent.7.2.0.js", "/cmis_web/static/lib/cmisjs/cmis.0.3.1.js", + "/cmis_web/static/src/cmis_document/*", "/cmis_web/static/src/cmis_folder/cmis_folder.js", "/cmis_web/static/src/cmis_folder/cmis_folder.scss", "/cmis_web/static/src/cmis_folder/cmis_folder.xml", diff --git a/cmis_web/static/src/cmis_actions/cmis_actions.js b/cmis_web/static/src/cmis_actions/cmis_actions.js index 70cde9b2..f4fe926d 100644 --- a/cmis_web/static/src/cmis_actions/cmis_actions.js +++ b/cmis_web/static/src/cmis_actions/cmis_actions.js @@ -11,16 +11,24 @@ import {CmisAttachmentViewer} from "../cmis_attachment_viewer/cmis_attachment_vi import {CmisObjectWrapper} from "../cmis_object_wrapper_service"; import {Dropdown} from "@web/core/dropdown/dropdown"; import {DropdownItem} from "@web/core/dropdown/dropdown_item"; -import {cmisTableProps} from "../cmis_table/cmis_table"; import {registry} from "@web/core/registry"; import {useService} from "@web/core/utils/hooks"; -const {Component} = owl; +const {Component, useState} = owl; export class CmisActions extends Component { setup() { this.dialogService = useService("dialog"); this.allowableActions = this.props.cmisObject.allowableActions; + this.dynamicActions = this.props.dynamicActions || {}; + + this.state = useState({ + dynamicActions: this.dynamicActions, + }); + } + + onClickDynamicAction(name) { + this.props.dynamicActions[name].actionClick(this.props.cmisObject); } onClickDownload() { @@ -28,9 +36,10 @@ export class CmisActions extends Component { } onClickPreview() { + const props = this.props; this.dialogService.add(CmisAttachmentViewer, { - cmisObject: this.props.cmisObject, - cmisFolderObjects: this.props.cmisFolderObjects, + cmisObject: props.cmisObject, + cmisFolderObjects: [props.cmisObject], }); } @@ -50,9 +59,19 @@ export class CmisActions extends Component { CmisActions.template = "cmis_web.CmisActions"; CmisActions.components = {Dropdown, DropdownItem}; CmisActions.props = { - ...cmisTableProps, cmisObject: CmisObjectWrapper, - cmisFolderObjects: {type: Array, element: CmisObjectWrapper}, + renameObject: Function, + updateDocumentContent: Function, + deleteObject: { + type: Function, + optional: true, + }, + dynamicActions: [ + { + type: Object, + optional: true, + }, + ], }; registry.category("view_widgets").add("cmis_actions", CmisActions); diff --git a/cmis_web/static/src/cmis_actions/cmis_actions.xml b/cmis_web/static/src/cmis_actions/cmis_actions.xml index aef4868f..bb9585d0 100644 --- a/cmis_web/static/src/cmis_actions/cmis_actions.xml +++ b/cmis_web/static/src/cmis_actions/cmis_actions.xml @@ -28,6 +28,24 @@ > + + + + + + + + Rename diff --git a/cmis_web/static/src/cmis_document/cmis_document.js b/cmis_web/static/src/cmis_document/cmis_document.js new file mode 100644 index 00000000..d3985bad --- /dev/null +++ b/cmis_web/static/src/cmis_document/cmis_document.js @@ -0,0 +1,297 @@ +/** @odoo-module **/ +/* global cmis */ + +/* --------------------------------------------------------- ++ * Odoo cmis_web ++ * Authors Laurent Mignon 2016, Quentin Groulard 2023 Acsone SA/NV ++ * License in __openerp__.py at root level of the module ++ *--------------------------------------------------------- ++*/ + +import {AddDocumentDialog} from "../add_document_dialog/add_document_dialog"; +import {CmisActions} from "../cmis_actions/cmis_actions"; +import {CmisObjectWrapper} from "../cmis_object_wrapper_service"; +import {ConfirmationDialog} from "@web/core/confirmation_dialog/confirmation_dialog"; +import {RenameDialog} from "../rename_dialog/rename_dialog"; +import {UpdateDocumentContentDialog} from "../update_document_content_dialog/update_document_content_dialog"; +import {WarningDialog} from "@web/core/errors/error_dialogs"; + +import {registry} from "@web/core/registry"; +import {sprintf} from "@web/core/utils/strings"; +import {standardFieldProps} from "@web/views/fields/standard_field_props"; +import {useService} from "@web/core/utils/hooks"; + +import framework from "web.framework"; + +const {Component, onWillRender, useState} = owl; + +export class CmisDocumentField extends Component { + setup() { + this.rpc = useService("rpc"); + this.orm = useService("orm"); + this.cmisObjectWrapperService = useService("cmisObjectWrapperService"); + this.dialogService = useService("dialog"); + + this.backend = this.props.backend; + this.state = useState({ + value: this.props.value, + cmisObjectsWrap: [], + cmisObjectWrap: {}, + allowableActions: {}, + hasData: false, + }); + + this.cmisSession = null; + this.documentId = null; + this.displayDocumentId = null; + + this.initCmisSession(); + + onWillRender(async () => { + this.setDocumentId(); + }); + } + + getCmisObjectWrapperParams() { + return {}; + } + + initCmisSession() { + if (this.backend.backend_error) { + this.dialogService.add(WarningDialog, { + title: "CMIS Error", + message: this.backend.backend_error, + }); + return; + } + this.cmisSession = cmis.createSession(this.backend.location); + this.cmisSession.setGlobalHandlers( + this.onCmisError.bind(this), + this.onCmisError.bind(this) + ); + this.cmisSession.setCharacterSet(document.characterSet); + } + + get dynamicActionsProps() { + const props = { + renameObject: this.renameObject.bind(this), + updateDocumentContent: this.updateDocumentContent.bind(this), + dynamicActions: { + delete_link: { + name: this.env._t("Delete Link"), + actionClick: this.deleteLink.bind(this), + }, + }, + }; + return props; + } + + async setDocumentId() { + if (this.documentId === this.state.value) { + return; + } + this.documentId = this.state.value; + + if (!this.documentId) { + return; + } + + var self = this; + const loadCmisRepositories = new Promise(function (resolve, reject) { + if (self.cmisSession.repositories) { + resolve(); + } + self.cmisSession + .loadRepositories() + .ok(() => resolve()) + .notOk((error) => reject(error)); + }); + + loadCmisRepositories.then(() => this.displayDocument()); + } + + async displayDocument() { + if (this.displayDocumentId === this.documentId) { + return; + } + this.displayDocumentId = this.documentId; + this.queryCmisData(); + } + + async queryCmisData() { + var self = this; + const options = { + includeAllowableActions: true, + }; + const cmisData = await new Promise((resolve) => { + self.cmisSession + .getObject(self.displayDocumentId, "latest", options) + .ok(function (data) { + resolve(data); + }); + }); + const params = this.getCmisObjectWrapperParams(); + this.state.allowableActions = cmisData.allowableActions; + this.state.allowableActions.canDeleteObject = false; + this.state.cmisObjectWrap = new CmisObjectWrapper( + cmisData, + this.cmisSession, + params + ); + this.state.hasData = true; + } + + onClickAddDocument() { + const dialogProps = { + confirm: (files) => { + this._getDocumentsFromFiles(files).then((documents) => { + this.uploadFile(documents); + }); + }, + }; + this.dialogService.add(AddDocumentDialog, dialogProps); + } + + onCmisError(error) { + framework.unblockUI(); + if (error) { + this.dialogService.add(WarningDialog, { + title: "CMIS Error", + message: error.body.message, + }); + } + } + + async uploadFile(documents) { + if (!this.props.record.resId) { + this.dialogService.add(WarningDialog, { + title: "CMIS Error", + message: this.env._t("Create your object first"), + }); + return; + } + const cmisValue = await this.rpc("/web/cmis/field/create_document_value", { + model_name: this.props.record.resModel, + res_id: this.props.record.data.id, + field_name: this.props.name, + documents: documents, + }); + await this.props.record.load(); + this.props.record.model.notify(); + this.state.value = cmisValue.value; + } + + _getDocumentsFromFiles(files) { + return Promise.all( + _.map(files, function (file) { + return new Promise(function (resolve, reject) { + var reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = function () { + resolve({ + name: file.name, + mimetype: file.type, + data: reader.result, + }); + }; + reader.onerror = (error) => reject(error); + }); + }) + ); + } + + renameObject(cmisObject) { + var self = this; + const dialogProps = { + title: `Rename ${cmisObject.name}`, + name: cmisObject.name, + confirm: (newName) => { + if (newName !== cmisObject.name) { + this.cmisSession + .updateProperties(cmisObject.objectId, {"cmis:name": newName}) + .ok(function () { + self.queryCmisData(); + }); + } + }, + }; + this.dialogService.add(RenameDialog, dialogProps); + } + + updateDocumentContent(cmisObject) { + var self = this; + const dialogProps = { + title: `Update content of ${cmisObject.name}`, + confirm: (file) => { + if (file) { + this.cmisSession + .setContentStream(cmisObject.objectId, file, true, file.name) + .ok(function () { + self.queryCmisData(); + }); + } + }, + }; + this.dialogService.add(UpdateDocumentContentDialog, dialogProps); + } + + deleteLink(cmisObject) { + const self = this; + const dialogProps = { + title: "Delete Link with the file", + body: sprintf('Confirm the link deletion of "%s".', cmisObject.name), + confirmLabel: "Delete link", + confirm: () => { + return self.performDeleteLink(); + }, + cancel: () => { + return; + }, + }; + this.dialogService.add(ConfirmationDialog, dialogProps); + } + + async performDeleteLink() { + const props = this.props; + const record = props.record; + const values = {}; + values[props.name] = false; + await this.orm.write(record.resModel, [record.data.id], values); + await record.load(); + record.model.notify(); + this.state.value = false; + } +} + +CmisDocumentField.template = "cmis_web.CmisDocumentField"; +CmisDocumentField.supportedTypes = ["cmis_document"]; +CmisDocumentField.components = {CmisActions}; +CmisDocumentField.props = { + ...standardFieldProps, + backend: [ + { + type: Object, + optional: true, + shape: { + id: Number, + location: String, + name: {type: String, optional: true}, + share_location: {type: String, optional: true}, + alfresco_api_location: {type: String, optional: true}, + }, + }, + { + type: Object, + optional: true, + shape: {backend_error: String}, + }, + ], +}; + +CmisDocumentField.extractProps = ({field}) => { + return { + backend: field.backend, + }; +}; + +registry.category("fields").add("cmis_document", CmisDocumentField); diff --git a/cmis_web/static/src/cmis_document/cmis_document.scss b/cmis_web/static/src/cmis_document/cmis_document.scss new file mode 100644 index 00000000..3a25aeea --- /dev/null +++ b/cmis_web/static/src/cmis_document/cmis_document.scss @@ -0,0 +1,17 @@ +.cmis-document { + .cmis-document-action { + cursor: pointer; + width: fit-content; + &:hover { + color: $btn-link-color !important; + } + + .action-label { + color: rgba(52, 58, 64, 0.4); + &:hover { + color: $btn-link-color !important; + } + } + + } +} diff --git a/cmis_web/static/src/cmis_document/cmis_document.xml b/cmis_web/static/src/cmis_document/cmis_document.xml new file mode 100644 index 00000000..9dd3de68 --- /dev/null +++ b/cmis_web/static/src/cmis_document/cmis_document.xml @@ -0,0 +1,41 @@ + + + + + + + + + + Link with an existing document + + + + + + + + + Add a document + + + + + + + + + + + + + + + diff --git a/cmis_web/static/src/cmis_folder/cmis_folder.js b/cmis_web/static/src/cmis_folder/cmis_folder.js index e4ee0b78..dbe08223 100644 --- a/cmis_web/static/src/cmis_folder/cmis_folder.js +++ b/cmis_web/static/src/cmis_folder/cmis_folder.js @@ -74,6 +74,8 @@ export class CmisFolderField extends Component { }); }); const params = this.getCmisObjectWrapperParams(); + console.log("cmisObjectData"); + console.log(cmisObjectsData); this.state.cmisObjectsWrap = this.cmisObjectWrapperService.wrap( cmisObjectsData.objects, this.cmisSession, diff --git a/cmis_web/static/src/cmis_table/cmis_table.js b/cmis_web/static/src/cmis_table/cmis_table.js index 3dd87da8..a9598c6e 100644 --- a/cmis_web/static/src/cmis_table/cmis_table.js +++ b/cmis_web/static/src/cmis_table/cmis_table.js @@ -194,6 +194,17 @@ export class CmisTable extends Component { return value ? value : ""; } + get dynamicActionsProps() { + console.log(this.props); + const props = { + deleteObject: this.props.deleteObject, + renameObject: this.props.renameObject, + updateDocumentContent: this.props.updateDocumentContent, + dynamicActions: {}, + }; + return props; + } + get nbCols() { let nbCols = this.state.columns.length; // Column selector diff --git a/cmis_web/static/src/cmis_table/cmis_table.xml b/cmis_web/static/src/cmis_table/cmis_table.xml index 0cf1025d..c7b9e4aa 100644 --- a/cmis_web/static/src/cmis_table/cmis_table.xml +++ b/cmis_web/static/src/cmis_table/cmis_table.xml @@ -130,9 +130,8 @@ t-att-class="getCellClass(column, cmisObject)" > diff --git a/cmis_web_alf/__manifest__.py b/cmis_web_alf/__manifest__.py index a828eddb..c68b0e76 100644 --- a/cmis_web_alf/__manifest__.py +++ b/cmis_web_alf/__manifest__.py @@ -21,6 +21,7 @@ "assets": { "web.assets_backend": [ "/cmis_web_alf/static/src/images/images.scss", + "/cmis_web_alf/static/src/cmis_document/cmis_document.js", "/cmis_web_alf/static/src/cmis_folder/cmis_folder.js", "/cmis_web_alf/static/src/cmis_folder/cmis_folder.xml", "/cmis_web_alf/static/src/cmis_table/cmis_table.js", diff --git a/cmis_web_alf/static/src/cmis_document/cmis_document.js b/cmis_web_alf/static/src/cmis_document/cmis_document.js new file mode 100644 index 00000000..32b9dc15 --- /dev/null +++ b/cmis_web_alf/static/src/cmis_document/cmis_document.js @@ -0,0 +1,40 @@ +/** @odoo-module **/ + +/* --------------------------------------------------------- ++ * Odoo cmis_web ++ * Authors Laurent Mignon 2016, Quentin Groulard 2023 Acsone SA/NV ++ * License in __openerp__.py at root level of the module ++ *--------------------------------------------------------- ++*/ + +import {CmisDocumentField} from "@cmis_web/cmis_document/cmis_document"; +import {patch} from "@web/core/utils/patch"; + +patch(CmisDocumentField.prototype, "open_in_alfresco", { + get dynamicActionsProps() { + const props = this._super(...arguments); + props.openInAlf = this.openInAlf.bind(this); + return props; + }, + + onClickOpenInAlf() { + this.openInAlf(this.displayDocumentId); + }, + + async openInAlf(cmisObjectId) { + const url = await this.rpc("/web/cmis/content_details_url", { + backend_id: this.backend.id, + cmis_objectid: cmisObjectId, + }); + window.open(url); + }, + + getCmisObjectWrapperParams() { + const params = this._super(...arguments); + params.alfrescoApiLocation = this.backend.alfresco_api_location; + return params; + }, +}); + +CmisDocumentField.props.backend[0].shape.share_location = String; +CmisDocumentField.props.backend[0].shape.alfresco_api_location = String; diff --git a/cmis_web_alf/static/src/cmis_table/cmis_table.js b/cmis_web_alf/static/src/cmis_table/cmis_table.js index b6ad48c4..3cb7ccd3 100644 --- a/cmis_web_alf/static/src/cmis_table/cmis_table.js +++ b/cmis_web_alf/static/src/cmis_table/cmis_table.js @@ -7,6 +7,15 @@ + *--------------------------------------------------------- +*/ -import {cmisTableProps} from "@cmis_web/cmis_table/cmis_table"; +import {CmisTable, cmisTableProps} from "@cmis_web/cmis_table/cmis_table"; +import {patch} from "@web/core/utils/patch"; + +patch(CmisTable.prototype, "open_in_alfresco", { + get dynamicActionsProps() { + const props = this._super(...arguments); + props.openInAlf = this.props.openInAlf; + return props; + }, +}); cmisTableProps.openInAlf = Function; diff --git a/cmis_web_proxy/__manifest__.py b/cmis_web_proxy/__manifest__.py index d525c9b6..809dac21 100644 --- a/cmis_web_proxy/__manifest__.py +++ b/cmis_web_proxy/__manifest__.py @@ -16,6 +16,7 @@ "images": ["static/description/main_icon.png"], "assets": { "web.assets_backend": [ + "/cmis_web_proxy/static/src/cmis_document/cmis_document.js", "/cmis_web_proxy/static/src/cmis_folder/cmis_folder.js", "/cmis_web_proxy/static/src/cmis_object_wrapper_service.js", ], diff --git a/cmis_web_proxy/static/src/cmis_document/cmis_document.js b/cmis_web_proxy/static/src/cmis_document/cmis_document.js new file mode 100644 index 00000000..51f071da --- /dev/null +++ b/cmis_web_proxy/static/src/cmis_document/cmis_document.js @@ -0,0 +1,52 @@ +/** @odoo-module **/ + +/* --------------------------------------------------------- ++ * Odoo cmis_web ++ * Authors Laurent Mignon 2016, Maxime Franco 2023 Acsone SA/NV ++ * License in __openerp__.py at root level of the module ++ *--------------------------------------------------------- ++*/ + +import {CmisDocumentField} from "@cmis_web/cmis_document/cmis_document"; +import {patch} from "@web/core/utils/patch"; + +patch(CmisDocumentField.prototype, "open_with_proxy", { + getCmisObjectWrapperParams() { + const params = this._super(...arguments); + params.applyOdooSecurity = this.backend.apply_odoo_security; + return params; + }, + + genCmisSessionToken() { + return JSON.stringify({ + model: this.props.record.resModel, + res_id: this.props.record.resId, + field_name: this.props.name, + }); + }, + + setCmisSessionToken() { + if (this.backend.apply_odoo_security) { + this.cmisSession.setToken(this.genCmisSessionToken()); + } + }, + + async setDocumentId() { + var self = this; + self.setCmisSessionToken(); + this._super(...arguments); + }, + + getPreviewUrlParams() { + // Pas sur de l'utilité de la méthode je n'ia trouvé aucun appel à cette fonction + var params = this._super(...arguments); + if (this.backend.apply_odoo_security) { + // Add the token as parameter and into the http headers + var token = this.genCmisSessionToken(); + params.token = token; + } + return params; + }, +}); + +CmisDocumentField.props.backend[0].shape.apply_odoo_security = Boolean; diff --git a/cmis_web_proxy_alf/__manifest__.py b/cmis_web_proxy_alf/__manifest__.py index a02a62fa..b170f7ff 100644 --- a/cmis_web_proxy_alf/__manifest__.py +++ b/cmis_web_proxy_alf/__manifest__.py @@ -15,6 +15,7 @@ "images": ["static/description/main_icon.png"], "assets": { "web.assets_backend": [ + "/cmis_web_proxy_alf/static/src/cmis_document/cmis_document.js", "/cmis_web_proxy_alf/static/src/cmis_folder/cmis_folder.js", "/cmis_web_proxy_alf/static/src/cmis_object_wrapper_service.js", ], diff --git a/cmis_web_proxy_alf/static/src/cmis_document/cmis_document.js b/cmis_web_proxy_alf/static/src/cmis_document/cmis_document.js new file mode 100644 index 00000000..2c5f9508 --- /dev/null +++ b/cmis_web_proxy_alf/static/src/cmis_document/cmis_document.js @@ -0,0 +1,21 @@ +/** @odoo-module **/ + +/* --------------------------------------------------------- ++ * Odoo cmis_web ++ * Authors Laurent Mignon 2016, Maxime Franco 2023 Acsone SA/NV ++ * License in __openerp__.py at root level of the module ++ *--------------------------------------------------------- ++*/ + +import {CmisDocumentField} from "@cmis_web/cmis_document/cmis_document"; +import {patch} from "@web/core/utils/patch"; + +patch(CmisDocumentField.prototype, "open_with_proxy_alf", { + getCmisObjectWrapperParams() { + const params = this._super(...arguments); + params.alfrescoApiLocation = this.backend.alfresco_api_location; + return params; + }, +}); + +CmisDocumentField.props.backend[0].shape.alfresco_api_location = String;