New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Master website chatter jem #15405
Master website chatter jem #15405
Conversation
5e9d7ff
to
ec8001c
Compare
ec8001c
to
c4d11db
Compare
c4d11db
to
b383828
Compare
b383828
to
eb775a0
Compare
|
||
def _special_access_object(res_model, res_id, token='', token_field=''): | ||
record = request.env[res_model].browse(res_id) | ||
if token and record and token == getattr(record.sudo(), token_field, None): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we use consteq to compare tokens ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, but it have to check than the token_field is filled to avoid given None as argument to consteq. So the condition looks like :
if token and record and getattr(record.sudo(), token_field, None) and consteq(getattr(record.sudo(), token_field, None), str(token)):
@@ -27,7 +33,9 @@ def _message_post_helper(res_model='', res_id=None, message='', token='', token_ | |||
""" | |||
record = request.env[res_model].browse(res_id) | |||
author_id = request.env.user.partner_id.id if request.env.user.partner_id else False | |||
if token and record and token == getattr(record.sudo(), token_field, None): | |||
|
|||
access_as_sudo = _special_access_object(res_model, res_id, token=token, token_field=token_field) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we do something if the token is not correct ? If I understand well the record will not be accessed as sudo, but maybe it could just avoid doing stuff if a wrong token is given.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Like raising a forbidden
? It could be cool indeed !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so do we do it ? does it make sense or is it overkill ?
return request.redirect(url) | ||
|
||
def _website_message_post(self, res_model, res_id, message, **kw): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the purpose of that wrapper ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is a hook, to create the rating in website_rating. Otherwise, super will return the url, and don't have the message_id to associate the rating with.
Maybe _message_post_helper
should be a controller method. What do you think ?
@http.route(['/website_mail/post/post'], type='http', methods=['POST'], auth='public', website=True) | ||
def chatter_post(self, res_model='', res_id=None, message='', redirect=None, **kw): | ||
def chatter_post(self, res_model, res_id, message, **kw): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a naming thing: chatter_post, website_message_post, message_post_helper, website_chatter_init, website_message_fetch -> there is some space for prefixing convention cleaning ;) .
|
||
@http.route('/website_mail/fetch', type='json', auth='public', website=True) | ||
def website_message_fetch(self, res_model, res_id, domain=False, limit=False, offset=False, **kw): | ||
if not limit: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not putting it directly in the parameters ?
# Only search into website_message_ids, so apply the same domain to perform only one search | ||
# extract domain from the 'website_message_ids' field | ||
field_domain = request.env[res_model]._fields['website_message_ids'].domain | ||
domain += field_domain(request.env[res_model].browse(res_id)) if callable(field_domain) else field_domain |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually I don't think we have to browse res_id. Indeed domains are only model dependent, not record dependent at the ORM level. I think we should keep the same semantic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Smart guy !
domain += [('res_id', '=', res_id)] | ||
# Check access | ||
Message = request.env['mail.message'] | ||
access_as_sudo = _special_access_object(res_model, res_id, token=kw.get('token'), token_field=kw.get('token_field')) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For example here, shouldn't a wrong token send you to hell, instead of probably crashing due to access rights issues ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technical review. Ow.
}, | ||
fetch_message: function(domain){ | ||
var self = this; | ||
var data = this._prepare_fetch_message_params(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unnecessary variable
_load_templates: function(){ | ||
return ajax.loadXML('/website_mail/static/src/xml/website_mail.xml', qweb); | ||
}, | ||
fetch_message: function(domain){ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we use the same naming convention, and use message_fetch, message_fetch_prepare, ... ?
init: function(parent, options){ | ||
this._super.apply(this, arguments); | ||
console.log($(parent).data()); | ||
this.options = _.defaults($(parent).data(), { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Strange construct, where does it come from ?
}); | ||
return messages; | ||
}, | ||
_pager: function(page, total, step, scope){ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would either remove parameters or explicitely set them to this.get(parameter) when calling the function, to lessen the number of variable processing.
var pmin = Math.max(page - parseInt(Math.floor(scope/2)), 1); | ||
var pmax = Math.min(pmin + scope, page_count); | ||
|
||
if(pmax - pmin < scope){ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whut ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I simply transform the algo from python side. Without trying to understand ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, looks like the python side did the work twice because it wasn't sure it did it well ?
It looks like it's a verification of the min/max done above :p It shouldn't ever be useful
message.rating_value = mapping.get(message.id, 0.0) | ||
|
||
def _search_rating_value(self, operator, value): | ||
self.env.cr.execute(""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I fear it will become quickly hard to perform a search. Once you have a lot of ratings, you will have a domain term like [('id', 'in', [A MAGNIFICIENT ARRAY])]. Don't you think we should store the rating value in order to search efficiently on it ?
this.change_current_page(1, domain); | ||
}, | ||
// utils | ||
round_to_half: function(value) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not a JS expert, but don't we have access to some method allowing to do it :p ?
if(this.get('rating_value')){ | ||
domain = [['rating_value', '=', this.get('rating_value')]]; | ||
} | ||
console.log('change to ',this.get('rating_value')); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Leftover log.
@@ -1,5 +1,4 @@ | |||
import ir_http | |||
import rating |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mmmh ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Normal, since I have moved this file from website_sale ;)
@@ -2,6 +2,12 @@ | |||
<odoo> | |||
<data noupdate="1"> | |||
|
|||
<record id="mail_message_rule_public" model="ir.rule"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is some mismatch in the commits. One commit add a hardcoded domain, one commit adds a void file, and this one finishes the work by removing the hardcoded domain and adding stuff in the void file. I fear you will have to do strange stuff when rebasing. Bon courage.
5c794d7
to
839be6a
Compare
839be6a
to
a329f63
Compare
217c6bf
to
5fdac06
Compare
5bafd83
to
e934cde
Compare
e934cde
to
344de27
Compare
|
||
def _special_access_object(res_model, res_id, token='', token_field=''): | ||
record = request.env[res_model].browse(res_id) | ||
if token and record and getattr(record.sudo(), token_field, None) and consteq(getattr(record.sudo(), token_field, None), token): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should remove the None from the second getattr(record.sudo(), token_field, None)
We already know it exists so no default is needed, and should we remove the verification before somehow, consteq raises a TypeError with a None argument.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd also do a .exists() on the record, and maybe put the .sudo() on the first line too.
@@ -27,7 +33,9 @@ def _message_post_helper(res_model='', res_id=None, message='', token='', token_ | |||
""" | |||
record = request.env[res_model].browse(res_id) | |||
author_id = request.env.user.partner_id.id if request.env.user.partner_id else False | |||
if token and record and token == getattr(record.sudo(), token_field, None): | |||
|
|||
access_as_sudo = _special_access_object(res_model, res_id, token=token, token_field=token_field) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so do we do it ? does it make sense or is it overkill ?
//-------------------------------------------------------------------------- | ||
|
||
/** | ||
* Fetch the messages nad the message count from the server for the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nad ^^
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oupssss
* | ||
* @private | ||
* @params {Number} page | ||
* @returns {Object} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
optional parameters are set in brackets @param {number} [page]
(+ param singular and JS types don't start with a capital letters :p )
var step = this.options['pager_step']; | ||
|
||
// Compute Pager | ||
var page_count = parseInt(Math.ceil(parseFloat(total) / step)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think the parseInt and parseFloat are necessary here, unless this.get('message_count') is a string, but I don't think it can be one.
if(domain){ | ||
d = domain; | ||
} | ||
this.set('domain', d); // trigger fetch message |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand why we set this.get('domain')
to a clone of itself, or why we clone the this.get('domain')
if we're setting it to the domain parameter.
If it's to trigger the domain onchange, maybe we could just the method instead of cloning it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if we keep the onchange triggers this way: var d = (domain ? domain : _.clone(this.get('domain'))
But imo changing the domain should change the page (or force its onchange if no page change), and changing the page should trigger the message fetching.
var pmin = Math.max(page - parseInt(Math.floor(scope/2)), 1); | ||
var pmax = Math.min(pmin + scope, page_count); | ||
|
||
if(pmax - pmin < scope){ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, looks like the python side did the work twice because it wasn't sure it did it well ?
It looks like it's a verification of the min/max done above :p It shouldn't ever be useful
<input type='hidden' name="sha_time" t-att-value="widget.options['sha_time']" t-if="widget.options['sha_time']"/> | ||
<input type='hidden' name="token_field" t-att-value="widget.options['token_field']" t-if="widget.options['token_field']"/> | ||
<div class="alert alert-danger mt8 mb0 o_website_mail_composer_error" style="display:none;"> | ||
Oops! Something went wrong. Try to reload the page and to log in. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
gotto make a choice "and log in" or "to log in" but not "and to log in" ^^
/** | ||
* Update the messages format | ||
* | ||
* @params {Array<Object>} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@param
comment to propagate in this file too
* @private | ||
* @param {MouseEvent} event | ||
*/ | ||
_onMoveStar: function(e){ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
while testing the stars on the website, I found it a bit odd that after you click a certain rating, if you happen to hover on another star, your clicked rating gets lost.
Also, if you send a rating without any message, you get a refresh, but the rating doesn't go through. It'd be better if it didn't submit but had something like when you don't fill a required field on a form.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and it's done !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
first batch of answers
is_user_public = bool(request.env.user == request.website.user_id) | ||
message_data = self.website_message_fetch(res_model, res_id, domain=domain, limit=limit, **kwargs) | ||
display_composer = False | ||
if kwargs.get('allow_composer'): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those are very optional parameters, so not really necessary, IMHO.
} | ||
|
||
@http.route('/website_mail/fetch', type='json', auth='public', website=True) | ||
def website_message_fetch(self, res_model, res_id, domain=False, limit=10, offset=0, **kw): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same answer here ;)
//-------------------------------------------------------------------------- | ||
|
||
/** | ||
* Fetch the messages nad the message count from the server for the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oupssss
</ul> | ||
</section> | ||
<div id="discussion" class="hidden-print o_website_mail_thread" | ||
t-att-data-token="token" t-att-public-user="website.env.user == request.website.user_id" t-att-data-res_model="object._name" t-att-data-res_id="object.id" t-att-data-token_field="token_field" t-att-data-pager_step="message_per_page or 10" t-att-data-allow_composer="'0' if disable_composer else '1'"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Setting t-att-token=="object[token_field]"
will crash if token_field is not set. And since we usually use the template without the token mecanism, it should be better like this ;)
344de27
to
e3067fb
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
second
* @param {Number} page | ||
* @param {Array} domain | ||
*/ | ||
_changeCurrentPage: function(page, domain){ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes that for this, but when changing domain, I have to reset the current_page
to trigger the messages fetch. But if I set the current_page to 1, and I'm already on the page one, replacing 1
by 1
does not trigger the fetching. So I have to use the domain, which is an object to trigger the fetching.
* @private | ||
* @param {MouseEvent} event | ||
*/ | ||
_onMoveStar: function(e){ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and it's done !
e3067fb
to
707ad75
Compare
707ad75
to
b1a97df
Compare
mail.thread mixin will (re)provide the field 'website_message_ids'. Models can redifined its domain, since it can differ a bit (blog, forum, ... are not the same). Even if the domain can differ, we still keep it on the mixin since it will be required for the new chatter frontend (see following commits).
Message are now fetched and display using javascript to optimize performance. Also the chatter is completely rewrite : add pager, filter possiblity, ... This commit is mainly technical: posting feature does not change. Only the display with pager is new. The goal is also to prepare frontend chatter for a better integration with rating widget. The mail.thread mixin is extended to manage the field website_message_ids (display all messages that can be seen on frontend by user (employee or not). When fetching or posting message, a check is done to see if we need to do it as sudo(). website_mail implement posting messages with token or sha_in (using in website_quote). The same verification is now done when fetching messages, via '_special_access_object' method.
rating module provide now post a message with a rating value, though `message_post` method. 'mail.message' also has now a `rating_value` field to ease searching and filtering. This will help the ecommerce (and other website modules) to post a comment associate with a rating.
Extend new dynamic chatter to manage rating when posting comment. The model should inherit of rating.mixin and its frontend chatter should allow rating to enjoy star widget and rating card. This is more generic : stats, rating, ... are manage in one place (this module) for all document.
Slide provided a chatter where you can post message as public user. We decided to drop that feature in order to avoid spamming. Like for all other document, you must at least, be portal user to post a comment. Reusing the standart chatter highly simplify the code.
13f6626
to
870b511
Compare
This commit brings a new frontend chatter. The old one displayed all message on the website page, and that could be very slow when many many messages are posted on a document. The new chatter is more dynamic: we introduce a pager, to fetch and display only a few messages. It also still support to post a message if you are not logged in, using a token mecanism (see website_quote). Moreover, a better integration with rating: in the eshop, when posting a comment, the visitor can add a rating value (stars).
Task : https://www.odoo.com/web#id=30059&view_type=form&model=project.task&action=333&active_id=809&menu_id=4720
Purpose :
In all website modules (blog, ecommerce, ...) we can post comments. But having a huge number of comments can be problemtic, since they all will be displayed on the same page (loading page time can be .... long).
The udemy comment zone is quite smart (see https://www.udemy.com/learn-html5-programming-from-scratch/ for instance). There is a pager for comments, and you can filter the rating (ecommerce support rating).
--
I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr