[IMP] base: add default image for users + placeholder if unassigned
1/ When the new database is created without demo data, the admin has a 'silhouette' as a default picture. When a new user is created without picture given by the current user, the new user will have a 'silhouette' as a default profile picture. 2/ web: image for fa-user-slash. This image will be used when a record is unassigned. 3/ web, *: Change placeholder by default when record is unassigned. We want to have a fa-user-slash icon when a record is unassigned instead of 'placeholder.png'. A method is created in the BaseModel to have a generic method to change easily the placeholder for other models. 4/ Adapt kanban test to keep the same behaviour. Attention the behaviour is a bit different. Because, now the default image is given by the server to change easily the default image when a record doesn't have an image. Thus, we don't say if it's the default placeholder, but we can say it's not the same image of the record (in this test, the record, it's the partner). 5/ misc: display 'Unassigned' in the hover on kanban cards if record is unassigned closes odoo/odoo#41356 Taskid: 2060206 Related: odoo/enterprise#7758 Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com> Co-authored-by: jdoutreloux <jud@odoo.com> Co-authored-by: Yannick Tivisse <yti@odoo.com>
This commit is contained in:
committed by
Yannick Tivisse
co-authored by
jdoutreloux
Yannick Tivisse
parent
be01db2f71
commit
51b36c3dbb
@@ -59,7 +59,8 @@
|
||||
<div t-attf-class="oe_kanban_card oe_kanban_global_click">
|
||||
<div>
|
||||
<strong><span><field name="analytic_id"/></span></strong>
|
||||
<img t-att-src="kanban_image('res.users', 'image_128', record.user_id.raw_value)" t-att-title="record.user_id.value" t-att-alt="record.user_id.value" class="oe_kanban_avatar o_image_24_cover float-right"/>
|
||||
<t t-set="unassigned"><t t-esc="_t('Unassigned')"/></t>
|
||||
<img t-att-src="kanban_image('res.users', 'image_128', record.user_id.raw_value)" t-att-title="record.user_id.value || unassigned" t-att-alt="record.user_id.value" class="oe_kanban_avatar o_image_24_cover float-right"/>
|
||||
</div>
|
||||
<div t-if="record.date_start.value"><i class="fa fa-calendar"></i> From <field name="date_start"/> <t t-if="record.date_stop.value">to <field name="date_stop"/></t></div>
|
||||
<div t-if="record.product_id.value"><strong>Product</strong> <field name="product_id"/> </div>
|
||||
|
||||
@@ -377,7 +377,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_kanban_bottom_right">
|
||||
<img t-att-src="kanban_image('res.users', 'image_128', record.user_id.raw_value)" t-att-title="record.user_id.value" t-att-alt="record.user_id.value" class="oe_kanban_avatar float-right"/>
|
||||
<t t-set="unassigned"><t t-esc="_t('Unassigned')"/></t>
|
||||
<img t-att-src="kanban_image('res.users', 'image_128', record.user_id.raw_value)" t-att-title="record.user_id.value || unassigned" t-att-alt="record.user_id.value" class="oe_kanban_avatar float-right"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -508,7 +509,8 @@
|
||||
<field name="activity_ids" widget="kanban_activity"/>
|
||||
</div>
|
||||
<div class="oe_kanban_bottom_right">
|
||||
<img t-att-src="kanban_image('res.users', 'image_128', record.user_id.raw_value)" t-att-title="record.user_id.value" t-att-alt="record.user_id.value" class="oe_kanban_avatar"/>
|
||||
<t t-set="unassigned"><t t-esc="_t('Unassigned')"/></t>
|
||||
<img t-att-src="kanban_image('res.users', 'image_128', record.user_id.raw_value)" t-att-title="record.user_id.value || unassigned" t-att-alt="record.user_id.value" class="oe_kanban_avatar"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -329,7 +329,8 @@
|
||||
<field name="legend_blocked" invisible="1"/>
|
||||
<field name="legend_done" invisible="1"/>
|
||||
</div>
|
||||
<img t-att-src="kanban_image('res.users', 'image_128', record.user_id.raw_value)" t-att-title="record.user_id.value" t-att-alt="record.user_id.value" class="oe_kanban_avatar"/>
|
||||
<t t-set="unassigned"><t t-esc="_t('Unassigned')"/></t>
|
||||
<img t-att-src="kanban_image('res.users', 'image_128', record.user_id.raw_value)" t-att-title="record.user_id.value || unassigned" t-att-alt="record.user_id.value" class="oe_kanban_avatar"/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -164,7 +164,8 @@
|
||||
</div>
|
||||
<div class="oe_kanban_bottom_right">
|
||||
<field name="kanban_state" widget="state_selection"/>
|
||||
<img t-att-src="kanban_image('res.users', 'image_128', record.user_id.raw_value)" t-att-title="record.user_id.value" t-att-alt="record.user_id.value" class="oe_kanban_avatar float-right"/>
|
||||
<t t-set="unassigned"><t t-esc="_t('Unassigned')"/></t>
|
||||
<img t-att-src="kanban_image('res.users', 'image_128', record.user_id.raw_value)" t-att-title="record.user_id.value || unassigned" t-att-alt="record.user_id.value" class="oe_kanban_avatar float-right"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -351,7 +351,8 @@
|
||||
<field name='mailing_model_id' attrs="{'invisible': [('mailing_model_name','=','mailing.list')]}"/>
|
||||
<span attrs="{'invisible': [('mailing_model_name','!=','mailing.list')]}">Mailing Contact</span>
|
||||
</span>
|
||||
<img t-att-src="kanban_image('res.users', 'image_128', record.user_id.raw_value)" t-att-title="record.user_id.value" t-att-alt="record.user_id.value" class="o_image_24_cover oe_kanban_avatar float-right"/>
|
||||
<t t-set="unassigned"><t t-esc="_t('Unassigned')"/></t>
|
||||
<img t-att-src="kanban_image('res.users', 'image_128', record.user_id.raw_value)" t-att-title="record.user_id.value || unassigned" t-att-alt="record.user_id.value" class="o_image_24_cover oe_kanban_avatar float-right"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
@@ -256,7 +256,8 @@
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<div class="oe_kanban_bottom_right">
|
||||
<img t-att-src="kanban_image('res.users', 'image_128', record.user_id.raw_value)" t-att-title="record.user_id.value" t-att-alt="record.user_id.value" class="oe_kanban_avatar o_image_24_cover float-right"/>
|
||||
<t t-set="unassigned"><t t-esc="_t('Unassigned')"/></t>
|
||||
<img t-att-src="kanban_image('res.users', 'image_128', record.user_id.raw_value)" t-att-title="record.user_id.value || unassigned" t-att-alt="record.user_id.value" class="oe_kanban_avatar o_image_24_cover float-right"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -660,7 +661,8 @@
|
||||
</div>
|
||||
<div class="oe_kanban_bottom_right" t-if="!selection_mode">
|
||||
<field name="kanban_state" widget="state_selection" groups="base.group_user" invisible="context.get('fsm_mode', False)"/>
|
||||
<img t-att-src="kanban_image('res.users', 'image_128', record.user_id.raw_value)" t-att-title="record.user_id.value" t-att-alt="record.user_id.value" class="oe_kanban_avatar"/>
|
||||
<t t-set="unassigned"><t t-esc="_t('Unassigned')"/></t>
|
||||
<img t-att-src="kanban_image('res.users', 'image_128', record.user_id.raw_value)" t-att-title="record.user_id.value || unassigned" t-att-alt="record.user_id.value" class="oe_kanban_avatar"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -244,7 +244,8 @@
|
||||
<field name="vendor_id"/>
|
||||
</div>
|
||||
<div class="oe_kanban_bottom_right">
|
||||
<img t-att-src="kanban_image('res.users', 'image_128', record.user_id.raw_value)" t-att-title="record.user_id.value" t-att-alt="record.user_id.value" class="oe_kanban_avatar float-right"/>
|
||||
<t t-set="unassigned"><t t-esc="_t('Unassigned')"/></t>
|
||||
<img t-att-src="kanban_image('res.users', 'image_128', record.user_id.raw_value)" t-att-title="record.user_id.value || unassigned" t-att-alt="record.user_id.value" class="oe_kanban_avatar float-right"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -78,7 +78,8 @@
|
||||
<div class="o_kanban_record_bottom">
|
||||
<div class="oe_kanban_bottom_left"/>
|
||||
<div class="oe_kanban_bottom_right">
|
||||
<img t-att-src="kanban_image('res.users', 'image_128', record.user_id.raw_value)" t-att-title="record.user_id.value" t-att-alt="record.user_id.value" class="oe_kanban_avatar"/>
|
||||
<t t-set="unassigned"><t t-esc="_t('Unassigned')"/></t>
|
||||
<img t-att-src="kanban_image('res.users', 'image_128', record.user_id.raw_value)" t-att-title="record.user_id.value || unassigned" t-att-alt="record.user_id.value" class="oe_kanban_avatar"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1367,7 +1367,8 @@ class Binary(http.Controller):
|
||||
|
||||
@staticmethod
|
||||
def placeholder(image='placeholder.png'):
|
||||
with tools.file_open(get_resource_path('web', 'static/src/img', image), 'rb') as fd:
|
||||
image_path = image.lstrip('/').split('/') if '/' in image else ['web', 'static', 'src', 'img', image]
|
||||
with tools.file_open(get_resource_path(*image_path), 'rb') as fd:
|
||||
return fd.read()
|
||||
|
||||
@http.route(['/web/content',
|
||||
@@ -1437,23 +1438,35 @@ class Binary(http.Controller):
|
||||
def _content_image(self, xmlid=None, model='ir.attachment', id=None, field='datas',
|
||||
filename_field='name', unique=None, filename=None, mimetype=None,
|
||||
download=None, width=0, height=0, crop=False, quality=0, access_token=None,
|
||||
placeholder='placeholder.png', **kwargs):
|
||||
placeholder=None, **kwargs):
|
||||
status, headers, image_base64 = request.env['ir.http'].binary_content(
|
||||
xmlid=xmlid, model=model, id=id, field=field, unique=unique, filename=filename,
|
||||
filename_field=filename_field, download=download, mimetype=mimetype,
|
||||
default_mimetype='image/png', access_token=access_token)
|
||||
|
||||
return Binary._content_image_get_response(status, headers, image_base64, field=field, download=download,
|
||||
width=width, height=height, crop=crop, quality=quality,
|
||||
placeholder=placeholder)
|
||||
return Binary._content_image_get_response(
|
||||
status, headers, image_base64, model=model, id=id, field=field, download=download,
|
||||
width=width, height=height, crop=crop, quality=quality,
|
||||
placeholder=placeholder)
|
||||
|
||||
@staticmethod
|
||||
def _content_image_get_response(status, headers, image_base64, field='datas', download=None,
|
||||
width=0, height=0, crop=False, quality=0, placeholder='placeholder.png'):
|
||||
def _content_image_get_response(
|
||||
status, headers, image_base64, model='ir.attachment', id=None,
|
||||
field='datas', download=None, width=0, height=0, crop=False,
|
||||
quality=0, placeholder='placeholder.png'):
|
||||
if status in [301, 304] or (status != 200 and download):
|
||||
return request.env['ir.http']._response_by_status(status, headers, image_base64)
|
||||
if not image_base64:
|
||||
image_base64 = base64.b64encode(Binary.placeholder(image=placeholder))
|
||||
if placeholder is None and model in request.env:
|
||||
# Try to browse the record in case a specific placeholder
|
||||
# is supposed to be used. (eg: Unassigned users on a task)
|
||||
record = request.env[model].browse(int(id)) if id else request.env[model]
|
||||
placeholder_filename = record._get_placeholder_filename(field=field)
|
||||
placeholder_content = Binary.placeholder(image=placeholder_filename)
|
||||
else:
|
||||
placeholder_content = Binary.placeholder()
|
||||
image_base64 = base64.b64encode(placeholder_content)
|
||||
|
||||
if not (width or height):
|
||||
width, height = odoo.tools.image_guess_size_from_field_name(field)
|
||||
|
||||
|
||||
@@ -184,14 +184,13 @@ var KanbanRecord = Widget.extend({
|
||||
* @returns {string} the url of the image
|
||||
*/
|
||||
_getImageURL: function (model, field, id, placeholder) {
|
||||
id = (_.isArray(id) ? id[0] : id) || false;
|
||||
placeholder = placeholder || "/web/static/src/img/placeholder.png";
|
||||
id = (_.isArray(id) ? id[0] : id) || null;
|
||||
var isCurrentRecord = this.modelName === model && this.recordData.id === id;
|
||||
var url;
|
||||
if (isCurrentRecord && this.record[field] && this.record[field].raw_value && !utils.is_bin_size(this.record[field].raw_value)) {
|
||||
// Use magic-word technique for detecting image type
|
||||
url = 'data:image/' + this.file_type_magic_word[this.record[field].raw_value[0]] + ';base64,' + this.record[field].raw_value;
|
||||
} else if (!model || !field || !id || (isCurrentRecord && this.record[field] && !this.record[field].raw_value)) {
|
||||
} else if (placeholder && (!model || !field || !id || (isCurrentRecord && this.record[field] && !this.record[field].raw_value))) {
|
||||
url = placeholder;
|
||||
} else {
|
||||
var session = this.getSession();
|
||||
|
||||
@@ -5264,8 +5264,15 @@ QUnit.module('Views', {
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
var images = kanban.el.querySelectorAll('img');
|
||||
var placeholders = [];
|
||||
for (var [index, img] of images.entries()) {
|
||||
if (img.dataset.src.indexOf(this.data.partner.records[index].image) === -1) {
|
||||
// Then we display a placeholder
|
||||
placeholders.push(img);
|
||||
}
|
||||
}
|
||||
|
||||
var placeholders = kanban.$('img[data-src$="/web/static/src/img/placeholder.png"]');
|
||||
assert.strictEqual(placeholders.length, this.data.partner.records.length - 1,
|
||||
"partner with no image should display the placeholder");
|
||||
|
||||
|
||||
@@ -211,7 +211,8 @@
|
||||
<t t-else=""><i class="fa fa-file-pdf-o mr-2" aria-label="Document" role="img" title="Document"/></t>
|
||||
<field name="slide_type"/>
|
||||
</span>
|
||||
<img t-att-src="kanban_image('res.users', 'image_128', record.user_id.raw_value)" t-att-title="record.user_id.value" t-att-alt="record.user_id.value" class="oe_kanban_avatar o_image_24_cover"/>
|
||||
<t t-set="unassigned"><t t-esc="_t('Unassigned')"/></t>
|
||||
<img t-att-src="kanban_image('res.users', 'image_128', record.user_id.raw_value)" t-att-title="record.user_id.value || unassigned" t-att-alt="record.user_id.value" class="oe_kanban_avatar o_image_24_cover"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -19,6 +19,7 @@ System</span>]]></field>
|
||||
<field name="company_id" ref="main_company"/>
|
||||
<field name="company_ids" eval="[(4, ref('main_company'))]"/>
|
||||
<field name="groups_id" eval="[(6,0,[])]"/>
|
||||
<field name="image_1920" type="base64" file="base/static/img/avatar_grey.png"/>
|
||||
<field name="signature"><![CDATA[<span>-- <br/>
|
||||
Administrator</span>]]></field>
|
||||
</record>
|
||||
@@ -29,7 +30,7 @@ Administrator</span>]]></field>
|
||||
<field name="login">default</field>
|
||||
<field name="active" eval="False"/>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="public_user" model="res.users">
|
||||
<field name="name">Public user</field>
|
||||
<field name="login">public</field>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import contextlib
|
||||
|
||||
import base64
|
||||
import pytz
|
||||
import datetime
|
||||
import ipaddress
|
||||
@@ -21,7 +22,8 @@ from odoo.exceptions import AccessDenied, AccessError, UserError, ValidationErro
|
||||
from odoo.http import request
|
||||
from odoo.osv import expression
|
||||
from odoo.service.db import check_super
|
||||
from odoo.tools import partition, collections, frozendict, lazy_property
|
||||
from odoo.tools import partition, collections, frozendict, lazy_property, image_process
|
||||
from odoo.modules.module import get_module_resource
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -214,6 +216,17 @@ class Users(models.Model):
|
||||
def _companies_count(self):
|
||||
return self.env['res.company'].sudo().search_count([])
|
||||
|
||||
@api.model
|
||||
def _get_default_image(self):
|
||||
""" Get a default image when the user is created without image
|
||||
|
||||
Inspired to _get_default_image method in
|
||||
https://github.com/odoo/odoo/blob/11.0/odoo/addons/base/res/res_partner.py
|
||||
"""
|
||||
image_path = get_module_resource('base', 'static/img', 'avatar.png')
|
||||
image = base64.b64encode(open(image_path, 'rb').read())
|
||||
return image_process(image, colorize=True)
|
||||
|
||||
partner_id = fields.Many2one('res.partner', required=True, ondelete='restrict', auto_join=True,
|
||||
string='Related Partner', help='Partner-related data of the user')
|
||||
login = fields.Char(required=True, help="Used to log into the system")
|
||||
@@ -258,6 +271,7 @@ class Users(models.Model):
|
||||
compute='_compute_accesses_count')
|
||||
groups_count = fields.Integer('# Groups', help='Number of groups that apply to the current user',
|
||||
compute='_compute_accesses_count')
|
||||
image_1920 = fields.Image(related='partner_id.image_1920', inherited=True, readonly=False, default=_get_default_image)
|
||||
|
||||
_sql_constraints = [
|
||||
('login_key', 'UNIQUE (login)', 'You can not have two users with the same login !')
|
||||
@@ -913,6 +927,13 @@ class Users(models.Model):
|
||||
def _register_hook(self):
|
||||
if hasattr(self, 'check_credentials'):
|
||||
_logger.warning("The check_credentials method of res.users has been renamed _check_credentials. One of your installed modules defines one, but it will not be called anymore.")
|
||||
|
||||
def _get_placeholder_filename(self, field=None):
|
||||
image_fields = ['image_%s' % size for size in [1920, 1024, 512, 256, 128]]
|
||||
if field in image_fields and not self:
|
||||
return 'base/static/img/user-slash.png'
|
||||
return super()._get_placeholder_filename(field=field)
|
||||
|
||||
#
|
||||
# Implied groups
|
||||
#
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.6 KiB |
@@ -6152,6 +6152,13 @@ Record ids: %(records)s
|
||||
|
||||
return result
|
||||
|
||||
def _get_placeholder_filename(self, field=None):
|
||||
""" Returns the filename of the placeholder to use,
|
||||
set on web/static/src/img by default, or the
|
||||
complete path to access it (eg: module/path/to/image.png).
|
||||
"""
|
||||
return 'placeholder.png'
|
||||
|
||||
collections.Set.register(BaseModel)
|
||||
# not exactly true as BaseModel doesn't have __reversed__, index or count
|
||||
collections.Sequence.register(BaseModel)
|
||||
|
||||
Reference in New Issue
Block a user