[IMP] website_blog: path computation (unique key) for paragraphs in medium-like blogs
authorThibault Delavallée <tde@openerp.com>
Thu, 3 Apr 2014 11:26:56 +0000 (13:26 +0200)
committerThibault Delavallée <tde@openerp.com>
Thu, 3 Apr 2014 11:26:56 +0000 (13:26 +0200)
is now done server-side and stored directly in the html content, as post processing.
The front-end attaches messages based on the attribute data-chatter-id, but do not
compute the attribute itself anymore.
Misc cleaning of code.

bzr revid: tde@openerp.com-20140403112656-ane3ovhh70qdc8b4

addons/website_blog/controllers/main.py
addons/website_blog/models/mail_message.py
addons/website_blog/models/website_blog.py
addons/website_blog/static/src/js/website_blog.inline.discussion.js
addons/website_blog/static/src/js/website_blog.js
addons/website_blog/views/website_blog_templates.xml

index 5e4baf1..1a0bcf9 100644 (file)
@@ -210,7 +210,6 @@ class WebsiteBlog(http.Controller):
             ('id', 'not in', visited_ids),
         ], order='ranking desc', limit=1, context=context)
         next_post = next_post_id and blog_post_obj.browse(cr, uid, next_post_id[0], context=context) or False
-        print next_post
 
         values = {
             'tags': tags,
@@ -259,7 +258,7 @@ class WebsiteBlog(http.Controller):
             type='comment',
             subtype='mt_comment',
             author_id=partner_ids[0],
-            discussion=post.get('discussion'),
+            path=post.get('path', False),
             context=dict(context, mail_create_nosubcribe=True))
         return message_id
 
@@ -327,10 +326,10 @@ class WebsiteBlog(http.Controller):
         return werkzeug.utils.redirect("/blog/%s/post/%s/?enable_editor=1" % (post.blog_id.id, nid))
 
     @http.route('/blogpost/get_discussion/', type='json', auth="public", website=True)
-    def discussion(self, post_id=0, discussion=None, count=False, **post):
+    def discussion(self, post_id=0, path=None, count=False, **post):
         cr, uid, context = request.cr, request.uid, request.context
         mail_obj = request.registry.get('mail.message')
-        domain = [('res_id', '=', int(post_id)) ,('model','=','blog.post'), ('discussion_key', '=', discussion)]
+        domain = [('res_id', '=', int(post_id)), ('model', '=', 'blog.post'), ('path', '=', path)]
         #check current user belongs to website publisher group
         publish = request.registry['res.users'].has_group(cr, uid, 'base.group_website_publisher')
         if not publish:
index 72fdb01..e1b60f6 100644 (file)
@@ -7,6 +7,7 @@ class MailMessage(osv.Model):
     _inherit = 'mail.message'
 
     _columns = {
-        'discussion_key': fields.char('Discussion Key',
-            help='Used in Blogs to display messages in a group based on their discussion key.'),
+        'path': fields.char(
+            'Discussion Path', select=1,
+            help='Used to display messages in a paragraph-based chatter using a unique path;'),
     }
index 784c7bd..b2ab267 100644 (file)
@@ -2,9 +2,11 @@
 
 from datetime import datetime
 import difflib
+import lxml
 import random
 
 from openerp import tools
+from openerp import SUPERUSER_ID
 from openerp.osv import osv, fields
 from openerp.tools.translate import _
 
@@ -66,7 +68,7 @@ class BlogPost(osv.Model):
         'website_message_ids': fields.one2many(
             'mail.message', 'res_id',
             domain=lambda self: [
-                '&', '&', ('model', '=', self._name), ('type', '=', 'comment'), ('discussion_key', '=', False)
+                '&', '&', ('model', '=', self._name), ('type', '=', 'comment'), ('path', '=', False)
             ],
             string='Website Messages',
             help="Website communication history",
@@ -103,6 +105,59 @@ class BlogPost(osv.Model):
         'author_id': lambda self, cr, uid, ctx=None: self.pool['res.users'].browse(cr, uid, uid, context=ctx).partner_id.id,
     }
 
+    def html_tag_nodes(self, html, attribute=None, tags=None, context=None):
+        """ Processing of html content to tag paragraphs and set them an unique
+        ID.
+        :return result: (html, mappin), where html is the updated html with ID
+                        and mapping is a list of (old_ID, new_ID), where old_ID
+                        is None is the paragraph is a new one. """
+        mapping = []
+        if not html:
+            return html, mapping
+        if tags is None:
+            tags = ['p']
+        if attribute is None:
+            attribute = 'data-unique-id'
+        counter = 0
+
+        # form a tree
+        root = lxml.html.fragment_fromstring(html, create_parent='div')
+        if not len(root) and root.text is None and root.tail is None:
+            return html, mapping
+
+        # check all nodes, replace :
+        # - img src -> check URL
+        # - a href -> check URL
+        for node in root.iter():
+            if not node.tag in tags:
+                continue
+            ancestor_tags = [parent.tag for parent in node.iterancestors()]
+            ancestor_tags.pop()
+            new_attribute = '/'.join(reversed(ancestor_tags))
+            old_attribute = node.get(attribute)
+            node.set(attribute, new_attribute)
+            mapping.append((old_attribute, counter))
+            counter += 1
+
+        html = lxml.html.tostring(root, pretty_print=False, method='html')
+        # this is ugly, but lxml/etree tostring want to put everything in a 'div' that breaks the editor -> remove that
+        if html.startswith('<div>') and html.endswith('</div>'):
+            html = html[5:-6]
+        return html, mapping
+
+    def _postproces_content(self, cr, uid, id, content=None, context=None):
+        if content is None:
+            content = self.browse(cr, uid, id, context=context).content
+        if content is False:
+            return content
+        content, mapping = self.html_tag_nodes(content, attribute='data-chatter-id', tags=['p'], context=context)
+        for old_attribute, new_attribute in mapping:
+            if not old_attribute:
+                continue
+            msg_ids = self.pool['mail.message'].search(cr, SUPERUSER_ID, [('path', '=', old_attribute)], context=context)
+            self.pool['mail.message'].write(cr, SUPERUSER_ID, msg_ids, {'path': new_attribute}, context=context)
+        return content
+
     def create_history(self, cr, uid, ids, vals, context=None):
         for i in ids:
             history = self.pool.get('blog.post.history')
@@ -116,12 +171,16 @@ class BlogPost(osv.Model):
     def create(self, cr, uid, vals, context=None):
         if context is None:
             context = {}
+        if 'content' in vals:
+            vals['content'] = self._postproces_content(cr, uid, None, vals['content'], context=context)
         create_context = dict(context, mail_create_nolog=True)
         post_id = super(BlogPost, self).create(cr, uid, vals, context=create_context)
         self.create_history(cr, uid, [post_id], vals, context)
         return post_id
 
     def write(self, cr, uid, ids, vals, context=None):
+        if 'content' in vals:
+            vals['content'] = self._postproces_content(cr, uid, None, vals['content'], context=context)
         result = super(BlogPost, self).write(cr, uid, ids, vals, context)
         self.create_history(cr, uid, ids, vals, context)
         return result
index 574a298..56851c3 100644 (file)
@@ -11,7 +11,6 @@
             var self = this ;
             self.discus_identifier;
             var defaults = {
-                identifier: 'name',
                 position: 'right',
                 post_id: $('#blog_post_name').attr('data-blog-id'),
                 content : false,
@@ -23,7 +22,7 @@
         do_render: function(data) {
             var self = this;
             if ($('#discussions_wrapper').length === 0 && self.settings.content.length > 0) {
-                $('<div id="discussions_wrapper"></div>').appendTo($('#blog_content'));
+                $('<div id="discussions_wrapper"></div>').insertAfter($('#blog_content'));
             }
             // Attach a discussion to each paragraph.
             $(self.settings.content).each(function(i) {
@@ -36,8 +35,8 @@
                 }
                 if(!$(event.target).hasClass('discussion-link') && !$(event.target).parents('.popover').length){
                     if($('.move_discuss').length){
-                        $('.js_discuss').next().removeClass('move_discuss');
-                        $('.js_discuss').next().animate({
+                        $('[enable_chatter_discuss=True]').removeClass('move_discuss');
+                        $('[enable_chatter_discuss=True]').animate({
                             'marginLeft': "+=40%"
                         });
                         $('#discussions_wrapper').animate({
                 }
             });
         },
-        prepare_data : function(identifier,comment_count) {
+        prepare_data : function(identifier, comment_count) {
             var self = this;
             return openerp.jsonRpc("/blogpost/get_discussion/", 'call', {
                 'post_id': self.settings.post_id,
-                'discussion':identifier,
-                'count' : comment_count, //if true only get length of total comment, display on discussion thread.
+                'path': identifier,
+                'count': comment_count, //if true only get length of total comment, display on discussion thread.
             })
         },
         discussion_handler : function(i, node) {
             var self = this;
-            var identifier;
-            // You can force a specific identifier by adding an attribute to the paragraph.
-            if (node.attr('data-discus-identifier')) {
-                identifier = node.attr('data-discus-identifier');
-            }
-            else {
-                while ($('[data-discus-identifier="' + self.settings.identifier + '-' + i + '"]').length > 0) {
-                    i++;
-                }
-                identifier = self.settings.identifier + '-' + i;
+            var identifier = node.attr('data-chatter-id');
+            if (identifier) {
+                self.prepare_data(identifier, true).then( function (data) {
+                    self.prepare_discuss_link(data, identifier, node);
+                });
             }
-            self.prepare_data(identifier,true).then(function(data){
-                self.prepare_discuss_link(data,identifier,node)
-            });
         },
         prepare_discuss_link :  function(data, identifier, node) {
             var self = this;
@@ -79,7 +70,7 @@
                 .attr('data-discus-identifier', identifier)
                 .attr('data-discus-position', self.settings.position)
                 .text(data > 0 ? data : '+')
-                .attr('data-contentwrapper','.mycontent')
+                .attr('data-contentwrapper', '.mycontent')
                 .wrap('<div class="discussion" />')
                 .parent()
                 .appendTo('#discussions_wrapper');
@@ -87,7 +78,8 @@
                 'top': node.offset().top,
                 'left': self.settings.position == 'right' ? node.outerWidth() + node.offset().left: node.offset().left - a.outerWidth()
             });
-            node.attr('data-discus-identifier', identifier).mouseover(function() {
+            // node.attr('data-discus-identifier', identifier)
+            node.mouseover(function() {
                 a.addClass("hovered");
             }).mouseout(function() {
                 a.removeClass("hovered");
@@ -96,8 +88,8 @@
             a.delegate('a.discussion-link', "click", function(e) {
                 e.preventDefault();
                 if(!$('.move_discuss').length){
-                    $('.js_discuss').next().addClass('move_discuss');
-                    $('.js_discuss').next().animate({
+                    $('[enable_chatter_discuss=True]').addClass('move_discuss');
+                    $('[enable_chatter_discuss=True]').animate({
                         'marginLeft': "-=40%"
                     });
                     $('#discussions_wrapper').animate({
             if(!val) return
             openerp.jsonRpc("/blogpost/post_discussion", 'call', {
                 'blog_post_id': self.settings.post_id,
-                'discussion': self.discus_identifier,
+                'path': self.discus_identifier,
                 'comment': val[0],
                 'name' : val[1],
                 'email': val[2],
index 6e8dbb2..3217bf1 100644 (file)
@@ -24,8 +24,8 @@ $(document).ready(function() {
         });
     }
 
-    var content = $(".js_discuss").next().find('p');
-    if(content){
+    var content = $("div[enable_chatter_discuss='True']").find('p[data-chatter-id]');
+    if (content) {
         openerp.jsonRpc("/blog/get_user/", 'call', {}).then(function(data){
             $('#discussions_wrapper').empty();
             new openerp.website.blog_discussion({'content' : content, 'public_user':data[0]});
index 121e497..fa6c6af 100644 (file)
 <!-- Options: Blog Post: user can add Inline Discussion -->
 <template id="opt_blog_post_inline_discussion" name="Allow comment in text"
         inherit_option_id="website_blog.blog_post_complete">
-    <xpath expr="//div[@id='blog_content']" position="before">
-        <div class="js_discuss"/>
+    <xpath expr="//div[@id='blog_content']" position="attributes">
+        <attribute name="enable_chatter_discuss">True</attribute>
     </xpath>
 </template>