Skip to content

Commit

Permalink
🎉 ir_attachment_google_drive
Browse files Browse the repository at this point in the history
  • Loading branch information
em230418 committed Mar 11, 2020
1 parent 15b7e93 commit 8ffa07b
Show file tree
Hide file tree
Showing 10 changed files with 363 additions and 0 deletions.
51 changes: 51 additions & 0 deletions ir_attachment_google_drive/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
.. image:: https://img.shields.io/badge/license-MIT-blue.svg
:target: https://opensource.org/licenses/MIT
:alt: License: MIT

=================================
Google Drive Attachment Storage
=================================

TODO description intro

TODO detailed description

Credits
=======

Contributors
------------
* `Eugene Molotov <https://it-projects.info/team/em230418>`__:

* :one::zero: init version of the module

Sponsors
--------
* `IT-Projects LLC <https://it-projects.info>`__

Maintainers
-----------
* `IT-Projects LLC <https://it-projects.info>`__

To get a guaranteed support
you are kindly requested to purchase the module
at `odoo apps store <https://apps.odoo.com/apps/modules/11.0/ir_attachment_google_drive/>`__.

Thank you for understanding!

`IT-Projects Team <https://www.it-projects.info/team>`__

Further information
===================

Demo: http://runbot.it-projects.info/demo/misc-addons/11.0

HTML Description: https://apps.odoo.com/apps/modules/11.0/ir_attachment_google_drive/

Usage instructions: `<doc/index.rst>`_

Changelog: `<doc/changelog.rst>`_

Notifications on updates: `via Atom <https://github.com/it-projects-llc/misc-addons/commits/11.0/ir_attachment_google_drive.atom>`_, `by Email <https://blogtrottr.com/?subscribe=https://github.com/it-projects-llc/misc-addons/commits/11.0/ir_attachment_google_drive.atom>`_

Tested on Odoo 11.0 a827d3015c6994bc3c779f9ba5cd270d8bdd8edd
3 changes: 3 additions & 0 deletions ir_attachment_google_drive/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# License MIT (https://opensource.org/licenses/MIT).

from . import models
39 changes: 39 additions & 0 deletions ir_attachment_google_drive/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Copyright 2020 Eugene Molotov <https://it-projects.info/team/em230418>
# License MIT (https://opensource.org/licenses/MIT).

{
"name": """Google Drive Attachment Storage""",
"summary": """TODO description intro""",
"category": "Extra Tools",
# "live_test_url": "http://apps.it-projects.info/shop/product/DEMO-URL?version=12.0",
"images": [],
"version": "11.0.1.0.0",
"application": False,
"author": "IT-Projects LLC, Eugene Molotov",
"support": "apps@it-projects.info",
"website": "https://apps.odoo.com/apps/modules/11.0/ir_attachment_google_drive/",
"license": "Other OSI approved licence", # MIT
# "price": 9.00,
# "currency": "EUR",
"depends": ["base", "google_drive"],
"external_dependencies": {"python": [], "bin": []},
"data": ["views/res_config_settings_views.xml"],
"demo": [],
"qweb": [],
"post_load": None,
"pre_init_hook": None,
"post_init_hook": None,
"uninstall_hook": None,
"auto_install": False,
"installable": True,
# "demo_title": "Google Drive Attachment Storage",
# "demo_addons": [
# ],
# "demo_addons_hidden": [
# ],
# "demo_url": "DEMO-URL",
# "demo_summary": "TODO description intro",
# "demo_images": [
# "images/MAIN_IMAGE",
# ]
}
4 changes: 4 additions & 0 deletions ir_attachment_google_drive/doc/changelog.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
`1.0.0`
-------

- **Init version**
35 changes: 35 additions & 0 deletions ir_attachment_google_drive/doc/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
=================================
Google Drive Attachment Storage
=================================

Installation
============
{Instruction about things to do before actual installation}

* {OPTIONAL }`Activate longpolling <https://odoo-development.readthedocs.io/en/latest/admin/longpolling.html>`__
* {Additional notes if any}
* `Install <https://odoo-development.readthedocs.io/en/latest/odoo/usage/install-module.html>`__ this module in a usual way

Configuration
=============

{Instruction how to configure the module before start to use it}

* `Log in as SUPERUSER <https://odoo-development.readthedocs.io/en/latest/odoo/usage/login-as-superuser.html>`__
* `Activate Developer Mode <https://odoo-development.readthedocs.io/en/latest/odoo/usage/debug-mode.html>`__
* Open menu ``[[ {Menu} ]] >> {Submenu} >> {Subsubmenu}``
* Click ``[{Button Name}]``

Usage
=====

{Instruction for daily usage. It should describe how to check that module works. What shall user do and what would user get.}

* Open menu ``[[ {Menu} ]]>> {Submenu} >> {Subsubmenu}``
* Click ``[{Button Name}]``
* RESULT: {what user gets, how the modules changes default behaviour}

Uninstallation
==============

{Optional section for uninstallation notes. Delete it if you don't have notes for uninstallation.}
4 changes: 4 additions & 0 deletions ir_attachment_google_drive/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)

from . import ir_attachment
from . import res_config_settings
172 changes: 172 additions & 0 deletions ir_attachment_google_drive/models/ir_attachment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# Copyright 2020 Eugene Molotov <https://it-projects.info/team/em230418>
# License MIT (https://opensource.org/licenses/MIT).

import base64
import json
import logging

import requests
from urllib3.fields import RequestField
from urllib3.filepost import choose_boundary, encode_multipart_formdata

from odoo import api, models
from odoo.tools import human_size
from odoo.tools.safe_eval import safe_eval

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

_logger = logging.getLogger(__name__)

PREFIX = "google_drive://"


# https://stackoverflow.com/a/47682897
def encode_multipart_related(fields, boundary=None):
if boundary is None:
boundary = choose_boundary()

body, _ = encode_multipart_formdata(fields, boundary)
content_type = str("multipart/related; boundary=%s" % boundary)

return body, content_type


def encode_media_related(metadata, media, media_content_type):
rf1 = RequestField(
name="placeholder",
data=json.dumps(metadata),
headers={"Content-Type": "application/json; charset=UTF-8"},
)
rf2 = RequestField(
name="placeholder2", data=media, headers={"Content-Type": media_content_type},
)
return encode_multipart_related([rf1, rf2])


class IrAttachment(models.Model):

_inherit = "ir.attachment"

def _get_google_drive_auth_header(self):
access_token = self.env.context.get("google_drive_access_token")
if not access_token:
access_token = self.env["google.drive.config"].get_access_token()
return {"Authorization": "Bearer " + access_token}

# это нагло скопировано с ir_attachment_url
@api.multi
def _filter_protected_attachments(self):
return self.filtered(
lambda r: r.res_model not in ["ir.ui.view", "ir.ui.menu"]
or not r.name.startswith("/web/content/")
or not r.name.startswith("/web/static/")
)

def _inverse_datas(self):
condition = (
self.env["ir.config_parameter"]
.sudo()
.get_param("google_drive.attachment_condition")
)

if condition:
condition = safe_eval(condition, mode="eval")
our_records = self.sudo().search([("id", "in", self.ids)] + condition)
else:
our_records = self

our_records = our_records._filter_protected_attachments()

if our_records:
# make sure, you can use google drive
# otherwise - use default behaviour
try:
google_drive_access_token = self.env[
"google.drive.config"
].get_access_token()
except Exception:
_logger.exception(
"Google Drive is not configured properly. Keeping attachments as usual"
)
return super(IrAttachment, self)._inverse_datas()

self = self.with_context(
google_drive_access_token=google_drive_access_token
)

for attach in our_records:
bin_value = base64.b64decode(attach.datas)
fname = self._file_write_google_drive(bin_value, attach.datas_fname)
vals = {
"file_size": len(bin_value),
"checksum": self._compute_checksum(bin_value),
"index_content": self._index(
bin_value, attach.datas_fname, attach.mimetype
),
"store_fname": fname,
"db_datas": False,
"type": "binary",
"datas_fname": attach.datas_fname,
}
super(IrAttachment, attach.sudo()).write(vals)

return super(IrAttachment, self - our_records)._inverse_datas()

def _file_read(self, fname, bin_size=False):
if not fname.startswith(PREFIX):
return super(IrAttachment, self)._file_read(fname, bin_size)

file_id = fname[len(PREFIX) :]
_logger.debug("reading file with id {}".format(file_id))

if bin_size:
request_url = "https://www.googleapis.com/drive/v2/files/{}".format(file_id)
else:
request_url = "https://www.googleapis.com/drive/v2/files/{}?alt=media".format(
file_id
)

r = requests.get(
request_url, headers=self._get_google_drive_auth_header(), timeout=TIMEOUT
)
r.raise_for_status()

if bin_size:
return human_size(int(r.json()["fileSize"]))
else:
return base64.b64encode(r.content)

def _file_write_google_drive(self, bin_value, original_filename):
metadata = {}
if original_filename:
metadata["title"] = original_filename

data, content_type = encode_media_related(
metadata, bin_value, "application/octet-stream"
)

headers = self._get_google_drive_auth_header()
headers["Content-Type"] = content_type

r = requests.post(
"https://www.googleapis.com/upload/drive/v2/files?uploadType=multipart",
headers=headers,
data=data,
)
r.raise_for_status()
file_id = r.json()["id"]
_logger.debug("uploaded file with id {}".format(file_id))
return PREFIX + file_id

def _file_delete(self, fname):
if not fname.startswith(PREFIX):
return super(IrAttachment, self)._file_delete(fname)

file_id = fname[len(PREFIX) :]

r = requests.delete(
"https://www.googleapis.com/drive/v2/files/{}".format(file_id),
headers=self._get_google_drive_auth_header(),
timeout=TIMEOUT,
)
r.raise_for_status()
37 changes: 37 additions & 0 deletions ir_attachment_google_drive/models/res_config_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Copyright 2020 Eugene Molotov <https://it-projects.info/team/em230418>
# License MIT (https://opensource.org/licenses/MIT).

from odoo import api, fields, models


class ResConfigSettings(models.TransientModel):

_inherit = "res.config.settings"

google_drive_attachment_condition = fields.Char(
string="Google Drive Condition",
help="""Specify valid odoo search domain here,
e.g. [('res_model', 'in', ['product.image'])] -- store data of product.image only.
Empty condition means all models""",
config_parameter="google_drive.attachment_condition",
)

@api.model
def get_values(self):
res = super(ResConfigSettings, self).get_values()
icp = self.env["ir.config_parameter"].sudo()

res.update(
google_drive_attachment_condition=icp.get_param(
"google_drive.attachment_condition", ""
),
)
return res

def set_values(self):
super(ResConfigSettings, self).set_values()
icp = self.env["ir.config_parameter"].sudo()
icp.set_param(
"google_drive.attachment_condition",
self.google_drive_attachment_condition or "",
)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 18 additions & 0 deletions ir_attachment_google_drive/views/res_config_settings_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright 2020 Eugene Molotov <https://it-projects.info/team/em230418>
License MIT (https://opensource.org/licenses/MIT). -->
<odoo>
<record id="res_config_settings_view_form" model="ir.ui.view">
<field name="name">res.config.settings.view.form.inherit.google.drive</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="google_drive.res_config_settings_view_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='google_drive_uri']/../../.." position="inside">
<div class="mt16 row">
<label for="google_drive_attachment_condition" class="col-md-3 o_light_label"/>
<field name="google_drive_attachment_condition"/>
</div>
</xpath>
</field>
</record>
</odoo>

0 comments on commit 8ffa07b

Please sign in to comment.