Skip to content

Commit

Permalink
[MERGE][REF] website_slides: allow / improve channel and slide creation
Browse files Browse the repository at this point in the history
This merge provides new modal to create slide channel from the 'New' website
menu, like blogpost or forum. It also improves the upload slide widget from
channel homepage.

This merge also brings new slide type 'webpage'. This allow user to create
completely customized content as a slide.

This merge is linked to Task-1938643. More generally it is linked to the
eLearning feature [1]. For more details see subcommits.

[1] see task ID 1902304 (main eLearning task) PR #29876

closes #30991
  • Loading branch information
robodoo committed Feb 12, 2019
2 parents 0284e5f + 1b1732b commit 77b5673
Show file tree
Hide file tree
Showing 14 changed files with 659 additions and 376 deletions.
6 changes: 3 additions & 3 deletions addons/website/views/website_navbar_templates.xml
Expand Up @@ -147,15 +147,15 @@
</a>
</div>
<div groups="base.group_system" name="module_website_slides" t-att-data-module-id="env.ref('base.module_website_slides').id" t-att-data-module-shortdesc="env.ref('base.module_website_slides').shortdesc" class="col-md-4 mb8 o_new_content_element">
<a href="#" data-action="new_slide">
<a href="#" data-action="new_slide_channel">
<i class="fa fa-youtube-play"/>
<p>New Slide</p>
<p>New Slide Channel</p>
</a>
</div>
<div groups="base.group_system" name="module_website_livechat" t-att-data-module-id="env.ref('base.module_website_livechat').id" t-att-data-module-shortdesc="env.ref('base.module_website_livechat').shortdesc" class="col-md-4 mb8 o_new_content_element">
<a href="#" data-action="new_channel">
<i class="fa fa-hashtag"/>
<p>New Channel</p>
<p>New Livechat Channel</p>
</a>
</div>
</div>
Expand Down
43 changes: 37 additions & 6 deletions addons/website_slides/controllers/main.py
Expand Up @@ -114,10 +114,11 @@ def channel(self, channel, category=None, tag=None, page=1, slide_type=None, sor

if search:
domain += [
'|', '|',
'|', '|', '|',
('name', 'ilike', search),
('description', 'ilike', search),
('index_content', 'ilike', search)]
('index_content', 'ilike', search),
('html_content', 'ilike', search)]
pager_args['search'] = search
else:
if category:
Expand Down Expand Up @@ -183,6 +184,23 @@ def channel(self, channel, category=None, tag=None, page=1, slide_type=None, sor

return request.render('website_slides.home', values)

@http.route(['/slides/channel/add'], type='http', auth='user', methods=['POST'], website=True)
def slide_channel_create(self, *args, **kw):
# `tag_ids` is a string representing a list of int with coma. i.e.: '2,5,7'
# We don't want to allow user to create tags and tag groups on the fly.
tag_ids = []
if kw.get('tag_ids'):
tag_ids = [int(item) for item in kw['tag_ids'].split(',')]

channel = request.env['slide.channel'].create({
'name': kw['name'],
'description': kw.get('description'),
'channel_type': kw.get('channel_type', 'documentation'),
'user_id': request.env.user.id,
'tag_ids': [(6, 0, tag_ids)],
})
return werkzeug.utils.redirect("/slides/%s" % (slug(channel)))

# --------------------------------------------------
# SLIDE.SLIDE CONTOLLERS
# --------------------------------------------------
Expand Down Expand Up @@ -257,8 +275,8 @@ def slide_channel_join(self, channel_id):
return {'error': 'join_done'}
return success

@http.route(['/slides/dialog_preview'], type='json', auth='user', methods=['POST'], website=True)
def dialog_preview(self, **data):
@http.route(['/slides/prepare_preview'], type='json', auth='user', methods=['POST'], website=True)
def prepare_preview(self, **data):
Slide = request.env['slide.slide']
document_type, document_id = Slide._find_document_data_from_url(data['url'])
preview = {}
Expand All @@ -279,13 +297,14 @@ def dialog_preview(self, **data):
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
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({
Expand Down Expand Up @@ -317,7 +336,19 @@ def create_slide(self, *args, **post):
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)}

redirect_url = "/slides/slide/%s" % (slide.id)
if slide.slide_type == 'webpage':
redirect_url += "?enable_editor=1"
return {'url': redirect_url}

@http.route(['/slides/channel/tag/search_read'], type='json', auth='user', methods=['POST'], website=True)
def slide_channel_tag_search_read(self, fields, domain):
can_create = request.env['slide.channel.tag'].check_access_rights('create', raise_exception=False)
return {
'read_results': request.env['slide.channel.tag'].search_read(domain, fields),
'can_create': can_create,
}

@http.route(['/slides/tag/search_read'], type='json', auth='user', methods=['POST'], website=True)
def slide_tag_search_read(self, fields, domain):
Expand Down
9 changes: 7 additions & 2 deletions addons/website_slides/models/slide_channel.py
Expand Up @@ -67,6 +67,7 @@ def _default_access_token(self):
('documentation', 'Documentation'), ('training', 'Training')],
string="Course type", default="documentation", required=True)
sequence = fields.Integer(default=10, help='Display order')
user_id = fields.Many2one('res.users', string='Responsible', default=lambda self: self.env.uid)
tag_ids = fields.Many2many(
'slide.channel.tag', 'slide_channel_tag_rel', 'channel_id', 'tag_id',
string='Tags', help='Used to categorize and filter displayed channels/courses')
Expand All @@ -90,6 +91,7 @@ def _default_access_token(self):
nbr_documents = fields.Integer('Number of Documents', compute='_compute_slides_statistics', store=True)
nbr_videos = fields.Integer('Number of Videos', compute='_compute_slides_statistics', store=True)
nbr_infographics = fields.Integer('Number of Infographics', compute='_compute_slides_statistics', store=True)
nbr_webpages = fields.Integer("Number of Webpages", compute='_compute_slides_statistics', store=True)
total_slides = fields.Integer('# Slides', compute='_compute_slides_statistics', store=True, oldname='total')
total_views = fields.Integer('# Views', compute='_compute_slides_statistics', store=True)
total_votes = fields.Integer('# Votes', compute='_compute_slides_statistics', store=True)
Expand Down Expand Up @@ -149,7 +151,7 @@ def _compute_is_member(self):
'slide_ids.likes', 'slide_ids.dislikes', 'slide_ids.total_views')
def _compute_slides_statistics(self):
result = dict.fromkeys(self.ids, dict(
nbr_presentations=0, nbr_documents=0, nbr_videos=0, nbr_infographics=0,
nbr_presentations=0, nbr_documents=0, nbr_videos=0, nbr_infographics=0, nbr_webpages=0,
total_slides=0, total_views=0, total_votes=0, total_time=0))
read_group_res = self.env['slide.slide'].read_group(
[('is_published', '=', True), ('channel_id', 'in', self.ids)],
Expand All @@ -162,6 +164,7 @@ def _compute_slides_statistics(self):
result[cid]['nbr_documents'] += res_group.get('slide_type', '') == 'document' and res_group['__count'] or 0
result[cid]['nbr_videos'] += res_group.get('slide_type', '') == 'video' and res_group['__count'] or 0
result[cid]['nbr_infographics'] += res_group.get('slide_type', '') == 'infographic' and res_group['__count'] or 0
result[cid]['nbr_webpages'] += res_group.get('slide_type', '') == 'webpage' and res_group['__count'] or 0
result[cid]['total_slides'] += res_group['__count']
result[cid]['total_views'] += res_group.get('total_views', 0)
result[cid]['total_votes'] += res_group.get('likes', 0)
Expand Down Expand Up @@ -363,6 +366,7 @@ class Category(models.Model):
nbr_documents = fields.Integer("Number of Documents", compute='_count_presentations', store=True)
nbr_videos = fields.Integer("Number of Videos", compute='_count_presentations', store=True)
nbr_infographics = fields.Integer("Number of Infographics", compute='_count_presentations', store=True)
nbr_webpages = fields.Integer("Number of Webpages", compute='_count_presentations', store=True)
total_slides = fields.Integer(compute='_count_presentations', store=True, oldname='total')

@api.depends('slide_ids.slide_type', 'slide_ids.is_published')
Expand All @@ -379,4 +383,5 @@ def _count_presentations(self):
record.nbr_documents = result[record.id].get('document', 0)
record.nbr_videos = result[record.id].get('video', 0)
record.nbr_infographics = result[record.id].get('infographic', 0)
record.total_slides = record.nbr_presentations + record.nbr_documents + record.nbr_videos + record.nbr_infographics
record.nbr_webpages = result[record.id].get('webpage', 0)
record.total_slides = record.nbr_presentations + record.nbr_documents + record.nbr_videos + record.nbr_infographics + record.nbr_webpages
17 changes: 15 additions & 2 deletions addons/website_slides/models/slide_slide.py
Expand Up @@ -31,6 +31,15 @@ class SlidePartnerRelation(models.Model):
completed = fields.Boolean('Completed')


class SlideLink(models.Model):
_name = 'slide.slide.link'
_description = "External URL for a particular slide"

slide_id = fields.Many2one('slide.slide', required=True)
name = fields.Char('Title', required=True)
link = fields.Char("External Link", required=True)


class EmbeddedSlide(models.Model):
""" Embedding in third party websites. Track view count, generate statistics. """
_name = 'slide.embed'
Expand Down Expand Up @@ -74,6 +83,7 @@ class Slide(models.Model):
- Document
- Infographic
- Video
- Webpage
Slide has various statistics like view count, embed count, like, dislikes """

Expand All @@ -93,7 +103,7 @@ class Slide(models.Model):
'website_published', 'website_url', 'website_meta_title', 'website_meta_description', 'website_meta_keywords', 'website_meta_og_img']

_sql_constraints = [
('name_uniq', 'UNIQUE(channel_id, name)', 'The slide name must be unique within a channel')
('exclusion_html_content_and_url', "CHECK(html_content IS NULL OR url IS NULL)", "A slide is either filled with a document url or HTML content. Not both.")
]

# description
Expand Down Expand Up @@ -122,15 +132,18 @@ class Slide(models.Model):
('infographic', 'Infographic'),
('presentation', 'Presentation'),
('document', 'Document'),
('webpage', 'Web Page'),
('video', 'Video')],
string='Type', required=True,
default='document',
default='document', readonly=True,
help="The document type will be set automatically based on the document URL and properties (e.g. height and width for presentation and document).")
index_content = fields.Text('Transcript')
datas = fields.Binary('Content', attachment=True)
url = fields.Char('Document URL', help="Youtube or Google Document URL")
document_id = fields.Char('Document ID', help="Youtube or Google Document ID")
link_ids = fields.One2many('slide.slide.link', 'slide_id', string="External URL for this slide")
mime_type = fields.Char('Mime-type')
html_content = fields.Html("HTML Content", help="Custom HTML content for slides of type 'Web Page'.", translate=True)
# website
website_id = fields.Many2one(related='channel_id.website_id', readonly=True)
date_published = fields.Datetime('Publish Date')
Expand Down
2 changes: 2 additions & 0 deletions addons/website_slides/security/ir.model.access.csv
Expand Up @@ -17,3 +17,5 @@ access_slide_category_all,slide.category.all,model_slide_category,,1,0,0,0
access_slide_category_publisher,slide.category.publisher,model_slide_category,website.group_website_publisher,1,1,1,1
access_slide_embed_all,slide.embed.all,model_slide_embed,,1,0,0,0
access_slide_embed_user,slide.embed.user,model_slide_embed,base.group_user,1,1,1,1
access_slide_slide_link_all,slide.slide.link.all,model_slide_slide_link,,1,0,0,0
access_slide_slide_link_publisher,slide.slide.link.publisher,model_slide_slide_link,website.group_website_publisher,1,1,1,1

0 comments on commit 77b5673

Please sign in to comment.