diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/website_blog/models | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/website_blog/models')
| -rw-r--r-- | addons/website_blog/models/__init__.py | 5 | ||||
| -rw-r--r-- | addons/website_blog/models/website.py | 64 | ||||
| -rw-r--r-- | addons/website_blog/models/website_blog.py | 269 |
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 |
