summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorAurélien Warnon <awa@odoo.com>2019-08-12 13:26:36 +0000
committerThibault Delavallée <tde@odoo.com>2019-08-12 15:40:36 +0000
commit5269d8c27f9f5e7b2d0ab86db6423f6ffa227f40 (patch)
treed75a94adc11cfa5ed8ac37d3935c14c939d17eaa /addons
parent2af9570c5bea778b4a9581273a83744ba39b80cf (diff)
[REF] website_slides: improve duration computation of a slide/course
The completion time of a slide now has default values for slides of type * video (duration of the YouTube video); * PDF (number of pages * 5 minutes); This computation is done when uploading a slide in frontend or by an onchange in backend. Field can still be given / modified by elearning people as result may not be reliable (pdf parsing is not accurate, and/or you may estimate time differently from strict page-based computation). The course will sum up all durations of its (active) slides. Display has been changed from "x minutes" to "x hours y minutes". Task ID 2007294
Diffstat (limited to 'addons')
-rw-r--r--addons/website_slides/controllers/main.py4
-rw-r--r--addons/website_slides/data/slide_slide_demo.xml2
-rw-r--r--addons/website_slides/models/slide_slide.py27
-rw-r--r--addons/website_slides/static/src/js/slides_upload.js6
-rw-r--r--addons/website_slides/static/src/xml/website_slides_upload.xml6
-rw-r--r--addons/website_slides/views/slide_channel_views.xml2
-rw-r--r--addons/website_slides/views/slide_slide_views.xml5
-rw-r--r--addons/website_slides/views/website_slides_templates_course.xml4
-rw-r--r--addons/website_slides/views/website_slides_templates_homepage.xml2
9 files changed, 53 insertions, 5 deletions
diff --git a/addons/website_slides/controllers/main.py b/addons/website_slides/controllers/main.py
index 85aeab3b224..d33867331ab 100644
--- a/addons/website_slides/controllers/main.py
+++ b/addons/website_slides/controllers/main.py
@@ -795,6 +795,10 @@ class WebsiteSlides(WebsiteProfile):
if not can_upload:
return {'error': _('You cannot upload on this channel.')}
+ if post.get('duration'):
+ # minutes to hours conversion
+ values['completion_time'] = int(post['duration']) / 60
+
# handle creation of new categories on the fly
if post.get('category_id'):
if post['category_id'][0] == 0:
diff --git a/addons/website_slides/data/slide_slide_demo.xml b/addons/website_slides/data/slide_slide_demo.xml
index b35f2defc5f..0a88c4a7498 100644
--- a/addons/website_slides/data/slide_slide_demo.xml
+++ b/addons/website_slides/data/slide_slide_demo.xml
@@ -445,7 +445,7 @@
<field name="is_published" eval="True"/>
<field name="is_preview" eval="False"/>
<field name="public_views">0</field>
- <field name="completion_time">1</field>
+ <field name="completion_time">3</field>
<field name="description">Learn of to identify quality wood in order to create solid furnitures.</field>
</record>
<record id="slide_slide_demo_5_2" model="slide.slide">
diff --git a/addons/website_slides/models/slide_slide.py b/addons/website_slides/models/slide_slide.py
index 2536817eedf..a1b99fe90f7 100644
--- a/addons/website_slides/models/slide_slide.py
+++ b/addons/website_slides/models/slide_slide.py
@@ -6,6 +6,7 @@ import datetime
import io
import re
import requests
+import PyPDF2
from PIL import Image
from werkzeug import urls
@@ -104,7 +105,7 @@ class Slide(models.Model):
channel_id = fields.Many2one('slide.channel', string="Channel", required=True)
tag_ids = fields.Many2many('slide.tag', 'rel_slide_tag', 'slide_id', 'tag_id', string='Tags')
is_preview = fields.Boolean('Is Preview', default=False, help="The course is accessible by anyone : the users don't need to join the channel to access the content of the course.")
- completion_time = fields.Float('Duration', default=1, digits=(10, 2))
+ completion_time = fields.Float('Completion Time', digits=(10, 4), help="The estimated completion time for this slide")
# Categories
is_category = fields.Boolean('Is a category', default=False)
category_id = fields.Many2one('slide.slide', string="Category", compute="_compute_category_id", store=True)
@@ -305,6 +306,15 @@ class Slide(models.Model):
for key, value in values.items():
self[key] = value
+ @api.onchange('datas')
+ def _on_change_datas(self):
+ """ For PDFs, we assume that it takes 5 minutes to read a page. """
+ if self.datas:
+ data = base64.b64decode(self.datas)
+ if data.startswith(b'%PDF-'):
+ pdf = PyPDF2.PdfFileReader(io.BytesIO(data), overwriteWarnings=False)
+ self.completion_time = (5 * len(pdf.pages)) / 60
+
@api.depends('name', 'channel_id.website_id.domain')
def _compute_website_url(self):
# TDE FIXME: clena this link.tracker strange stuff
@@ -655,8 +665,11 @@ class Slide(models.Model):
return {'error': _('Unknown document')}
def _parse_youtube_document(self, document_id, only_preview_fields):
+ """ If we receive a duration (YT video), we use it to determine the slide duration.
+ The received duration is under a special format (e.g: PT1M21S15, meaning 1h 21m 15s). """
+
key = self.env['website'].get_current_website().website_slide_google_app_key
- fetch_res = self._fetch_data('https://www.googleapis.com/youtube/v3/videos', {'id': document_id, 'key': key, 'part': 'snippet', 'fields': 'items(id,snippet)'}, 'json')
+ fetch_res = self._fetch_data('https://www.googleapis.com/youtube/v3/videos', {'id': document_id, 'key': key, 'part': 'snippet,contentDetails', 'fields': 'items(id,snippet,contentDetails)'}, 'json')
if fetch_res.get('error'):
return fetch_res
@@ -665,6 +678,14 @@ class Slide(models.Model):
if not items:
return {'error': _('Please enter valid Youtube or Google Doc URL')}
youtube_values = items[0]
+
+ youtube_duration = youtube_values.get('contentDetails', {}).get('duration')
+ if youtube_duration:
+ parsed_duration = re.search(r'^PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?$', youtube_duration)
+ values['completion_time'] = (int(parsed_duration.group(1) or 0)) + \
+ (int(parsed_duration.group(2) or 0) / 60) + \
+ (int(parsed_duration.group(3) or 0) / 3600)
+
if youtube_values.get('snippet'):
snippet = youtube_values['snippet']
if only_preview_fields:
@@ -673,7 +694,9 @@ class Slide(models.Model):
'title': snippet['title'],
'description': snippet['description']
})
+
return values
+
values.update({
'name': snippet['title'],
'image_1920': self._fetch_data(snippet['thumbnails']['high']['url'], {}, 'image')['values'],
diff --git a/addons/website_slides/static/src/js/slides_upload.js b/addons/website_slides/static/src/js/slides_upload.js
index 6b704c5d1c0..23e6411f512 100644
--- a/addons/website_slides/static/src/js/slides_upload.js
+++ b/addons/website_slides/static/src/js/slides_upload.js
@@ -130,6 +130,7 @@ var SlideUploadDialog = Dialog.extend({
'url': this._formGetFieldValue('url'),
'description': this._formGetFieldValue('description'),
'index_content': this._formGetFieldValue('index_content'),
+ 'duration': this._formGetFieldValue('duration'),
'is_published': forcePublished,
}, this._getSelect2DropdownValues()); // add tags and category
@@ -441,6 +442,7 @@ var SlideUploadDialog = Dialog.extend({
*/
Util = PDFJS.Util;
PDFJS.getDocument(new Uint8Array(buffer), null, passwordNeeded).then(function getPdf(pdf) {
+ self._formSetFieldValue('duration', (pdf.pdfInfo.numPages || 0) * 5);
pdf.getPage(1).then(function getFirstPage(page) {
var scale = 1;
var viewport = page.getViewport(scale);
@@ -508,6 +510,10 @@ var SlideUploadDialog = Dialog.extend({
if (data.error) {
self._alertDisplay(data.error);
} else {
+ if (data.completion_time) {
+ // hours to minutes conversion
+ self._formSetFieldValue('duration', Math.round(data.completion_time * 60));
+ }
self.$('#slide-image').attr('src', data.url_src);
self._formSetFieldValue('name', data.title);
self._formSetFieldValue('description', data.description);
diff --git a/addons/website_slides/static/src/xml/website_slides_upload.xml b/addons/website_slides/static/src/xml/website_slides_upload.xml
index 64668bfc153..e5d5e0d839f 100644
--- a/addons/website_slides/static/src/xml/website_slides_upload.xml
+++ b/addons/website_slides/static/src/xml/website_slides_upload.xml
@@ -60,6 +60,12 @@
<input id="tag_ids" name="tag_ids" type="hidden"/>
</div>
</div>
+ <div class="form-group row">
+ <label for="duration" class="col-form-label col-md-3">Completion Time (minutes)</label>
+ <div class="col-md-9">
+ <input type="number" id="duration" name="duration" placeholder="Estimated slide completion time" class="form-control"/>
+ </div>
+ </div>
</t>
<!--
diff --git a/addons/website_slides/views/slide_channel_views.xml b/addons/website_slides/views/slide_channel_views.xml
index e877311de22..3a7dd71c964 100644
--- a/addons/website_slides/views/slide_channel_views.xml
+++ b/addons/website_slides/views/slide_channel_views.xml
@@ -257,7 +257,7 @@
<field name="total_views"/>
</div>
<div class="d-flex" name="info_total_time">
- <span class="mr-auto"><label for="total_time" class="mb0">Watch Time</label></span>
+ <span class="mr-auto"><label for="total_time" class="mb0">Completion Time</label></span>
<field name="total_time" widget="float_time"/>
</div>
</div>
diff --git a/addons/website_slides/views/slide_slide_views.xml b/addons/website_slides/views/slide_slide_views.xml
index d145869f52f..050a40f6201 100644
--- a/addons/website_slides/views/slide_slide_views.xml
+++ b/addons/website_slides/views/slide_slide_views.xml
@@ -78,6 +78,11 @@
<field name="website_id" groups="website.group_multi_website"/>
<field name="website_url"/>
<field name="is_preview"/>
+ <label for="completion_time" string="Completion Time"/>
+ <div>
+ <field name="completion_time" widget="float_time" class="oe_inline"/>
+ <span> hours</span>
+ </div>
<field name="date_published" string="Published Date"/>
</group>
</group>
diff --git a/addons/website_slides/views/website_slides_templates_course.xml b/addons/website_slides/views/website_slides_templates_course.xml
index 554b8486239..15b41e90c71 100644
--- a/addons/website_slides/views/website_slides_templates_course.xml
+++ b/addons/website_slides/views/website_slides_templates_course.xml
@@ -278,6 +278,10 @@
<th class="border-top-0">Last Update</th>
<td class="border-top-0"><t t-esc="channel.slide_last_update" t-options="{'widget': 'date'}"/></td>
</tr>
+ <tr t-if="channel.total_time">
+ <th class="border-top-0">Completion Time</th>
+ <td class="border-top-0"><t class="font-weight-bold" t-esc="channel.total_time" t-options="{'widget': 'duration', 'unit': 'hour', 'round': 'minute'}"/></td>
+ </tr>
<tr>
<th>Members</th>
<td><t t-esc="channel.members_count"/></td>
diff --git a/addons/website_slides/views/website_slides_templates_homepage.xml b/addons/website_slides/views/website_slides_templates_homepage.xml
index 561ab5f2823..d326044dd42 100644
--- a/addons/website_slides/views/website_slides_templates_homepage.xml
+++ b/addons/website_slides/views/website_slides_templates_homepage.xml
@@ -318,7 +318,7 @@
</div>
<div class="card-footer bg-white text-600 px-3">
<div class="d-flex justify-content-between align-items-center">
- <small t-if="channel.total_time" class="font-weight-bold"><t t-esc="channel.total_time * 60" t-options="{&quot;widget&quot;: &quot;integer&quot;}"/> min</small>
+ <small t-if="channel.total_time" class="font-weight-bold" t-esc="channel.total_time" t-options="{'widget': 'duration', 'unit': 'hour', 'round': 'minute'}"/>
<div class="d-flex flex-grow-1 justify-content-end">
<t t-if="channel.is_member and channel.completed">
<span class="badge badge-pill badge-success pull-right py-1 px-2"><i class="fa fa-check"/> Completed</span>