# -*- coding: utf-8 -*- # Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details. import base64 import logging import werkzeug from flectra import http, _ from flectra.exceptions import AccessError, UserError from flectra.http import request from flectra.addons.http_routing.models.ir_http import slug from flectra.addons.website.models.ir_http import sitemap_qs2dom _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 _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.env.user == request.website.user_id, '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 = [] if not request.env.user.has_group('website.group_website_designer'): domain += [("website_ids", "in", request.website.id)] channels = request.env['slide.channel'].search(domain, order='sequence, id') if not channels: return request.render("website_slides.channel_not_found") elif len(channels) == 1: return request.redirect("/slides/%s" % channels.id) return request.render('website_slides.channels', { 'channels': channels, 'user': request.env.user, 'is_public_user': request.env.user == request.website.user_id }) def sitemap_slide(env, rule, qs): Channel = env['slide.channel'] dom = sitemap_qs2dom(qs=qs, route='/slides/', field=Channel._rec_name) for channel in Channel.search(dom): loc = '/slides/%s' % slug(channel) if not qs or qs.lower() in loc: yield {'loc': loc} @http.route([ '''/slides/''', '''/slides//page/''', '''/slides//''', '''/slides///page/''', '''/slides//tag/''', '''/slides//tag//page/''', '''/slides//category/''', '''/slides//category//page/''', '''/slides//category//''', '''/slides//category///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): user = request.env.user Slide = request.env['slide.slide'] if not request.env.user.has_group('website.group_website_publisher') \ and request.website.id not in channel.website_ids.ids: return request.render('website.404') domain = [('channel_id', '=', channel.id), ('website_ids', 'in', request.website.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': user == request.website.user_id, 'display_channel_settings': not request.httprequest.cookies.get('slides_channel_%s' % (channel.id), False) and channel.can_see_full, } 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//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//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.session.uid): 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//promote''', type='http', auth='user', website=True) def slide_set_promoted(self, slide): 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): slide = request.env['slide.slide'].browse(int(slide_id)) slide.likes += 1 return slide.likes @http.route('/slides/slide/dislike', type='json', auth="user", website=True) def slide_dislike(self, slide_id): slide = request.env['slide.slide'].browse(int(slide_id)) slide.dislikes += 1 return slide.dislikes @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 click here to view it ') % 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)} # -------------------------------------------------- # EMBED IN THIRD PARTY WEBSITES # -------------------------------------------------- @http.route('/slides/embed/', 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})