Skip to content
Browse files

[IMP] website_sale: Showcase Product Videos on eCommerece

This commit adds facility to showcase product videos on
it's eCommerce page. They can be added on 'product.image'
model, same as the extra images, thus allowing to enter
multiple videos per template and per variant.

Alos, associated image will be used as a thumbnail to the
video on product's website page.

Task - 1945483
  • Loading branch information...
dja-odoo committed Mar 29, 2019
1 parent e05311c commit fded6e4dc4152d0439b3b323f387f9f206a33d8d
@@ -1,7 +1,10 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from odoo import models, fields
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError

from import get_video_embed_url

class ProductImage(models.Model):
@@ -17,3 +20,17 @@ class ProductImage(models.Model):

product_tmpl_id = fields.Many2one('product.template', "Product Template", index=True, ondelete='cascade')
product_variant_id = fields.Many2one('product.product', "Product Variant", index=True, ondelete='cascade')
video_url = fields.Char('Video URL',
help='URL of a video for showcasing your product.')
embed_code = fields.Char(compute="_compute_embed_code")

def _compute_embed_code(self):
for image in self:
image.embed_code = get_video_embed_url(image.video_url)

def _check_valid_video_url(self):
for image in self:
if image.video_url and not image.embed_code:
raise ValidationError(_("Provided video URL for '%s' is not valid. Please enter a valid video URL.") %
@@ -0,0 +1,28 @@
odoo.define('website_sale.video_field_preview', function (require) {
"use strict";

var AbstractField = require('web.AbstractField');
var core = require('web.core');
var fieldRegistry = require('web.field_registry');

var QWeb = core.qweb;

* Displays preview of the video showcasing product.
var FieldVideoPreview = AbstractField.extend({
className: 'd-block o_field_video_preview',

_render: function () {
this.$el.html(QWeb.render('productVideo', {
embedCode: this.value,

fieldRegistry.add('video_preview', FieldVideoPreview);

return FieldVideoPreview;

@@ -613,6 +613,8 @@ {

.carousel-control-prev, .carousel-control-next {
height: 70%;
top: 15%;
opacity: 0.5;
cursor: pointer;
&:focus {
@@ -633,7 +635,13 @@ {
text-indent: unset;
border: 1px solid gray('600');
opacity: 0.5;
position: relative;

.o_product_video_thumb {
@include o-position-absolute($top: 50%, $left: 50%);
transform: translate(-50%, -50%);
color: gray('400');
&.active {
opacity: 1;
border: 1px solid theme-color('primary');
@@ -49,9 +49,7 @@

> img {
border: 1px solid gray('400');
min-height: 200px;
max-height: 350px; // Fallback for browsers that dosn't support responsive units
max-height: 50vh;
height: 200px;
width: auto;

@@ -61,4 +59,14 @@
.o_video_container {
height: 200px;
position: relative;
@include o-we-preview-box($text-muted);
.o_invalid_warning {
width: 90%;
@include o-position-absolute($top: 50%, $left: 50%);
transform: translate(-50%, -50%);
@@ -18,5 +18,10 @@
<t t-name="productVideo">
<div class="embed-responsive embed-responsive-16by9 mt-2" t-if="embedCode">
<t t-raw="embedCode"/>

@@ -102,7 +102,7 @@
<field name="website_style_ids" widget="many2many_tags" groups="base.group_no_one"/>
<group name="product_template_images" string="Extra Product Images">
<group name="product_template_images" string="Extra Product Images / Videos">
<field name="product_template_image_ids" class="o_website_sale_image_list" context="{'default_name': name}" mode="kanban" options="{'create_text':'Add an Image'}" nolabel="1"/>
@@ -116,7 +116,7 @@
<field name="inherit_id" ref="product.product_variant_easy_edit_view"/>
<field name="arch" type="xml">
<sheet position="inside">
<group name="product_variant_images" string="Extra Variant Images">
<group name="product_variant_images" string="Extra Variant Images / Videos">
<field name="product_variant_image_ids" class="o_website_sale_image_list" context="{'default_name': name}" mode="kanban" options="{'create_text':'Add an Image'}" nolabel="1"/>
@@ -208,10 +208,25 @@
<h2><field name="name" placeholder="Image Name"/></h2>
<!-- Unfortunately for now we can't drag and drop kanban o2m, so we have to let the user input the sequence manually. -->
<label for="sequence" string="Sequence"/><br/>
<field name="sequence"/>
<field name="sequence"/><br/><br/>
<label for="sequence" string="Video URL"/><br/>
<field name="video_url"/><br/>
<div class="col-md-6 col-xl-7 text-center o_website_sale_image_modal_container">
<field name="image_original" widget="image"/>
<div class="row">
<div class="col">
<field name="image_original" widget="image"/>
<div class="col" attrs="{'invisible': [('video_url', 'in', ['', False])]}">
<div class="o_video_container p-2">
<span>Video Preview</span>
<field name="embed_code" class="mt-2" widget="video_preview"/>
<h4 class="o_invalid_warning text-muted text-center" attrs="{'invisible': [('embed_code', '!=', False)]}">
Please enter a valid Video URL.
@@ -3,6 +3,7 @@
<!-- Layout and common templates -->
<template id="assets_backend" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<script type="text/javascript" src="/website_sale/static/src/js/website_sale_video_field_preview.js"></script>
<script type="text/javascript" src="/website_sale/static/src/js/website_sale_backend.js"></script>
<link rel="stylesheet" type="text/scss" href="/website_sale/static/src/scss/website_sale_dashboard.scss"/>
<link rel="stylesheet" type="text/scss" href="/website_sale/static/src/scss/website_sale_backend.scss"/>
@@ -1686,7 +1687,10 @@
<div class="carousel-inner h-100">
<t t-foreach="product_images" t-as="product_image">
<div t-attf-class="carousel-item h-100#{' active' if product_image_first else ''}">
<div t-field="product_image.image" class="d-flex align-items-center justify-content-center h-100" t-options='{"widget": "image", "preview_image": "image", "class": "product_detail_img mh-100", "alt-field": "name", "zoom": product_image.can_image_be_zoomed and "image_original"}'/>
<div t-if="product_image._name == 'product.image' and product_image.embed_code" class="d-flex align-items-center justify-content-center h-100 embed-responsive embed-responsive-16by9">
<t t-raw="product_image.embed_code"/>
<div t-else="" t-field="product_image.image" class="d-flex align-items-center justify-content-center h-100" t-options='{"widget": "image", "preview_image": "image", "class": "product_detail_img mh-100", "alt-field": "name", "zoom": product_image.can_image_be_zoomed and "image_original"}'/>
@@ -1703,6 +1707,7 @@
<ol t-if="len(product_images) > 1" class="carousel-indicators d-inline-block position-static mx-auto my-0 p-1 text-left">
<t t-foreach="product_images" t-as="product_image"><li t-attf-class="d-inline-block m-1 align-top {{'active' if product_image_first else ''}}" data-target="#o-carousel-product" t-att-data-slide-to="str(product_image_index)">
<div t-field="product_image.image_small" class="d-flex align-items-center justify-content-center h-100" t-options='{"widget": "image", "alt-field": "name"}'/>
<i t-if="product_image._name == 'product.image' and product_image.embed_code" class="fa fa-2x fa-play-circle-o o_product_video_thumb"/>

0 comments on commit fded6e4

Please sign in to comment.
You can’t perform that action at this time.