2018-01-16 06:58:15 +01:00
# -*- coding: utf-8 -*-
2018-01-16 11:34:37 +01:00
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
2018-01-16 06:58:15 +01:00
import logging
import json
import re
import requests
import werkzeug . urls
2018-01-16 11:34:37 +01:00
from flectra import api , fields , models
from flectra . exceptions import RedirectWarning , UserError
from flectra . tools . safe_eval import safe_eval
from flectra . tools . translate import _
2018-01-16 06:58:15 +01:00
2018-01-16 11:34:37 +01:00
from flectra . addons . google_account . models . google_service import GOOGLE_TOKEN_ENDPOINT , TIMEOUT
2018-01-16 06:58:15 +01:00
_logger = logging . getLogger ( __name__ )
class GoogleDrive ( models . Model ) :
_name = ' google.drive.config '
_description = " Google Drive templates config "
@api.multi
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 [ ' res.users ' ] . browse ( self . env . user . id ) . _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 = _ ( " You haven ' t configured ' Authorization Code ' generated from google, Please generate and configure it . " )
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. " ) )
2018-01-16 11:34:37 +01:00
record_url = " Click on link to open Record in Flectra \n %s /?db= %s #id= %s &model= %s " % ( google_web_base_url , self . _cr . dbname , res_id , res_model )
2018-01-16 06:58:15 +01:00
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 ' ] )
2018-01-16 11:34:37 +01:00
request_url = " https://www.googleapis.com/drive/v2/files/ %s /permissions?emailMessage=This+is+a+drive+file+created+by+Flectra&sendNotificationEmails=false&access_token= %s " % ( key , access_token )
2018-01-16 06:58:15 +01:00
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
'''
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
domain = [ ( ' id ' , ' in ' , [ res_id ] ) ] + safe_eval ( config . filter_id . domain )
additionnal_context = safe_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 ' , ondelete = ' set null ' , required = True )
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
@api.multi
def _compute_ressource_id ( self ) :
result = { }
for record in self :
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. " ) )
return result
@api.multi
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
return True
def get_google_scope ( self ) :
return ' https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/drive.file '