Permalink
Browse files

Merge pull request #703 from KolushovAlexandr/12.0-af29d67045486936f1…

…ad5fe49b66e1c297f4560e

12.0 af29d67
  • Loading branch information...
yelizariev committed Dec 12, 2018
2 parents 65ab335 + f0233fa commit 0386cbcd43fea482749bda68911b0831c74869e0
@@ -35,7 +35,7 @@
<field name="users" eval="[(4, ref('base.user_root'))]"/>
</record>

<record id="base.default_user" model="res.users">
<record id="base.user_admin" model="res.users">
<field name="groups_id" eval="[(4,ref('base_attendance.group_hr_attendance_manager'))]"/>
</record>

@@ -30,16 +30,23 @@ odoo.define('base_attendance.tour', function (require) {
}

var steps = [{
trigger: 'a.oe_menu_toggler:contains("Attendance")',
trigger: 'a.full[href="#"]',
content: _t("Click to open app list"),
position: 'bottom',
}, {
trigger: 'a.dropdown-item.o_app:contains("Attendance")',
content: _t("Click to enter menu attendances"),
position: 'bottom',
}, {
trigger: 'a.oe_menu_leaf:contains("Kiosk")',
trigger: 'a.dropdown-toggle.o-no-caret.o_menu_header_lvl_1:contains("Attendance")',
content: _t("Click to open Manage Attendances menu"),
}, {
trigger: 'a.dropdown-item.o_menu_entry_lvl_2:contains("Kiosk")',
content: _t("Click to enter Kiosk"),
}];

steps = steps.concat(partner_check_in_out("Laith Jubair", 'red'));
steps = steps.concat(partner_check_in_out("Laith Jubair", 'green'));
steps = steps.concat(partner_check_in_out("Brandon Freeman", 'red'));
steps = steps.concat(partner_check_in_out("Brandon Freeman", 'green'));

tour.register('test_kiosk_tour', { test: true, url: '/web' }, steps);
});
@@ -1,2 +1,3 @@

from . import models
from . import controllers
@@ -3,7 +3,7 @@
"summary": """Upload attachments on Amazon S3""",
"category": "Tools",
"images": [],
"version": "11.0.1.1.2",
"version": "11.0.1.2.0",
"application": False,

"author": "IT-Projects LLC, Ildar Nasyrov",
@@ -18,6 +18,7 @@
],
"external_dependencies": {"python": ['boto3'], "bin": []},
"data": [
"security/ir.model.access.csv",
"views/res_config_settings_views.xml",
],
"qweb": [
@@ -0,0 +1 @@
from . import main
@@ -0,0 +1,88 @@
# -*- coding: utf-8 -*-
# Copyright 2018 Ivan Yelizariev <https://it-projects.info/team/yelizariev>
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
import logging
import werkzeug

import odoo
from odoo.http import request, route
from odoo.addons.web.controllers.main import Binary
# TODO some code can be part of ir_attachment_url

_logger = logging.getLogger(__name__)


class BinaryExtended(Binary):

def redirect_to_url(self, url):
return werkzeug.utils.redirect(url, code=301)

@route()
def content_image(self, xmlid=None, model='ir.attachment', id=None, field='datas', filename_field='datas_fname', unique=None, filename=None, mimetype=None, download=None, width=0, height=0):

res = super(BinaryExtended, self).content_image(xmlid, model, id, field, filename_field, unique, filename, mimetype, download, width, height)

if not (res.status_code == 301 and (width or height)):
return res

# * check that it's image on s3
# * upload resized image if needed
# * return url to resized image

# FIND ATTACHMENT. The code is copy-pasted from binary_content method
env = request.env
# get object and content
obj = None
if xmlid:
obj = env.ref(xmlid, False)
elif id and model in env.registry:
obj = env[model].browse(int(id))

attachment = None
if model == 'ir.attachment':
attachment = obj
else:
attachment = env['ir.http'].find_field_attachment(env, model, field, obj)

if not attachment:
# imposible case?
_logger.error('Attachment is not found')
return res

# FIX SIZES
height = int(height or 0)
width = int(width or 0)
# resize maximum 500*500
if width > 500:
width = 500
if height > 500:
height = 500

# CHECK FOR CACHE.
# We may already uploaded that resized image
cache = env['ir.attachment.resized'].sudo().search([
('attachment_id', '=', attachment.id),
('width', '=', width),
('height', '=', height),
])
if cache:
url = cache.resized_attachment_id.url
return self.redirect_to_url(url)

# PREPARE CACHE
content = attachment.datas
content = odoo.tools.image_resize_image(base64_source=content, size=(width or None, height or None), encoding='base64', filetype='PNG')
resized_attachment = env['ir.attachment'].with_context(force_s3=True).create({
'name': '%sx%s %s' % (width, height, attachment.name),
'datas': content,
})

env['ir.attachment.resized'].sudo().create({
'attachment_id': attachment.id,
'width': width,
'height': height,
'resized_attachment_id': resized_attachment.id,
})

url = resized_attachment.url
return self.redirect_to_url(url)
@@ -1,3 +1,8 @@
`1.2.0`
-------

- **Improvement:** Save resized image to s3 instead of passing original (big) image

`1.1.2`
-------

@@ -5,14 +5,42 @@
Installation
============

* `Install <https://odoo-development.readthedocs.io/en/latest/odoo/usage/install-module.html>`__ this module in a usual way
* `Using this quickstart instruction <https://boto3.readthedocs.io/en/latest/guide/quickstart.html>`__ install boto3 library and get credentials for it
* `Using this instruction <http://mikeferrier.com/2011/10/27/granting-access-to-a-single-s3-bucket-using-amazon-iam>`__ grant access to your s3 bucket
* Set your S3 bucket as public
* Optionaly, add following parameter to prevent heavy logs from boto3 library:

--log-handler=boto3.resources.action:WARNING


IAM
---

Minimal access policy for s3 credentials are as following::

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:CreateBucket",
"s3:GetBucketLocation",
"s3:PutObjectAcl"
],
"Resource": [
"arn:aws:s3:::YOUBUCKETNAMEHERE",
"arn:aws:s3:::YOUBUCKETNAMEHERE/*"
]
}
]
}
You can also remove ``"s3:CreateBucket"`` if bucket already exists.

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

@@ -1,9 +1,12 @@
# -*- coding: utf-8 -*-
# Copyright 2016-2018 Ildar Nasyrov <https://it-projects.info/team/iledarn>
# Copyright 2016-2018 Ivan Yelizariev <https://it-projects.info/team/yelizariev>
import base64
import os
import hashlib
import logging

from odoo import api, models, _
from odoo import api, models, _, fields
from odoo.tools.safe_eval import safe_eval

_logger = logging.getLogger(__name__)
@@ -15,9 +18,21 @@
found on your installation')


class IrAttachmentResized(models.Model):
_name = 'ir.attachment.resized'
_description = 'Url to resized image'

attachment_id = fields.Many2one('ir.attachment')
width = fields.Integer()
height = fields.Integer()
resized_attachment_id = fields.Many2one('ir.attachment', ondelete='cascade')


class IrAttachment(models.Model):
_inherit = 'ir.attachment'

resized_ids = fields.One2many('ir.attachment.resized', 'attachment_id')

def _get_s3_settings(self, param_name, os_var_name):
config_obj = self.env['ir.config_parameter']
res = config_obj.sudo().get_param(param_name)
@@ -61,11 +76,12 @@ def _get_s3_resource(self):

def _inverse_datas(self):
condition = self._get_s3_settings('s3.condition', 'S3_CONDITION')
if condition:
if condition and not self.env.context.get('force_s3'):
condition = safe_eval(condition, mode="eval")
s3_records = self.sudo().search([('id', 'in', self.ids)] + condition)
else:
# if there is no condition then store all attachments on s3
# if there is no condition or force_s3 in context
# then store all attachments on s3
s3_records = self

if s3_records:
@@ -77,7 +93,9 @@ def _inverse_datas(self):
s3_records = s3_records._filter_protected_attachments()
s3_records = s3_records.filtered(lambda r: r.type != 'url')

resized_to_remove = self.env['ir.attachment.resized'].sudo()
for attach in self & s3_records: # datas field has got empty somehow in the result of ``s3_records = self.sudo().search([('id', 'in', self.ids)] + condition)`` search for non-superusers but it is in original recordset. Here we use original (with datas) in case it intersects with the search result
resized_to_remove |= attach.sudo().resized_ids
value = attach.datas
bin_data = base64.b64decode(value) if value else b''
fname = hashlib.sha1(bin_data).hexdigest()
@@ -101,4 +119,6 @@ def _inverse_datas(self):
}
super(IrAttachment, attach.sudo()).write(vals)

resized_to_remove.mapped('resized_attachment_id').unlink()
resized_to_remove.unlink()
super(IrAttachment, self - s3_records)._inverse_datas()
@@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_ir_attachment_resized,access_ir_attachment_resized,model_ir_attachment_resized,base.group_user,1,0,0,0
@@ -3,7 +3,7 @@
"summary": """Use attachment URL and upload data to external storage""",
"category": "Tools",
"images": [],
"version": "12.0.1.1.6",
"version": "12.0.1.1.7",
"application": False,

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

- **Fix:** Product Variant were downloaded on server instead of passing url

`1.1.6`
-------

@@ -1,3 +1,7 @@
# Copyright 2016-2018 Ildar Nasyrov <https://it-projects.info/team/iledarn>
# Copyright 2017 Dinar Gabbasov <https://it-projects.info/team/GabbasovDinar>
# Copyright 2016-2018 Ivan Yelizariev <https://it-projects.info/team/yelizariev>
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
import mimetypes
import base64
import hashlib
@@ -15,6 +19,33 @@
class IrHttp(models.AbstractModel):
_inherit = 'ir.http'

@classmethod
def _find_field_attachment(cls, env, m, f, id):
domain = [
('res_model', '=', m),
('res_field', '=', f),
('res_id', '=', id),
('type', '=', 'url'),
]
return env['ir.attachment'].sudo().search(domain)

@classmethod
def find_field_attachment(cls, env, model, field, obj):
is_attachment = env[model]._fields[field].attachment
is_product = model == 'product.product' and field.startswith('image')
if not (is_attachment or is_product):
return None

att = cls._find_field_attachment(env, model, field, obj.id)

if not att and model == 'product.product':
# Special case. Product.product's image are computed and
# use product.template's image in most cases. But due to
# this computation odoo pass binary data (by downloading it
# from s3) instead of url. So, make a workaround for it
att = cls._find_field_attachment(env, 'product.template', field, obj.product_tmpl_id.id)
return att

@classmethod
def binary_content(cls, xmlid=None, model='ir.attachment', id=None, field='datas',
unique=False, filename=None, filename_field='datas_fname', download=False,
@@ -83,8 +114,7 @@ def binary_content(cls, xmlid=None, model='ir.attachment', id=None, field='datas
if module_resource_path.startswith(module_path):
with open(module_resource_path, 'rb') as f:
content = base64.b64encode(f.read())
# lint error fix (unused variable)
# last_update = pycompat.text_type(os.path.getmtime(module_resource_path))
# 'last_update' variable removed for lint error fix

if not module_resource_path:
module_resource_path = obj.url
@@ -93,19 +123,12 @@ def binary_content(cls, xmlid=None, model='ir.attachment', id=None, field='datas
status = 301
content = module_resource_path
else:
# begin redefined part of original binary_content of odoo/base/addons/models/ir_http
is_attachment = env[model]._fields[field].attachment
if is_attachment:
domain = [
('res_model', '=', model),
('res_field', '=', field),
('res_id', '=', obj.id),
('type', '=', 'url'),
]
att = env['ir.attachment'].sudo().search(domain)
if att:
content = att.url
status = 301
# begin redefined part of original binary_content of odoo/base/addons/ir/ir_http
att = env['ir.http'].find_field_attachment(env, model, field, obj)
if att:
content = att.url
status = 301

if not content:
content = obj[field] or ''
# end redefined part of original binary_content
@@ -3,7 +3,7 @@
"summary": """Open attached images in popup""",
"category": "Web",
"images": ['images/screenshot-1.png'],
"version": "11.0.1.0.0",
"version": "11.0.1.0.1",
"application": False,

"author": "IT-Projects LLC, Dinar Gabbasov",
@@ -1,4 +1,9 @@
`1.0.1`
-------

**Fix:** Incorrect opening of the popup when using multiple binary fields in the model

`1.0.0`
-------

- Init version
**Init version**
Oops, something went wrong.

0 comments on commit 0386cbc

Please sign in to comment.