summaryrefslogtreecommitdiff
path: root/addons/google_drive/models/google_drive.py
blob: 6e3eeb9c79e5aa1a428015b25d97f9d8ea93491d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import ast
import logging
import json
import re

import requests
import werkzeug.urls

from odoo import api, fields, models
from odoo.exceptions import RedirectWarning, UserError
from odoo.tools.translate import _

from odoo.addons.google_account.models.google_service import GOOGLE_TOKEN_ENDPOINT, TIMEOUT

_logger = logging.getLogger(__name__)


class GoogleDrive(models.Model):

    _name = 'google.drive.config'
    _description = "Google Drive templates config"

    def get_google_drive_url(self, res_id, template_id):
        self.ensure_one()
        self = self.sudo()

        model = self.model_id
        filter_name = self.filter_id.name if self.filter_id else False
        record = self.env[model.model].browse(res_id).read()[0]
        record.update({
            'model': model.name,
            'filter': filter_name
        })
        name_gdocs = self.name_template
        try:
            name_gdocs = name_gdocs % record
        except:
            raise UserError(_("At least one key cannot be found in your Google Drive name pattern."))

        attachments = self.env["ir.attachment"].search([('res_model', '=', model.model), ('name', '=', name_gdocs), ('res_id', '=', res_id)])
        url = False
        if attachments:
            url = attachments[0].url
        else:
            url = self.copy_doc(res_id, template_id, name_gdocs, model.model).get('url')
        return url

    @api.model
    def get_access_token(self, scope=None):
        Config = self.env['ir.config_parameter'].sudo()
        google_drive_refresh_token = Config.get_param('google_drive_refresh_token')
        user_is_admin = self.env.is_admin()
        if not google_drive_refresh_token:
            if user_is_admin:
                dummy, action_id = self.env['ir.model.data'].get_object_reference('base_setup', 'action_general_configuration')
                msg = _("There is no refresh code set for Google Drive. You can set it up from the configuration panel.")
                raise RedirectWarning(msg, action_id, _('Go to the configuration panel'))
            else:
                raise UserError(_("Google Drive is not yet configured. Please contact your administrator."))
        google_drive_client_id = Config.get_param('google_drive_client_id')
        google_drive_client_secret = Config.get_param('google_drive_client_secret')
        #For Getting New Access Token With help of old Refresh Token
        data = {
            'client_id': google_drive_client_id,
            'refresh_token': google_drive_refresh_token,
            'client_secret': google_drive_client_secret,
            'grant_type': "refresh_token",
            'scope': scope or 'https://www.googleapis.com/auth/drive'
        }
        headers = {"Content-type": "application/x-www-form-urlencoded"}
        try:
            req = requests.post(GOOGLE_TOKEN_ENDPOINT, data=data, headers=headers, timeout=TIMEOUT)
            req.raise_for_status()
        except requests.HTTPError:
            if user_is_admin:
                dummy, action_id = self.env['ir.model.data'].get_object_reference('base_setup', 'action_general_configuration')
                msg = _("Something went wrong during the token generation. Please request again an authorization code .")
                raise RedirectWarning(msg, action_id, _('Go to the configuration panel'))
            else:
                raise UserError(_("Google Drive is not yet configured. Please contact your administrator."))
        return req.json().get('access_token')

    @api.model
    def copy_doc(self, res_id, template_id, name_gdocs, res_model):
        google_web_base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
        access_token = self.get_access_token()
        # Copy template in to drive with help of new access token
        request_url = "https://www.googleapis.com/drive/v2/files/%s?fields=parents/id&access_token=%s" % (template_id, access_token)
        headers = {"Content-type": "application/x-www-form-urlencoded"}
        try:
            req = requests.get(request_url, headers=headers, timeout=TIMEOUT)
            req.raise_for_status()
            parents_dict = req.json()
        except requests.HTTPError:
            raise UserError(_("The Google Template cannot be found. Maybe it has been deleted."))

        record_url = "Click on link to open Record in Odoo\n %s/?db=%s#id=%s&model=%s" % (google_web_base_url, self._cr.dbname, res_id, res_model)
        data = {
            "title": name_gdocs,
            "description": record_url,
            "parents": parents_dict['parents']
        }
        request_url = "https://www.googleapis.com/drive/v2/files/%s/copy?access_token=%s" % (template_id, access_token)
        headers = {
            'Content-type': 'application/json',
            'Accept': 'text/plain'
        }
        # resp, content = Http().request(request_url, "POST", data_json, headers)
        req = requests.post(request_url, data=json.dumps(data), headers=headers, timeout=TIMEOUT)
        req.raise_for_status()
        content = req.json()
        res = {}
        if content.get('alternateLink'):
            res['id'] = self.env["ir.attachment"].create({
                'res_model': res_model,
                'name': name_gdocs,
                'res_id': res_id,
                'type': 'url',
                'url': content['alternateLink']
            }).id
            # Commit in order to attach the document to the current object instance, even if the permissions has not been written.
            self._cr.commit()
            res['url'] = content['alternateLink']
            key = self._get_key_from_url(res['url'])
            request_url = "https://www.googleapis.com/drive/v2/files/%s/permissions?emailMessage=This+is+a+drive+file+created+by+Odoo&sendNotificationEmails=false&access_token=%s" % (key, access_token)
            data = {'role': 'writer', 'type': 'anyone', 'value': '', 'withLink': True}
            try:
                req = requests.post(request_url, data=json.dumps(data), headers=headers, timeout=TIMEOUT)
                req.raise_for_status()
            except requests.HTTPError:
                raise self.env['res.config.settings'].get_config_warning(_("The permission 'reader' for 'anyone with the link' has not been written on the document"))
            if self.env.user.email:
                data = {'role': 'writer', 'type': 'user', 'value': self.env.user.email}
                try:
                    requests.post(request_url, data=json.dumps(data), headers=headers, timeout=TIMEOUT)
                except requests.HTTPError:
                    pass
        return res

    @api.model
    def get_google_drive_config(self, res_model, res_id):
        '''
        Function called by the js, when no google doc are yet associated with a record, with the aim to create one. It
        will first seek for a google.docs.config associated with the model `res_model` to find out what's the template
        of google doc to copy (this is usefull if you want to start with a non-empty document, a type or a name
        different than the default values). If no config is associated with the `res_model`, then a blank text document
        with a default name is created.
          :param res_model: the object for which the google doc is created
          :param ids: the list of ids of the objects for which the google doc is created. This list is supposed to have
            a length of 1 element only (batch processing is not supported in the code, though nothing really prevent it)
          :return: the config id and config name
        '''
        # TO DO in master: fix my signature and my model
        if isinstance(res_model, str):
            res_model = self.env['ir.model'].search([('model', '=', res_model)]).id
        if not res_id:
            raise UserError(_("Creating google drive may only be done by one at a time."))
        # check if a model is configured with a template
        configs = self.search([('model_id', '=', res_model)])
        config_values = []
        for config in configs.sudo():
            if config.filter_id:
                if config.filter_id.user_id and config.filter_id.user_id.id != self.env.user.id:
                    #Private
                    continue
                try:
                    domain = [('id', 'in', [res_id])] + ast.literal_eval(config.filter_id.domain)
                except:
                    raise UserError(_("The document filter must not include any 'dynamic' part, so it should not be based on the current time or current user, for example."))
                additionnal_context = ast.literal_eval(config.filter_id.context)
                google_doc_configs = self.env[config.filter_id.model_id].with_context(**additionnal_context).search(domain)
                if google_doc_configs:
                    config_values.append({'id': config.id, 'name': config.name})
            else:
                config_values.append({'id': config.id, 'name': config.name})
        return config_values

    name = fields.Char('Template Name', required=True)
    model_id = fields.Many2one('ir.model', 'Model', required=True, ondelete='cascade')
    model = fields.Char('Related Model', related='model_id.model', readonly=True)
    filter_id = fields.Many2one('ir.filters', 'Filter', domain="[('model_id', '=', model)]")
    google_drive_template_url = fields.Char('Template URL', required=True)
    google_drive_resource_id = fields.Char('Resource Id', compute='_compute_ressource_id')
    google_drive_client_id = fields.Char('Google Client', compute='_compute_client_id')
    name_template = fields.Char('Google Drive Name Pattern', default='Document %(name)s', help='Choose how the new google drive will be named, on google side. Eg. gdoc_%(field_name)s', required=True)
    active = fields.Boolean('Active', default=True)

    def _get_key_from_url(self, url):
        word = re.search("(key=|/d/)([A-Za-z0-9-_]+)", url)
        if word:
            return word.group(2)
        return None

    def _compute_ressource_id(self):
        for record in self:
            if record.google_drive_template_url:
                word = self._get_key_from_url(record.google_drive_template_url)
                if word:
                    record.google_drive_resource_id = word
                else:
                    raise UserError(_("Please enter a valid Google Document URL."))
            else:
                record.google_drive_resource_id = False

    def _compute_client_id(self):
        google_drive_client_id = self.env['ir.config_parameter'].sudo().get_param('google_drive_client_id')
        for record in self:
            record.google_drive_client_id = google_drive_client_id

    @api.onchange('model_id')
    def _onchange_model_id(self):
        if self.model_id:
            self.model = self.model_id.model
        else:
            self.filter_id = False
            self.model = False

    @api.constrains('model_id', 'filter_id')
    def _check_model_id(self):
        if self.filter_id and self.model_id.model != self.filter_id.model_id:
            return False
        if self.model_id.model and self.filter_id:
            # force an execution of the filter to verify compatibility
            self.get_google_drive_config(self.model_id.model, 1)
        return True

    def get_google_scope(self):
        return 'https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/drive.file'