[MERGE] mail: move website part of message and rating to mail / portal

PURPOSE

Clean posting process and improve mail.message definition and comprehension.
Move as much possible content from website_* modules to portal_* modules
to ease their use in customer portal without having to depend on website.

SPECIFICATIONS: MAIL.MESSAGE DESCRIPTION

Move description from website_mail directly to mail. Anyway it is a
non stored computed field and therefore having it available directly
in mail does not cost anything.

SPECIFICATIONS: MAIL.MESSAGE WEBSITE PUBLISHED

Website mail defines a website_published field allowing to publish / unpublish
comments on the frontend of some modules. This field has several drawbacks :

  * it is used only for front-end people (portal, public) and has no real
    effect in chatter / classic discussions;
  * it is used only in some advanced front-end module and is not available
    in portal by default;
  * its naming is not really correct as it is not linked to fields coming
    from the website_published mixin and its behavior is not really
    the same;
  * its use is a bit duplicated with internal flag coming from subtype
    allowing to hide messages related to an internal subtype;
  * there are overrides of standard mail.message methods just to handle
    this flag;

In this merge we change that field by an is_internal flag directly on
mail.message model itself.It tells if share people (customers, share users)
are allowed to read the message. This field can be given through posting
API or set manually using widgets. It is also used in access rights custom
methods and managed like the internal flag of subtypes.

Mailgateway was already using an internal flag for internal note replies. It
is renamed to is_internal and propagated as it is now a standard field. It
also eases code understanding.

Portal is updated to allow managing the flag directly. It means customer portal
now natively allows to moderate customer comments without any need of website
modules.

Rating is updated accordingly. An is_internal field is added, replacing the
related on website published.

Also rename subtype parameter of message_post to subtype_xmlid. In
order to be more explicit subtype parameter is renamed to subtype_xmlid.
It therefore clearly indicates it should be a valid subtype Xml ID. Support
of ill formatted Xml IDs is removed because there is no reason to try to
add some random prefix. Give something that exists or go to hell, punk !

Finally mail.message and rating.rating form views are updated to include
is_internal flag and perform some view cleaning. An entry of ratings in
Technical/Discuss is added for debugging / management purpose.

SPECIFICATIONS: MOVE WEBSITE RATING TO PORTAL RATING

Now that publish feature is replaced by internal directly in mail and portal
rating portal inclusion can be done safely in a portal_rating module instead
of website_rating. It allows to remove the dependency and give the rating
feature of customer chatter directly in portal applications.

LINKS

Task ID 2071556
PR #38692

Related: odoo/enterprise#6410
Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
Odoo's Mergebot
2019-12-02 18:16:31 +01:00
committed by GitHub
135 changed files with 566 additions and 587 deletions
+3 -3
View File
@@ -1002,9 +1002,9 @@ file_filter = addons/website_profile/i18n/<lang>.po
source_file = addons/website_profile/i18n/website_profile.pot
source_lang = en
[odoo-master.website_rating]
file_filter = addons/website_rating/i18n/<lang>.po
source_file = addons/website_rating/i18n/website_rating.pot
[odoo-master.portal_rating]
file_filter = addons/portal_rating/i18n/<lang>.po
source_file = addons/portal_rating/i18n/portal_rating.pot
source_lang = en
[odoo-master.website_sale]
+2 -2
View File
@@ -226,7 +226,7 @@ class Attendee(models.Model):
result = self.write({'state': 'accepted'})
for attendee in self:
if attendee.event_id:
attendee.event_id.message_post(body=_("%s has accepted invitation") % (attendee.common_name), subtype="calendar.subtype_invitation")
attendee.event_id.message_post(body=_("%s has accepted invitation") % (attendee.common_name), subtype_xmlid="calendar.subtype_invitation")
return result
def do_decline(self):
@@ -234,7 +234,7 @@ class Attendee(models.Model):
res = self.write({'state': 'declined'})
for attendee in self:
if attendee.event_id:
attendee.event_id.message_post(body=_("%s has declined invitation") % (attendee.common_name), subtype="calendar.subtype_invitation")
attendee.event_id.message_post(body=_("%s has declined invitation") % (attendee.common_name), subtype_xmlid="calendar.subtype_invitation")
return res
+4 -4
View File
@@ -562,13 +562,13 @@ class Challenge(models.Model):
challenge.message_post(
body=body_html,
partner_ids=challenge.mapped('user_ids.partner_id.id'),
subtype='mail.mt_comment',
subtype_xmlid='mail.mt_comment',
email_layout_xmlid='mail.mail_notification_light',
)
if challenge.report_message_group_id:
challenge.report_message_group_id.message_post(
body=body_html,
subtype='mail.mt_comment')
subtype_xmlid='mail.mt_comment')
else:
# generate individual reports
@@ -586,13 +586,13 @@ class Challenge(models.Model):
challenge.message_notify(
body=body_html,
partner_ids=[user.partner_id.id],
subtype='mail.mt_comment',
subtype_xmlid='mail.mt_comment',
email_layout_xmlid='mail.mail_notification_light',
)
if challenge.report_message_group_id:
challenge.report_message_group_id.message_post(
body=body_html,
subtype='mail.mt_comment',
subtype_xmlid='mail.mt_comment',
email_layout_xmlid='mail.mail_notification_light',
)
return challenge.write({'last_report_date': fields.Date.today()})
+1 -1
View File
@@ -219,7 +219,7 @@ class Goal(models.Model):
self.message_notify(
body=body_html,
partner_ids=[self.user_id.partner_id.id],
subtype='mail.mt_comment',
subtype_xmlid='mail.mt_comment',
email_layout_xmlid='mail.mail_notification_light',
)
+1 -1
View File
@@ -666,7 +666,7 @@ class HolidaysRequest(models.Model):
# Automatic validation should be done in sudo, because user might not have the rights to do it by himself
holiday_sudo.action_validate()
holiday_sudo.message_subscribe(partner_ids=[holiday._get_responsible_for_approval().partner_id.id])
holiday_sudo.message_post(body=_("The time off has been automatically approved"), subtype="mt_comment") # Message from OdooBot (sudo)
holiday_sudo.message_post(body=_("The time off has been automatically approved"), subtype_xmlid="mail.mt_comment") # Message from OdooBot (sudo)
elif not self._context.get('import_file'):
holiday_sudo.activity_update()
return holiday
@@ -608,7 +608,7 @@ class TestAccessRightsWrite(TestHrHolidaysAccessRightsCommon):
""" User may communicate on its own leaves, even if validated """
self.employee_leave.with_user(self.user_employee_id).message_post(
body='I haz messaging',
subtype='mail.mt_comment',
subtype_xmlid='mail.mt_comment',
message_type='comment'
)
@@ -616,7 +616,7 @@ class TestAccessRightsWrite(TestHrHolidaysAccessRightsCommon):
self.employee_leave.with_user(self.user_employee_id).message_post(
body='I still haz messaging',
subtype='mail.mt_comment',
subtype_xmlid='mail.mt_comment',
message_type='comment'
)
@@ -448,7 +448,7 @@ class Applicant(models.Model):
applicant.job_id.write({'no_of_hired_employee': applicant.job_id.no_of_hired_employee + 1})
applicant.job_id.message_post(
body=_('New Employee %s Hired') % applicant.partner_name if applicant.partner_name else applicant.name,
subtype="hr_recruitment.mt_job_applicant_hired")
subtype_xmlid="hr_recruitment.mt_job_applicant_hired")
applicant.message_post_with_view(
'hr_recruitment.applicant_hired_template',
values={'applicant': applicant},
+1
View File
@@ -112,6 +112,7 @@ class LivechatController(http.Controller):
'rating': rate,
'consumed': True,
'feedback': reason,
'is_internal': False,
}
if not channel.rating_ids:
res_model_id = request.env['ir.model'].sudo().search([('model', '=', channel._name)], limit=1).id
+1 -1
View File
@@ -53,7 +53,7 @@ class MailChatController(BusController):
message = mail_channel.with_context(mail_create_nosubscribe=True).message_post(author_id=author_id,
email_from=email_from, body=body,
message_type='comment',
subtype='mail.mt_comment')
subtype_xmlid='mail.mt_comment')
return message and message.id or False
@route(['/mail/chat_history'], type="json", auth="public", cors="*")
+1 -1
View File
@@ -27,7 +27,7 @@ def _admin_password_warn(uid):
.message_post(
body=_("Your password is the default (admin)! If this system is exposed to untrusted users it is important to change it immediately for security reasons. I will keep nagging you about it!"),
message_type='comment',
subtype='mail.mt_comment'
subtype_xmlid='mail.mt_comment'
)
class Home(web.Home):
+4 -4
View File
@@ -280,7 +280,7 @@ class Channel(models.Model):
if not self.email_send:
notification = _('<div class="o_mail_notification">left <a href="#" class="o_channel_redirect" data-oe-id="%s">#%s</a></div>') % (self.id, self.name,)
# post 'channel left' message as root since the partner just unsubscribed from the channel
self.sudo().message_post(body=notification, subtype="mail.mt_comment", author_id=partner.id)
self.sudo().message_post(body=notification, subtype_xmlid="mail.mt_comment", author_id=partner.id)
return result
def _notify_get_groups(self):
@@ -780,7 +780,7 @@ class Channel(models.Model):
}
else:
notification = _('<div class="o_mail_notification">joined <a href="#" class="o_channel_redirect" data-oe-id="%s">#%s</a></div>') % (channel.id, channel.name,)
self.message_post(body=notification, message_type="notification", subtype="mail.mt_comment", author_id=partner.id)
self.message_post(body=notification, message_type="notification", subtype_xmlid="mail.mt_comment", author_id=partner.id)
# broadcast the channel header to the added partner
self._broadcast(partner_ids)
@@ -861,7 +861,7 @@ class Channel(models.Model):
added = self.action_follow()
if added and self.channel_type == 'channel' and not self.email_send:
notification = _('<div class="o_mail_notification">joined <a href="#" class="o_channel_redirect" data-oe-id="%s">#%s</a></div>') % (self.id, self.name,)
self.message_post(body=notification, message_type="notification", subtype="mail.mt_comment")
self.message_post(body=notification, message_type="notification", subtype_xmlid="mail.mt_comment")
if added and self.moderation_guidelines:
self._send_guidelines(self.env.user.partner_id)
@@ -886,7 +886,7 @@ class Channel(models.Model):
'channel_partner_ids': [(4, self.env.user.partner_id.id)]
})
notification = _('<div class="o_mail_notification">created <a href="#" class="o_channel_redirect" data-oe-id="%s">#%s</a></div>') % (new_channel.id, new_channel.name,)
new_channel.message_post(body=notification, message_type="notification", subtype="mail.mt_comment")
new_channel.message_post(body=notification, message_type="notification", subtype_xmlid="mail.mt_comment")
channel_info = new_channel.channel_info('creation')[0]
self.env['bus.bus'].sendone((self._cr.dbname, 'res.partner', self.env.user.partner_id.id), channel_info)
return channel_info
+22 -6
View File
@@ -12,7 +12,6 @@ from odoo import _, api, fields, models, modules, tools
from odoo.exceptions import AccessError
from odoo.http import request
from odoo.osv import expression
from odoo.tools import groupby
_logger = logging.getLogger(__name__)
_image_dataurl = re.compile(r'(data:image/[a-z]+?);base64,([a-z0-9+/\n]{3,}=*)\n*([\'"])(?: data-filename="([^"]*)")?', re.I)
@@ -43,6 +42,9 @@ class Message(models.Model):
subject = fields.Char('Subject')
date = fields.Datetime('Date', default=fields.Datetime.now)
body = fields.Html('Contents', default='', sanitize_style=True)
description = fields.Char(
'Short description', compute="_compute_description",
help='Message description: either the subject, or the beginning of the body')
attachment_ids = fields.Many2many(
'ir.attachment', 'message_attachment_rel',
'message_id', 'attachment_id',
@@ -71,6 +73,7 @@ class Message(models.Model):
mail_activity_type_id = fields.Many2one(
'mail.activity.type', 'Mail Activity Type',
index=True, ondelete='set null')
is_internal = fields.Boolean('Employee Only', help='Hide to public / portal users, independently from subtype configuration.')
# origin
email_from = fields.Char('From', help="Email address of the sender. This field is set when no matching partner is found and replaces the author_id field in the chatter.")
author_id = fields.Many2one(
@@ -124,7 +127,7 @@ class Message(models.Model):
('rejected', 'Rejected')], string="Moderation Status", index=True)
moderator_id = fields.Many2one('res.users', string="Moderated By", index=True)
need_moderation = fields.Boolean('Need moderation', compute='_compute_need_moderation', search='_search_need_moderation')
#keep notification layout informations to be able to generate mail again
# keep notification layout informations to be able to generate mail again
email_layout_xmlid = fields.Char('Layout', copy=False) # xml id of layout
add_sign = fields.Boolean(default=True)
# `test_adv_activity`, `test_adv_activity_full`, `test_message_assignation_inbox`,...
@@ -137,6 +140,14 @@ class Message(models.Model):
mail_ids = fields.One2many('mail.mail', 'mail_message_id', string='Mails', groups="base.group_system")
canned_response_ids = fields.One2many('mail.shortcode', 'message_ids', string="Canned Responses", store=False)
def _compute_description(self):
for message in self:
if message.subject:
message.description = message.subject
else:
plaintext_ct = '' if not message.body else tools.html2plaintext(message.body)
message.description = plaintext_ct[:30] + '%s' % (' [...]' if len(plaintext_ct) >= 30 else '')
def _get_needaction(self):
""" Need action on a mail.message = notified on my channel """
my_messages = self.env['mail.notification'].sudo().search([
@@ -224,9 +235,9 @@ class Message(models.Model):
return super(Message, self)._search(
args, offset=offset, limit=limit, order=order,
count=count, access_rights_uid=access_rights_uid)
# Non-employee see only messages with a subtype (aka, no internal logs)
# Non-employee see only messages with a subtype and not internal
if not self.env['res.users'].has_group('base.group_user'):
args = ['&', '&', ('subtype_id', '!=', False), ('subtype_id.internal', '=', False)] + list(args)
args = expression.AND([self._get_search_domain_share(), args])
# Perform a super with count as False, to have the ids, not a counter
ids = super(Message, self)._search(
args, offset=offset, limit=limit, order=order,
@@ -349,7 +360,9 @@ class Message(models.Model):
FROM "%s" AS message
LEFT JOIN "mail_message_subtype" as subtype
ON message.subtype_id = subtype.id
WHERE message.message_type = %%s AND (message.subtype_id IS NULL OR subtype.internal IS TRUE) AND message.id = ANY (%%s)''' % (self._table), ('comment', self.ids,))
WHERE message.message_type = %%s AND
(message.is_internal IS TRUE OR message.subtype_id IS NULL OR subtype.internal IS TRUE) AND
message.id = ANY (%%s)''' % (self._table), ('comment', self.ids,))
if self._cr.fetchall():
raise AccessError(
_('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') % (self._description, operation)
@@ -1200,7 +1213,7 @@ class Message(models.Model):
else:
messages |= message
for author, author_messages in groupby(messages, itemgetter('author_id')):
for author, author_messages in tools.groupby(messages, itemgetter('author_id')):
self.env['bus.bus'].sendone(
(self._cr.dbname, 'res.partner', author.id),
{'type': 'mail_failure', 'elements': self.env['mail.message'].concat(*author_messages)._format_mail_failures()}
@@ -1269,3 +1282,6 @@ class Message(models.Model):
'message_needaction',
'message_needaction_counter',
], ids=[res_id])
def _get_search_domain_share(self):
return ['&', '&', ('is_internal', '=', False), ('subtype_id', '!=', False), ('subtype_id.internal', '=', False)]
+18 -19
View File
@@ -1056,7 +1056,7 @@ class MailThread(models.AbstractModel):
parent_message = self.env['mail.message'].sudo().browse(message_dict['parent_id'])
partner_ids = []
if not subtype_id:
if message_dict.pop('internal', False):
if message_dict.get('is_internal'):
subtype_id = self.env['ir.model.data'].xmlid_to_res_id('mail.mt_note')
if parent_message and parent_message.author_id:
partner_ids = [parent_message.author_id.id]
@@ -1367,7 +1367,7 @@ class MailThread(models.AbstractModel):
'references': references,
'in_reply_to': in-reply-to,
'parent_id': parent mail.message based on in_reply_to or references,
'internal': answer to an internal message (note),
'is_internal': answer to an internal message (note),
'date': date,
'attachments': [('file1', 'bytes'),
('file2', 'bytes')}
@@ -1437,18 +1437,15 @@ class MailThread(models.AbstractModel):
stored_date = datetime.datetime.now()
msg_dict['date'] = stored_date.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
parent_ids = False
if msg_dict['in_reply_to']:
parent_ids = self.env['mail.message'].search([('message_id', '=', msg_dict['in_reply_to'])], limit=1)
if parent_ids:
msg_dict['parent_id'] = parent_ids.id
msg_dict['internal'] = parent_ids.subtype_id and parent_ids.subtype_id.internal or False
if msg_dict['references'] and 'parent_id' not in msg_dict:
if msg_dict['references'] and not parent_ids:
references_msg_id_list = tools.mail_header_msgid_re.findall(msg_dict['references'])
parent_ids = self.env['mail.message'].search([('message_id', 'in', [x.strip() for x in references_msg_id_list])], limit=1)
if parent_ids:
msg_dict['parent_id'] = parent_ids.id
msg_dict['internal'] = parent_ids.subtype_id and parent_ids.subtype_id.internal or False
if parent_ids:
msg_dict['parent_id'] = parent_ids.id
msg_dict['is_internal'] = parent_ids.subtype_id and parent_ids.subtype_id.internal or False
msg_dict.update(self._message_parse_extract_payload(message, save_original=save_original))
msg_dict.update(self._message_parse_extract_bounce(message, msg_dict))
@@ -1777,7 +1774,7 @@ class MailThread(models.AbstractModel):
def message_post(self, *,
body='', subject=None, message_type='notification',
email_from=None, author_id=None, parent_id=False,
subtype_id=False, subtype=None, partner_ids=None, channel_ids=None,
subtype_xmlid=None, subtype_id=False, partner_ids=None, channel_ids=None,
attachments=None, attachment_ids=None,
add_sign=True, record_name=False,
**kwargs):
@@ -1791,8 +1788,6 @@ class MailThread(models.AbstractModel):
:param int parent_id: handle thread formation
:param int subtype_id: subtype_id of the message, mainly use fore
followers mechanism
:param int subtype: xmlid that will be used to compute subtype_id
if subtype_id is not given.
:param list(int) partner_ids: partner_ids to notify
:param list(int) channel_ids: channel_ids to notify
:param list(tuple(str,str), tuple(str,str, dict) or int) attachments : list of attachment tuples in the form
@@ -1814,7 +1809,9 @@ class MailThread(models.AbstractModel):
raise ValueError('message_post should only be call to post message on record. Use message_notify instead')
if 'model' in msg_kwargs or 'res_id' in msg_kwargs:
raise ValueError("message_post doesn't support model and res_id parameters anymore. Please call message_post on record")
raise ValueError("message_post doesn't support model and res_id parameters anymore. Please call message_post on record.")
if 'subtype' in kwargs:
raise ValueError("message_post doesn't support subtype parameter anymore. Please give a valid subtype_id or subtype_xmlid value instead.")
self = self.with_lang() # add lang to context imediatly since it will be usefull in various flows latter.
@@ -1832,11 +1829,10 @@ class MailThread(models.AbstractModel):
# Find the message's author
author_id, email_from = self._message_compute_author(author_id, email_from, raise_exception=True)
if subtype_xmlid:
subtype_id = self.env['ir.model.data'].xmlid_to_res_id(subtype_xmlid)
if not subtype_id:
subtype = subtype or 'mt_note'
if '.' not in subtype:
subtype = 'mail.%s' % subtype
subtype_id = self.env['ir.model.data'].xmlid_to_res_id(subtype)
subtype_id = self.env['ir.model.data'].xmlid_to_res_id('mail_mt_note')
# automatically subscribe recipients if asked to
if self._context.get('mail_post_autofollow') and partner_ids:
@@ -1997,6 +1993,7 @@ class MailThread(models.AbstractModel):
'email_from': email_from,
'partner_ids': partner_ids,
'subtype_id': self.env['ir.model.data'].xmlid_to_res_id('mail.mt_note'),
'is_internal': True,
'record_name': False,
'reply_to': MailThread._notify_get_reply_to(default=email_from, records=None)[False],
'message_id': tools.generate_tracking_message_id('message-notify'),
@@ -2025,6 +2022,7 @@ class MailThread(models.AbstractModel):
'model': kwargs.get('model', self._name),
'res_id': self.ids[0] if self.ids else False,
'subtype_id': self.env['ir.model.data'].xmlid_to_res_id('mail.mt_note'),
'is_internal': True,
'record_name': False,
'reply_to': self.env['mail.thread']._notify_get_reply_to(default=email_from, records=None)[False],
'message_id': tools.generate_tracking_message_id('message-notify'), # why? this is all but a notify
@@ -2047,6 +2045,7 @@ class MailThread(models.AbstractModel):
'message_type': message_type,
'model': self._name,
'subtype_id': self.env['ir.model.data'].xmlid_to_res_id('mail.mt_note'),
'is_internal': True,
'record_name': False,
'reply_to': self.env['mail.thread']._notify_get_reply_to(default=email_from, records=None)[False],
'message_id': tools.generate_tracking_message_id('message-notify'), # why? this is all but a notify
@@ -2402,7 +2401,7 @@ class MailThread(models.AbstractModel):
if pid:
if active is False:
continue
pdata = {'id': pid, 'active': active, 'share': pshare, 'groups': groups}
pdata = {'id': pid, 'active': active, 'share': pshare, 'groups': groups or []}
if notif == 'inbox':
recipient_data['partners'].append(dict(pdata, notif=notif, type='user'))
elif not pshare and notif: # has an user and is not shared, is therefore user
+1 -1
View File
@@ -104,7 +104,7 @@ class PublisherWarrantyContract(AbstractModel):
poster = user
for message in result["messages"]:
try:
poster.message_post(body=message, subtype='mt_comment', partner_ids=[user.partner_id.id])
poster.message_post(body=message, subtype_xmlid='mail.mt_comment', partner_ids=[user.partner_id.id])
except Exception:
pass
if result.get('enterprise_info'):
@@ -205,14 +205,14 @@ var ChatterComposer = BasicComposer.extend({
return new Promise(function (resolve, reject) {
self._super().then(function (message) {
message = _.extend(message, {
subtype: 'mail.mt_comment',
subtype_xmlid: 'mail.mt_comment',
message_type: 'comment',
context: _.defaults({}, self.context, session.user_context),
});
// Subtype
if (self.options.isLog) {
message.subtype = 'mail.mt_note';
message.subtype_xmlid = 'mail.mt_note';
}
// Partner_ids
+1 -1
View File
@@ -1496,7 +1496,7 @@ var Discuss = AbstractAction.extend({
var self = this;
var options = {};
if (this._selectedMessage) {
messageData.subtype = this._selectedMessage.isNote() ? 'mail.mt_note': 'mail.mt_comment';
messageData.subtype_xmlid = this._selectedMessage.isNote() ? 'mail.mt_note': 'mail.mt_comment';
messageData.subtype_id = false;
messageData.message_type = 'comment';
@@ -443,7 +443,7 @@ var Channel = SearchableThread.extend(ThreadTypingMixin, {
return this._super.apply(this, arguments).then(function (messageData) {
_.extend(messageData, {
message_type: 'comment',
subtype: 'mail.mt_comment',
subtype_xmlid: 'mail.mt_comment',
command: data.command,
});
return self._rpc({
@@ -331,7 +331,7 @@ var DocumentThread = Thread.extend({
_.extend(messageData, {
context: data.context,
message_type: data.message_type,
subtype: data.subtype || "mail.mt_comment",
subtype_xmlid: data.subtype_xmlid || "mail.mt_comment",
subtype_id: data.subtype_id,
});
return self._rpc({
+6 -6
View File
@@ -1008,8 +1008,8 @@ QUnit.test('chatter: post, receive and star messages', async function (assert) {
body: args.kwargs.body,
date: "2016-12-20 10:35:40",
id: messageID,
is_note: args.kwargs.subtype === 'mail.mt_note',
is_discussion: args.kwargs.subtype === 'mail.mt_comment',
is_note: args.kwargs.subtype_xmlid === 'mail.mt_note',
is_discussion: args.kwargs.subtype_xmlid === 'mail.mt_comment',
is_notification: false,
is_starred: false,
model: 'partner',
@@ -1403,8 +1403,8 @@ QUnit.test('chatter: post a message and switch in edit mode', async function (as
body: args.kwargs.body,
date: "2016-12-20 10:35:40",
id: 42,
is_note: args.kwargs.subtype === 'mail.mt_note',
is_discussion: args.kwargs.subtype === 'mail.mt_comment',
is_note: args.kwargs.subtype_xmlid === 'mail.mt_note',
is_discussion: args.kwargs.subtype_xmlid === 'mail.mt_comment',
is_starred: false,
model: 'partner',
res_id: 2,
@@ -3061,8 +3061,8 @@ QUnit.test('chatter: suggested partner auto-follow on message post', async funct
body: args.kwargs.body,
date: "2016-12-20 10:35:40",
id: messageID,
is_note: args.kwargs.subtype === 'mail.mt_note',
is_discussion: args.kwargs.subtype === 'mail.mt_comment',
is_note: args.kwargs.subtype_xmlid === 'mail.mt_note',
is_discussion: args.kwargs.subtype_xmlid === 'mail.mt_comment',
is_notification: false,
is_starred: false,
model: 'partner',
+1 -1
View File
@@ -62,6 +62,6 @@
name="Email Blacklist"
action="mail_blacklist_action"
parent="mail.mail_menu_technical"
sequence="20"/>
sequence="22"/>
</odoo>
+1 -1
View File
@@ -50,7 +50,7 @@
id="menu_email_followers"
parent="mail.mail_menu_technical"
action="action_view_followers"
sequence="20"
sequence="21"
groups="base.group_no_one"/>
</data>
+11 -10
View File
@@ -18,8 +18,8 @@
</record>
<!-- mail.message form: short view !-->
<record model="ir.ui.view" id="view_message_form">
<field name="name">mail.message.form</field>
<record id="mail_message_view_form" model="ir.ui.view">
<field name="name">mail.message.view.form</field>
<field name="model">mail.message</field>
<field name="priority">20</field>
<field name="arch" type="xml">
@@ -31,16 +31,17 @@
<field name="date"/>
<field name="email_from"/>
<field name="author_id"/>
<field name="record_name"/>
<field name="moderator_id"/>
</group>
<group>
<field name="parent_id"/>
<field name="model"/>
<field name="res_id" widget="integer"/>
<field name="message_type"/>
<field name="subtype_id"/>
<field name="is_internal"/>
</group>
<group>
<field name="model"/>
<field name="res_id" widget="integer"/>
<field name="record_name"/>
<field name="parent_id"/>
<field name="moderation_status"/>
<field name="moderator_id"/>
</group>
</group>
<notebook>
@@ -81,7 +82,7 @@
</field>
</group>
</page>
<page string="Tracking">
<page string="Tracking" name="page_tracking">
<field name="tracking_value_ids"/>
</page>
</notebook>
@@ -52,7 +52,7 @@
id="mail_notification_menu"
parent="mail.mail_menu_technical"
action="mail_notification_action"
sequence="30"
sequence="20"
groups="base.group_no_one"/>
</data></odoo>
+1 -1
View File
@@ -24,6 +24,6 @@ class Channel(models.Model):
'name': 'OdooBot'
})
message = _("Hello,<br/>Odoo's chat helps employees collaborate efficiently. I'm here to help you discover its features.<br/><b>Try to send me an emoji :)</b>")
channel.sudo().message_post(body=message, author_id=odoobot_id, message_type="comment", subtype="mail.mt_comment")
channel.sudo().message_post(body=message, author_id=odoobot_id, message_type="comment", subtype_xmlid="mail.mt_comment")
self.env.user.odoobot_state = 'onboarding_emoji'
return channel
+9 -2
View File
@@ -86,7 +86,7 @@ def _message_post_helper(res_model, res_id, message, token='', _hash=False, pid=
message_post_args = dict(
body=message,
message_type=kw.pop('message_type', "comment"),
subtype=kw.pop('subtype', "mt_comment"),
subtype_xmlid=kw.pop('subtype_xmlid', "mail.mt_comment"),
author_id=author_id,
**kw
)
@@ -158,6 +158,7 @@ class PortalChatter(http.Controller):
'options': {
'message_count': message_data['message_count'],
'is_user_public': is_user_public,
'is_user_employee': request.env.user.has_group('base.group_user'),
'is_user_publisher': request.env.user.has_group('website.group_website_publisher'),
'display_composer': display_composer,
'partner_id': request.env.user.partner_id.id
@@ -183,13 +184,19 @@ class PortalChatter(http.Controller):
raise Forbidden()
# Non-employee see only messages with not internal subtype (aka, no internal logs)
if not request.env['res.users'].has_group('base.group_user'):
domain = expression.AND([Message._non_employee_message_domain(), domain])
domain = expression.AND([Message._get_search_domain_share(), domain])
Message = request.env['mail.message'].sudo()
return {
'messages': Message.search(domain, limit=limit, offset=offset).portal_message_format(),
'message_count': Message.search_count(domain)
}
@http.route(['/mail/update_is_internal'], type='json', auth="user", website=True)
def portal_message_update_is_internal(self, message_id, is_internal):
message = request.env['mail.message'].browse(int(message_id))
message.write({'is_internal': is_internal})
return message.is_internal
class MailController(MailController):
+1 -5
View File
@@ -10,7 +10,7 @@ class MailMessage(models.Model):
def portal_message_format(self):
return self._portal_message_format([
'id', 'body', 'date', 'author_id', 'email_from', # base message fields
'message_type', 'subtype_id', 'subject', # message specific
'message_type', 'subtype_id', 'is_internal', 'subject', # message specific
'model', 'res_id', 'record_name', # document related
])
@@ -24,7 +24,3 @@ class MailMessage(models.Model):
if not attachment.get('access_token'):
attachment['access_token'] = IrAttachmentSudo.browse(attachment['id']).generate_access_token()[0]
return message_values
@api.model
def _non_employee_message_domain(self):
return ['&', ('subtype_id', '!=', False), ('subtype_id.internal', '=', False)]
+32 -1
View File
@@ -20,7 +20,8 @@ var PortalChatter = publicWidget.Widget.extend({
template: 'portal.Chatter',
xmlDependencies: ['/portal/static/src/xml/portal_chatter.xml'],
events: {
"click .o_portal_chatter_pager_btn": '_onClickPager',
'click .o_portal_chatter_pager_btn': '_onClickPager',
'click .o_portal_chatter_js_is_internal': 'async _onClickUpdateIsInternal',
},
/**
@@ -45,6 +46,7 @@ var PortalChatter = publicWidget.Widget.extend({
'pager_scope': 5,
'pager_start': 1,
'is_user_public': true,
'is_user_employee': false,
'is_user_publisher': false,
'hash': false,
'pid': false,
@@ -254,6 +256,35 @@ var PortalChatter = publicWidget.Widget.extend({
var page = $(ev.currentTarget).data('page');
this._changeCurrentPage(page);
},
/**
* Toggle is_internal state of message. Update both node data and
* classes to ensure DOM is updated accordingly to RPC call result.
* @private
* @returns {Promise}
*/
_onClickUpdateIsInternal: function (ev) {
ev.preventDefault();
var $elem = $(ev.currentTarget);
return this._rpc({
route: '/mail/update_is_internal',
params: {
message_id: $elem.data('message-id'),
is_internal: ! $elem.data('is-internal'),
},
}).then(function (result) {
$elem.data('is-internal', result);
if (result === true) {
$elem.addClass('o_portal_message_internal_on');
$elem.removeClass('o_portal_message_internal_off');
}
else {
$elem.addClass('o_portal_message_internal_off');
$elem.removeClass('o_portal_message_internal_on');
}
});
},
});
publicWidget.registry.portalChatter = publicWidget.Widget.extend({
+12
View File
@@ -524,4 +524,16 @@ form label {
opacity: 1;
}
}
.o_portal_message_internal_off {
.btn-danger {
display: none;
}
}
.o_portal_message_internal_on {
.btn-success {
display: none;
}
}
}
@@ -92,6 +92,7 @@
<div class="media o_portal_chatter_message" t-att-id="'message-' + message.id">
<img class="o_portal_chatter_avatar" t-att-src="message.author_avatar_url" alt="avatar"/>
<div class="media-body">
<t t-call="portal.chatter_internal_toggle" t-if="widget.options['is_user_employee']"/>
<div class="o_portal_chatter_message_title">
<h5><t t-esc="message.author_id[1]"/></h5>
@@ -110,6 +111,18 @@
</div>
</t>
<!-- Chatter: internal toggle widget -->
<t t-name="portal.chatter_internal_toggle">
<div t-attf-class="float-right o_portal_chatter_js_is_internal #{message.is_internal and 'o_portal_message_internal_on' or 'o_portal_message_internal_off'}"
t-att-data-message-id="message.id"
t-att-data-is-internal="message.is_internal">
<button class="btn btn-danger"
title="Currently restricted to internal employees, click to make it available to everyone viewing this document.">Employees Only</button>
<button class="btn btn-success"
title="Currently available to everyone viewing this document, click to restrict to internal employees.">Visible</button>
</div>
</t>
<t t-name="portal.pager">
<div class="o_portal_chatter_pager">
<t t-if="!_.isEmpty(widget.get('pager'))">
@@ -2,13 +2,17 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': 'Website Rating',
'name': 'Portal Rating',
'category': 'Hidden',
'version': '1.0',
'description': """
Bridge module for rating on website stuff.
Bridge module adding rating capabilities on portal. It includes notably
inclusion of rating directly within the customer portal discuss widget.
""",
'depends': ['rating', 'website_mail'],
'depends': [
'portal',
'rating',
],
'data': [
'views/assets.xml',
'views/rating_views.xml',
+251
View File
@@ -0,0 +1,251 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * portal_rating
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server saas~12.5\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-08-26 08:17+0000\n"
"PO-Revision-Date: 2019-08-26 08:17+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: portal_rating
#. openerp-web
#: code:addons/portal_rating/static/src/xml/portal_chatter.xml:0
#, python-format
msgid ""
"#{widget.options['force_submit_url'] ? widget.options['force_submit_url'] : "
"'/mail/chatter_post'}"
msgstr ""
#. module: portal_rating
#. openerp-web
#: code:addons/portal_rating/static/src/xml/portal_rating_composer.xml:0
#, python-format
msgid "&times;"
msgstr ""
#. module: portal_rating
#. openerp-web
#: code:addons/portal_rating/static/src/xml/portal_rating_composer.xml:0
#: code:addons/portal_rating/static/src/xml/portal_rating_composer.xml:0
#, python-format
msgid "Add a review"
msgstr ""
#. module: portal_rating
#. openerp-web
#: code:addons/portal_rating/static/src/xml/portal_tools.xml:0
#, python-format
msgid "Average"
msgstr ""
#. module: portal_rating
#. openerp-web
#: code:addons/portal_rating/static/src/xml/portal_chatter.xml:0
#, python-format
msgid "Cancel"
msgstr ""
#. module: portal_rating
#. openerp-web
#: code:addons/portal_rating/static/src/xml/portal_chatter.xml:0
#, python-format
msgid "Comment"
msgstr ""
#. module: portal_rating
#: model:ir.model.fields,field_description:portal_rating.field_rating_rating__publisher_id
msgid "Commented by"
msgstr ""
#. module: portal_rating
#: model:ir.model.fields,field_description:portal_rating.field_rating_rating__publisher_datetime
msgid "Commented on"
msgstr ""
#. module: portal_rating
#. openerp-web
#: code:addons/portal_rating/static/src/xml/portal_chatter.xml:0
#, python-format
msgid "Delete"
msgstr ""
#. module: portal_rating
#. openerp-web
#: code:addons/portal_rating/static/src/xml/portal_tools.xml:0
#, python-format
msgid "Details"
msgstr ""
#. module: portal_rating
#. openerp-web
#: code:addons/portal_rating/static/src/xml/portal_chatter.xml:0
#, python-format
msgid "Edit"
msgstr ""
#. module: portal_rating
#. openerp-web
#: code:addons/portal_rating/static/src/xml/portal_tools.xml:0
#: code:addons/portal_rating/static/src/xml/portal_tools.xml:0
#, python-format
msgid "Half a star"
msgstr ""
#. module: portal_rating
#. openerp-web
#: code:addons/portal_rating/static/src/js/portal_composer.js:0
#, python-format
msgid "I don't like it"
msgstr ""
#. module: portal_rating
#. openerp-web
#: code:addons/portal_rating/static/src/js/portal_composer.js:0
#, python-format
msgid "I hate it"
msgstr ""
#. module: portal_rating
#. openerp-web
#: code:addons/portal_rating/static/src/js/portal_composer.js:0
#, python-format
msgid "I like it"
msgstr ""
#. module: portal_rating
#. openerp-web
#: code:addons/portal_rating/static/src/js/portal_composer.js:0
#, python-format
msgid "I love it"
msgstr ""
#. module: portal_rating
#: code:addons/portal_rating/controllers/portal_rating.py:0
#, python-format
msgid "Invalid rating"
msgstr ""
#. module: portal_rating
#. openerp-web
#: code:addons/portal_rating/static/src/js/portal_composer.js:0
#, python-format
msgid "It's okay"
msgstr ""
#. module: portal_rating
#: model:ir.model,name:portal_rating.model_mail_message
msgid "Message"
msgstr ""
#. module: portal_rating
#. openerp-web
#: code:addons/portal_rating/static/src/xml/portal_rating_composer.xml:0
#: code:addons/portal_rating/static/src/xml/portal_rating_composer.xml:0
#, python-format
msgid "Modify your review"
msgstr ""
#. module: portal_rating
#. openerp-web
#: code:addons/portal_rating/static/src/xml/portal_tools.xml:0
#: code:addons/portal_rating/static/src/xml/portal_tools.xml:0
#, python-format
msgid "One star"
msgstr ""
#. module: portal_rating
#: code:addons/portal_rating/models/rating.py:0
#, python-format
msgid "Only the publisher of the website can change the rating comment"
msgstr ""
#. module: portal_rating
#. openerp-web
#: code:addons/portal_rating/static/src/xml/portal_chatter.xml:0
#, python-format
msgid "Post comment"
msgstr ""
#. module: portal_rating
#: model:ir.model.fields,field_description:portal_rating.field_rating_rating__website_published
msgid "Published"
msgstr ""
#. module: portal_rating
#. openerp-web
#: code:addons/portal_rating/static/src/xml/portal_chatter.xml:0
#: code:addons/portal_rating/static/src/xml/portal_chatter.xml:0
#, python-format
msgid "Published on"
msgstr ""
#. module: portal_rating
#: model:ir.model.fields,field_description:portal_rating.field_rating_rating__publisher_comment
msgid "Publisher Comment"
msgstr ""
#. module: portal_rating
#: model_terms:ir.ui.view,arch_db:portal_rating.rating_rating_view_form
msgid "Publisher comment"
msgstr ""
#. module: portal_rating
#: model:ir.model,name:portal_rating.model_rating_rating
msgid "Rating"
msgstr ""
#. module: portal_rating
#. openerp-web
#: code:addons/portal_rating/static/src/xml/portal_tools.xml:0
#, python-format
msgid "Remove Selection"
msgstr ""
#. module: portal_rating
#. openerp-web
#: code:addons/portal_rating/static/src/xml/portal_chatter.xml:0
#, python-format
msgid "Update comment"
msgstr ""
#. module: portal_rating
#: model:ir.model.fields,help:portal_rating.field_rating_rating__website_published
msgid "Visible on the website as a comment"
msgstr ""
#. module: portal_rating
#. openerp-web
#: code:addons/portal_rating/static/src/js/portal_chatter.js:0
#, python-format
msgid "Write your comment"
msgstr ""
#. module: portal_rating
#. openerp-web
#: code:addons/portal_rating/static/src/xml/portal_chatter.xml:0
#: code:addons/portal_rating/static/src/xml/portal_chatter.xml:0
#, python-format
msgid "avatar"
msgstr ""
#. module: portal_rating
#. openerp-web
#: code:addons/portal_rating/static/src/xml/portal_rating_composer.xml:0
#, python-format
msgid "ratings)"
msgstr ""
#. module: portal_rating
#. openerp-web
#: code:addons/portal_rating/static/src/xml/portal_tools.xml:0
#, python-format
msgid "stars"
msgstr ""
@@ -6,8 +6,6 @@ from odoo import fields, models, exceptions, _
class Rating(models.Model):
_inherit = 'rating.rating'
# Add this related field to mail.message for performance reason
website_published = fields.Boolean(related='message_id.website_published', store=True, readonly=False)
# Adding information for comment a rating message
publisher_comment = fields.Text("Publisher Comment")
publisher_id = fields.Many2one('res.partner', 'Commented by',
@@ -30,8 +30,8 @@ PortalChatter.include({
}),
xmlDependencies: (PortalChatter.prototype.xmlDependencies || [])
.concat([
'/website_rating/static/src/xml/portal_tools.xml',
'/website_rating/static/src/xml/portal_chatter.xml'
'/portal_rating/static/src/xml/portal_tools.xml',
'/portal_rating/static/src/xml/portal_chatter.xml'
]),
/**
@@ -256,7 +256,7 @@ PortalChatter.include({
var oldRating = this.messages[messageIndex].rating;
data.rating.publisher_comment = oldRating.publisher_comment ? oldRating.publisher_comment : '';
this._getCommentContainer($source).html($(qweb.render("website_rating.chatter_rating_publisher_form", data)));
this._getCommentContainer($source).html($(qweb.render("portal_rating.chatter_rating_publisher_form", data)));
this._focusTextComment($source);
},
@@ -309,7 +309,7 @@ PortalChatter.include({
if (self.messages[messageIndex].rating.publisher_comment !== '') {
// Remove the button comment if exist and render the comment
self._getCommentButton($source).addClass('d-none');
self._getCommentContainer($source).html($(qweb.render("website_rating.chatter_rating_publisher_comment", {
self._getCommentContainer($source).html($(qweb.render("portal_rating.chatter_rating_publisher_comment", {
rating: self.messages[messageIndex].rating,
is_publisher: self.options.is_user_publisher
})));
@@ -335,7 +335,7 @@ PortalChatter.include({
rating: this.messages[messageIndex].rating,
is_publisher: this.options.is_user_publisher
};
this._getCommentContainer($source).html($(qweb.render("website_rating.chatter_rating_publisher_comment", data)));
this._getCommentContainer($source).html($(qweb.render("portal_rating.chatter_rating_publisher_comment", data)));
} else {
this._getCommentContainer($source).empty();
}
@@ -16,11 +16,11 @@ var STAR_RATING_RATIO = 2; // conversion factor from the star (1-5) to the db r
* a popup with the portal composer when clicking on it.
**/
var RatingPopupComposer = publicWidget.Widget.extend({
template: 'website_rating.PopupComposer',
template: 'portal_rating.PopupComposer',
xmlDependencies: [
'/portal/static/src/xml/portal_chatter.xml',
'/website_rating/static/src/xml/portal_tools.xml',
'/website_rating/static/src/xml/portal_rating_composer.xml',
'/portal_rating/static/src/xml/portal_tools.xml',
'/portal_rating/static/src/xml/portal_rating_composer.xml',
],
init: function (parent, options) {
@@ -9,7 +9,7 @@
<attribute name="t-attf-action">#{widget.options['force_submit_url'] ? widget.options['force_submit_url'] : '/mail/chatter_post'}</attribute>
</t>
<t t-jquery=".o_portal_chatter_composer_form input[name='csrf_token']" t-operation="after">
<t t-call="website_rating.rating_star_input">
<t t-call="portal_rating.rating_star_input">
<t t-set="default_rating" t-value="widget.options['default_rating_value']"/>
</t>
<input type="hidden" name="message_id" t-att-value="widget.options['default_message_id']" t-if="widget.options['default_message_id']"/>
@@ -19,7 +19,7 @@
<t t-extend="portal.chatter_messages">
<t t-jquery="t[t-raw='message.body']" t-operation="before">
<t t-if="message['rating_value']">
<t t-call="website_rating.rating_stars_static">
<t t-call="portal_rating.rating_stars_static">
<t t-set="val" t-value="message.rating_value"/>
</t>
</t>
@@ -28,7 +28,7 @@
<!--Only possible if a rating is link to the message, for now we can't comment if no rating
is link to the message (because publisher comment data
is on the rating.rating model - one comment max) -->
<t t-if="message.rating and message.rating.id" t-call="website_rating.chatter_rating_publisher">
<t t-if="message.rating and message.rating.id" t-call="portal_rating.chatter_rating_publisher">
<t t-set="is_publisher" t-value="widget.options['is_user_publisher']"/>
<t t-set="rating" t-value="message.rating"/>
</t>
@@ -38,7 +38,7 @@
<t t-extend="portal.Chatter">
<t t-jquery="t[t-call='portal.chatter_message_count']" t-operation="replace">
<t t-if="widget.options['display_rating']">
<t t-call="website_rating.rating_card"/>
<t t-call="portal_rating.rating_card"/>
</t>
<t t-if="!widget.options['display_rating']">
<t t-call="portal.chatter_message_count"/>
@@ -49,7 +49,7 @@
<!--
New templates specific of rating in Chatter
-->
<t t-name="website_rating.chatter_rating_publisher">
<t t-name="portal_rating.chatter_rating_publisher">
<div class="o_wrating_publisher_container">
<button t-if="is_publisher"
t-attf-class="btn px-2 btn-sm border o_wrating_js_publisher_comment_btn {{ rating.publisher_comment !== '' ? 'd-none' : '' }}"
@@ -57,12 +57,12 @@
<i class="fa fa-comment text-muted mr-1"/>Comment
</button>
<div class="o_wrating_publisher_comment mt-2 mb-2">
<t t-if="rating.publisher_comment" t-call="website_rating.chatter_rating_publisher_comment"/>
<t t-if="rating.publisher_comment" t-call="portal_rating.chatter_rating_publisher_comment"/>
</div>
</div>
</t>
<t t-name="website_rating.chatter_rating_publisher_comment">
<t t-name="portal_rating.chatter_rating_publisher_comment">
<div class="media o_portal_chatter_message">
<img class="o_portal_chatter_avatar" t-att-src="rating.publisher_avatar" alt="avatar"/>
<div class="media-body">
@@ -89,7 +89,7 @@
</div>
</div>
</t>
<t t-name="website_rating.chatter_rating_publisher_form">
<t t-name="portal_rating.chatter_rating_publisher_form">
<div t-if="is_publisher" class="media o_portal_chatter_message shadow bg-white rounded px-3 py-3 my-1">
<img class="o_portal_chatter_avatar" t-att-src="rating.publisher_avatar" alt="avatar"/>
<div class="media-body">
@@ -4,10 +4,10 @@
Popup Rating Composer Widget
It can alse be used to modify a message
-->
<t t-name="website_rating.PopupComposer">
<t t-name="portal_rating.PopupComposer">
<div class="d-flex flex-wrap align-items-center">
<div class="text-nowrap">
<t t-call="website_rating.rating_stars_static">
<t t-call="portal_rating.rating_stars_static">
<t t-set="val" t-value="widget.rating_avg || 0"/>
<t t-set="inline_mode" t-value="true"/>
</t>
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="website_rating.rating_stars_static">
<t t-name="portal_rating.rating_stars_static">
<t t-set="val_integer" t-value="Math.floor(val)"/>
<t t-set="val_decimal" t-value="val - val_integer"/>
<t t-set="empty_star" t-value="5 - (val_integer+Math.ceil(val_decimal))"/>
@@ -17,13 +17,13 @@
</div>
</t>
<t t-name="website_rating.rating_card">
<t t-name="portal_rating.rating_card">
<div class="row o_website_rating_card_container">
<div class="col-lg-3 offset-lg-2" t-if="!_.isEmpty(widget.get('rating_card_values'))">
<p><strong>Average</strong></p>
<div class="o_website_rating_avg text-center">
<h1><t t-esc="widget.get('rating_card_values')['avg']"/></h1>
<t t-call="website_rating.rating_stars_static">
<t t-call="portal_rating.rating_stars_static">
<t t-set="val" t-value="widget.get('rating_card_values')['avg'] || 0"/>
</t>
<t t-call="portal.chatter_message_count"/>
@@ -61,7 +61,7 @@
</div>
</t>
<t t-name="website_rating.rating_star_input">
<t t-name="portal_rating.rating_star_input">
<div class="o_rating_star_card" t-if="widget.options['display_rating']">
<t t-set="val_integer" t-value="Math.floor(default_rating)"/>
<t t-set="val_decimal" t-value="default_rating - val_integer"/>
+13
View File
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="assets_frontend" inherit_id="web_editor.assets_frontend" name="Portal Rating Assets">
<xpath expr="//link[last()]" position="after">
<link rel="stylesheet" type="text/scss" href="/portal_rating/static/src/scss/portal_rating.scss"/>
</xpath>
<xpath expr="//script[last()]" position="after">
<script type="text/javascript" src="/portal_rating/static/src/js/portal_chatter.js"></script>
<script type="text/javascript" src="/portal_rating/static/src/js/portal_composer.js"></script>
<script type="text/javascript" src="/portal_rating/static/src/js/portal_rating_composer.js"></script>
</xpath>
</template>
</odoo>
@@ -5,7 +5,7 @@
<field name="model">rating.rating</field>
<field name="inherit_id" ref="rating.rating_rating_view_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='feedback']" position="after">
<xpath expr="//field[@name='is_internal']" position="after">
<field name="publisher_comment" string="Publisher comment"/>
</xpath>
</field>
+2 -2
View File
@@ -930,8 +930,8 @@ class Task(models.Model):
return self.project_id.partner_id
return res
def rating_apply(self, rate, token=None, feedback=None, subtype=None):
return super(Task, self).rating_apply(rate, token=token, feedback=feedback, subtype="project.mt_task_rating")
def rating_apply(self, rate, token=None, feedback=None, subtype_xmlid=None):
return super(Task, self).rating_apply(rate, token=token, feedback=feedback, subtype_xmlid="project.mt_task_rating")
def _rating_get_parent_field_name(self):
return 'project_id'
+3 -2
View File
@@ -10,11 +10,12 @@ This module allows a customer to give rating.
'mail',
],
'data': [
'views/rating_view.xml',
'views/rating_rating_views.xml',
'views/rating_template.xml',
'views/mail_message_views.xml',
'views/assets.xml',
'security/ir.model.access.csv'
],
],
'installable': True,
'auto_install': False,
}
+1
View File
@@ -53,6 +53,7 @@ class Rating(models.Model):
'mail.message', string="Linked message",
index=True, ondelete='cascade',
help="Associated message when posting a review. Mainly used in website addons.")
is_internal = fields.Boolean('Employee Only', readonly=False, related='message_id.is_internal', store=True)
access_token = fields.Char('Security Token', default=_default_access_token, help="Access token to set the rating of the value")
consumed = fields.Boolean(string="Filled Rating", help="Enabled if the rating has been filled.")
+5 -4
View File
@@ -134,7 +134,8 @@ class RatingMixin(models.AbstractModel):
'partner_id': partner.id,
'rated_partner_id': rated_partner.id,
'res_model_id': record_model_id,
'res_id': self.id
'res_id': self.id,
'is_internal': False,
})
else:
rating = ratings[0]
@@ -168,7 +169,7 @@ class RatingMixin(models.AbstractModel):
subtype_id=subtype_id
)
def rating_apply(self, rate, token=None, feedback=None, subtype=None):
def rating_apply(self, rate, token=None, feedback=None, subtype_xmlid=None):
""" Apply a rating given a token. If the current model inherits from
mail.thread mixin, a message is posted on its chatter. User going through
this method should have at least employee rights because of rating
@@ -178,7 +179,7 @@ class RatingMixin(models.AbstractModel):
:param float rate : the rating value to apply
:param string token : access token
:param string feedback : additional feedback
:param string subtype : xml id of a valid mail.message.subtype
:param string subtype_xmlid : xml id of a valid mail.message.subtype
:returns rating.rating record
"""
@@ -194,7 +195,7 @@ class RatingMixin(models.AbstractModel):
self.message_post(
body="<img src='/rating/static/src/img/rating_%s.png' alt=':%s/10' style='width:18px;height:18px;float:left;margin-right: 5px;'/>%s"
% (rate, rate, feedback),
subtype=subtype or "mail.mt_comment",
subtype_xmlid=subtype_xmlid or "mail.mt_comment",
author_id=rating.partner_id and rating.partner_id.id or None # None will set the default author in mail_thread.py
)
if hasattr(self, 'stage_id') and self.stage_id and hasattr(self.stage_id, 'auto_validation_kanban_state') and self.stage_id.auto_validation_kanban_state:
@@ -0,0 +1,15 @@
<?xml version="1.0"?>
<odoo><data>
<record id="mail_message_view_form" model="ir.ui.view">
<field name="name">mail.message.view.form.inherit.rating</field>
<field name="model">mail.message</field>
<field name="inherit_id" ref="mail.mail_message_view_form"/>
<field name="arch" type="xml">
<xpath expr="//page[@name='page_tracking']" position="after">
<page string="Ratings" name="page_rating">
<field name="rating_ids"/>
</page>
</xpath>
</field>
</record>
</data></odoo>
@@ -32,22 +32,21 @@
</div>
<group>
<group>
<field name="res_name"/>
<field name="parent_res_name"/>
<field name="res_name" string="Document"/>
<field name="parent_res_name" string="Parent Holder"/>
<field name="rated_partner_id"/>
<field name="partner_id"/>
<field name="rating" invisible="1"/>
<field name="create_date" string="Submitted on"/>
<field name="is_internal"/>
</group>
<group>
<div colspan="2" class="text-center">
<field name="partner_id"/>
<div colspan="2" class="text-center" name="rating_image_container">
<field name="rating_image" widget='image'/>
<div class="mt4">
<strong><field name="rating_text"/></strong>
</div>
</div>
</group>
<group>
<field name="create_date" string="Submitted on"/>
<field name="feedback" attrs="{'invisible': [('feedback','=',False)]}"/>
</group>
</group>
@@ -169,4 +168,17 @@
</field>
</record>
<record id="rating_rating_view" model="ir.actions.act_window">
<field name="name">Ratings</field>
<field name="res_model">rating.rating</field>
<field name="view_mode">kanban,tree,graph,pivot,form</field>
</record>
<!-- Add menu entry in Technical/Discuss -->
<menuitem name="Ratings"
id="rating_rating_menu_technical"
parent="mail.mail_menu_technical"
action="rating_rating_view"
sequence="30"/>
</odoo>

Some files were not shown because too many files have changed in this diff Show More