*: base, mrp_account, product, mail, website_blog, website_event, website_forum This commit improves the button that allows to go to the backend view of an object when you are on its corresponding page on the website. The button is more visible and the user can see which object he is going to edit. Note that this commit also: - Changes the access key to translate a website page to ALT + T. - Adds a new access key to edit an object in backend with ALT + E. - Removes the possibility to duplicate a blog post (but this feature will be reintroduced later for all models, generically) **. **: Note that the duplication of blog posts actually had a mistake: The controller to create a new blog post has been added with [1] where it has been decided to not be a follower of the blog posts at their creation. A new controller has been added by [2] to be able to duplicate a blog post, to be consistent with [1], here also the user does not become a follower of the new blog post (the copy). So far, so good. Finally, [3] has changed the blog post creation controller so that the user who creates the blog post is a follower of the new blog post. Unfortunately the same change was not made for the duplicate controller, which is a mistake. There is no reason to be a follower of the newly created blog posts when you go through the add blog post controller but not when you go through the duplication controller. The behaviors should be consistent and there is no reason for there to be a difference. [1]: https://github.com/odoo/odoo/commit/4c3b516a7b988d758a67ff19242e8ed0837d756c [2]: https://github.com/odoo/odoo/commit/fe40538aff2b65f7719840c8e2d6e51e858f560f [3]: https://github.com/odoo/odoo/commit/4bf9dc4078a5ca413539a6fd16400fd5aad46907 task-2889929 closes odoo/odoo#97353 Related: odoo/enterprise#30075 Signed-off-by: Quentin Smetz (qsm) <qsm@odoo.com> Co-authored-by: qsm-odoo <qsm@odoo.com>
303 lines
14 KiB
Python
303 lines
14 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import werkzeug
|
|
import itertools
|
|
import pytz
|
|
import babel.dates
|
|
from collections import OrderedDict
|
|
|
|
from odoo import http, fields, tools
|
|
from odoo.addons.http_routing.models.ir_http import slug, unslug
|
|
from odoo.addons.website.controllers.main import QueryURL
|
|
from odoo.addons.portal.controllers.portal import _build_url_w_params
|
|
from odoo.http import request
|
|
from odoo.osv import expression
|
|
from odoo.tools import html2plaintext
|
|
from odoo.tools.misc import get_lang
|
|
from odoo.tools import sql
|
|
|
|
|
|
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)
|
|
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, search=None):
|
|
""" Prepare all values to display the blogs index page or one specific blog"""
|
|
BlogPost = request.env['blog.post']
|
|
BlogTag = request.env['blog.tag']
|
|
|
|
# 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 []
|
|
active_tags = BlogTag
|
|
if active_tag_ids:
|
|
active_tags = BlogTag.browse(active_tag_ids).exists()
|
|
fixed_tag_slug = ",".join(slug(t) for t in active_tags)
|
|
if fixed_tag_slug != tags:
|
|
path = request.httprequest.full_path
|
|
new_url = path.replace("/tag/%s" % tags, fixed_tag_slug and "/tag/%s" % fixed_tag_slug or "", 1)
|
|
if new_url != path: # check that really replaced and avoid loop
|
|
return request.redirect(new_url, 301)
|
|
domain += [('tag_ids', 'in', active_tags.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.is_view_active('website_blog.opt_blog_cover_post')
|
|
fullwidth_cover = request.website.is_view_active('website_blog.opt_blog_cover_post_fullwidth_design')
|
|
|
|
# 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
|
|
if not blog:
|
|
if use_cover and not fullwidth_cover:
|
|
offset += 1
|
|
|
|
options = {
|
|
'displayDescription': True,
|
|
'displayDetail': False,
|
|
'displayExtraDetail': False,
|
|
'displayExtraLink': False,
|
|
'displayImage': False,
|
|
'allowFuzzy': not request.params.get('noFuzzy'),
|
|
'blog': str(blog.id) if blog else None,
|
|
'tag': ','.join([str(id) for id in active_tags.ids]),
|
|
'date_begin': date_begin,
|
|
'date_end': date_end,
|
|
'state': state,
|
|
}
|
|
total, details, fuzzy_search_term = request.website._search_with_fuzzy("blog_posts_only", search,
|
|
limit=page * self._blog_post_per_page, order="is_published desc, post_date desc, id asc", options=options)
|
|
posts = details[0].get('results', BlogPost)
|
|
first_post = BlogPost
|
|
if posts and not blog and posts[0].website_published:
|
|
first_post = posts[0]
|
|
posts = posts[offset:offset + self._blog_post_per_page]
|
|
|
|
url_args = dict()
|
|
if search:
|
|
url_args["search"] = search
|
|
|
|
if date_begin and date_end:
|
|
url_args["date_begin"] = date_begin
|
|
url_args["date_end"] = date_end
|
|
|
|
pager = tools.lazy(lambda: request.website.pager(
|
|
url=request.httprequest.path.partition('/page/')[0],
|
|
total=total,
|
|
page=page,
|
|
step=self._blog_post_per_page,
|
|
url_args=url_args,
|
|
))
|
|
|
|
if not blogs:
|
|
all_tags = request.env['blog.tag']
|
|
else:
|
|
all_tags = tools.lazy(lambda: blogs.all_tags(join=True) if not blog else blogs.all_tags().get(blog.id, request.env['blog.tag']))
|
|
tag_category = tools.lazy(lambda: sorted(all_tags.mapped('category_id'), key=lambda category: category.name.upper()))
|
|
other_tags = tools.lazy(lambda: 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
|
|
# and avoid accessing related blogs one by one
|
|
posts.blog_id
|
|
|
|
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_tags.ids,
|
|
'domain': domain,
|
|
'state_info': state and {"state": state, "published": published_count, "unpublished": unpublished_count},
|
|
'blogs': blogs,
|
|
'blog': blog,
|
|
'search': fuzzy_search_term or search,
|
|
'search_count': total,
|
|
'original_search': fuzzy_search_term and search,
|
|
}
|
|
|
|
@http.route([
|
|
'/blog',
|
|
'/blog/page/<int:page>',
|
|
'/blog/tag/<string:tag>',
|
|
'/blog/tag/<string:tag>/page/<int:page>',
|
|
'''/blog/<model("blog.blog"):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, sitemap=True)
|
|
def blog(self, blog=None, tag=None, page=1, search=None, **opt):
|
|
Blog = request.env['blog.blog']
|
|
blogs = tools.lazy(lambda: Blog.search(request.website.website_domain(), order="create_date asc, id asc"))
|
|
|
|
if not blog and len(blogs) == 1:
|
|
return request.redirect('/blog/%s' % slug(blogs[0]), code=302)
|
|
|
|
date_begin, date_end, state = opt.get('date_begin'), opt.get('date_end'), opt.get('state')
|
|
|
|
if tag and request.httprequest.method == 'GET':
|
|
# redirect get tag-1,tag-2 -> get tag-1
|
|
tags = tag.split(',')
|
|
if len(tags) > 1:
|
|
url = QueryURL('' if blog else '/blog', ['blog', 'tag'], blog=blog, tag=tags[0], date_begin=date_begin, date_end=date_end, search=search)()
|
|
return request.redirect(url, code=302)
|
|
|
|
values = self._prepare_blog_values(blogs=blogs, blog=blog, date_begin=date_begin, date_end=date_end, tags=tag, state=state, page=page, search=search)
|
|
|
|
# in case of a redirection need by `_prepare_blog_values` we follow it
|
|
if isinstance(values, werkzeug.wrappers.Response):
|
|
return values
|
|
|
|
if blog:
|
|
values['main_object'] = blog
|
|
values['blog_url'] = QueryURL('', ['blog', 'tag'], blog=blog, tag=tag, date_begin=date_begin, date_end=date_end, search=search)
|
|
else:
|
|
values['blog_url'] = QueryURL('/blog', ['tag'], date_begin=date_begin, date_end=date_end, search=search)
|
|
|
|
return request.render("website_blog.blog_post_short", values)
|
|
|
|
@http.route(['''/blog/<model("blog.blog"):blog>/feed'''], type='http', auth="public", website=True, sitemap=True)
|
|
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"):blog>/post/<model("blog.post", "[('blog_id','=',blog.id)]"):blog_post>''',
|
|
], type='http', auth="public", website=True, sitemap=False)
|
|
def old_blog_post(self, blog, blog_post, tag_id=None, page=1, enable_editor=None, **post):
|
|
# Compatibility pre-v14
|
|
return request.redirect(_build_url_w_params("/blog/%s/%s" % (slug(blog), slug(blog_post)), request.params), code=301)
|
|
|
|
@http.route([
|
|
'''/blog/<model("blog.blog"):blog>/<model("blog.post", "[('blog_id','=',blog.id)]"):blog_post>''',
|
|
], type='http', auth="public", website=True, sitemap=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
|
|
"""
|
|
BlogPost = request.env['blog.post']
|
|
date_begin, date_end = post.get('date_begin'), post.get('date_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/%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,
|
|
}
|
|
response = request.render("website_blog.blog_post_complete", values)
|
|
|
|
if blog_post.id not in request.session.get('posts_viewed', []):
|
|
if sql.increment_fields_skiplock(blog_post, 'visits'):
|
|
if not request.session.get('posts_viewed'):
|
|
request.session['posts_viewed'] = []
|
|
request.session['posts_viewed'].append(blog_post.id)
|
|
request.session.modified = True
|
|
return response
|