*base, website_blog, website_event, website_forum, website_slides With this commit: 1. It is now possible to add the 'Edit in backend' entry in the frontend navbar for any desired model, not only the ones which have the published mixin. It will simply redirect to the main_object form view. This commit add it to Forum and Blog. 2. When clicking on 'Edit in backend', it is now possible to land on another module than Website. That's especially useful for events and slides which are not directly related to the website module as they have their own module. 3. Remove the custom Edit in backend from the forum homepage (fa-cog). Opportunity was also taken to remove the 'edit welcome message'. It will now be editable through the editor (welcome message will appear in edit mode). Thus we got rid of the custom edit welcome message controller and views. Closes #36325
299 lines
13 KiB
Python
299 lines
13 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import json
|
|
import werkzeug
|
|
import itertools
|
|
import pytz
|
|
import babel.dates
|
|
from collections import OrderedDict
|
|
|
|
from odoo import http, fields
|
|
from odoo.addons.http_routing.models.ir_http import slug, unslug
|
|
from odoo.addons.website.controllers.main import QueryURL
|
|
from odoo.http import request
|
|
from odoo.tools import html2plaintext
|
|
from odoo.tools.misc import get_lang
|
|
|
|
|
|
class WebsiteBlog(http.Controller):
|
|
_blog_post_per_page = 12 # multiple of 2,3,4
|
|
_post_comment_per_page = 10
|
|
|
|
def tags_list(self, tag_ids, current_tag):
|
|
tag_ids = list(tag_ids) # required to avoid using the same list
|
|
if current_tag in tag_ids:
|
|
tag_ids.remove(current_tag)
|
|
else:
|
|
tag_ids.append(current_tag)
|
|
tag_ids = request.env['blog.tag'].browse(tag_ids).exists()
|
|
return ','.join(slug(tag) for tag in tag_ids)
|
|
|
|
def nav_list(self, blog=None):
|
|
dom = blog and [('blog_id', '=', blog.id)] or []
|
|
if not request.env.user.has_group('website.group_website_designer'):
|
|
dom += [('post_date', '<=', fields.Datetime.now())]
|
|
groups = request.env['blog.post']._read_group_raw(
|
|
dom,
|
|
['name', 'post_date'],
|
|
groupby=["post_date"], orderby="post_date desc")
|
|
for group in groups:
|
|
(r, label) = group['post_date']
|
|
start, end = r.split('/')
|
|
group['post_date'] = label
|
|
group['date_begin'] = start
|
|
group['date_end'] = end
|
|
|
|
locale = get_lang(request.env).code
|
|
start = pytz.UTC.localize(fields.Datetime.from_string(start))
|
|
tzinfo = pytz.timezone(request.context.get('tz', 'utc') or 'utc')
|
|
|
|
group['month'] = babel.dates.format_datetime(start, format='MMMM', tzinfo=tzinfo, locale=locale)
|
|
group['year'] = babel.dates.format_datetime(start, format='YYYY', tzinfo=tzinfo, locale=locale)
|
|
|
|
return OrderedDict((year, [m for m in months]) for year, months in itertools.groupby(groups, lambda g: g['year']))
|
|
|
|
def _prepare_blog_values(self, blogs, blog=False, date_begin=False, date_end=False, tags=False, state=False, page=False):
|
|
""" Prepare all values to display the blogs index page or one specific blog"""
|
|
BlogPost = request.env['blog.post']
|
|
|
|
# prepare domain
|
|
domain = request.website.website_domain()
|
|
|
|
if blog:
|
|
domain += [('blog_id', '=', blog.id)]
|
|
|
|
if date_begin and date_end:
|
|
domain += [("post_date", ">=", date_begin), ("post_date", "<=", date_end)]
|
|
|
|
active_tag_ids = tags and [unslug(tag)[1] for tag in tags.split(',')] or []
|
|
if active_tag_ids:
|
|
fixed_tag_slug = ",".join(slug(t) for t in request.env['blog.tag'].browse(active_tag_ids))
|
|
if fixed_tag_slug != tags:
|
|
return request.redirect(request.httprequest.full_path.replace("/tag/%s/" % tags, "/tag/%s/" % fixed_tag_slug, 1), 301)
|
|
|
|
domain += [('tag_ids', 'in', active_tag_ids)]
|
|
|
|
if request.env.user.has_group('website.group_website_designer'):
|
|
count_domain = domain + [("website_published", "=", True), ("post_date", "<=", fields.Datetime.now())]
|
|
published_count = BlogPost.search_count(count_domain)
|
|
unpublished_count = BlogPost.search_count(domain) - published_count
|
|
|
|
if state == "published":
|
|
domain += [("website_published", "=", True), ("post_date", "<=", fields.Datetime.now())]
|
|
elif state == "unpublished":
|
|
domain += ['|', ("website_published", "=", False), ("post_date", ">", fields.Datetime.now())]
|
|
else:
|
|
domain += [("post_date", "<=", fields.Datetime.now())]
|
|
|
|
use_cover = request.website.viewref('website_blog.opt_blog_cover_post').active
|
|
fullwidth_cover = request.website.viewref('website_blog.opt_blog_cover_post_fullwidth_design').active
|
|
|
|
# if blog, we show blog title, if use_cover and not fullwidth_cover we need pager + latest always
|
|
offset = (page - 1) * self._blog_post_per_page
|
|
first_post = BlogPost
|
|
if not blog:
|
|
first_post = BlogPost.search(domain + [('website_published', '=', True)], order="post_date desc, id asc", limit=1)
|
|
if use_cover and not fullwidth_cover:
|
|
offset += 1
|
|
|
|
posts = BlogPost.search(domain, offset=offset, limit=self._blog_post_per_page, order="is_published desc, post_date desc, id asc")
|
|
total = BlogPost.search_count(domain)
|
|
|
|
pager = request.website.pager(
|
|
url=request.httprequest.path.partition('/page/')[0],
|
|
total=total,
|
|
page=page,
|
|
step=self._blog_post_per_page,
|
|
)
|
|
|
|
all_tags = blog and blogs.all_tags()[blog.id] or blogs.all_tags(join=True)
|
|
tag_category = sorted(all_tags.mapped('category_id'), key=lambda category: category.name.upper())
|
|
other_tags = sorted(all_tags.filtered(lambda x: not x.category_id), key=lambda tag: tag.name.upper())
|
|
|
|
# for performance prefetch the first post with the others
|
|
post_ids = (first_post | posts).ids
|
|
|
|
return {
|
|
'date_begin': date_begin,
|
|
'date_end': date_end,
|
|
'first_post': first_post.with_prefetch(post_ids),
|
|
'other_tags': other_tags,
|
|
'tag_category': tag_category,
|
|
'nav_list': self.nav_list(),
|
|
'tags_list': self.tags_list,
|
|
'pager': pager,
|
|
'posts': posts.with_prefetch(post_ids),
|
|
'tag': tags,
|
|
'active_tag_ids': active_tag_ids,
|
|
'domain': domain,
|
|
'state_info': state and {"state": state, "published": published_count, "unpublished": unpublished_count},
|
|
'blogs': blogs,
|
|
'blog': blog,
|
|
}
|
|
|
|
@http.route([
|
|
'/blog',
|
|
'/blog/page/<int:page>',
|
|
'/blog/tag/<string:tag>',
|
|
'/blog/tag/<string:tag>/page/<int:page>',
|
|
'''/blog/<model("blog.blog", "[('website_id', 'in', (False, current_website_id))]"):blog>''',
|
|
'''/blog/<model("blog.blog"):blog>/page/<int:page>''',
|
|
'''/blog/<model("blog.blog"):blog>/tag/<string:tag>''',
|
|
'''/blog/<model("blog.blog"):blog>/tag/<string:tag>/page/<int:page>''',
|
|
], type='http', auth="public", website=True)
|
|
def blog(self, blog=None, tag=None, page=1, **opt):
|
|
Blog = request.env['blog.blog']
|
|
if blog and not blog.can_access_from_current_website():
|
|
raise werkzeug.exceptions.NotFound()
|
|
|
|
blogs = Blog.search(request.website.website_domain(), order="create_date asc, id asc")
|
|
|
|
if not blog and len(blogs) == 1:
|
|
return werkzeug.utils.redirect('/blog/%s' % slug(blogs[0]), code=302)
|
|
|
|
date_begin, date_end, state = opt.get('date_begin'), opt.get('date_end'), opt.get('state')
|
|
|
|
values = self._prepare_blog_values(blogs=blogs, blog=blog, date_begin=date_begin, date_end=date_end, tags=tag, state=state, page=page)
|
|
|
|
if blog:
|
|
values['main_object'] = blog
|
|
values['edit_in_backend'] = True
|
|
values['blog_url'] = QueryURL('', ['blog', 'tag'], blog=blog, tag=tag, date_begin=date_begin, date_end=date_end)
|
|
else:
|
|
values['blog_url'] = QueryURL('/blog', ['tag'], date_begin=date_begin, date_end=date_end)
|
|
|
|
return request.render("website_blog.blog_post_short", values)
|
|
|
|
@http.route(['''/blog/<model("blog.blog", "[('website_id', 'in', (False, current_website_id))]"):blog>/feed'''], type='http', auth="public")
|
|
def blog_feed(self, blog, limit='15', **kwargs):
|
|
v = {}
|
|
v['blog'] = blog
|
|
v['base_url'] = blog.get_base_url()
|
|
v['posts'] = request.env['blog.post'].search([('blog_id', '=', blog.id)], limit=min(int(limit), 50), order="post_date DESC")
|
|
v['html2plaintext'] = html2plaintext
|
|
r = request.render("website_blog.blog_feed", v, headers=[('Content-Type', 'application/atom+xml')])
|
|
return r
|
|
|
|
@http.route([
|
|
'''/blog/<model("blog.blog", "[('website_id', 'in', (False, current_website_id))]"):blog>/post/<model("blog.post", "[('blog_id','=',blog[0])]"):blog_post>''',
|
|
], type='http', auth="public", website=True)
|
|
def blog_post(self, blog, blog_post, tag_id=None, page=1, enable_editor=None, **post):
|
|
""" Prepare all values to display the blog.
|
|
|
|
:return dict values: values for the templates, containing
|
|
|
|
- 'blog_post': browse of the current post
|
|
- 'blog': browse of the current blog
|
|
- 'blogs': list of browse records of blogs
|
|
- 'tag': current tag, if tag_id in parameters
|
|
- 'tags': all tags, for tag-based navigation
|
|
- 'pager': a pager on the comments
|
|
- 'nav_list': a dict [year][month] for archives navigation
|
|
- 'next_post': next blog post, to direct the user towards the next interesting post
|
|
"""
|
|
if not blog.can_access_from_current_website():
|
|
raise werkzeug.exceptions.NotFound()
|
|
|
|
BlogPost = request.env['blog.post']
|
|
date_begin, date_end = post.get('date_begin'), post.get('date_end')
|
|
|
|
pager_url = "/blogpost/%s" % blog_post.id
|
|
|
|
pager = request.website.pager(
|
|
url=pager_url,
|
|
total=len(blog_post.website_message_ids),
|
|
page=page,
|
|
step=self._post_comment_per_page,
|
|
scope=7
|
|
)
|
|
pager_begin = (page - 1) * self._post_comment_per_page
|
|
pager_end = page * self._post_comment_per_page
|
|
comments = blog_post.website_message_ids[pager_begin:pager_end]
|
|
|
|
domain = request.website.website_domain()
|
|
blogs = blog.search(domain, order="create_date, id asc")
|
|
|
|
tag = None
|
|
if tag_id:
|
|
tag = request.env['blog.tag'].browse(int(tag_id))
|
|
blog_url = QueryURL('', ['blog', 'tag'], blog=blog_post.blog_id, tag=tag, date_begin=date_begin, date_end=date_end)
|
|
|
|
if not blog_post.blog_id.id == blog.id:
|
|
return request.redirect("/blog/%s/post/%s" % (slug(blog_post.blog_id), slug(blog_post)), code=301)
|
|
|
|
tags = request.env['blog.tag'].search([])
|
|
|
|
# Find next Post
|
|
blog_post_domain = [('blog_id', '=', blog.id)]
|
|
if not request.env.user.has_group('website.group_website_designer'):
|
|
blog_post_domain += [('post_date', '<=', fields.Datetime.now())]
|
|
|
|
all_post = BlogPost.search(blog_post_domain)
|
|
|
|
if blog_post not in all_post:
|
|
return request.redirect("/blog/%s" % (slug(blog_post.blog_id)))
|
|
|
|
# should always return at least the current post
|
|
all_post_ids = all_post.ids
|
|
current_blog_post_index = all_post_ids.index(blog_post.id)
|
|
nb_posts = len(all_post_ids)
|
|
next_post_id = all_post_ids[(current_blog_post_index + 1) % nb_posts] if nb_posts > 1 else None
|
|
next_post = next_post_id and BlogPost.browse(next_post_id) or False
|
|
|
|
values = {
|
|
'tags': tags,
|
|
'tag': tag,
|
|
'blog': blog,
|
|
'blog_post': blog_post,
|
|
'blogs': blogs,
|
|
'main_object': blog_post,
|
|
'nav_list': self.nav_list(blog),
|
|
'enable_editor': enable_editor,
|
|
'next_post': next_post,
|
|
'date': date_begin,
|
|
'blog_url': blog_url,
|
|
'pager': pager,
|
|
'comments': comments,
|
|
}
|
|
response = request.render("website_blog.blog_post_complete", values)
|
|
|
|
request.session[request.session.sid] = request.session.get(request.session.sid, [])
|
|
if not (blog_post.id in request.session[request.session.sid]):
|
|
request.session[request.session.sid].append(blog_post.id)
|
|
# Increase counter
|
|
blog_post.sudo().write({
|
|
'visits': blog_post.visits + 1,
|
|
})
|
|
return response
|
|
|
|
@http.route('/blog/<int:blog_id>/post/new', type='http', auth="public", website=True)
|
|
def blog_post_create(self, blog_id, **post):
|
|
# Use sudo so this line prevents both editor and admin to access blog from another website
|
|
# as browse() will return the record even if forbidden by security rules but editor won't
|
|
# be able to access it
|
|
if not request.env['blog.blog'].browse(blog_id).sudo().can_access_from_current_website():
|
|
raise werkzeug.exceptions.NotFound()
|
|
|
|
new_blog_post = request.env['blog.post'].create({
|
|
'blog_id': blog_id,
|
|
'is_published': False,
|
|
})
|
|
return werkzeug.utils.redirect("/blog/%s/post/%s?enable_editor=1" % (slug(new_blog_post.blog_id), slug(new_blog_post)))
|
|
|
|
@http.route('/blog/post_duplicate', type='http', auth="public", website=True, methods=['POST'])
|
|
def blog_post_copy(self, blog_post_id, **post):
|
|
""" Duplicate a blog.
|
|
|
|
:param blog_post_id: id of the blog post currently browsed.
|
|
|
|
:return redirect to the new blog created
|
|
"""
|
|
new_blog_post = request.env['blog.post'].with_context(mail_create_nosubscribe=True).browse(int(blog_post_id)).copy()
|
|
return werkzeug.utils.redirect("/blog/%s/post/%s?enable_editor=1" % (slug(new_blog_post.blog_id), slug(new_blog_post)))
|
|
|
|
@http.route(['/blog/render_latest_posts'], type='json', auth='public', website=True)
|
|
def render_latest_posts(self, template, domain, limit=None, order='published_date desc'):
|
|
posts = request.env['blog.post'].search(domain, limit=limit, order=order)
|
|
return request.env.ref(template).render({'posts': posts})
|