summaryrefslogtreecommitdiff
path: root/addons/website_blog/models
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/website_blog/models
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/website_blog/models')
-rw-r--r--addons/website_blog/models/__init__.py5
-rw-r--r--addons/website_blog/models/website.py64
-rw-r--r--addons/website_blog/models/website_blog.py269
3 files changed, 338 insertions, 0 deletions
diff --git a/addons/website_blog/models/__init__.py b/addons/website_blog/models/__init__.py
new file mode 100644
index 00000000..71070d1a
--- /dev/null
+++ b/addons/website_blog/models/__init__.py
@@ -0,0 +1,5 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import website
+from . import website_blog
diff --git a/addons/website_blog/models/website.py b/addons/website_blog/models/website.py
new file mode 100644
index 00000000..fb153e3d
--- /dev/null
+++ b/addons/website_blog/models/website.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, models, _
+from odoo.addons.http_routing.models.ir_http import url_for
+
+
+class Website(models.Model):
+ _inherit = "website"
+
+ @api.model
+ def page_search_dependencies(self, page_id=False):
+ dep = super(Website, self).page_search_dependencies(page_id=page_id)
+
+ page = self.env['website.page'].browse(int(page_id))
+ path = page.url
+
+ dom = [
+ ('content', 'ilike', path)
+ ]
+ posts = self.env['blog.post'].search(dom)
+ if posts:
+ page_key = _('Blog Post')
+ if len(posts) > 1:
+ page_key = _('Blog Posts')
+ dep[page_key] = []
+ for p in posts:
+ dep[page_key].append({
+ 'text': _('Blog Post <b>%s</b> seems to have a link to this page !', p.name),
+ 'item': p.name,
+ 'link': p.website_url,
+ })
+
+ return dep
+
+ @api.model
+ def page_search_key_dependencies(self, page_id=False):
+ dep = super(Website, self).page_search_key_dependencies(page_id=page_id)
+
+ page = self.env['website.page'].browse(int(page_id))
+ key = page.key
+
+ dom = [
+ ('content', 'ilike', key)
+ ]
+ posts = self.env['blog.post'].search(dom)
+ if posts:
+ page_key = _('Blog Post')
+ if len(posts) > 1:
+ page_key = _('Blog Posts')
+ dep[page_key] = []
+ for p in posts:
+ dep[page_key].append({
+ 'text': _('Blog Post <b>%s</b> seems to be calling this file !', p.name),
+ 'item': p.name,
+ 'link': p.website_url,
+ })
+
+ return dep
+
+ def get_suggested_controllers(self):
+ suggested_controllers = super(Website, self).get_suggested_controllers()
+ suggested_controllers.append((_('Blog'), url_for('/blog'), 'website_blog'))
+ return suggested_controllers
diff --git a/addons/website_blog/models/website_blog.py b/addons/website_blog/models/website_blog.py
new file mode 100644
index 00000000..4341966c
--- /dev/null
+++ b/addons/website_blog/models/website_blog.py
@@ -0,0 +1,269 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from datetime import datetime
+import random
+import json
+
+from odoo import api, models, fields, _
+from odoo.addons.http_routing.models.ir_http import slug
+from odoo.tools.translate import html_translate
+from odoo.tools import html2plaintext
+
+
+class Blog(models.Model):
+ _name = 'blog.blog'
+ _description = 'Blog'
+ _inherit = ['mail.thread', 'website.seo.metadata', 'website.multi.mixin', 'website.cover_properties.mixin']
+ _order = 'name'
+
+ name = fields.Char('Blog Name', required=True, translate=True)
+ subtitle = fields.Char('Blog Subtitle', translate=True)
+ active = fields.Boolean('Active', default=True)
+ content = fields.Html('Content', translate=html_translate, sanitize=False)
+ blog_post_ids = fields.One2many('blog.post', 'blog_id', 'Blog Posts')
+ blog_post_count = fields.Integer("Posts", compute='_compute_blog_post_count')
+
+ @api.depends('blog_post_ids')
+ def _compute_blog_post_count(self):
+ for record in self:
+ record.blog_post_count = len(record.blog_post_ids)
+
+ 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.ids)
+ ])
+ for blog_post in post_ids:
+ blog_post.active = vals['active']
+ return res
+
+ @api.returns('mail.message', lambda value: value.id)
+ def message_post(self, *, parent_id=False, subtype_id=False, **kwargs):
+ """ Temporary workaround to avoid spam. If someone replies on a channel
+ through the 'Presentation Published' email, it should be considered as a
+ note as we don't want all channel followers to be notified of this answer. """
+ self.ensure_one()
+ if parent_id:
+ parent_message = self.env['mail.message'].sudo().browse(parent_id)
+ if parent_message.subtype_id and parent_message.subtype_id == self.env.ref('website_blog.mt_blog_blog_published'):
+ subtype_id = self.env.ref('mail.mt_note').id
+ return super(Blog, self).message_post(parent_id=parent_id, subtype_id=subtype_id, **kwargs)
+
+ def all_tags(self, join=False, min_limit=1):
+ BlogTag = self.env['blog.tag']
+ 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}
+ all_tags = set()
+ for blog_id, freq, tag_id in self._cr.fetchall():
+ if freq >= min_limit:
+ if join:
+ all_tags.add(tag_id)
+ else:
+ tag_by_blog[blog_id].append(tag_id)
+
+ if join:
+ return BlogTag.browse(all_tags)
+
+ for blog_id in tag_by_blog:
+ tag_by_blog[blog_id] = BlogTag.browse(tag_by_blog[blog_id])
+
+ return tag_by_blog
+
+
+class BlogTagCategory(models.Model):
+ _name = 'blog.tag.category'
+ _description = 'Blog Tag Category'
+ _order = 'name'
+
+ name = fields.Char('Name', required=True, translate=True)
+ tag_ids = fields.One2many('blog.tag', 'category_id', string='Tags')
+
+ _sql_constraints = [
+ ('name_uniq', 'unique (name)', "Tag category already exists !"),
+ ]
+
+
+class BlogTag(models.Model):
+ _name = 'blog.tag'
+ _description = 'Blog Tag'
+ _inherit = ['website.seo.metadata']
+ _order = 'name'
+
+ name = fields.Char('Name', required=True, translate=True)
+ category_id = fields.Many2one('blog.tag.category', 'Category', index=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.multi.mixin', 'website.cover_properties.mixin']
+ _order = 'id DESC'
+ _mail_post_access = 'read'
+
+ def _compute_website_url(self):
+ super(BlogPost, self)._compute_website_url()
+ for blog_post in self:
+ blog_post.website_url = "/blog/%s/%s" % (slug(blog_post.blog_id), slug(blog_post))
+
+ def _default_content(self):
+ return '''
+ <p class="o_default_snippet_text">''' + _("Start writing here...") + '''</p>
+ '''
+ 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)
+ author_avatar = fields.Binary(related='author_id.image_128', string="Avatar", readonly=False)
+ author_name = fields.Char(related='author_id.display_name', string="Author Name", readonly=False, store=True)
+ active = fields.Boolean('Active', default=True)
+ 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=False)
+ teaser = fields.Text('Teaser', compute='_compute_teaser', inverse='_set_teaser')
+ teaser_manual = fields.Text(string='Teaser Content')
+
+ website_message_ids = fields.One2many(domain=lambda self: [('model', '=', self._name), ('message_type', '=', 'comment')])
+
+ # creation / update stuff
+ create_date = fields.Datetime('Created on', index=True, readonly=True)
+ published_date = fields.Datetime('Published Date')
+ post_date = fields.Datetime('Publishing date', compute='_compute_post_date', inverse='_set_post_date', store=True,
+ help="The blog post will be visible for your visitors as of this date on the website if it is set as published.")
+ create_uid = fields.Many2one('res.users', 'Created by', index=True, readonly=True)
+ write_date = fields.Datetime('Last Updated on', index=True, readonly=True)
+ write_uid = fields.Many2one('res.users', 'Last Contributor', index=True, readonly=True)
+ visits = fields.Integer('No of Views', copy=False, default=0)
+ website_id = fields.Many2one(related='blog_id.website_id', readonly=True, store=True)
+
+ @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 = content[:200] + '...'
+
+ def _set_teaser(self):
+ for blog_post in self:
+ blog_post.teaser_manual = blog_post.teaser
+
+ @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
+
+ def _set_post_date(self):
+ for blog_post in self:
+ blog_post.published_date = blog_post.post_date
+ if not blog_post.published_date:
+ blog_post._write(dict(post_date=blog_post.create_date)) # dont trigger inverse function
+
+ def _check_for_publication(self, vals):
+ if vals.get('is_published'):
+ for post in self.filtered(lambda p: p.active):
+ 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'].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
+
+ def write(self, vals):
+ result = True
+ # archiving a blog post, unpublished the blog post
+ if 'active' in vals and not vals['active']:
+ vals['is_published'] = False
+ for post in self:
+ copy_vals = dict(vals)
+ published_in_vals = set(vals.keys()) & {'is_published', 'website_published'}
+ if (published_in_vals and 'published_date' not in vals and
+ (not post.published_date or post.published_date <= fields.Datetime.now())):
+ copy_vals['published_date'] = vals[list(published_in_vals)[0]] and fields.Datetime.now() or False
+ result &= super(BlogPost, self).write(copy_vals)
+ self._check_for_publication(vals)
+ return result
+
+ @api.returns('self', lambda value: value.id)
+ def copy_data(self, default=None):
+ self.ensure_one()
+ name = _("%s (copy)", self.name)
+ default = dict(default or {}, name=name)
+ return super(BlogPost, self).copy_data(default)
+
+ def get_access_action(self, access_uid=None):
+ """ Instead of the classic form view, redirect to the post on website
+ directly if user is an employee or if the post is published. """
+ self.ensure_one()
+ user = access_uid and self.env['res.users'].sudo().browse(access_uid) or self.env.user
+ if user.share and not self.sudo().website_published:
+ return super(BlogPost, self).get_access_action(access_uid)
+ return {
+ 'type': 'ir.actions.act_url',
+ 'url': self.website_url,
+ 'target': 'self',
+ 'target_type': 'public',
+ 'res_id': self.id,
+ }
+
+ def _notify_get_groups(self, msg_vals=None):
+ """ Add access button to everyone if the document is published. """
+ groups = super(BlogPost, self)._notify_get_groups(msg_vals=msg_vals)
+
+ if self.website_published:
+ for group_name, group_method, group_data in groups:
+ group_data['has_button_access'] = True
+
+ return groups
+
+ def _notify_record_by_inbox(self, message, recipients_data, msg_vals=False, **kwargs):
+ """ Override to avoid keeping all notified recipients of a comment.
+ We avoid tracking needaction on post comments. Only emails should be
+ sufficient. """
+ if msg_vals.get('message_type', message.message_type) == 'comment':
+ return
+ return super(BlogPost, self)._notify_record_by_inbox(message, recipients_data, msg_vals=msg_vals, **kwargs)
+
+ def _default_website_meta(self):
+ res = super(BlogPost, self)._default_website_meta()
+ res['default_opengraph']['og:description'] = res['default_twitter']['twitter:description'] = self.subtitle
+ res['default_opengraph']['og:type'] = 'article'
+ res['default_opengraph']['article:published_time'] = self.post_date
+ res['default_opengraph']['article:modified_time'] = self.write_date
+ res['default_opengraph']['article:tag'] = self.tag_ids.mapped('name')
+ # background-image might contain single quotes eg `url('/my/url')`
+ res['default_opengraph']['og:image'] = res['default_twitter']['twitter:image'] = json.loads(self.cover_properties).get('background-image', 'none')[4:-1].strip("'")
+ res['default_opengraph']['og:title'] = res['default_twitter']['twitter:title'] = self.name
+ res['default_meta_description'] = self.subtitle
+ return res