Browse files

[FEAT] ir_attachment_s3: Added support for Minio

  • Loading branch information...
mlaitinen committed Dec 28, 2018
1 parent d1ce509 commit 685068f8391a843d461937a27640f21de6ede52f
@@ -5,13 +5,15 @@
* The module allows to upload the attachments in Amazon S3 automatically without storing them in Odoo database. It will allow to reduce the load on your server. Attachments will be uploaded on S3 depending on the condition you specified in Odoo settings. So you can choose and manage which type of attachments should be uploaded on S3.
* It is useful in cases where your database was crashed, because you will be able to easily restore all attachments from external storage at any time.
* The possibility to use one external storage for any number of databases.
* Minio can also be used instead of Amazon S3


* Ildar Nasyrov <>
* Miku Laitinen <>

@@ -3,7 +3,7 @@
"summary": """Upload attachments on Amazon S3""",
"category": "Tools",
"images": [],
"version": "",
"version": "",
"application": False,

"author": "IT-Projects LLC, Ildar Nasyrov",
@@ -1,3 +1,8 @@

- **NEW:** Allow using Minio instead of Amazon S3


@@ -62,6 +62,8 @@ Configuration
* ``s3.condition``: only the attachments that meet the condition will be sent to s3 (e.g. ``[('res_model', 'in', ['product.image'])]``) - it is actually the way of specifying the models with ``fields.Binary`` fields that should be stored on s3 instead of local file storage or db. Don't specify anything if you want to store all your attachment data from ``fields.Binary`` and also ordinary attachments on s3.
* ``s3.access_key_id``: S3 access key ID
* ``s3.secret_key``: S3 secret access key
* ``s3.minio``: true if the files are saved to Minio instead of S3, false otherwise
* ``s3.url``: Minio URL

The settings are also available from the ``Settings >> Technical >> Database Structure >> S3 Settings``.

@@ -6,6 +6,7 @@
import hashlib
import logging

from botocore.config import Config
from odoo import api, models, _, fields
from import safe_eval

@@ -45,11 +46,20 @@ def _get_s3_settings(self, param_name, os_var_name):

def _get_s3_object_url(self, s3, s3_bucket_name, key_name):
bucket_location = s3.meta.client.get_bucket_location(Bucket=s3_bucket_name)
location_constraint = bucket_location.get('LocationConstraint')
domain_part = 's3' + '-' + location_constraint if location_constraint else 's3'
object_url = "https://{0}{1}/{2}".format(

is_minio = self._get_s3_settings('s3.minio', 'S3_MINIO')
url = self._get_s3_settings('s3.url', 'S3_URL')

if is_minio and url:
url_start = url
bucket_location = s3.meta.client.get_bucket_location(Bucket=s3_bucket_name)
location_constraint = bucket_location.get('LocationConstraint')
domain_part = 's3' + '-' + location_constraint if location_constraint else 's3'
url_start = "https://{0}".format(domain_part)

object_url = "{0}/{1}/{2}".format(
return object_url
@@ -59,6 +69,14 @@ def _get_s3_resource(self):
access_key_id = self._get_s3_settings('s3.access_key_id', 'S3_ACCESS_KEY_ID')
secret_key = self._get_s3_settings('s3.secret_key', 'S3_SECRET_KEY')
bucket_name = self._get_s3_settings('s3.bucket', 'S3_BUCKET')
is_minio = self._get_s3_settings('s3.minio', 'S3_MINIO')
if is_minio:
url = self._get_s3_settings('s3.url', 'S3_URL')
if not url:
raise UserWarning(_('The URL is mandatory when Minio is used'))
config = Config(signature_version='s3v4')
url = config = None

if not access_key_id or not secret_key or not bucket_name:'Amazon S3 credentials are not defined properly. Attachments won\'t be saved on S3.'))
@@ -68,6 +86,8 @@ def _get_s3_resource(self):
bucket = s3.Bucket(bucket_name)
if not bucket:
@@ -15,6 +15,9 @@ class ResConfigSettings(models.TransientModel):
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""")
s3_minio = fields.Boolean(string='Use Minio')
s3_url = fields.Char(string='Minio URL',
help="Minio URL with scheme prefix (https://, http://)")

def get_values(self):
@@ -24,12 +27,16 @@ def get_values(self):
s3_access_key_id = ICPSudo.get_param("s3.access_key_id", default='')
s3_secret_key = ICPSudo.get_param("s3.secret_key", default='')
s3_condition = ICPSudo.get_param("s3.condition", default='')
s3_minio = ICPSudo.get_param("s3.minio", default=False)
s3_url = ICPSudo.get_param("s3.url", default='')

return res

@@ -41,6 +48,8 @@ def set_values(self):
ICPSudo.set_param("s3.access_key_id", self.s3_access_key_id or '')
ICPSudo.set_param("s3.secret_key", self.s3_secret_key or '')
ICPSudo.set_param("s3.condition", self.s3_condition or '')
ICPSudo.set_param("s3.minio", self.s3_url or False)
ICPSudo.set_param("s3.url", self.s3_url or '')

def upload_existing(self):
condition = self.s3_condition and safe_eval(self.s3_condition, mode="eval") or []
@@ -70,7 +79,9 @@ def upload_existing(self):
except Exception as e:
raise exceptions.UserError(e.message)
raise exceptions.UserError(e.message
if hasattr(e, 'message')
else str(e))

vals = {
'file_size': len(bin_data),
@@ -36,6 +36,18 @@
<field name="s3_condition" class="oe_inline"/>
<div class="content-group">
<div class="mt16 row">
<label for="s3_minio" string="Use Minio?" class="col-xs-3 col-md-3 o_light_label"/>
<field name="s3_minio" class="oe_inline"/>
<div class="content-group" attrs="{'invisible':[('s3_minio', '=', False)]}">
<div class="mt16 row">
<label for="s3_url" string="Minio URL" class="col-xs-3 col-md-3 o_light_label"/>
<field name="s3_url" class="oe_inline" attrs="{'required':[('s3_minio', '!=', False)]}"/>
<div class="content-group">
<div class="mt16">
<button name="upload_existing" type="object" string="Upload existing attachments"/>

0 comments on commit 685068f

Please sign in to comment.