[MOV] slides: Rename and split files according to their underlying models
Purpose of this merge is to polish a bit this module before turning it into "courses" (see task #1902304 for more details on this) Contains * split python model into separate main files to ease models understanding; * split views into separate main files to ease views finding; * rename files according to guidelines; * merge templates in the same file to have all available at hand; * move assets in their own file in order to remove noise from templates; * Split javascript widgets into their own separate files No functional behavior change should occur with this commit. Only move has been performed. Note that we might lose part of the history as splitting files creates a new history for new files. Task: #1909281 Closes: #28679
This commit is contained in:
@@ -19,11 +19,17 @@ Share and Publish Videos, Presentations and Documents'
|
||||
""",
|
||||
'depends': ['website', 'website_mail'],
|
||||
'data': [
|
||||
'views/assets.xml',
|
||||
'views/res_config_settings_views.xml',
|
||||
'views/website_slides.xml',
|
||||
'views/website_slides_embed.xml',
|
||||
'views/website_slides_backend.xml',
|
||||
'data/website_slides_data.xml',
|
||||
'views/website_slides_templates.xml',
|
||||
'views/website_slides_embed_templates.xml',
|
||||
'views/slide_slide_views.xml',
|
||||
'views/slide_channel_views.xml',
|
||||
'views/website_slides_menu_views.xml',
|
||||
'data/website_slides_ir_data.xml',
|
||||
'data/mail_data.xml',
|
||||
'data/slide_data.xml',
|
||||
'data/website_data.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'security/website_slides_security.xml'
|
||||
],
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="slide_template_published" model="mail.template">
|
||||
<field name="name">Slide Published</field>
|
||||
<field name="model_id" ref="model_slide_slide"/>
|
||||
<field name="subject">New ${object.slide_type} published on ${object.channel_id.name}</field>
|
||||
<field name="body_html" type="html">
|
||||
<div style="margin: 0px; padding: 0px;">
|
||||
<p style="margin: 0px; padding: 0px; font-size: 13px;">
|
||||
Hello<br/><br/>
|
||||
A new ${object.slide_type} <strong>${object.name}</strong> has been published on ${object.channel_id.name} at ${format_tz(object.write_date, tz=user.tz)}
|
||||
<div style="margin: 16px 8px 16px 8px; text-align: center;">
|
||||
<a href="${object.website_url}">
|
||||
<img alt="${object.name}" src="${ctx['base_url']}/web/image/slide.slide/${object.id}/image" style="height:auto; width:150px; margin: 16px;"/>
|
||||
</a>
|
||||
</div>
|
||||
<div style="margin: 16px 8px 16px 8px; text-align: center;">
|
||||
<a href="${object.website_url}"
|
||||
style="background-color: #875a7b; padding: 8px 16px 8px 16px; text-decoration: none; color: #fff; border-radius: 5px;">View <strong>${object.name}</strong></a>
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
</field>
|
||||
<field name="auto_delete" eval="True"/>
|
||||
<field name="user_signature" eval="True"/>
|
||||
</record>
|
||||
|
||||
<record id="slide_template_shared" model="mail.template">
|
||||
<field name="name">Slide Shared</field>
|
||||
<field name="model_id" ref="model_slide_slide"/>
|
||||
<field name="subject">${user.name} shared a ${object.slide_type} with you!</field>
|
||||
<field name="email_from">${(user.email or '')|safe}</field>
|
||||
<field name="email_to">${ctx['email']}</field>
|
||||
<field name="body_html" type="html">
|
||||
<div style="margin: 0px; padding: 0px;">
|
||||
<p style="margin: 0px; padding: 0px; font-size: 13px;">
|
||||
Hello<br/><br/>
|
||||
${user.name} shared the ${object.slide_type} <strong>${object.name}</strong> with you!
|
||||
<div style="margin: 16px 8px 16px 8px; text-align: center;">
|
||||
<a href="${object.website_url}">
|
||||
<img alt="${object.name}" src="${ctx['base_url']}/web/image/slide.slide/${object.id}/image" style="height:auto; width:150px; margin: 16px;"/>
|
||||
</a>
|
||||
</div>
|
||||
<div style="margin: 16px 8px 16px 8px; text-align: center;">
|
||||
<a href="${object.website_url}"
|
||||
style="background-color: #875a7b; padding: 8px 16px 8px 16px; text-decoration: none; color: #fff; border-radius: 5px;">View <strong>${object.name}</strong></a>
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
</field>
|
||||
<field name="auto_delete" eval="True"/>
|
||||
<field name="user_signature" eval="True"/>
|
||||
</record>
|
||||
|
||||
<!-- Channel subtypes -->
|
||||
<record id="mt_channel_slide_published" model="mail.message.subtype">
|
||||
<field name="name">Presentation Published</field>
|
||||
<field name="res_model">slide.channel</field>
|
||||
<field name="default" eval="True"/>
|
||||
<field name="description">Presentation Published</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record model="slide.channel" id="channel_public">
|
||||
<field name="name">Public Channel</field>
|
||||
<field name="promote_strategy">most_viewed</field>
|
||||
<field name="website_published" eval="True"/>
|
||||
<field name="visibility">public</field>
|
||||
<field name="description">Default channel for slides, all public users can access content of this channel.</field>
|
||||
</record>
|
||||
<record model="slide.channel" id="channel_partial">
|
||||
<field name="name">Partner Channel</field>
|
||||
<field name="website_published" eval="True"/>
|
||||
<field name="group_ids" eval="[(4, ref('base.group_portal')), (4, ref('base.group_user'))]"/>
|
||||
<field name="upload_group_ids" eval="[(4, ref('base.group_user'))]"/>
|
||||
<field name="visibility">partial</field>
|
||||
<field name="description">Used to publish slides in partner network privately.</field>
|
||||
</record>
|
||||
<record model="slide.channel" id="channel_private">
|
||||
<field name="name">Employee Channel</field>
|
||||
<field name="group_ids" eval="[(4, ref('base.group_user'))]"/>
|
||||
<field name="visibility">private</field>
|
||||
<field name="description">Used to publish internal slides of company.</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="website.default_website" model="website">
|
||||
<field name="website_slide_google_app_key">AIzaSyDOWlmDW-7DbLmOR9ZsT5AOEXf4n6TFwQA</field>
|
||||
</record>
|
||||
|
||||
<record id="website_menu_slides" model="website.menu">
|
||||
<field name="name">Presentations</field>
|
||||
<field name="url">/slides</field>
|
||||
<field name="parent_id" ref="website.main_menu"/>
|
||||
<field name="sequence" type="int">50</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -1,109 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
|
||||
<record id="website.default_website" model="website">
|
||||
<field name="website_slide_google_app_key">AIzaSyDOWlmDW-7DbLmOR9ZsT5AOEXf4n6TFwQA</field>
|
||||
</record>
|
||||
|
||||
<record id="website_menu_slides" model="website.menu">
|
||||
<field name="name">Presentations</field>
|
||||
<field name="url">/slides</field>
|
||||
<field name="parent_id" ref="website.main_menu"/>
|
||||
<field name="sequence" type="int">50</field>
|
||||
</record>
|
||||
|
||||
<record id="slide_template_published" model="mail.template">
|
||||
<field name="name">Slide Published</field>
|
||||
<field name="model_id" ref="model_slide_slide"/>
|
||||
<field name="subject">New ${object.slide_type} published on ${object.channel_id.name}</field>
|
||||
<field name="body_html" type="html">
|
||||
<div style="margin: 0px; padding: 0px;">
|
||||
<p style="margin: 0px; padding: 0px; font-size: 13px;">
|
||||
Hello<br/><br/>
|
||||
A new ${object.slide_type} <strong>${object.name}</strong> has been published on ${object.channel_id.name} at ${format_tz(object.write_date, tz=user.tz)}
|
||||
<div style="margin: 16px 8px 16px 8px; text-align: center;">
|
||||
<a href="${object.website_url}">
|
||||
<img alt="${object.name}" src="${ctx['base_url']}/web/image/slide.slide/${object.id}/image" style="height:auto; width:150px; margin: 16px;"/>
|
||||
</a>
|
||||
</div>
|
||||
<div style="margin: 16px 8px 16px 8px; text-align: center;">
|
||||
<a href="${object.website_url}"
|
||||
style="background-color: #875a7b; padding: 8px 16px 8px 16px; text-decoration: none; color: #fff; border-radius: 5px;">View <strong>${object.name}</strong></a>
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
</field>
|
||||
<field name="auto_delete" eval="True"/>
|
||||
<field name="user_signature" eval="True"/>
|
||||
</record>
|
||||
|
||||
<record id="slide_template_shared" model="mail.template">
|
||||
<field name="name">Slide Shared</field>
|
||||
<field name="model_id" ref="model_slide_slide"/>
|
||||
<field name="subject">${user.name} shared a ${object.slide_type} with you!</field>
|
||||
<field name="email_from">${(user.email or '')|safe}</field>
|
||||
<field name="email_to">${ctx['email']}</field>
|
||||
<field name="body_html" type="html">
|
||||
<div style="margin: 0px; padding: 0px;">
|
||||
<p style="margin: 0px; padding: 0px; font-size: 13px;">
|
||||
Hello<br/><br/>
|
||||
${user.name} shared the ${object.slide_type} <strong>${object.name}</strong> with you!
|
||||
<div style="margin: 16px 8px 16px 8px; text-align: center;">
|
||||
<a href="${object.website_url}">
|
||||
<img alt="${object.name}" src="${ctx['base_url']}/web/image/slide.slide/${object.id}/image" style="height:auto; width:150px; margin: 16px;"/>
|
||||
</a>
|
||||
</div>
|
||||
<div style="margin: 16px 8px 16px 8px; text-align: center;">
|
||||
<a href="${object.website_url}"
|
||||
style="background-color: #875a7b; padding: 8px 16px 8px 16px; text-decoration: none; color: #fff; border-radius: 5px;">View <strong>${object.name}</strong></a>
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
</field>
|
||||
<field name="auto_delete" eval="True"/>
|
||||
<field name="user_signature" eval="True"/>
|
||||
</record>
|
||||
|
||||
<record model="slide.channel" id="channel_public">
|
||||
<field name="name">Public Channel</field>
|
||||
<field name="promote_strategy">most_viewed</field>
|
||||
<field name="website_published" eval="True"/>
|
||||
<field name="visibility">public</field>
|
||||
<field name="description">Default channel for slides, all public users can access content of this channel.</field>
|
||||
</record>
|
||||
<record model="slide.channel" id="channel_partial">
|
||||
<field name="name">Partner Channel</field>
|
||||
<field name="website_published" eval="True"/>
|
||||
<field name="group_ids" eval="[(4, ref('base.group_portal')), (4, ref('base.group_user'))]"/>
|
||||
<field name="upload_group_ids" eval="[(4, ref('base.group_user'))]"/>
|
||||
<field name="visibility">partial</field>
|
||||
<field name="description">Used to publish slides in partner network privately.</field>
|
||||
</record>
|
||||
<record model="slide.channel" id="channel_private">
|
||||
<field name="name">Employee Channel</field>
|
||||
<field name="group_ids" eval="[(4, ref('base.group_user'))]"/>
|
||||
<field name="visibility">private</field>
|
||||
<field name="description">Used to publish internal slides of company.</field>
|
||||
</record>
|
||||
|
||||
<!-- Channel subtypes -->
|
||||
<record id="mt_channel_slide_published" model="mail.message.subtype">
|
||||
<field name="name">Presentation Published</field>
|
||||
<field name="res_model">slide.channel</field>
|
||||
<field name="default" eval="True"/>
|
||||
<field name="description">Presentation Published</field>
|
||||
</record>
|
||||
|
||||
<!-- jump to Channel on installation complete -->
|
||||
<record id="action_open_channels" model="ir.actions.act_url">
|
||||
<field name="name">Channels</field>
|
||||
<field name="target">self</field>
|
||||
<field name="url" eval="'/slides'"/>
|
||||
</record>
|
||||
<record id="base.open_menu" model="ir.actions.todo">
|
||||
<field name="action_id" ref="action_open_channels"/>
|
||||
<field name="state">open</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<!-- jump to Channel on installation complete -->
|
||||
<record id="action_open_channels" model="ir.actions.act_url">
|
||||
<field name="name">Channels</field>
|
||||
<field name="target">self</field>
|
||||
<field name="url" eval="'/slides'"/>
|
||||
</record>
|
||||
<record id="base.open_menu" model="ir.actions.todo">
|
||||
<field name="action_id" ref="action_open_channels"/>
|
||||
<field name="state">open</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -1,5 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import slides
|
||||
from . import slide_slide
|
||||
from . import slide_channel
|
||||
from . import res_config_settings
|
||||
from . import website
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models, SUPERUSER_ID, _
|
||||
from odoo.addons.http_routing.models.ir_http import slug
|
||||
from odoo.tools.translate import html_translate
|
||||
|
||||
|
||||
class Channel(models.Model):
|
||||
""" A channel is a container of slides. It has group-based access configuration
|
||||
allowing to configure slide upload and access. Slides can be promoted in
|
||||
channels. """
|
||||
_name = 'slide.channel'
|
||||
_description = 'Slide Channel'
|
||||
_inherit = ['mail.thread', 'website.seo.metadata', 'website.published.multi.mixin']
|
||||
_order = 'sequence, id'
|
||||
_order_by_strategy = {
|
||||
'most_viewed': 'total_views desc',
|
||||
'most_voted': 'likes desc',
|
||||
'latest': 'date_published desc',
|
||||
}
|
||||
|
||||
name = fields.Char('Name', translate=True, required=True)
|
||||
active = fields.Boolean(default=True)
|
||||
description = fields.Html('Description', translate=html_translate, sanitize_attributes=False)
|
||||
sequence = fields.Integer(default=10, help='Display order')
|
||||
category_ids = fields.One2many('slide.category', 'channel_id', string="Categories")
|
||||
slide_ids = fields.One2many('slide.slide', 'channel_id', string="Slides")
|
||||
promote_strategy = fields.Selection([
|
||||
('none', 'No Featured Presentation'),
|
||||
('latest', 'Latest Published'),
|
||||
('most_voted', 'Most Voted'),
|
||||
('most_viewed', 'Most Viewed'),
|
||||
('custom', 'Featured Presentation')],
|
||||
string="Featuring Policy", default='most_voted', required=True)
|
||||
custom_slide_id = fields.Many2one('slide.slide', string='Slide to Promote')
|
||||
promoted_slide_id = fields.Many2one('slide.slide', string='Featured Slide', compute='_compute_promoted_slide_id', store=True)
|
||||
|
||||
@api.depends('custom_slide_id', 'promote_strategy', 'slide_ids.likes',
|
||||
'slide_ids.total_views', "slide_ids.date_published")
|
||||
def _compute_promoted_slide_id(self):
|
||||
for record in self:
|
||||
if record.promote_strategy == 'none':
|
||||
record.promoted_slide_id = False
|
||||
elif record.promote_strategy == 'custom':
|
||||
record.promoted_slide_id = record.custom_slide_id
|
||||
elif record.promote_strategy:
|
||||
slides = self.env['slide.slide'].search(
|
||||
[('website_published', '=', True), ('channel_id', '=', record.id)],
|
||||
limit=1, order=self._order_by_strategy[record.promote_strategy])
|
||||
record.promoted_slide_id = slides and slides[0] or False
|
||||
|
||||
nbr_presentations = fields.Integer('Number of Presentations', compute='_count_presentations', store=True)
|
||||
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)
|
||||
total = fields.Integer(compute='_count_presentations', store=True)
|
||||
|
||||
@api.depends('slide_ids.slide_type', 'slide_ids.website_published')
|
||||
def _count_presentations(self):
|
||||
result = dict.fromkeys(self.ids, dict())
|
||||
res = self.env['slide.slide'].read_group(
|
||||
[('website_published', '=', True), ('channel_id', 'in', self.ids)],
|
||||
['channel_id', 'slide_type'], ['channel_id', 'slide_type'],
|
||||
lazy=False)
|
||||
for res_group in res:
|
||||
result[res_group['channel_id'][0]][res_group['slide_type']] = result[res_group['channel_id'][0]].get(res_group['slide_type'], 0) + res_group['__count']
|
||||
for record in self:
|
||||
record.nbr_presentations = result[record.id].get('presentation', 0)
|
||||
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 = record.nbr_presentations + record.nbr_documents + record.nbr_videos + record.nbr_infographics
|
||||
|
||||
publish_template_id = fields.Many2one(
|
||||
'mail.template', string='Published Template',
|
||||
help="Email template to send slide publication through email",
|
||||
default=lambda self: self.env['ir.model.data'].xmlid_to_res_id('website_slides.slide_template_published'))
|
||||
share_template_id = fields.Many2one(
|
||||
'mail.template', string='Shared Template',
|
||||
help="Email template used when sharing a slide",
|
||||
default=lambda self: self.env['ir.model.data'].xmlid_to_res_id('website_slides.slide_template_shared'))
|
||||
visibility = fields.Selection([
|
||||
('public', 'Public'),
|
||||
('private', 'Private'),
|
||||
('partial', 'Show channel but restrict presentations')],
|
||||
default='public', required=True)
|
||||
group_ids = fields.Many2many(
|
||||
'res.groups', 'rel_channel_groups', 'channel_id', 'group_id',
|
||||
string='Channel Groups', help="Groups allowed to see presentations in this channel")
|
||||
access_error_msg = fields.Html(
|
||||
'Error Message', help="Message to display when not accessible due to access rights",
|
||||
default=lambda s: _("<p>This channel is private and its content is restricted to some users.</p>"),
|
||||
translate=html_translate, sanitize_attributes=False)
|
||||
upload_group_ids = fields.Many2many(
|
||||
'res.groups', 'rel_upload_groups', 'channel_id', 'group_id',
|
||||
string='Upload Groups', help="Groups allowed to upload presentations in this channel. If void, every user can upload.")
|
||||
# not stored access fields, depending on each user
|
||||
can_see = fields.Boolean('Can See', compute='_compute_access', search='_search_can_see')
|
||||
can_see_full = fields.Boolean('Full Access', compute='_compute_access')
|
||||
can_upload = fields.Boolean('Can Upload', compute='_compute_access')
|
||||
|
||||
def _search_can_see(self, operator, value):
|
||||
if operator not in ('=', '!=', '<>'):
|
||||
raise ValueError('Invalid operator: %s' % (operator,))
|
||||
|
||||
if not value:
|
||||
operator = operator == "=" and '!=' or '='
|
||||
|
||||
if self._uid == SUPERUSER_ID:
|
||||
return [(1, '=', 1)]
|
||||
|
||||
# Better perfs to split request and use inner join that left join
|
||||
req = """
|
||||
SELECT id FROM slide_channel WHERE visibility='public'
|
||||
UNION
|
||||
SELECT c.id
|
||||
FROM slide_channel c
|
||||
INNER JOIN rel_channel_groups rg on c.id = rg.channel_id
|
||||
INNER JOIN res_groups g on g.id = rg.group_id
|
||||
INNER JOIN res_groups_users_rel u on g.id = u.gid and uid = %s
|
||||
"""
|
||||
op = operator == "=" and "inselect" or "not inselect"
|
||||
# don't use param named because orm will add other param (test_active, ...)
|
||||
return [('id', op, (req, (self._uid, )))]
|
||||
|
||||
@api.one
|
||||
@api.depends('visibility', 'group_ids', 'upload_group_ids')
|
||||
def _compute_access(self):
|
||||
self.can_see = self.visibility in ['public', 'private'] or bool(self.group_ids & self.env.user.groups_id)
|
||||
self.can_see_full = self.visibility == 'public' or bool(self.group_ids & self.env.user.groups_id)
|
||||
self.can_upload = self.can_see and (not self.upload_group_ids or bool(self.upload_group_ids & self.env.user.groups_id))
|
||||
|
||||
@api.multi
|
||||
@api.depends('name')
|
||||
def _compute_website_url(self):
|
||||
super(Channel, self)._compute_website_url()
|
||||
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
|
||||
for channel in self:
|
||||
if channel.id: # avoid to perform a slug on a not yet saved record in case of an onchange.
|
||||
channel.website_url = '%s/slides/%s' % (base_url, slug(channel))
|
||||
|
||||
@api.onchange('visibility')
|
||||
def change_visibility(self):
|
||||
if self.visibility == 'public':
|
||||
self.group_ids = False
|
||||
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
res = super(Channel, self).write(vals)
|
||||
if 'active' in vals:
|
||||
# archiving/unarchiving a channel does it on its slides, too
|
||||
self.with_context(active_test=False).mapped('slide_ids').write({'active': vals['active']})
|
||||
return res
|
||||
|
||||
@api.multi
|
||||
@api.returns('mail.message', lambda value: value.id)
|
||||
def message_post(self, parent_id=False, subtype=None, **kwargs):
|
||||
""" Temporary workaround to avoid spam. If someone replies on a channel
|
||||
through the 'Presentation Published' email, it should be considered as a
|
||||
note as we don't want all channel followers to be notified of this answer. """
|
||||
self.ensure_one()
|
||||
if parent_id:
|
||||
parent_message = self.env['mail.message'].sudo().browse(parent_id)
|
||||
if parent_message.subtype_id and parent_message.subtype_id == self.env.ref('website_slides.mt_channel_slide_published'):
|
||||
if kwargs.get('subtype_id'):
|
||||
kwargs['subtype_id'] = False
|
||||
subtype = 'mail.mt_note'
|
||||
return super(Channel, self).message_post(parent_id=parent_id, subtype=subtype, **kwargs)
|
||||
|
||||
def list_all(self):
|
||||
return {
|
||||
'channels': [{'id': channel.id, 'name': channel.name, 'website_url': channel.website_url} for channel in self.search([])]
|
||||
}
|
||||
|
||||
|
||||
class Category(models.Model):
|
||||
""" Channel contain various categories to manage its slides """
|
||||
_name = 'slide.category'
|
||||
_description = "Slides Category"
|
||||
_order = "sequence, id"
|
||||
|
||||
name = fields.Char('Name', translate=True, required=True)
|
||||
channel_id = fields.Many2one('slide.channel', string="Channel", required=True, ondelete='cascade')
|
||||
sequence = fields.Integer(default=10, help='Display order')
|
||||
slide_ids = fields.One2many('slide.slide', 'category_id', string="Slides")
|
||||
nbr_presentations = fields.Integer("Number of Presentations", compute='_count_presentations', store=True)
|
||||
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)
|
||||
total = fields.Integer(compute='_count_presentations', store=True)
|
||||
|
||||
@api.depends('slide_ids.slide_type', 'slide_ids.website_published')
|
||||
def _count_presentations(self):
|
||||
result = dict.fromkeys(self.ids, dict())
|
||||
res = self.env['slide.slide'].read_group(
|
||||
[('website_published', '=', True), ('category_id', 'in', self.ids)],
|
||||
['category_id', 'slide_type'], ['category_id', 'slide_type'],
|
||||
lazy=False)
|
||||
for res_group in res:
|
||||
result[res_group['category_id'][0]][res_group['slide_type']] = result[res_group['category_id'][0]].get(res_group['slide_type'], 0) + res_group['__count']
|
||||
for record in self:
|
||||
record.nbr_presentations = result[record.id].get('presentation', 0)
|
||||
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 = record.nbr_presentations + record.nbr_documents + record.nbr_videos + record.nbr_infographics
|
||||
@@ -14,213 +14,11 @@ from werkzeug import urls
|
||||
from odoo import api, fields, models, SUPERUSER_ID, _
|
||||
from odoo.addons.http_routing.models.ir_http import slug
|
||||
from odoo.tools import image
|
||||
from odoo.tools.translate import html_translate
|
||||
from odoo.exceptions import Warning
|
||||
from odoo.http import request
|
||||
from odoo.addons.http_routing.models.ir_http import url_for
|
||||
|
||||
|
||||
class Channel(models.Model):
|
||||
""" A channel is a container of slides. It has group-based access configuration
|
||||
allowing to configure slide upload and access. Slides can be promoted in
|
||||
channels. """
|
||||
_name = 'slide.channel'
|
||||
_description = 'Slide Channel'
|
||||
_inherit = ['mail.thread', 'website.seo.metadata', 'website.published.multi.mixin']
|
||||
_order = 'sequence, id'
|
||||
_order_by_strategy = {
|
||||
'most_viewed': 'total_views desc',
|
||||
'most_voted': 'likes desc',
|
||||
'latest': 'date_published desc',
|
||||
}
|
||||
|
||||
name = fields.Char('Name', translate=True, required=True)
|
||||
active = fields.Boolean(default=True)
|
||||
description = fields.Html('Description', translate=html_translate, sanitize_attributes=False)
|
||||
sequence = fields.Integer(default=10, help='Display order')
|
||||
category_ids = fields.One2many('slide.category', 'channel_id', string="Categories")
|
||||
slide_ids = fields.One2many('slide.slide', 'channel_id', string="Slides")
|
||||
promote_strategy = fields.Selection([
|
||||
('none', 'No Featured Presentation'),
|
||||
('latest', 'Latest Published'),
|
||||
('most_voted', 'Most Voted'),
|
||||
('most_viewed', 'Most Viewed'),
|
||||
('custom', 'Featured Presentation')],
|
||||
string="Featuring Policy", default='most_voted', required=True)
|
||||
custom_slide_id = fields.Many2one('slide.slide', string='Slide to Promote')
|
||||
promoted_slide_id = fields.Many2one('slide.slide', string='Featured Slide', compute='_compute_promoted_slide_id', store=True)
|
||||
|
||||
@api.depends('custom_slide_id', 'promote_strategy', 'slide_ids.likes',
|
||||
'slide_ids.total_views', "slide_ids.date_published")
|
||||
def _compute_promoted_slide_id(self):
|
||||
for record in self:
|
||||
if record.promote_strategy == 'none':
|
||||
record.promoted_slide_id = False
|
||||
elif record.promote_strategy == 'custom':
|
||||
record.promoted_slide_id = record.custom_slide_id
|
||||
elif record.promote_strategy:
|
||||
slides = self.env['slide.slide'].search(
|
||||
[('website_published', '=', True), ('channel_id', '=', record.id)],
|
||||
limit=1, order=self._order_by_strategy[record.promote_strategy])
|
||||
record.promoted_slide_id = slides and slides[0] or False
|
||||
|
||||
nbr_presentations = fields.Integer('Number of Presentations', compute='_count_presentations', store=True)
|
||||
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)
|
||||
total = fields.Integer(compute='_count_presentations', store=True)
|
||||
|
||||
@api.depends('slide_ids.slide_type', 'slide_ids.website_published')
|
||||
def _count_presentations(self):
|
||||
result = dict.fromkeys(self.ids, dict())
|
||||
res = self.env['slide.slide'].read_group(
|
||||
[('website_published', '=', True), ('channel_id', 'in', self.ids)],
|
||||
['channel_id', 'slide_type'], ['channel_id', 'slide_type'],
|
||||
lazy=False)
|
||||
for res_group in res:
|
||||
result[res_group['channel_id'][0]][res_group['slide_type']] = result[res_group['channel_id'][0]].get(res_group['slide_type'], 0) + res_group['__count']
|
||||
for record in self:
|
||||
record.nbr_presentations = result[record.id].get('presentation', 0)
|
||||
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 = record.nbr_presentations + record.nbr_documents + record.nbr_videos + record.nbr_infographics
|
||||
|
||||
publish_template_id = fields.Many2one(
|
||||
'mail.template', string='Published Template',
|
||||
help="Email template to send slide publication through email",
|
||||
default=lambda self: self.env['ir.model.data'].xmlid_to_res_id('website_slides.slide_template_published'))
|
||||
share_template_id = fields.Many2one(
|
||||
'mail.template', string='Shared Template',
|
||||
help="Email template used when sharing a slide",
|
||||
default=lambda self: self.env['ir.model.data'].xmlid_to_res_id('website_slides.slide_template_shared'))
|
||||
visibility = fields.Selection([
|
||||
('public', 'Public'),
|
||||
('private', 'Private'),
|
||||
('partial', 'Show channel but restrict presentations')],
|
||||
default='public', required=True)
|
||||
group_ids = fields.Many2many(
|
||||
'res.groups', 'rel_channel_groups', 'channel_id', 'group_id',
|
||||
string='Channel Groups', help="Groups allowed to see presentations in this channel")
|
||||
access_error_msg = fields.Html(
|
||||
'Error Message', help="Message to display when not accessible due to access rights",
|
||||
default=lambda s: _("<p>This channel is private and its content is restricted to some users.</p>"),
|
||||
translate=html_translate, sanitize_attributes=False)
|
||||
upload_group_ids = fields.Many2many(
|
||||
'res.groups', 'rel_upload_groups', 'channel_id', 'group_id',
|
||||
string='Upload Groups', help="Groups allowed to upload presentations in this channel. If void, every user can upload.")
|
||||
# not stored access fields, depending on each user
|
||||
can_see = fields.Boolean('Can See', compute='_compute_access', search='_search_can_see')
|
||||
can_see_full = fields.Boolean('Full Access', compute='_compute_access')
|
||||
can_upload = fields.Boolean('Can Upload', compute='_compute_access')
|
||||
|
||||
def _search_can_see(self, operator, value):
|
||||
if operator not in ('=', '!=', '<>'):
|
||||
raise ValueError('Invalid operator: %s' % (operator,))
|
||||
|
||||
if not value:
|
||||
operator = operator == "=" and '!=' or '='
|
||||
|
||||
if self._uid == SUPERUSER_ID:
|
||||
return [(1, '=', 1)]
|
||||
|
||||
# Better perfs to split request and use inner join that left join
|
||||
req = """
|
||||
SELECT id FROM slide_channel WHERE visibility='public'
|
||||
UNION
|
||||
SELECT c.id
|
||||
FROM slide_channel c
|
||||
INNER JOIN rel_channel_groups rg on c.id = rg.channel_id
|
||||
INNER JOIN res_groups g on g.id = rg.group_id
|
||||
INNER JOIN res_groups_users_rel u on g.id = u.gid and uid = %s
|
||||
"""
|
||||
op = operator == "=" and "inselect" or "not inselect"
|
||||
# don't use param named because orm will add other param (test_active, ...)
|
||||
return [('id', op, (req, (self._uid, )))]
|
||||
|
||||
@api.one
|
||||
@api.depends('visibility', 'group_ids', 'upload_group_ids')
|
||||
def _compute_access(self):
|
||||
self.can_see = self.visibility in ['public', 'private'] or bool(self.group_ids & self.env.user.groups_id)
|
||||
self.can_see_full = self.visibility == 'public' or bool(self.group_ids & self.env.user.groups_id)
|
||||
self.can_upload = self.can_see and (not self.upload_group_ids or bool(self.upload_group_ids & self.env.user.groups_id))
|
||||
|
||||
@api.multi
|
||||
@api.depends('name')
|
||||
def _compute_website_url(self):
|
||||
super(Channel, self)._compute_website_url()
|
||||
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
|
||||
for channel in self:
|
||||
if channel.id: # avoid to perform a slug on a not yet saved record in case of an onchange.
|
||||
channel.website_url = '%s/slides/%s' % (base_url, slug(channel))
|
||||
|
||||
@api.onchange('visibility')
|
||||
def change_visibility(self):
|
||||
if self.visibility == 'public':
|
||||
self.group_ids = False
|
||||
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
res = super(Channel, self).write(vals)
|
||||
if 'active' in vals:
|
||||
# archiving/unarchiving a channel does it on its slides, too
|
||||
self.with_context(active_test=False).mapped('slide_ids').write({'active': vals['active']})
|
||||
return res
|
||||
|
||||
@api.multi
|
||||
@api.returns('mail.message', lambda value: value.id)
|
||||
def message_post(self, parent_id=False, subtype=None, **kwargs):
|
||||
""" Temporary workaround to avoid spam. If someone replies on a channel
|
||||
through the 'Presentation Published' email, it should be considered as a
|
||||
note as we don't want all channel followers to be notified of this answer. """
|
||||
self.ensure_one()
|
||||
if parent_id:
|
||||
parent_message = self.env['mail.message'].sudo().browse(parent_id)
|
||||
if parent_message.subtype_id and parent_message.subtype_id == self.env.ref('website_slides.mt_channel_slide_published'):
|
||||
if kwargs.get('subtype_id'):
|
||||
kwargs['subtype_id'] = False
|
||||
subtype = 'mail.mt_note'
|
||||
return super(Channel, self).message_post(parent_id=parent_id, subtype=subtype, **kwargs)
|
||||
|
||||
def list_all(self):
|
||||
return {
|
||||
'channels': [{'id': channel.id, 'name': channel.name, 'website_url': channel.website_url} for channel in self.search([])]
|
||||
}
|
||||
|
||||
|
||||
class Category(models.Model):
|
||||
""" Channel contain various categories to manage its slides """
|
||||
_name = 'slide.category'
|
||||
_description = "Slides Category"
|
||||
_order = "sequence, id"
|
||||
|
||||
name = fields.Char('Name', translate=True, required=True)
|
||||
channel_id = fields.Many2one('slide.channel', string="Channel", required=True, ondelete='cascade')
|
||||
sequence = fields.Integer(default=10, help='Display order')
|
||||
slide_ids = fields.One2many('slide.slide', 'category_id', string="Slides")
|
||||
nbr_presentations = fields.Integer("Number of Presentations", compute='_count_presentations', store=True)
|
||||
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)
|
||||
total = fields.Integer(compute='_count_presentations', store=True)
|
||||
|
||||
@api.depends('slide_ids.slide_type', 'slide_ids.website_published')
|
||||
def _count_presentations(self):
|
||||
result = dict.fromkeys(self.ids, dict())
|
||||
res = self.env['slide.slide'].read_group(
|
||||
[('website_published', '=', True), ('category_id', 'in', self.ids)],
|
||||
['category_id', 'slide_type'], ['category_id', 'slide_type'],
|
||||
lazy=False)
|
||||
for res_group in res:
|
||||
result[res_group['category_id'][0]][res_group['slide_type']] = result[res_group['category_id'][0]].get(res_group['slide_type'], 0) + res_group['__count']
|
||||
for record in self:
|
||||
record.nbr_presentations = result[record.id].get('presentation', 0)
|
||||
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 = record.nbr_presentations + record.nbr_documents + record.nbr_videos + record.nbr_infographics
|
||||
|
||||
|
||||
class EmbeddedSlide(models.Model):
|
||||
""" Embedding in third party websites. Track view count, generate statistics. """
|
||||
_name = 'slide.embed'
|
||||
@@ -2,225 +2,34 @@
|
||||
odoo.define('website_slides.slides', function (require) {
|
||||
"use strict";
|
||||
|
||||
var ajax = require('web.ajax');
|
||||
var core = require('web.core');
|
||||
var time = require('web.time');
|
||||
var Widget = require('web.Widget');
|
||||
var local_storage = require('web.local_storage');
|
||||
require('root.widget');
|
||||
var time = require('web.time');
|
||||
require('root.widget');
|
||||
|
||||
var _t = core._t;
|
||||
var page_widgets = {};
|
||||
|
||||
(function () {
|
||||
var widget_parent = $('body');
|
||||
|
||||
|
||||
$("timeago.timeago").each(function (index, el) {
|
||||
var datetime = $(el).attr('datetime'),
|
||||
datetime_obj = time.str_to_datetime(datetime),
|
||||
// if presentation 7 days, 24 hours, 60 min, 60 second, 1000 millis old(one week)
|
||||
// then return fix formate string else timeago
|
||||
display_str = "";
|
||||
if (datetime_obj && new Date().getTime() - datetime_obj.getTime() > 7 * 24 * 60 * 60 * 1000) {
|
||||
display_str = moment(datetime_obj).format('ll');
|
||||
} else {
|
||||
display_str = moment(datetime_obj).fromNow();
|
||||
}
|
||||
$(el).text(display_str);
|
||||
});
|
||||
|
||||
// To prevent showing channel settings alert box once user closed it.
|
||||
$('.o_slides_hide_channel_settings').on('click', function(ev) {
|
||||
var channel_id = $(this).data("channelId");
|
||||
ev.preventDefault();
|
||||
document.cookie = "slides_channel_" + channel_id + " = closed";
|
||||
return true;
|
||||
});
|
||||
|
||||
/*
|
||||
* Like/Dislike Buttons Widget
|
||||
*/
|
||||
var LikeButton = Widget.extend({
|
||||
setElement: function($el){
|
||||
this._super.apply(this, arguments);
|
||||
this.$el.on('click', this, _.bind(this.apply_action, this));
|
||||
},
|
||||
apply_action: function(ev){
|
||||
var button = $(ev.currentTarget);
|
||||
var slide_id = button.data('slide-id');
|
||||
var user_id = button.data('user-id');
|
||||
var is_public = button.data('public-user');
|
||||
var href = button.data('href');
|
||||
if(is_public){
|
||||
this.popover_alert(button, _.str.sprintf(_t('Please <a href="/web?redirect=%s">login</a> to vote this slide'), (document.URL)));
|
||||
}else{
|
||||
var target = button.find('.fa');
|
||||
if (local_storage.getItem('slide_vote_' + slide_id) !== user_id.toString()) {
|
||||
ajax.jsonRpc(href, 'call', {slide_id: slide_id}).then(function (data) {
|
||||
target.text(data);
|
||||
local_storage.setItem('slide_vote_' + slide_id, user_id);
|
||||
});
|
||||
} else {
|
||||
this.popover_alert(button, _t('You have already voted for this slide'));
|
||||
}
|
||||
}
|
||||
},
|
||||
popover_alert: function($el, message){
|
||||
$el.popover({
|
||||
trigger: 'focus',
|
||||
placement: 'bottom',
|
||||
container: 'body',
|
||||
html: true,
|
||||
content: function(){
|
||||
return message;
|
||||
}
|
||||
}).popover('show');
|
||||
},
|
||||
});
|
||||
|
||||
page_widgets['likeButton'] = new LikeButton(widget_parent).setElement($('.oe_slide_js_like'));
|
||||
page_widgets['dislikeButton'] = new LikeButton(widget_parent).setElement($('.oe_slide_js_unlike'));
|
||||
|
||||
/*
|
||||
* Embedded Code Widget
|
||||
*/
|
||||
var SlideSocialEmbed = Widget.extend({
|
||||
events: {
|
||||
'change input' : 'change_page',
|
||||
},
|
||||
init: function(parent, max_page){
|
||||
this._super(parent);
|
||||
this.max_page = max_page || false;
|
||||
},
|
||||
change_page: function(ev){
|
||||
ev.preventDefault();
|
||||
var input = this.$('input');
|
||||
var page = parseInt(input.val());
|
||||
if (this.max_page && !(page > 0 && page <= this.max_page)) {
|
||||
page = 1;
|
||||
}
|
||||
this.update_embedded_code(page);
|
||||
},
|
||||
update_embedded_code: function(page){
|
||||
var embed_input = this.$('.slide_embed_code');
|
||||
var new_code = embed_input.val().replace(/(page=).*?([^\d]+)/, '$1' + page + '$2');
|
||||
embed_input.val(new_code);
|
||||
},
|
||||
});
|
||||
|
||||
$('iframe.o_wslides_iframe_viewer').ready(function() {
|
||||
// TODO : make it work. For now, once the iframe is loaded, the value of #page_count is
|
||||
// still now set (the pdf is still loading)
|
||||
var $iframe = $(this);
|
||||
var max_page = $iframe.contents().find('#page_count').val();
|
||||
new SlideSocialEmbed($iframe, max_page).setElement($('.oe_slide_js_embed_code_widget'));
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
* Send by email Widget
|
||||
*/
|
||||
var ShareMail = Widget.extend({
|
||||
events: {
|
||||
'click button' : 'send_mail',
|
||||
},
|
||||
send_mail: function(){
|
||||
var self = this;
|
||||
var input = this.$('input');
|
||||
var slide_id = this.$('button').data('slide-id');
|
||||
if(input.val() && input[0].checkValidity()){
|
||||
this.$el.removeClass('o_has_error').find('.form-control, .custom-select').removeClass('is-invalid');
|
||||
ajax.jsonRpc('/slides/slide/send_share_email', 'call', {
|
||||
slide_id: slide_id,
|
||||
email: input.val(),
|
||||
}).then(function () {
|
||||
self.$el.html($('<div class="alert alert-info" role="alert"><strong>Thank you!</strong> Mail has been sent.</div>'));
|
||||
});
|
||||
}else{
|
||||
this.$el.addClass('o_has_error').find('.form-control, .custom-select').addClass('is-invalid');
|
||||
input.focus();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
page_widgets['share_mail'] = new ShareMail(widget_parent).setElement($('.oe_slide_js_share_email'));
|
||||
|
||||
/*
|
||||
* Social Sharing Statistics Widget
|
||||
*/
|
||||
if ($('div#statistic').length) {
|
||||
var slide_url = $("div#statistic").attr('slide-url');
|
||||
var social_urls = {
|
||||
'linkedin': 'https://www.linkedin.com/countserv/count/share?url=',
|
||||
'twitter': 'https://cdn.api.twitter.com/1/urls/count.json?url=',
|
||||
'facebook': 'https://graph.facebook.com/?id=',
|
||||
'gplus': 'https://clients6.google.com/rpc'
|
||||
}
|
||||
|
||||
var update_statistics = function(social_site, slide_url) {
|
||||
if (social_site == 'gplus') {
|
||||
$.ajax({
|
||||
url: social_urls['gplus'],
|
||||
type: "POST",
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify([{
|
||||
"method": "pos.plusones.get",
|
||||
"id": "p",
|
||||
"params": {
|
||||
"nolog": true,
|
||||
"id": slide_url,
|
||||
"source": "widget",
|
||||
"userId": "@viewer",
|
||||
"groupId": "@self"
|
||||
},
|
||||
// TDE NOTE: should there be a key here ?
|
||||
"jsonrpc": "2.0",
|
||||
"apiVersion": "v1"
|
||||
}]),
|
||||
success: function(data) {
|
||||
$('#google-badge').text(data[0].result.metadata.globalCounts.count || 0);
|
||||
$('#total-share').text(parseInt($('#total-share').text()) + parseInt($('#google-badge').text()));
|
||||
},
|
||||
});
|
||||
(function () {
|
||||
$("timeago.timeago").each(function (index, el) {
|
||||
var datetime = $(el).attr('datetime'),
|
||||
datetime_obj = time.str_to_datetime(datetime),
|
||||
// if presentation 7 days, 24 hours, 60 min, 60 second, 1000 millis old(one week)
|
||||
// then return fix formate string else timeago
|
||||
display_str = "";
|
||||
if (datetime_obj && new Date().getTime() - datetime_obj.getTime() > 7 * 24 * 60 * 60 * 1000) {
|
||||
display_str = moment(datetime_obj).format('ll');
|
||||
} else {
|
||||
$.ajax({
|
||||
url: social_urls[social_site] + slide_url,
|
||||
dataType: 'jsonp',
|
||||
success: function(data) {
|
||||
var shareCount = (social_site === 'facebook' ? data.shares : data.count) || 0;
|
||||
$('#' + social_site + '-badge').text(shareCount);
|
||||
$('#total-share').text(parseInt($('#total-share').text()) + parseInt($('#' + social_site+ '-badge').text()));
|
||||
},
|
||||
});
|
||||
display_str = moment(datetime_obj).fromNow();
|
||||
}
|
||||
};
|
||||
|
||||
$.each(social_urls, function(key, value) {
|
||||
update_statistics(key, slide_url);
|
||||
$(el).text(display_str);
|
||||
});
|
||||
|
||||
$("a.o_slides_social_share").on('click', function(ev) {
|
||||
// To prevent showing channel settings alert box once user closed it.
|
||||
$('.o_slides_hide_channel_settings').on('click', function(ev) {
|
||||
var channel_id = $(this).data("channelId");
|
||||
ev.preventDefault();
|
||||
var key = $(ev.currentTarget).attr('social-key');
|
||||
var popUpURL = $(ev.currentTarget).attr('href');
|
||||
var popUp = window.open(
|
||||
popUpURL,
|
||||
'Share Dialog',
|
||||
'width=626,height=436');
|
||||
$(window).on('focus', function() {
|
||||
if (popUp.closed) {
|
||||
update_statistics(key, slide_url);
|
||||
$(window).off('focus');
|
||||
}
|
||||
});
|
||||
document.cookie = "slides_channel_" + channel_id + " = closed";
|
||||
return true;
|
||||
});
|
||||
})();
|
||||
|
||||
return {
|
||||
page_widgets: {},
|
||||
}
|
||||
})();
|
||||
|
||||
return {
|
||||
page_widgets: page_widgets,
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
@@ -1,5 +1,50 @@
|
||||
/*global $, _, PDFSlidesViewer*/
|
||||
odoo.define('website_slides.slides_embed', function (require) {
|
||||
"use strict";
|
||||
|
||||
var time = require('web.time');
|
||||
var Widget = require('web.Widget');
|
||||
require('website_slides.slides');
|
||||
|
||||
(function () {
|
||||
/*
|
||||
* Embedded Code Widget
|
||||
*/
|
||||
var SlideSocialEmbed = Widget.extend({
|
||||
events: {
|
||||
'change input' : 'change_page',
|
||||
},
|
||||
init: function(parent, max_page){
|
||||
this._super(parent);
|
||||
this.max_page = max_page || false;
|
||||
},
|
||||
change_page: function(ev){
|
||||
ev.preventDefault();
|
||||
var input = this.$('input');
|
||||
var page = parseInt(input.val());
|
||||
if (this.max_page && !(page > 0 && page <= this.max_page)) {
|
||||
page = 1;
|
||||
}
|
||||
this.update_embedded_code(page);
|
||||
},
|
||||
update_embedded_code: function(page){
|
||||
var embed_input = this.$('.slide_embed_code');
|
||||
var new_code = embed_input.val().replace(/(page=).*?([^\d]+)/, '$1' + page + '$2');
|
||||
embed_input.val(new_code);
|
||||
},
|
||||
});
|
||||
|
||||
$('iframe.o_wslides_iframe_viewer').ready(function() {
|
||||
// TODO : make it work. For now, once the iframe is loaded, the value of #page_count is
|
||||
// still now set (the pdf is still loading)
|
||||
var $iframe = $(this);
|
||||
var max_page = $iframe.contents().find('#page_count').val();
|
||||
slides.page_widgets['social_embed'] = new SlideSocialEmbed($iframe, max_page).setElement($('.oe_slide_js_embed_code_widget'));
|
||||
});
|
||||
|
||||
})();
|
||||
|
||||
});
|
||||
/**
|
||||
This file is a minimal version of the PDFViewer widget.
|
||||
It is NOT use in the website_slides module, but it is
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*global $, _, PDFJS */
|
||||
odoo.define('website_slides.slides_like', function (require) {
|
||||
"use strict";
|
||||
|
||||
var ajax = require('web.ajax');
|
||||
var core = require('web.core');
|
||||
var Widget = require('web.Widget');
|
||||
var local_storage = require('web.local_storage');
|
||||
var slides = require('website_slides.slides');
|
||||
|
||||
var _t = core._t;
|
||||
|
||||
(function () {
|
||||
var widget_parent = $('body');
|
||||
|
||||
/*
|
||||
* Like/Dislike Buttons Widget
|
||||
*/
|
||||
var LikeButton = Widget.extend({
|
||||
setElement: function($el){
|
||||
this._super.apply(this, arguments);
|
||||
this.$el.on('click', this, _.bind(this.apply_action, this));
|
||||
},
|
||||
apply_action: function(ev){
|
||||
var button = $(ev.currentTarget);
|
||||
var slide_id = button.data('slide-id');
|
||||
var user_id = button.data('user-id');
|
||||
var is_public = button.data('public-user');
|
||||
var href = button.data('href');
|
||||
if(is_public){
|
||||
this.popover_alert(button, _.str.sprintf(_t('Please <a href="/web?redirect=%s">login</a> to vote this slide'), (document.URL)));
|
||||
}else{
|
||||
var target = button.find('.fa');
|
||||
if (local_storage.getItem('slide_vote_' + slide_id) !== user_id.toString()) {
|
||||
ajax.jsonRpc(href, 'call', {slide_id: slide_id}).then(function (data) {
|
||||
target.text(data);
|
||||
local_storage.setItem('slide_vote_' + slide_id, user_id);
|
||||
});
|
||||
} else {
|
||||
this.popover_alert(button, _t('You have already voted for this slide'));
|
||||
}
|
||||
}
|
||||
},
|
||||
popover_alert: function($el, message){
|
||||
$el.popover({
|
||||
trigger: 'focus',
|
||||
placement: 'bottom',
|
||||
container: 'body',
|
||||
html: true,
|
||||
content: function(){
|
||||
return message;
|
||||
}
|
||||
}).popover('show');
|
||||
},
|
||||
});
|
||||
|
||||
slides.page_widgets['likeButton'] = new LikeButton(widget_parent).setElement($('.oe_slide_js_like'));
|
||||
slides.page_widgets['dislikeButton'] = new LikeButton(widget_parent).setElement($('.oe_slide_js_unlike'));
|
||||
|
||||
})();
|
||||
|
||||
});
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
odoo.define('website_slides.slides_share', function (require) {
|
||||
"use strict";
|
||||
var ajax = require('web.ajax');
|
||||
var Widget = require('web.Widget');
|
||||
var slides = require('website_slides.slides');
|
||||
|
||||
(function () {
|
||||
var widget_parent = $('body');
|
||||
|
||||
/*
|
||||
* Send by email Widget
|
||||
*/
|
||||
var ShareMail = Widget.extend({
|
||||
events: {
|
||||
'click button' : 'send_mail',
|
||||
},
|
||||
send_mail: function(){
|
||||
var self = this;
|
||||
var input = this.$('input');
|
||||
var slide_id = this.$('button').data('slide-id');
|
||||
if(input.val() && input[0].checkValidity()){
|
||||
this.$el.removeClass('o_has_error').find('.form-control, .custom-select').removeClass('is-invalid');
|
||||
ajax.jsonRpc('/slides/slide/send_share_email', 'call', {
|
||||
slide_id: slide_id,
|
||||
email: input.val(),
|
||||
}).then(function () {
|
||||
self.$el.html($('<div class="alert alert-info" role="alert"><strong>Thank you!</strong> Mail has been sent.</div>'));
|
||||
});
|
||||
}else{
|
||||
this.$el.addClass('o_has_error').find('.form-control, .custom-select').addClass('is-invalid');
|
||||
input.focus();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
slides.page_widgets['share_mail'] = new ShareMail(widget_parent).setElement($('.oe_slide_js_share_email'));
|
||||
|
||||
/*
|
||||
* Social Sharing Statistics Widget
|
||||
*/
|
||||
if ($('div#statistic').length) {
|
||||
var slide_url = $("div#statistic").attr('slide-url');
|
||||
var social_urls = {
|
||||
'linkedin': 'https://www.linkedin.com/countserv/count/share?url=',
|
||||
'twitter': 'https://cdn.api.twitter.com/1/urls/count.json?url=',
|
||||
'facebook': 'https://graph.facebook.com/?id=',
|
||||
'gplus': 'https://clients6.google.com/rpc'
|
||||
}
|
||||
|
||||
var update_statistics = function(social_site, slide_url) {
|
||||
if (social_site == 'gplus') {
|
||||
$.ajax({
|
||||
url: social_urls['gplus'],
|
||||
type: "POST",
|
||||
dataType: 'json',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify([{
|
||||
"method": "pos.plusones.get",
|
||||
"id": "p",
|
||||
"params": {
|
||||
"nolog": true,
|
||||
"id": slide_url,
|
||||
"source": "widget",
|
||||
"userId": "@viewer",
|
||||
"groupId": "@self"
|
||||
},
|
||||
// TDE NOTE: should there be a key here ?
|
||||
"jsonrpc": "2.0",
|
||||
"apiVersion": "v1"
|
||||
}]),
|
||||
success: function(data) {
|
||||
$('#google-badge').text(data[0].result.metadata.globalCounts.count || 0);
|
||||
$('#total-share').text(parseInt($('#total-share').text()) + parseInt($('#google-badge').text()));
|
||||
},
|
||||
});
|
||||
} else {
|
||||
$.ajax({
|
||||
url: social_urls[social_site] + slide_url,
|
||||
dataType: 'jsonp',
|
||||
success: function(data) {
|
||||
var shareCount = (social_site === 'facebook' ? data.shares : data.count) || 0;
|
||||
$('#' + social_site + '-badge').text(shareCount);
|
||||
$('#total-share').text(parseInt($('#total-share').text()) + parseInt($('#' + social_site+ '-badge').text()));
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$.each(social_urls, function(key, value) {
|
||||
update_statistics(key, slide_url);
|
||||
});
|
||||
|
||||
$("a.o_slides_social_share").on('click', function(ev) {
|
||||
ev.preventDefault();
|
||||
var key = $(ev.currentTarget).attr('social-key');
|
||||
var popUpURL = $(ev.currentTarget).attr('href');
|
||||
var popUp = window.open(
|
||||
popUpURL,
|
||||
'Share Dialog',
|
||||
'width=626,height=436');
|
||||
$(window).on('focus', function() {
|
||||
if (popUp.closed) {
|
||||
update_statistics(key, slide_url);
|
||||
$(window).off('focus');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
})();
|
||||
})
|
||||
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" ?>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<template id="assets_frontend" inherit_id="website.assets_frontend" name="Slide">
|
||||
<xpath expr="." position="inside">
|
||||
<link rel="stylesheet" type="text/scss" href="/website_slides/static/src/scss/website_slides.scss" t-ignore="true"/>
|
||||
<!-- Js widgets, ... order loading files is IMPORTANT -->
|
||||
<script type="text/javascript" src="/website_slides/static/src/js/slides.js"/>
|
||||
<script type="text/javascript" src="/website_slides/static/src/js/slides_like.js"/>
|
||||
<script type="text/javascript" src="/website_slides/static/src/js/slides_share.js"/>
|
||||
<script type="text/javascript" src="/website_slides/static/src/js/slides_upload.js"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="assets_editor_inherit_website_slides" inherit_id="website.assets_editor" name="website_slides Assets Editor">
|
||||
<xpath expr="." position="inside">
|
||||
<script type="text/javascript" src="/website_slides/static/src/js/website_slides.editor.js"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<!-- Bundle (minimal) for embedded slide iframe -->
|
||||
<template id="website_slides.slide_embed_assets" name="Website slides embed assets">
|
||||
<t t-call="web._assets_helpers"><t t-set="no_bs_vars" t-value="True"/></t>
|
||||
<t t-call="web._assets_bootstrap"/>
|
||||
<link rel="stylesheet" type="text/scss" href="/website_slides/static/src/scss/website_slides.scss" t-ignore="true"/>
|
||||
|
||||
<t t-call="web.pdf_js_lib"></t>
|
||||
<script type="text/javascript" src="/website_slides/static/lib/pdfslidesviewer/PDFSlidesViewer.js"></script>
|
||||
<script type="text/javascript" src="/website_slides/static/src/js/slides_embed.js"></script>
|
||||
</template>
|
||||
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,145 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<!-- SLIDE.CATEGORY -->
|
||||
<record model="ir.ui.view" id="view_slides_category_form">
|
||||
<field name="name">slide.category.form</field>
|
||||
<field name="model">slide.category</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Category">
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="channel_id"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_slides_category_tree" model="ir.ui.view">
|
||||
<field name="name">slide.category.tree</field>
|
||||
<field name="model">slide.category</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Category">
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="name"/>
|
||||
<field name="channel_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_ir_slide_category" model="ir.actions.act_window">
|
||||
<field name="name">Categories</field>
|
||||
<field name="res_model">slide.category</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Create a new category
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- SLIDE.CHANNEL VIEWS -->
|
||||
<record model="ir.ui.view" id="view_slide_channel_form">
|
||||
<field name="name">slide.channel.form</field>
|
||||
<field name="model">slide.channel</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Channels">
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button class="oe_stat_button" name="website_publish_button"
|
||||
type="object" icon="fa-globe">
|
||||
<field name="website_published" widget="website_button"/>
|
||||
</button>
|
||||
<button name="toggle_active" type="object" confirm="(Un)archiving a channel automatically (un)archives its slides. Do you want to proceed?" class="oe_stat_button" icon="fa-archive">
|
||||
<field name="active" widget="boolean_button" options='{"terminology": "archive"}'/>
|
||||
</button>
|
||||
</div>
|
||||
<div class="oe_title">
|
||||
<div class="oe_edit_only">
|
||||
<label for="name" string="Name"/>
|
||||
</div>
|
||||
<h1>
|
||||
<field name="name" default_focus="1" placeholder="Name"/>
|
||||
</h1>
|
||||
</div>
|
||||
<field name="category_ids" widget="many2many_tags" placeholder="Categories" context="{'default_channel_id': active_id}"
|
||||
domain="[('channel_id','=', active_id)]"/>
|
||||
<notebook colspan="4">
|
||||
<page string="General">
|
||||
<group>
|
||||
<group>
|
||||
<field name="website_id" options="{'no_create': True}" groups="website.group_multi_website"/>
|
||||
<field name="promote_strategy" widget="radio"/>
|
||||
<field name="custom_slide_id" class="oe_edit_only" attrs="{'invisible': [('promote_strategy', '!=', 'custom')]}" domain="[('channel_id','=',id), ('website_published','=',True)]"/>
|
||||
<field name="promoted_slide_id" attrs="{'invisible': [('promote_strategy', '!=', 'custom')]}"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="publish_template_id" domain="[('model','=','slide.slide')]"/>
|
||||
<field name="share_template_id" domain="[('model','=','slide.slide')]"/>
|
||||
</group>
|
||||
</group>
|
||||
<label for="description"/>
|
||||
<field name="description" colspan="4" nolabel="1"/>
|
||||
</page>
|
||||
<page string="Security">
|
||||
<group>
|
||||
<field name="upload_group_ids" widget="many2many_tags"/>
|
||||
<field name="visibility" string="Channel visibility" widget="radio"/>
|
||||
<field name="group_ids" widget="many2many_tags" attrs="{'invisible':[('visibility', '=', 'public')], 'required':[('visibility', 'in',('private', 'partial'))]}"/>
|
||||
</group>
|
||||
<group attrs="{'invisible':[('visibility','in',('public'))]}" colspan="4">
|
||||
<label for="access_error_msg" string="Error Message"/>
|
||||
<field name="access_error_msg" colspan="4" nolabel="1"/>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_slide_channel_tree" model="ir.ui.view">
|
||||
<field name="name">slide.channel.tree</field>
|
||||
<field name="model">slide.channel</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Channel">
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="name"/>
|
||||
<field name="website_id" groups="website.group_multi_website"/>
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="visibility"/>
|
||||
<field name="promote_strategy"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="slide_channel_view_search" model="ir.ui.view">
|
||||
<field name="name">slide.channel.search</field>
|
||||
<field name="model">slide.channel</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Channel">
|
||||
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_slide_channels" model="ir.actions.act_window">
|
||||
<field name="name">Channels</field>
|
||||
<field name="res_model">slide.channel</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Create a channel
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,163 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<!-- SLIDE.TAG -->
|
||||
<record id="view_slide_tag_form" model="ir.ui.view">
|
||||
<field name="name">slide.tag.form</field>
|
||||
<field name="model">slide.tag</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Tag">
|
||||
<field name="name"/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_slide_tag_tree" model="ir.ui.view">
|
||||
<field name="name">slide.tag.tree</field>
|
||||
<field name="model">slide.tag</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Tags">
|
||||
<field name="name"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_slide_tag" model="ir.actions.act_window">
|
||||
<field name="name">Tags</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">slide.tag</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
|
||||
<!-- SLIDE.SLIDE -->
|
||||
<record id="view_slide_slide_form" model="ir.ui.view">
|
||||
<field name="name">slide.slide.form</field>
|
||||
<field name="model">slide.slide</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Slide">
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button class="oe_stat_button" name="website_publish_button"
|
||||
type="object" icon="fa-globe">
|
||||
<field name="website_published" widget="website_button"/>
|
||||
</button>
|
||||
<button name="toggle_active" type="object" class="oe_stat_button" icon="fa-archive">
|
||||
<field name="active" widget="boolean_button" options='{"terminology": "archive"}'/>
|
||||
</button>
|
||||
</div>
|
||||
<field name="image" widget="image" class="oe_avatar" options='{"preview_image": "image_thumb"}' readonly="1"/>
|
||||
<div class="oe_title">
|
||||
<div class="oe_edit_only">
|
||||
<label for="name" string="Name"/>
|
||||
</div>
|
||||
<h1>
|
||||
<field name="name" default_focus="1" placeholder="Name"/>
|
||||
</h1>
|
||||
<field name="tag_ids" widget="many2many_tags" placeholder="Tags..."/>
|
||||
</div>
|
||||
<group>
|
||||
<field name="channel_id"/>
|
||||
<field name="category_id" context="{'default_channel_id': channel_id}"/>
|
||||
<field name="website_id" groups="website.group_multi_website"/>
|
||||
<field name="website_url"/>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Description">
|
||||
<field name="description"/>
|
||||
</page>
|
||||
<page string="Document">
|
||||
<group>
|
||||
<field name="url" attrs="{'required': [('image', '=', False)]}"/>
|
||||
<field name="slide_type" readonly="1"/>
|
||||
<field name="document_id" readonly="1"/>
|
||||
<field name="mime_type" readonly="1"/>
|
||||
<field name="datas" readonly="1"/>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Website">
|
||||
<group>
|
||||
<group>
|
||||
<field name="download_security"/>
|
||||
<field name="date_published"/>
|
||||
<field name="likes"/>
|
||||
<field name="dislikes"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="slide_views"/>
|
||||
<field name="embed_views"/>
|
||||
<field name="total_views"/>
|
||||
</group>
|
||||
</group>
|
||||
<label for="index_content"/>
|
||||
<field name="index_content"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_slide_slide_tree" model="ir.ui.view">
|
||||
<field name="name">slide.slide.tree</field>
|
||||
<field name="model">slide.slide</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Slides">
|
||||
<field name="name"/>
|
||||
<field name="website_id" groups="website.group_multi_website"/>
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="slide_type"/>
|
||||
<field name="channel_id"/>
|
||||
<field name="category_id"/>
|
||||
<field name="date_published"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_slide_slide_search" model="ir.ui.view">
|
||||
<field name="name">slide.slide.filter</field>
|
||||
<field name="model">slide.slide</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Slides">
|
||||
<field name="name"/>
|
||||
<filter name="published" string="Published" domain="[('website_published', '=', True)]"/>
|
||||
<filter name="not_published" string="Waiting for validation" domain="[('website_published', '=', False)]"/>
|
||||
<separator/>
|
||||
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter string="Channel" name="channel" domain="[]" context="{'group_by': 'channel_id'}"/>
|
||||
<filter string="Category" name="category" domain="[]" context="{'group_by': 'category_id'}"/>
|
||||
<filter string="Type" name="type" domain="[]" context="{'group_by': 'slide_type'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_slide_slide_graph" model="ir.ui.view">
|
||||
<field name="name">slide.slide.graph</field>
|
||||
<field name="model">slide.slide</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Graph of Slides">
|
||||
<field name="date_published" interval="month" type="col"/>
|
||||
<field name="channel_id" type="row"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_slides_slides" model="ir.actions.act_window">
|
||||
<field name="name">Slides</field>
|
||||
<field name="res_model">slide.slide</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form,graph</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Add a new slide
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -1,326 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<!-- SLIDE.TAG -->
|
||||
<record id="view_slide_tag_form" model="ir.ui.view">
|
||||
<field name="name">slide.tag.form</field>
|
||||
<field name="model">slide.tag</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Tag">
|
||||
<field name="name"/>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_slide_tag_tree" model="ir.ui.view">
|
||||
<field name="name">slide.tag.tree</field>
|
||||
<field name="model">slide.tag</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Tags">
|
||||
<field name="name"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_slide_tag" model="ir.actions.act_window">
|
||||
<field name="name">Tags</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">slide.tag</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
|
||||
<!-- SLIDE.SLIDE -->
|
||||
<record id="view_slide_slide_form" model="ir.ui.view">
|
||||
<field name="name">slide.slide.form</field>
|
||||
<field name="model">slide.slide</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Slide">
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button class="oe_stat_button" name="website_publish_button"
|
||||
type="object" icon="fa-globe">
|
||||
<field name="website_published" widget="website_button"/>
|
||||
</button>
|
||||
<button name="toggle_active" type="object" class="oe_stat_button" icon="fa-archive">
|
||||
<field name="active" widget="boolean_button" options='{"terminology": "archive"}'/>
|
||||
</button>
|
||||
</div>
|
||||
<field name="image" widget="image" class="oe_avatar" options='{"preview_image": "image_thumb"}' readonly="1"/>
|
||||
<div class="oe_title">
|
||||
<div class="oe_edit_only">
|
||||
<label for="name" string="Name"/>
|
||||
</div>
|
||||
<h1>
|
||||
<field name="name" default_focus="1" placeholder="Name"/>
|
||||
</h1>
|
||||
<field name="tag_ids" widget="many2many_tags" placeholder="Tags..."/>
|
||||
</div>
|
||||
<group>
|
||||
<field name="channel_id"/>
|
||||
<field name="category_id" context="{'default_channel_id': channel_id}"/>
|
||||
<field name="website_id" groups="website.group_multi_website"/>
|
||||
<field name="website_url"/>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Description">
|
||||
<field name="description"/>
|
||||
</page>
|
||||
<page string="Document">
|
||||
<group>
|
||||
<field name="url" attrs="{'required': [('image', '=', False)]}"/>
|
||||
<field name="slide_type" readonly="1"/>
|
||||
<field name="document_id" readonly="1"/>
|
||||
<field name="mime_type" readonly="1"/>
|
||||
<field name="datas" readonly="1"/>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Website">
|
||||
<group>
|
||||
<group>
|
||||
<field name="download_security"/>
|
||||
<field name="date_published"/>
|
||||
<field name="likes"/>
|
||||
<field name="dislikes"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="slide_views"/>
|
||||
<field name="embed_views"/>
|
||||
<field name="total_views"/>
|
||||
</group>
|
||||
</group>
|
||||
<label for="index_content"/>
|
||||
<field name="index_content"/>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_slide_slide_tree" model="ir.ui.view">
|
||||
<field name="name">slide.slide.tree</field>
|
||||
<field name="model">slide.slide</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Slides">
|
||||
<field name="name"/>
|
||||
<field name="website_id" groups="website.group_multi_website"/>
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="slide_type"/>
|
||||
<field name="channel_id"/>
|
||||
<field name="category_id"/>
|
||||
<field name="date_published"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_slide_slide_search" model="ir.ui.view">
|
||||
<field name="name">slide.slide.filter</field>
|
||||
<field name="model">slide.slide</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Slides">
|
||||
<field name="name"/>
|
||||
<filter name="published" string="Published" domain="[('website_published', '=', True)]"/>
|
||||
<filter name="not_published" string="Waiting for validation" domain="[('website_published', '=', False)]"/>
|
||||
<separator/>
|
||||
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter string="Channel" name="channel" domain="[]" context="{'group_by': 'channel_id'}"/>
|
||||
<filter string="Category" name="category" domain="[]" context="{'group_by': 'category_id'}"/>
|
||||
<filter string="Type" name="type" domain="[]" context="{'group_by': 'slide_type'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_slide_slide_graph" model="ir.ui.view">
|
||||
<field name="name">slide.slide.graph</field>
|
||||
<field name="model">slide.slide</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Graph of Slides">
|
||||
<field name="date_published" interval="month" type="col"/>
|
||||
<field name="channel_id" type="row"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_slides_slides" model="ir.actions.act_window">
|
||||
<field name="name">Slides</field>
|
||||
<field name="res_model">slide.slide</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form,graph</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Add a new slide
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- SLIDE.CATEGORY -->
|
||||
<record model="ir.ui.view" id="view_slides_category_form">
|
||||
<field name="name">slide.category.form</field>
|
||||
<field name="model">slide.category</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Category">
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="channel_id"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_slides_category_tree" model="ir.ui.view">
|
||||
<field name="name">slide.category.tree</field>
|
||||
<field name="model">slide.category</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Category">
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="name"/>
|
||||
<field name="channel_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_ir_slide_category" model="ir.actions.act_window">
|
||||
<field name="name">Categories</field>
|
||||
<field name="res_model">slide.category</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Create a new category
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- SLIDE.CHANNEL VIEWS -->
|
||||
<record model="ir.ui.view" id="view_slide_channel_form">
|
||||
<field name="name">slide.channel.form</field>
|
||||
<field name="model">slide.channel</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Channels">
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button class="oe_stat_button" name="website_publish_button"
|
||||
type="object" icon="fa-globe">
|
||||
<field name="website_published" widget="website_button"/>
|
||||
</button>
|
||||
<button name="toggle_active" type="object" confirm="(Un)archiving a channel automatically (un)archives its slides. Do you want to proceed?" class="oe_stat_button" icon="fa-archive">
|
||||
<field name="active" widget="boolean_button" options='{"terminology": "archive"}'/>
|
||||
</button>
|
||||
</div>
|
||||
<div class="oe_title">
|
||||
<div class="oe_edit_only">
|
||||
<label for="name" string="Name"/>
|
||||
</div>
|
||||
<h1>
|
||||
<field name="name" default_focus="1" placeholder="Name"/>
|
||||
</h1>
|
||||
</div>
|
||||
<field name="category_ids" widget="many2many_tags" placeholder="Categories" context="{'default_channel_id': active_id}"
|
||||
domain="[('channel_id','=', active_id)]"/>
|
||||
<notebook colspan="4">
|
||||
<page string="General">
|
||||
<group>
|
||||
<group>
|
||||
<field name="website_id" options="{'no_create': True}" groups="website.group_multi_website"/>
|
||||
<field name="promote_strategy" widget="radio"/>
|
||||
<field name="custom_slide_id" class="oe_edit_only" attrs="{'invisible': [('promote_strategy', '!=', 'custom')]}" domain="[('channel_id','=',id), ('website_published','=',True)]"/>
|
||||
<field name="promoted_slide_id" attrs="{'invisible': [('promote_strategy', '!=', 'custom')]}"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="publish_template_id" domain="[('model','=','slide.slide')]"/>
|
||||
<field name="share_template_id" domain="[('model','=','slide.slide')]"/>
|
||||
</group>
|
||||
</group>
|
||||
<label for="description"/>
|
||||
<field name="description" colspan="4" nolabel="1"/>
|
||||
</page>
|
||||
<page string="Security">
|
||||
<group>
|
||||
<field name="upload_group_ids" widget="many2many_tags"/>
|
||||
<field name="visibility" string="Channel visibility" widget="radio"/>
|
||||
<field name="group_ids" widget="many2many_tags" attrs="{'invisible':[('visibility', '=', 'public')], 'required':[('visibility', 'in',('private', 'partial'))]}"/>
|
||||
</group>
|
||||
<group attrs="{'invisible':[('visibility','in',('public'))]}" colspan="4">
|
||||
<label for="access_error_msg" string="Error Message"/>
|
||||
<field name="access_error_msg" colspan="4" nolabel="1"/>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_slide_channel_tree" model="ir.ui.view">
|
||||
<field name="name">slide.channel.tree</field>
|
||||
<field name="model">slide.channel</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Channel">
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="name"/>
|
||||
<field name="website_id" groups="website.group_multi_website"/>
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="visibility"/>
|
||||
<field name="promote_strategy"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="slide_channel_view_search" model="ir.ui.view">
|
||||
<field name="name">slide.channel.search</field>
|
||||
<field name="model">slide.channel</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Channel">
|
||||
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_slide_channels" model="ir.actions.act_window">
|
||||
<field name="name">Channels</field>
|
||||
<field name="res_model">slide.channel</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Create a channel
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<menuitem name="Slides"
|
||||
id="menu_website_slides_root"
|
||||
sequence="32"
|
||||
parent="website.menu_website_configuration"
|
||||
groups="website.group_website_designer"
|
||||
action="action_slides_slides"/>
|
||||
|
||||
<menuitem name="Slides"
|
||||
id="menu_website_slides_root_global"
|
||||
sequence="140"
|
||||
parent="website.menu_website_global_configuration"
|
||||
groups="website.group_website_designer"/>
|
||||
|
||||
<menuitem action="action_slide_channels" id="menu_action_slide_channels_global"
|
||||
parent="menu_website_slides_root_global" sequence="1"/>
|
||||
|
||||
<menuitem action="action_ir_slide_category" id="menu_action_ir_slide_category_global" parent="menu_website_slides_root_global" sequence="2"/>
|
||||
|
||||
<menuitem action="action_slide_tag" id="menu_slide_tag" parent="menu_website_slides_root_global" sequence="10" groups="base.group_no_one"/>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
-11
@@ -1,17 +1,6 @@
|
||||
<?xml version="1.0" ?>
|
||||
<odoo>
|
||||
<data>
|
||||
<!-- Bundle (minimal) for embedded slide iframe -->
|
||||
<template id="website_slides.slide_embed_assets" name="Website slides embed assets">
|
||||
<t t-call="web._assets_helpers"><t t-set="no_bs_vars" t-value="True"/></t>
|
||||
<t t-call="web._assets_bootstrap"/>
|
||||
<link rel="stylesheet" type="text/scss" href="/website_slides/static/src/scss/website_slides.scss" t-ignore="true"/>
|
||||
|
||||
<t t-call="web.pdf_js_lib"></t>
|
||||
<script type="text/javascript" src="/website_slides/static/lib/pdfslidesviewer/PDFSlidesViewer.js"></script>
|
||||
<script type="text/javascript" src="/website_slides/static/src/js/slides_embed.js"></script>
|
||||
</template>
|
||||
|
||||
<!--
|
||||
This template is the PDF Viewer. It will mostly rendered throught an iFrame.
|
||||
The js file to bind event is slides_embed.js
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<menuitem name="Slides"
|
||||
id="menu_website_slides_root"
|
||||
sequence="32"
|
||||
parent="website.menu_website_configuration"
|
||||
groups="website.group_website_designer"
|
||||
action="action_slides_slides"/>
|
||||
|
||||
<menuitem name="Slides"
|
||||
id="menu_website_slides_root_global"
|
||||
sequence="140"
|
||||
parent="website.menu_website_global_configuration"
|
||||
groups="website.group_website_designer"/>
|
||||
|
||||
<menuitem action="website_slides.action_slide_channels" id="menu_action_slide_channels_global"
|
||||
parent="menu_website_slides_root_global" sequence="1"/>
|
||||
|
||||
<menuitem action="website_slides.action_ir_slide_category" id="menu_action_ir_slide_category_global" parent="menu_website_slides_root_global" sequence="2"/>
|
||||
|
||||
<menuitem action="website_slides.action_slide_tag" id="menu_slide_tag" parent="menu_website_slides_root_global" sequence="10" groups="base.group_no_one"/>
|
||||
|
||||
</data>
|
||||
</odoo>
|
||||
-16
@@ -2,22 +2,6 @@
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<!-- assets -->
|
||||
<template id="assets_frontend" inherit_id="website.assets_frontend" name="Slide">
|
||||
<xpath expr="." position="inside">
|
||||
<link rel="stylesheet" type="text/scss" href="/website_slides/static/src/scss/website_slides.scss" t-ignore="true"/>
|
||||
<!-- Js widgets, ... order loading files is IMPORTANT -->
|
||||
<script type="text/javascript" src="/website_slides/static/src/js/slides.js"/>
|
||||
<script type="text/javascript" src="/website_slides/static/src/js/slides_upload.js"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="assets_editor_inherit_website_slides" inherit_id="website.assets_editor" name="website_slides Assets Editor">
|
||||
<xpath expr="." position="inside">
|
||||
<script type="text/javascript" src="/website_slides/static/src/js/website_slides.editor.js"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<!-- Tools template: share on social networkds -->
|
||||
<template id='slides_share' name="Slides Media Share">
|
||||
<div>
|
||||
Reference in New Issue
Block a user