1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355
|
# -*- 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.exceptions import UserError
from odoo.http import request
from odoo.tools import html2plaintext
class WebsiteBlog(http.Controller):
_blog_post_per_page = 20
_post_comment_per_page = 10
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 = request.context.get('lang') or 'en_US'
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']))
@http.route([
'/blog',
'/blog/page/<int:page>',
], type='http', auth="public", website=True)
def blogs(self, page=1, **post):
Blog = request.env['blog.blog']
blogs = Blog.search([], limit=2)
if len(blogs) == 1:
return werkzeug.utils.redirect('/blog/%s' % slug(blogs[0]), code=302)
BlogPost = request.env['blog.post']
total = BlogPost.search([], count=True)
pager = request.website.pager(
url='/blog',
total=total,
page=page,
step=self._blog_post_per_page,
)
posts = BlogPost.search([], offset=(page - 1) * self._blog_post_per_page, limit=self._blog_post_per_page)
blog_url = QueryURL('', ['blog', 'tag'])
return request.render("website_blog.latest_blogs", {
'posts': posts,
'pager': pager,
'blog_url': blog_url,
})
@http.route([
'/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)
def blog(self, blog=None, tag=None, page=1, **opt):
""" Prepare all values to display the blog.
:return dict values: values for the templates, containing
- 'blog': current blog
- 'blogs': all blogs for navigation
- 'pager': pager of posts
- 'active_tag_ids' : list of active tag ids,
- 'tags_list' : function to built the comma-separated tag list ids (for the url),
- 'tags': all tags, for navigation
- 'state_info': state of published/unpublished filter
- 'nav_list': a dict [year][month] for archives navigation
- 'date': date_begin optional parameter, used in archives navigation
- 'blog_url': help object to create URLs
"""
date_begin, date_end, state = opt.get('date_begin'), opt.get('date_end'), opt.get('state')
published_count, unpublished_count = 0, 0
BlogPost = request.env['blog.post']
Blog = request.env['blog.blog']
blogs = Blog.search([], order="create_date asc")
# build the domain for blog post to display
domain = []
# retrocompatibility to accept tag as slug
active_tag_ids = tag and [int(unslug(t)[1]) for t in tag.split(',')] or []
if active_tag_ids:
domain += [('tag_ids', 'in', active_tag_ids)]
if blog:
domain += [('blog_id', '=', blog.id)]
if date_begin and date_end:
domain += [("post_date", ">=", date_begin), ("post_date", "<=", date_end)]
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())]
blog_url = QueryURL('', ['blog', 'tag'], blog=blog, tag=tag, date_begin=date_begin, date_end=date_end)
blog_posts = BlogPost.search(domain, order="post_date desc")
pager = request.website.pager(
url=request.httprequest.path.partition('/page/')[0],
total=len(blog_posts),
page=page,
step=self._blog_post_per_page,
url_args=opt,
)
pager_begin = (page - 1) * self._blog_post_per_page
pager_end = page * self._blog_post_per_page
blog_posts = blog_posts[pager_begin:pager_end]
all_tags = blog.all_tags()[blog.id]
# function to create the string list of tag ids, and toggle a given one.
# used in the 'Tags Cloud' template.
def tags_list(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)
values = {
'blog': blog,
'blogs': blogs,
'main_object': blog,
'tags': all_tags,
'state_info': {"state": state, "published": published_count, "unpublished": unpublished_count},
'active_tag_ids': active_tag_ids,
'tags_list' : tags_list,
'blog_posts': blog_posts,
'blog_posts_cover_properties': [json.loads(b.cover_properties) for b in blog_posts],
'pager': pager,
'nav_list': self.nav_list(blog),
'blog_url': blog_url,
'date': date_begin,
}
response = request.render("website_blog.blog_post_short", values)
return response
@http.route(['/blog/<model("blog.blog"):blog>/feed'], type='http', auth="public")
def blog_feed(self, blog, limit='15'):
v = {}
v['blog'] = blog
v['base_url'] = request.env['ir.config_parameter'].sudo().get_param('web.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[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
"""
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]
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)))
tags = request.env['blog.tag'].search([])
# Find next Post
all_post = BlogPost.search([('blog_id', '=', blog.id)])
if not request.env.user.has_group('website.group_website_designer'):
all_post = all_post.filtered(lambda r: r.post_date <= fields.Datetime.now())
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,
'blog_post_cover_properties': json.loads(blog_post.cover_properties),
'main_object': blog_post,
'nav_list': self.nav_list(blog),
'enable_editor': enable_editor,
'next_post': next_post,
'next_post_cover_properties': json.loads(next_post.cover_properties) if next_post else {},
'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
def _blog_post_message(self, blog_post_id, message_content, **post):
BlogPost = request.env['blog.post']
# for now, only portal and user can post comment on blog post.
if request.env.user.id == request.website.user_id.id:
raise UserError(_('Public user cannot post comments on blog post.'))
# get the partner of the current user
partner_id = request.env.user.partner_id.id
message = BlogPost.message_post(
int(blog_post_id),
body=message_content,
message_type='comment',
subtype='mt_comment',
author_id=partner_id,
path=post.get('path', False),
)
return message.id
def _get_discussion_detail(self, ids, publish=False, **post):
values = []
for message in request.env['mail.message'].sudo().browse(ids):
values.append({
"id": message.id,
"author_name": message.author_id.name,
"author_image": message.author_id.image and \
(b"data:image/png;base64,%s" % message.author_id.image) or \
b'/website_blog/static/src/img/anonymous.png',
"date": message.date,
'body': html2plaintext(message.body),
'website_published' : message.website_published,
'publish' : publish,
})
return values
@http.route(['/blog/post_discussion'], type='json', auth="public", website=True)
def post_discussion(self, blog_post_id, **post):
publish = request.env.user.has_group('website.group_website_publisher')
message_id = self._blog_post_message(blog_post_id, post.get('comment'), **post)
return self._get_discussion_detail([message_id], publish, **post)
@http.route('/blog/<int:blog_id>/post/new', type='http', auth="public", website=True)
def blog_post_create(self, blog_id, **post):
new_blog_post = request.env['blog.post'].create({
'blog_id': blog_id,
'website_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/post_get_discussion/', type='json', auth="public", website=True)
def discussion(self, post_id=0, path=None, count=False, **post):
domain = [('res_id', '=', int(post_id)), ('model', '=', 'blog.post'), ('path', '=', path)]
#check current user belongs to website publisher group
publish = request.env.user.has_group('website.group_website_publisher')
if not publish:
domain.append(('website_published', '=', True))
messages = request.env['mail.message'].sudo().search(domain, count=count)
if count:
return messages.ids
return self._get_discussion_detail(messages.ids, publish, **post)
@http.route('/blog/post_get_discussions/', type='json', auth="public", website=True)
def discussions(self, post_id=0, paths=None, count=False, **post):
ret = []
for path in paths:
result = self.discussion(post_id=post_id, path=path, count=count, **post)
ret.append({"path": path, "val": result})
return ret
@http.route('/blog/post_change_background', type='json', auth="public", website=True)
def change_bg(self, post_id=0, cover_properties={}, **post):
if not post_id:
return False
return request.env['blog.post'].browse(int(post_id)).write({'cover_properties': json.dumps(cover_properties)})
@http.route('/blog/get_user/', type='json', auth="public", website=True)
def get_user(self, **post):
return [False if request.session.uid else True]
|