Now having * sanitize: run the sanitizer to clean the html (removing javascripts, unwanted tags, ...) * sanitize_tags: only a subset of tags is allowed in html content. Unwelcomed tags are remove dand their content stripped. * sanitize_attributes: only a subset of attributes is allowed. * sanitize_style: only a subset of style attributes is allowed. Style attributes are parsed to keep only a white list. * strip_style: all style is removed. It bypasses sanitize_style as there is no need to sanitize something that is removed. * strip_classes: remove class attributes Fields parameters have also been updated to match the sanitize options. Html fields by default are sanitized with sanitize_tags activated but without any further options. All addons have been updated to match the new options according to their previous behavior.
214 lines
8.2 KiB
Python
214 lines
8.2 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from datetime import datetime
|
|
import random
|
|
|
|
from odoo import api, models, fields, _
|
|
from odoo.addons.website.models.website import slug
|
|
from odoo.tools.translate import html_translate
|
|
from odoo.tools import html2plaintext
|
|
|
|
|
|
class Blog(models.Model):
|
|
_name = 'blog.blog'
|
|
_description = 'Blogs'
|
|
_inherit = ['mail.thread', 'website.seo.metadata']
|
|
_order = 'name'
|
|
|
|
name = fields.Char('Blog Name', required=True, translate=True)
|
|
subtitle = fields.Char('Blog Subtitle', translate=True)
|
|
active = fields.Boolean('Active', default=True)
|
|
|
|
@api.multi
|
|
def write(self, vals):
|
|
res = super(Blog, self).write(vals)
|
|
if 'active' in vals:
|
|
# archiving/unarchiving a blog does it on its posts, too
|
|
post_ids = self.env['blog.post'].with_context(active_test=False).search([
|
|
('blog_id', 'in', self)
|
|
])
|
|
post_ids.write({'active': vals['active']})
|
|
return res
|
|
|
|
@api.multi
|
|
def all_tags(self, min_limit=1):
|
|
req = """
|
|
SELECT
|
|
p.blog_id, count(*), r.blog_tag_id
|
|
FROM
|
|
blog_post_blog_tag_rel r
|
|
join blog_post p on r.blog_post_id=p.id
|
|
WHERE
|
|
p.blog_id in %s
|
|
GROUP BY
|
|
p.blog_id,
|
|
r.blog_tag_id
|
|
ORDER BY
|
|
count(*) DESC
|
|
"""
|
|
self._cr.execute(req, [tuple(self.ids)])
|
|
tag_by_blog = {i.id: [] for i in self}
|
|
for blog_id, freq, tag_id in self._cr.fetchall():
|
|
if freq >= min_limit:
|
|
tag_by_blog[blog_id].append(tag_id)
|
|
|
|
BlogTag = self.env['blog.tag']
|
|
for blog_id in tag_by_blog:
|
|
tag_by_blog[blog_id] = BlogTag.browse(tag_by_blog[blog_id])
|
|
return tag_by_blog
|
|
|
|
|
|
class BlogTag(models.Model):
|
|
_name = 'blog.tag'
|
|
_description = 'Blog Tag'
|
|
_inherit = ['website.seo.metadata']
|
|
_order = 'name'
|
|
|
|
name = fields.Char('Name', required=True)
|
|
post_ids = fields.Many2many('blog.post', string='Posts')
|
|
|
|
_sql_constraints = [
|
|
('name_uniq', 'unique (name)', "Tag name already exists !"),
|
|
]
|
|
|
|
|
|
class BlogPost(models.Model):
|
|
_name = "blog.post"
|
|
_description = "Blog Post"
|
|
_inherit = ['mail.thread', 'website.seo.metadata', 'website.published.mixin']
|
|
_order = 'id DESC'
|
|
_mail_post_access = 'read'
|
|
|
|
@api.multi
|
|
def _compute_website_url(self):
|
|
super(BlogPost, self)._compute_website_url()
|
|
for blog_post in self:
|
|
blog_post.website_url = "/blog/%s/post/%s" % (slug(blog_post.blog_id), slug(blog_post))
|
|
|
|
@api.multi
|
|
def _compute_ranking(self):
|
|
res = {}
|
|
for blog_post in self:
|
|
age = datetime.now() - fields.Datetime.from_string(blog_post.post_date)
|
|
res[blog_post.id] = blog_post.visits * (0.5 + random.random()) / max(3, age.days)
|
|
return res
|
|
|
|
def _default_content(self):
|
|
return ''' <div class="container">
|
|
<section class="mt16 mb16">
|
|
<p class="o_default_snippet_text">''' + _("Start writing here...") + '''</p>
|
|
</section>
|
|
</div> '''
|
|
|
|
name = fields.Char('Title', required=True, translate=True, default='')
|
|
subtitle = fields.Char('Sub Title', translate=True)
|
|
author_id = fields.Many2one('res.partner', 'Author', default=lambda self: self.env.user.partner_id)
|
|
active = fields.Boolean('Active', default=True)
|
|
cover_properties = fields.Text(
|
|
'Cover Properties',
|
|
default='{"background-image": "none", "background-color": "oe_black", "opacity": "0.2", "resize_class": ""}')
|
|
blog_id = fields.Many2one('blog.blog', 'Blog', required=True, ondelete='cascade')
|
|
tag_ids = fields.Many2many('blog.tag', string='Tags')
|
|
content = fields.Html('Content', default=_default_content, translate=html_translate, sanitize_attributes=False)
|
|
teaser = fields.Text('Teaser', compute='_compute_teaser', inverse='_set_teaser')
|
|
teaser_manual = fields.Text(string='Teaser Content')
|
|
|
|
website_message_ids = fields.One2many(
|
|
'mail.message', 'res_id',
|
|
domain=lambda self: [
|
|
'&', '&', ('model', '=', self._name), ('message_type', '=', 'comment'), ('path', '=', False)
|
|
],
|
|
string='Website Messages',
|
|
help="Website communication history",
|
|
)
|
|
# creation / update stuff
|
|
create_date = fields.Datetime('Created on', index=True, readonly=True)
|
|
published_date = fields.Datetime('Published Date')
|
|
post_date = fields.Datetime('Published date', compute='_compute_post_date', inverse='_set_post_date', store=True)
|
|
create_uid = fields.Many2one('res.users', 'Created by', index=True, readonly=True)
|
|
write_date = fields.Datetime('Last Modified on', index=True, readonly=True)
|
|
write_uid = fields.Many2one('res.users', 'Last Contributor', index=True, readonly=True)
|
|
author_avatar = fields.Binary(related='author_id.image_small', string="Avatar")
|
|
visits = fields.Integer('No of Views')
|
|
ranking = fields.Float(compute='_compute_ranking', string='Ranking')
|
|
|
|
@api.multi
|
|
@api.depends('content', 'teaser_manual')
|
|
def _compute_teaser(self):
|
|
for blog_post in self:
|
|
if blog_post.teaser_manual:
|
|
blog_post.teaser = blog_post.teaser_manual
|
|
else:
|
|
content = html2plaintext(blog_post.content).replace('\n', ' ')
|
|
blog_post.teaser = ' '.join(filter(None, content.split(' '))[:50]) + '...'
|
|
|
|
@api.multi
|
|
def _set_teaser(self):
|
|
for blog_post in self:
|
|
blog_post.teaser_manual = blog_post.teaser
|
|
|
|
@api.multi
|
|
@api.depends('create_date', 'published_date')
|
|
def _compute_post_date(self):
|
|
for blog_post in self:
|
|
if blog_post.published_date:
|
|
blog_post.post_date = blog_post.published_date
|
|
else:
|
|
blog_post.post_date = blog_post.create_date
|
|
|
|
@api.multi
|
|
def _set_post_date(self):
|
|
for blog_post in self:
|
|
blog_post.published_date = blog_post.post_date
|
|
|
|
def _check_for_publication(self, vals):
|
|
if vals.get('website_published'):
|
|
for post in self:
|
|
post.blog_id.message_post_with_view(
|
|
'website_blog.blog_post_template_new_post',
|
|
subject=post.name,
|
|
values={'post': post},
|
|
subtype_id=self.env['ir.model.data'].sudo().xmlid_to_res_id('website_blog.mt_blog_blog_published'))
|
|
return True
|
|
return False
|
|
|
|
@api.model
|
|
def create(self, vals):
|
|
post_id = super(BlogPost, self.with_context(mail_create_nolog=True)).create(vals)
|
|
post_id._check_for_publication(vals)
|
|
return post_id
|
|
|
|
@api.multi
|
|
def write(self, vals):
|
|
self.ensure_one()
|
|
if 'website_published' in vals and 'published_date' not in vals:
|
|
if self.published_date <= fields.Datetime.now():
|
|
vals['published_date'] = vals['website_published'] and fields.Datetime.now()
|
|
result = super(BlogPost, self).write(vals)
|
|
self._check_for_publication(vals)
|
|
return result
|
|
|
|
@api.multi
|
|
def get_access_action(self):
|
|
""" Override method that generated the link to access the document. Instead
|
|
of the classic form view, redirect to the post on the website directly """
|
|
self.ensure_one()
|
|
return {
|
|
'type': 'ir.actions.act_url',
|
|
'url': '/blog/%s/post/%s' % (self.blog_id.id, self.id),
|
|
'target': 'self',
|
|
'res_id': post.id,
|
|
}
|
|
|
|
@api.multi
|
|
def _notification_get_recipient_groups(self, message, recipients):
|
|
""" Override to set the access button: everyone can see an access button
|
|
on their notification email. It will lead on the website view of the
|
|
post. """
|
|
res = super(BlogPost, self)._notification_get_recipient_groups(message, recipients)
|
|
access_action = self._notification_link_helper('view', model=message.model, res_id=message.res_id)
|
|
for category, data in res.iteritems():
|
|
res[category]['button_access'] = {'url': access_action, 'title': _('View Blog Post')}
|
|
return res
|