When a portal user post a message to review a channel, we want to allow him to edit its own comment and rating. This commit modifies the special check access method in portal to extract the security check and reuse it to allow user to update its comment on slide.channel only. We decided to reuse existing widget (new rating popup composer) to do the message modification. Task-1902304
371 lines
18 KiB
Python
371 lines
18 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import base64
|
|
import logging
|
|
import werkzeug
|
|
|
|
from odoo import http, _
|
|
from odoo.exceptions import AccessError, UserError
|
|
from odoo.http import request
|
|
from odoo.osv import expression
|
|
|
|
from odoo.addons.http_routing.models.ir_http import slug
|
|
from odoo.addons.website.models.ir_http import sitemap_qs2dom
|
|
from odoo.tools import html2plaintext
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class WebsiteSlides(http.Controller):
|
|
_slides_per_page = 12
|
|
_slides_per_list = 20
|
|
_order_by_criterion = {
|
|
'date': 'date_published desc',
|
|
'view': 'total_views desc',
|
|
'vote': 'likes desc',
|
|
}
|
|
|
|
def sitemap_slide(env, rule, qs):
|
|
Channel = env['slide.channel']
|
|
dom = sitemap_qs2dom(qs=qs, route='/slides/', field=Channel._rec_name)
|
|
dom += env['website'].get_current_website().website_domain()
|
|
for channel in Channel.search(dom):
|
|
loc = '/slides/%s' % slug(channel)
|
|
if not qs or qs.lower() in loc:
|
|
yield {'loc': loc}
|
|
|
|
def _set_viewed_slide(self, slide, view_mode):
|
|
slide_key = '%s_%s' % (view_mode, request.session.sid)
|
|
viewed_slides = request.session.setdefault(slide_key, list())
|
|
if slide.id not in viewed_slides:
|
|
if view_mode == 'slide':
|
|
slide.sudo().slide_views += 1
|
|
elif view_mode == 'embed':
|
|
slide.sudo().embed_views += 1
|
|
viewed_slides.append(slide.id)
|
|
request.session[slide_key] = viewed_slides
|
|
return True
|
|
|
|
def _get_slide_detail(self, slide):
|
|
most_viewed_slides = slide.get_most_viewed_slides(self._slides_per_list)
|
|
related_slides = slide.get_related_slides(self._slides_per_list)
|
|
return {
|
|
'slide': slide,
|
|
'most_viewed_slides': most_viewed_slides,
|
|
'related_slides': related_slides,
|
|
'user': request.env.user,
|
|
'is_public_user': request.website.is_public_user(),
|
|
'comments': slide.channel_id.can_see_full and slide.website_message_ids or [],
|
|
'private': not slide.channel_id.can_see_full,
|
|
}
|
|
|
|
# --------------------------------------------------
|
|
# MAIN / SEARCH
|
|
# --------------------------------------------------
|
|
|
|
@http.route('/slides', type='http', auth="public", website=True)
|
|
def slides_index(self, *args, **post):
|
|
""" Returns a list of available channels: if only one is available,
|
|
redirects directly to its slides
|
|
"""
|
|
domain = request.website.website_domain()
|
|
# search bar
|
|
search_term = post.get('search')
|
|
if search_term:
|
|
domain = expression.AND([domain, ['|', ('name', 'ilike', search_term), ('description', 'ilike', search_term)]])
|
|
|
|
channels = request.env['slide.channel'].search(domain, order='sequence, id')
|
|
if not channels:
|
|
return request.render("website_slides.channel_not_found", {'search_term': search_term})
|
|
elif len(channels) == 1 and not search_term: # don't auto redirect to only result when searching
|
|
return request.redirect("/slides/%s" % channels.id)
|
|
return request.render('website_slides.channels', {
|
|
'channels': channels,
|
|
'user': request.env.user,
|
|
'is_public_user': request.website.is_public_user(),
|
|
'search_term': search_term,
|
|
})
|
|
|
|
@http.route([
|
|
'''/slides/<model("slide.channel"):channel>''',
|
|
'''/slides/<model("slide.channel"):channel>/page/<int:page>''',
|
|
|
|
'''/slides/<model("slide.channel"):channel>/<string:slide_type>''',
|
|
'''/slides/<model("slide.channel"):channel>/<string:slide_type>/page/<int:page>''',
|
|
|
|
'''/slides/<model("slide.channel"):channel>/tag/<model("slide.tag"):tag>''',
|
|
'''/slides/<model("slide.channel"):channel>/tag/<model("slide.tag"):tag>/page/<int:page>''',
|
|
|
|
'''/slides/<model("slide.channel"):channel>/category/<model("slide.category"):category>''',
|
|
'''/slides/<model("slide.channel"):channel>/category/<model("slide.category"):category>/page/<int:page>''',
|
|
|
|
'''/slides/<model("slide.channel"):channel>/category/<model("slide.category"):category>/<string:slide_type>''',
|
|
'''/slides/<model("slide.channel"):channel>/category/<model("slide.category"):category>/<string:slide_type>/page/<int:page>'''],
|
|
type='http', auth="public", website=True, sitemap=sitemap_slide)
|
|
def channel(self, channel, category=None, tag=None, page=1, slide_type=None, sorting='creation', search=None, **kw):
|
|
if not channel.can_access_from_current_website():
|
|
raise werkzeug.exceptions.NotFound()
|
|
|
|
user = request.env.user
|
|
Slide = request.env['slide.slide']
|
|
domain = [('channel_id', '=', channel.id)]
|
|
pager_url = "/slides/%s" % (channel.id)
|
|
pager_args = {}
|
|
|
|
if search:
|
|
domain += [
|
|
'|', '|',
|
|
('name', 'ilike', search),
|
|
('description', 'ilike', search),
|
|
('index_content', 'ilike', search)]
|
|
pager_args['search'] = search
|
|
else:
|
|
if category:
|
|
domain += [('category_id', '=', category.id)]
|
|
pager_url += "/category/%s" % category.id
|
|
elif tag:
|
|
domain += [('tag_ids.id', '=', tag.id)]
|
|
pager_url += "/tag/%s" % tag.id
|
|
if slide_type:
|
|
domain += [('slide_type', '=', slide_type)]
|
|
pager_url += "/%s" % slide_type
|
|
|
|
if not sorting or sorting not in self._order_by_criterion:
|
|
sorting = 'date'
|
|
order = self._order_by_criterion[sorting]
|
|
pager_args['sorting'] = sorting
|
|
|
|
pager_count = Slide.search_count(domain)
|
|
pager = request.website.pager(url=pager_url, total=pager_count, page=page,
|
|
step=self._slides_per_page, scope=self._slides_per_page,
|
|
url_args=pager_args)
|
|
|
|
slides = Slide.search(domain, limit=self._slides_per_page, offset=pager['offset'], order=order)
|
|
values = {
|
|
'channel': channel,
|
|
'category': category,
|
|
'slides': slides,
|
|
'tag': tag,
|
|
'slide_type': slide_type,
|
|
'sorting': sorting,
|
|
'user': user,
|
|
'pager': pager,
|
|
'is_public_user': request.website.is_public_user(),
|
|
'display_channel_settings': not request.httprequest.cookies.get('slides_channel_%s' % (channel.id), False) and channel.can_see_full,
|
|
'rating_avg': channel.rating_avg,
|
|
'rating_count': channel.rating_count,
|
|
}
|
|
if not request.env.user._is_public():
|
|
last_message_values = request.env['mail.message'].search([('model', '=', channel._name), ('res_id', '=', channel.id), ('author_id', '=', user.partner_id.id), ('website_published', '=', True)], order='write_date DESC', limit=1).read(['body', 'rating_value'])
|
|
last_message_data = last_message_values[0] if last_message_values else {}
|
|
values.update({
|
|
'message_post_hash': channel._sign_token(request.env.user.partner_id.id),
|
|
'message_post_pid': request.env.user.partner_id.id,
|
|
'last_message_id': last_message_data.get('id'),
|
|
'last_message': html2plaintext(last_message_data.get('body', '')),
|
|
'last_rating_value': last_message_data.get('rating_value'),
|
|
})
|
|
if search:
|
|
values['search'] = search
|
|
return request.render('website_slides.slides_search', values)
|
|
|
|
# Display uncategorized slides
|
|
if not slide_type and not category:
|
|
category_datas = []
|
|
for category in Slide.read_group(domain, ['category_id'], ['category_id']):
|
|
category_id, name = category.get('category_id') or (False, _('Uncategorized'))
|
|
category_datas.append({
|
|
'id': category_id,
|
|
'name': name,
|
|
'total': category['category_id_count'],
|
|
'slides': Slide.search(category['__domain'], limit=4, offset=0, order=order)
|
|
})
|
|
values.update({
|
|
'category_datas': category_datas,
|
|
})
|
|
|
|
return request.render('website_slides.home', values)
|
|
|
|
# --------------------------------------------------
|
|
# SLIDE.SLIDE CONTOLLERS
|
|
# --------------------------------------------------
|
|
|
|
@http.route('''/slides/slide/<model("slide.slide", "[('channel_id.can_see', '=', True), ('website_id', 'in', (False, current_website_id))]"):slide>''', type='http', auth="public", website=True)
|
|
def slide_view(self, slide, **kwargs):
|
|
if not slide.channel_id.can_access_from_current_website():
|
|
raise werkzeug.exceptions.NotFound()
|
|
|
|
values = self._get_slide_detail(slide)
|
|
if not values.get('private'):
|
|
self._set_viewed_slide(slide, 'slide')
|
|
return request.render('website_slides.slide_detail_view', values)
|
|
|
|
@http.route('''/slides/slide/<model("slide.slide"):slide>/pdf_content''',
|
|
type='http', auth="public", website=True, sitemap=False)
|
|
def slide_get_pdf_content(self, slide):
|
|
response = werkzeug.wrappers.Response()
|
|
response.data = slide.datas and base64.b64decode(slide.datas) or b''
|
|
response.mimetype = 'application/pdf'
|
|
return response
|
|
|
|
@http.route('''/slides/slide/<model("slide.slide"):slide>/download''', type='http', auth="public", website=True, sitemap=False)
|
|
def slide_download(self, slide, **kw):
|
|
slide = slide.sudo()
|
|
if slide.download_security == 'public' or (slide.download_security == 'user' and request.env.user and request.env.user != request.website.user_id):
|
|
filecontent = base64.b64decode(slide.datas)
|
|
disposition = 'attachment; filename=%s.pdf' % werkzeug.urls.url_quote(slide.name)
|
|
return request.make_response(
|
|
filecontent,
|
|
[('Content-Type', 'application/pdf'),
|
|
('Content-Length', len(filecontent)),
|
|
('Content-Disposition', disposition)])
|
|
elif not request.session.uid and slide.download_security == 'user':
|
|
return request.redirect('/web/login?redirect=/slides/slide/%s' % (slide.id))
|
|
return request.render("website.403")
|
|
|
|
@http.route('''/slides/slide/<model("slide.slide"):slide>/promote''', type='http', auth='user', website=True)
|
|
def slide_set_promoted(self, slide, **kwargs):
|
|
slide.channel_id.promoted_slide_id = slide.id
|
|
return request.redirect("/slides/%s" % slide.channel_id.id)
|
|
|
|
# JSONRPC
|
|
@http.route('/slides/slide/like', type='json', auth="user", website=True)
|
|
def slide_like(self, slide_id, upvote):
|
|
if request.website.is_public_user():
|
|
return {'error': 'public_user'}
|
|
slide_users = request.env['slide.users'].sudo().search([
|
|
('slide_id', '=', slide_id),
|
|
('user_id', '=', request.env.uid)
|
|
])
|
|
if (upvote and slide_users.vote == 1) or (not upvote and slide_users.vote == -1):
|
|
return {'error': 'vote_done'}
|
|
slide = request.env['slide.slide'].browse(int(slide_id))
|
|
if upvote:
|
|
slide.action_like()
|
|
else:
|
|
slide.action_dislike()
|
|
slide.invalidate_cache()
|
|
return slide.read(['likes', 'dislikes', 'user_vote'])[0]
|
|
|
|
@http.route(['/slides/slide/send_share_email'], type='json', auth='user', website=True)
|
|
def slide_send_share_email(self, slide_id, email):
|
|
slide = request.env['slide.slide'].browse(int(slide_id))
|
|
result = slide.send_share_email(email)
|
|
return result
|
|
|
|
@http.route('/slides/slide/overlay', type='json', auth="public", website=True)
|
|
def slide_get_next_slides(self, slide_id):
|
|
slide = request.env['slide.slide'].browse(int(slide_id))
|
|
slides_to_suggest = 9
|
|
|
|
def slide_mapped_dict(slide):
|
|
return {
|
|
'img_src': '/web/image/slide.slide/%s/image_thumb' % (slide.id),
|
|
'caption': slide.name,
|
|
'url': slide.website_url
|
|
}
|
|
vals = [slide_mapped_dict(s) for s in slide.get_related_slides(slides_to_suggest)]
|
|
add_more_slide = slides_to_suggest - len(vals)
|
|
if max(add_more_slide, 0):
|
|
vals.extend(slide_mapped_dict(s) for s in slide.get_most_viewed_slides(add_more_slide))
|
|
return vals
|
|
|
|
# --------------------------------------------------
|
|
# TOOLS
|
|
# --------------------------------------------------
|
|
|
|
@http.route(['/slides/dialog_preview'], type='json', auth='user', methods=['POST'], website=True)
|
|
def dialog_preview(self, **data):
|
|
Slide = request.env['slide.slide']
|
|
document_type, document_id = Slide._find_document_data_from_url(data['url'])
|
|
preview = {}
|
|
if not document_id:
|
|
preview['error'] = _('Please enter valid youtube or google doc url')
|
|
return preview
|
|
existing_slide = Slide.search([('channel_id', '=', int(data['channel_id'])), ('document_id', '=', document_id)], limit=1)
|
|
if existing_slide:
|
|
preview['error'] = _('This video already exists in this channel <a target="_blank" href="/slides/slide/%s">click here to view it </a>') % existing_slide.id
|
|
return preview
|
|
values = Slide._parse_document_url(data['url'], only_preview_fields=True)
|
|
if values.get('error'):
|
|
preview['error'] = _('Could not fetch data from url. Document or access right not available.\nHere is the received response: %s') % values['error']
|
|
return preview
|
|
return values
|
|
|
|
@http.route(['/slides/add_slide'], type='json', auth='user', methods=['POST'], website=True)
|
|
def create_slide(self, *args, **post):
|
|
# check the size only when we upload a file.
|
|
if post.get('datas'):
|
|
file_size = len(post['datas']) * 3 / 4 # base64
|
|
if (file_size / 1024.0 / 1024.0) > 25:
|
|
return {'error': _('File is too big. File size cannot exceed 25MB')}
|
|
|
|
values = dict((fname, post[fname]) for fname in [
|
|
'name', 'url', 'tag_ids', 'slide_type', 'channel_id',
|
|
'mime_type', 'datas', 'description', 'image', 'index_content', 'website_published'] if post.get(fname))
|
|
if post.get('category_id'):
|
|
if post['category_id'][0] == 0:
|
|
values['category_id'] = request.env['slide.category'].create({
|
|
'name': post['category_id'][1]['name'],
|
|
'channel_id': values.get('channel_id')}).id
|
|
else:
|
|
values['category_id'] = post['category_id'][0]
|
|
|
|
# handle exception during creation of slide and sent error notification to the client
|
|
# otherwise client slide create dialog box continue processing even server fail to create a slide.
|
|
try:
|
|
slide_id = request.env['slide.slide'].create(values)
|
|
except (UserError, AccessError) as e:
|
|
_logger.error(e)
|
|
return {'error': e.name}
|
|
except Exception as e:
|
|
_logger.error(e)
|
|
return {'error': _('Internal server error, please try again later or contact administrator.\nHere is the error message: %s') % e}
|
|
return {'url': "/slides/slide/%s" % (slide_id.id)}
|
|
|
|
@http.route(['/slides/tag/search_read'], type='json', auth='user', methods=['POST'], website=True)
|
|
def slide_tag_search_read(self, fields, domain):
|
|
can_create = request.env['slide.tag'].check_access_rights('create', raise_exception=False)
|
|
return {
|
|
'read_results': request.env['slide.tag'].search_read(domain, fields),
|
|
'can_create': can_create,
|
|
}
|
|
|
|
@http.route(['/slides/category/search_read'], type='json', auth='user', methods=['POST'], website=True)
|
|
def slide_category_search_read(self, fields, domain):
|
|
can_create = request.env['slide.category'].check_access_rights('create', raise_exception=False)
|
|
return {
|
|
'read_results': request.env['slide.category'].search_read(domain, fields),
|
|
'can_create': can_create,
|
|
}
|
|
|
|
# --------------------------------------------------
|
|
# EMBED IN THIRD PARTY WEBSITES
|
|
# --------------------------------------------------
|
|
@http.route('/slides/embed/<int:slide_id>', type='http', auth='public', website=True, sitemap=False)
|
|
def slides_embed(self, slide_id, page="1", **kw):
|
|
# Note : don't use the 'model' in the route (use 'slide_id'), otherwise if public cannot access the embedded
|
|
# slide, the error will be the website.403 page instead of the one of the website_slides.embed_slide.
|
|
# Do not forget the rendering here will be displayed in the embedded iframe
|
|
|
|
# determine if it is embedded from external web page
|
|
referrer_url = request.httprequest.headers.get('Referer', '')
|
|
base_url = request.env['ir.config_parameter'].sudo().get_param('web.base.url')
|
|
is_embedded = referrer_url and not bool(base_url in referrer_url) or False
|
|
# try accessing slide, and display to corresponding template
|
|
try:
|
|
slide = request.env['slide.slide'].browse(slide_id)
|
|
if is_embedded:
|
|
request.env['slide.embed'].sudo().add_embed_url(slide.id, referrer_url)
|
|
values = self._get_slide_detail(slide)
|
|
values['page'] = page
|
|
values['is_embedded'] = is_embedded
|
|
if not values.get('private'):
|
|
self._set_viewed_slide(slide, 'embed')
|
|
return request.render('website_slides.embed_slide', values)
|
|
except AccessError: # TODO : please, make it clean one day, or find another secure way to detect
|
|
# if the slide can be embedded, and properly display the error message.
|
|
slide = request.env['slide.slide'].sudo().browse(slide_id)
|
|
return request.render('website_slides.embed_slide_forbidden', {'slide': slide})
|