diff --git a/document_url_google_drive/README.rst b/document_url_google_drive/README.rst new file mode 100644 index 00000000..d1e88a2c --- /dev/null +++ b/document_url_google_drive/README.rst @@ -0,0 +1,141 @@ +=========================== +Google Drive URL Attachment +=========================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:e7df1efead13f8fba62e6369e05f7f52b812a1d44741ab2832e9597f6ac748d9 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fknowledge-lightgray.png?logo=github + :target: https://github.com/OCA/knowledge/tree/16.0/document_url_google_drive + :alt: OCA/knowledge +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/knowledge-16-0/knowledge-16-0-document_url_google_drive + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/knowledge&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module extends the functionality of the document_url module and +allows you to attach a link to a file from your Google Drive + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +To configure this module, you need to: + +- Go to Settings -> General Settings and scroll down to the + Integrations section. + +- Enable "Google API", save. Setup instructions + https://developers.google.com/drive/picker/guides/overview + + - "Google Client ID" - enter the client ID from the Google API + console: + https://developers.google.com/identity/oauth2/web/guides/get-google-api-clientid + - "Google API key" - enter the API key from the Google API console. + - "Google App ID" - enter the ID of the Google application. The + default value is ``odoo``. + +You will be asked to authenticate when you add a link for the first +time. + +|Configuration| + +**To modify your personal login credentials later** + +- Open your user profile and set up personal access credentials on the + "Google API" tab. + + - field "Google Scope" - enter the scope for the Google API. The + default value is + ``https://www.googleapis.com/auth/drive.readonly``. + - field "Google Access Token" - your token will be displayed here. + It is necessary to edit it. + - field "Google Mime Types" - enter the file formats to be filtered + when selecting. Example: + ``application/pdf, image/jpeg, image/png``. By default, all files + are selected + + You can always log off from the current Google Account by clicking + the "logout" icon right to the "Add GDrive link". + +.. |Configuration| image:: https://raw.githubusercontent.com/OCA/knowledge/16.0/document_url_google_drive/static/img/google_api_settings.png + +Usage +===== + +To use this module, you need to: + +1. Open the form view of an object (Example: Customer Invoice + INV/2019/0007). +2. Go to the chatter and click on the attached icon. +3. Click **Add GDrive link**. +4. Fill the wizard fields and click on Add button. +5. In the open window, select the files you need and press the select + button. +6. You will see a new **URL attachment** in the set of attachments + related to the object. +7. In order to log in under another google user, click on the logout + icon located after **Add GDrive link** + +|Google Drive Link| + +.. |Google Drive Link| image:: https://raw.githubusercontent.com/OCA/knowledge/16.0/document_url_google_drive/static/img/gdrive_link.png + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Cetmix + +Contributors +------------ + +Cetmix Ivan Sokolov Mykola Demchuk + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/knowledge `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/document_url_google_drive/__init__.py b/document_url_google_drive/__init__.py new file mode 100644 index 00000000..68fa3356 --- /dev/null +++ b/document_url_google_drive/__init__.py @@ -0,0 +1,4 @@ +# Copyright (C) 2023 Cetmix OÜ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import models, wizard diff --git a/document_url_google_drive/__manifest__.py b/document_url_google_drive/__manifest__.py new file mode 100644 index 00000000..6771a476 --- /dev/null +++ b/document_url_google_drive/__manifest__.py @@ -0,0 +1,28 @@ +# Copyright (C) 2023 Cetmix OÜ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). +{ + "name": "Google Drive URL Attachment", + "summary": "Attach Google Drive link to Odoo document using Google Drive Picker", + "version": "16.0.1.0.0", + "category": "Tools", + "website": "https://github.com/OCA/knowledge", + "author": "Cetmix, Odoo Community Association (OCA)", + "license": "AGPL-3", + "installable": True, + "images": ["static/description/banner.png"], + "depends": [ + "document_url", + "google_account", + ], + "data": [ + "views/res_users_view.xml", + "views/res_config_settings_view.xml", + ], + "assets": { + "web.assets_backend": [ + "document_url_google_drive/static/src/js/attachment_google_picker.esm.js", + "document_url_google_drive/static/src/xml/google_picker_url.xml", + "document_url_google_drive/static/src/xml/attachment_google_picker.xml", + ], + }, +} diff --git a/document_url_google_drive/models/__init__.py b/document_url_google_drive/models/__init__.py new file mode 100644 index 00000000..e85fc905 --- /dev/null +++ b/document_url_google_drive/models/__init__.py @@ -0,0 +1,4 @@ +# Copyright (C) 2023 Cetmix OÜ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import res_users, res_config_settings diff --git a/document_url_google_drive/models/res_config_settings.py b/document_url_google_drive/models/res_config_settings.py new file mode 100644 index 00000000..fbc7eda7 --- /dev/null +++ b/document_url_google_drive/models/res_config_settings.py @@ -0,0 +1,26 @@ +# Copyright (C) 2023 Cetmix OÜ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + is_active_google_api = fields.Boolean( + string="Google APIs", + config_parameter="is_active_google_api", + ) + google_picker_client_id = fields.Char( + string="Google Client ID", + config_parameter="google_picker_client_id", + ) + google_picker_api_key = fields.Char( + string="Google API Key", + config_parameter="google_picker_api_key", + ) + google_picker_app_id = fields.Char( + string="Google App ID", + config_parameter="google_picker_app_id", + default="odoo", + ) diff --git a/document_url_google_drive/models/res_users.py b/document_url_google_drive/models/res_users.py new file mode 100644 index 00000000..88a33489 --- /dev/null +++ b/document_url_google_drive/models/res_users.py @@ -0,0 +1,58 @@ +# Copyright (C) 2023 Cetmix OÜ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ResUsers(models.Model): + _inherit = "res.users" + + google_picker_scope = fields.Char( + string="Google Scope", + default="https://www.googleapis.com/auth/drive.readonly", + ) + google_picker_access_token = fields.Char(string="Google Access Token") + google_picker_expires_date = fields.Integer(string="Google Expires Date") + google_picker_mime_types = fields.Char(string="Google Mime Types") + google_picker_active = fields.Boolean( + compute="_compute_google_picker_active", + ) + + def get_google_picker_params(self): + """ + Get Google Picker params + :return: dict + """ + self.ensure_one() + config = self.env["ir.config_parameter"].sudo() + google_service = self.env["google.service"] + + if not self.google_picker_active: + return {} + return { + "client_id": google_service._get_client_id("picker"), + "api_key": config.get_param("google_picker_api_key"), + "app_id": config.get_param("google_picker_app_id"), + "scope": self.google_picker_scope, + "access_token": self.google_picker_access_token, + "expires_date": self.google_picker_expires_date, + "mime_types": self.google_picker_mime_types, + } + + def save_google_picker_access_token(self, access_token, expires_date): + """ + Save Google Picker access token + :param access_token: str + :return: None + """ + self.ensure_one() + self.google_picker_access_token = access_token + self.google_picker_expires_date = expires_date + + def _compute_google_picker_active(self): + """ + Compute Google Picker Active + :return: None + """ + conf = self.env["ir.config_parameter"].sudo() + self.google_picker_active = conf.get_param("is_active_google_api") diff --git a/document_url_google_drive/readme/CONFIGURE.md b/document_url_google_drive/readme/CONFIGURE.md new file mode 100644 index 00000000..a053d3c9 --- /dev/null +++ b/document_url_google_drive/readme/CONFIGURE.md @@ -0,0 +1,31 @@ +To configure this module, you need to: + +- Go to Settings -> General Settings and scroll down to the Integrations section. + +- Enable "Google API", save. Setup instructions https://developers.google.com/drive/picker/guides/overview + + - "Google Client ID" - enter the client ID from the Google API console: https://developers.google.com/identity/oauth2/web/guides/get-google-api-clientid + - "Google API key" - enter the API key from the Google API console. + - "Google App ID" - enter the ID of the Google application. The default value is + `odoo`. + +You will be asked to authenticate when you add a link for the first time. + +![Configuration](../static/img/google_api_settings.png) + +**To modify your personal login credentials later** + +- Open your user profile and set up personal access credentials on the "Google + API" tab. + + - field "Google Scope" - enter the scope for the Google API. The default value is + `https://www.googleapis.com/auth/drive.readonly`. + - field "Google Access Token" - your token will be displayed here. It is necessary to + edit it. + - field "Google Mime Types" - enter the file formats to be filtered when selecting. + Example: `application/pdf, image/jpeg, image/png`. By default, all files are + selected + + You can always log off from the current Google Account by clicking the "logout" icon right to the "Add GDrive link". + + diff --git a/document_url_google_drive/readme/CONTRIBUTORS.md b/document_url_google_drive/readme/CONTRIBUTORS.md new file mode 100644 index 00000000..ff0bbe9d --- /dev/null +++ b/document_url_google_drive/readme/CONTRIBUTORS.md @@ -0,0 +1,3 @@ + Cetmix + Ivan Sokolov + Mykola Demchuk diff --git a/document_url_google_drive/readme/DESCRIPTION.md b/document_url_google_drive/readme/DESCRIPTION.md new file mode 100644 index 00000000..750f6b24 --- /dev/null +++ b/document_url_google_drive/readme/DESCRIPTION.md @@ -0,0 +1,2 @@ +This module extends the functionality of the document_url module and allows you to +attach a link to a file from your Google Drive diff --git a/document_url_google_drive/readme/USAGE.md b/document_url_google_drive/readme/USAGE.md new file mode 100644 index 00000000..8692ac9e --- /dev/null +++ b/document_url_google_drive/readme/USAGE.md @@ -0,0 +1,11 @@ +To use this module, you need to: + +1. Open the form view of an object (Example: Customer Invoice INV/2019/0007). +1. Go to the chatter and click on the attached icon. +1. Click **Add GDrive link**. +1. Fill the wizard fields and click on Add button. +1. In the open window, select the files you need and press the select button. +1. You will see a new **URL attachment** in the set of attachments related to the object. +1. In order to log in under another google user, click on the logout icon located after **Add GDrive link** + +![Google Drive Link](../static/img/gdrive_link.png) diff --git a/document_url_google_drive/readme/newsfragments/.gitkeep b/document_url_google_drive/readme/newsfragments/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/document_url_google_drive/static/description/banner.png b/document_url_google_drive/static/description/banner.png new file mode 100644 index 00000000..6339699d Binary files /dev/null and b/document_url_google_drive/static/description/banner.png differ diff --git a/document_url_google_drive/static/description/icon.png b/document_url_google_drive/static/description/icon.png new file mode 100644 index 00000000..3a0328b5 Binary files /dev/null and b/document_url_google_drive/static/description/icon.png differ diff --git a/document_url_google_drive/static/description/icon.svg b/document_url_google_drive/static/description/icon.svg new file mode 100644 index 00000000..a7a26d09 --- /dev/null +++ b/document_url_google_drive/static/description/icon.svg @@ -0,0 +1,79 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/document_url_google_drive/static/description/index.html b/document_url_google_drive/static/description/index.html new file mode 100644 index 00000000..f92d18ab --- /dev/null +++ b/document_url_google_drive/static/description/index.html @@ -0,0 +1,124 @@ +
+
+
+

Module name

+

This module was written to extend the functionality of ... to support ... and allow you to ...

+
+
+
+ +
+
+
+

Installation

+
+
+

To install this module, you need to: +

    +
  • ...
  • +
+

+
+
+
+ + + +
+
+
+
+ +
+
+
+

Configuration

+
+
+

To configure this module, you need to: +

    +
  • ...
  • +
+

+
+
+
+ + + +
+
+
+
+ +
+
+
+

Usage

+
+
+

To use this module, you need to: +

    +
  • ...
  • +
+

+

For further information, please visit: +

+

+
+
+
+ + + +
+
+
+
+ +
+
+
+

Known issues / Roadmap

+
+
+

+

    +
  • ...
  • +
+

+
+
+
+ + + +
+
+
+
+ +
+
+
+

Credits

+
+
+

Contributors

+ +
+
+

Maintainer

+

+ This module is maintained by the OCA.
+ OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.
+ To contribute to this module, please visit http://odoo-community.org.
+ +

+
+
+
diff --git a/document_url_google_drive/static/img/gdrive_link.png b/document_url_google_drive/static/img/gdrive_link.png new file mode 100644 index 00000000..5f628690 Binary files /dev/null and b/document_url_google_drive/static/img/gdrive_link.png differ diff --git a/document_url_google_drive/static/img/google_api_settings.png b/document_url_google_drive/static/img/google_api_settings.png new file mode 100644 index 00000000..17c930d2 Binary files /dev/null and b/document_url_google_drive/static/img/google_api_settings.png differ diff --git a/document_url_google_drive/static/src/img/logo-google-drive.png b/document_url_google_drive/static/src/img/logo-google-drive.png new file mode 100644 index 00000000..13ccc257 Binary files /dev/null and b/document_url_google_drive/static/src/img/logo-google-drive.png differ diff --git a/document_url_google_drive/static/src/js/attachment_google_picker.esm.js b/document_url_google_drive/static/src/js/attachment_google_picker.esm.js new file mode 100644 index 00000000..d3f69dc3 --- /dev/null +++ b/document_url_google_drive/static/src/js/attachment_google_picker.esm.js @@ -0,0 +1,195 @@ +/** @odoo-module **/ + +import {loadJS} from "@web/core/assets"; +import {registerMessagingComponent} from "@mail/utils/messaging_component"; +import {useComponentToModel} from "@mail/component_hooks/use_component_to_model"; +import {useService} from "@web/core/utils/hooks"; + +const {Component, onWillStart, useState} = owl; + +export class AttachmentGooglePicker extends Component { + /** + * @override + */ + setup() { + super.setup(); + useComponentToModel({fieldName: "component"}); + this.orm = useService("orm"); + this.user = useService("user"); + this.action = useService("action"); + this.state = useState({ + pickerInited: false, + gisInited: false, + api_key: "", + scopes: "", + client_id: "", + app_id: "", + accessToken: null, + expiresDate: 0, + }); + this.tokenClient = null; + + onWillStart(async () => { + await this.getUserAuthParams(); + if (!this.checkActive()) { + return; + } + await loadJS("https://apis.google.com/js/api.js", { + attrs: {async: true, defer: true}, + }); + await this.gapiLoaded(); + await loadJS("https://accounts.google.com/gsi/client", { + attrs: {async: true, defer: true}, + }); + await this.gisLoaded(); + }); + } + + // -------------------------------------------------------------------------- + // Public + // -------------------------------------------------------------------------- + + async _onAddGooglePickerUrl() { + if ( + this.state.accessToken && + this.state.expiresDate > Math.floor(Date.now() / 1000) + ) { + await this.createPicker(); + } else { + await this.handleAuthClick(); + } + } + + checkActive() { + return ( + Boolean(this.state.api_key) && + Boolean(this.state.scopes) && + Boolean(this.state.client_id) && + Boolean(this.state.app_id) + ); + } + + async _onSignOut() { + this.state.accessToken = null; + await this.saveUserAuthAccessToken(); + } + // -------------------------------------------------------------------------- + // Private + // -------------------------------------------------------------------------- + + async getUserAuthParams() { + const res = await this.orm.call("res.users", "get_google_picker_params", [ + this.user.userId, + ]); + if (!res) { + return; + } + this.state.client_id = res.client_id; + this.state.api_key = res.api_key; + this.state.app_id = res.app_id; + this.state.scopes = res.scope; + this.state.accessToken = res.access_token; + this.state.expiresDate = res.expires_date; + this.state.mime_types = res.mime_types; + } + + async saveUserAuthAccessToken() { + await this.orm.call("res.users", "save_google_picker_access_token", [ + this.user.userId, + this.state.accessToken, + this.state.expiresDate, + ]); + } + + async gapiLoaded() { + window.gapi.load("client:picker", this.initializePicker.bind(this)); + } + + async initializePicker() { + await window.gapi.client.load( + "https://www.googleapis.com/discovery/v1/apis/drive/v3/rest" + ); + this.state.pickerInited = true; + } + + handleAuthClick() { + this.tokenClient.callback = async (response) => { + if (response.error !== undefined) { + throw response; + } + this.state.accessToken = response.access_token; + this.state.expiresDate = + Math.floor(Date.now() / 1000) + response.expires_in; + await this.createPicker(); + await this.saveUserAuthAccessToken(); + }; + + if (this.state.accessToken === null) { + // Prompt the user to select a Google Account and ask for consent to share their data + // when establishing a new session. + this.tokenClient.requestAccessToken({ + prompt: "consent", + access_type: "offline", + }); + } else { + // Skip display of account chooser and consent dialog for an existing session. + this.tokenClient.requestAccessToken({prompt: "", access_type: "offline"}); + } + } + + async gisLoaded() { + this.tokenClient = window.google.accounts.oauth2.initTokenClient({ + client_id: this.state.client_id, + scope: this.state.scopes, + callback: "", + }); + this.state.gisInited = true; + } + + createPicker() { + const view = new window.google.picker.View(window.google.picker.ViewId.DOCS); + if (this.state.mime_types) { + view.setMimeTypes(this.state.mime_types); + } + const picker = new window.google.picker.PickerBuilder() + .enableFeature(window.google.picker.Feature.NAV_HIDDEN) + .enableFeature(window.google.picker.Feature.MULTISELECT_ENABLED) + .setDeveloperKey(this.state.api_key) + .setAppId(this.state.app_id) + .setOAuthToken(this.state.accessToken) + .addView(view) + .addView(new window.google.picker.DocsUploadView()) + .setCallback(this.pickerCallback.bind(this)) + .build(); + picker.setVisible(true); + } + + async pickerCallback(data) { + if (data.action === window.google.picker.Action.PICKED) { + for (const document of data.docs) { + await this.createAttachment(document); + } + await this._onAddedUrl(); + } + } + + async createAttachment(document) { + await this.orm.call("ir.attachment.add_url", "add_attachment_google_drive", [ + document.url, + document.name, + this.props.record.chatter.thread.model, + [this.props.record.chatter.thread.id], + ]); + } + + async _onAddedUrl() { + this.props.record.chatter.refresh(); + } +} + +Object.assign(AttachmentGooglePicker, { + props: {record: Object}, + template: "document_url_google_drive.GooglePickerUrl", +}); + +registerMessagingComponent(AttachmentGooglePicker); diff --git a/document_url_google_drive/static/src/xml/attachment_google_picker.xml b/document_url_google_drive/static/src/xml/attachment_google_picker.xml new file mode 100644 index 00000000..aa3eee5d --- /dev/null +++ b/document_url_google_drive/static/src/xml/attachment_google_picker.xml @@ -0,0 +1,25 @@ + + + + + + +