[IMP] website_[blog|event|forum|sale|slide]: make website specific
Set up env & tools for module website multiwebsite (blog, event, sale..)
website_id in modelConverter and ir_rule
add _compute_domain_keys to have a different cache per website
or rules would not be correctly website_dependant
eg: blog 1 on website 1, blog 2 on website 2
access blog 1 from website 1 => can access -> normal
access blog 1 from website 2 => can access -> should crash because (ir rule)
split mixing website.published.mixin and website.published.multi.mixin to have
website_id only on last one.
multi mixing will:
- override website_published compute to take current_website into account (not in backend)
- force website when clicking on published in backend
- website blog, website_sale, website_event, website_forum, website_slides are now multi website
Co-authored-by: Derie Romain <rde@odoo.com>
Co-authored-by: Kersten Jérémy <jke@odoo.com>
This commit is contained in:
co-authored by
Derie Romain
parent
8300155cd6
commit
066cfc9598
@@ -31,6 +31,7 @@ class WebsiteBackend(http.Controller):
|
||||
|
||||
if has_group_designer:
|
||||
config = request.env['res.config.settings'].sudo().create({})
|
||||
config.website_id = current_website
|
||||
if config.has_google_analytics_dashboard:
|
||||
dashboard_data['dashboards']['visits'] = dict(
|
||||
ga_client_id=config.google_management_client_id or '', # void string instead of stringified False
|
||||
@@ -39,7 +40,7 @@ class WebsiteBackend(http.Controller):
|
||||
return dashboard_data
|
||||
|
||||
@http.route('/website/dashboard/set_ga_data', type='json', auth='user')
|
||||
def website_set_ga_data(self, ga_client_id, ga_analytics_key):
|
||||
def website_set_ga_data(self, website_id, ga_client_id, ga_analytics_key):
|
||||
if not request.env.user.has_group('base.group_system'):
|
||||
return {
|
||||
'error': {
|
||||
@@ -54,10 +55,14 @@ class WebsiteBackend(http.Controller):
|
||||
'message': 'The Google Analytics Client ID or Key you entered seems incorrect.',
|
||||
}
|
||||
}
|
||||
Website = request.env['website']
|
||||
current_website = website_id and Website.browse(website_id) or Website.get_current_website()
|
||||
|
||||
request.env['res.config.settings'].create({
|
||||
'has_google_analytics': True,
|
||||
'has_google_analytics_dashboard': True,
|
||||
'google_management_client_id': ga_client_id,
|
||||
'google_analytics_key': ga_analytics_key,
|
||||
'website_id': current_website,
|
||||
}).execute()
|
||||
return True
|
||||
|
||||
@@ -7,6 +7,7 @@ from . import ir_http
|
||||
from . import ir_qweb
|
||||
from . import ir_qweb_fields
|
||||
from . import website
|
||||
from . import ir_rule
|
||||
from . import ir_ui_view
|
||||
from . import res_company
|
||||
from . import res_partner
|
||||
|
||||
@@ -267,6 +267,8 @@ class ModelConverter(ModelConverter):
|
||||
|
||||
def generate(self, uid, dom=None, args=None):
|
||||
Model = request.env[self.model].sudo(uid)
|
||||
# Allow to current_website_id directly in route domain
|
||||
args.update(current_website_id=request.env['website'].get_current_website().id)
|
||||
domain = safe_eval(self.domain, (args or {}).copy())
|
||||
if dom:
|
||||
domain += dom
|
||||
|
||||
@@ -8,5 +8,9 @@ class IrRule(models.Model):
|
||||
@api.model
|
||||
def _eval_context(self):
|
||||
res = super(IrRule, self)._eval_context()
|
||||
res['website_id'] = self._context.get('website_id')
|
||||
res['website_id'] = self.env['website'].get_current_website().id
|
||||
return res
|
||||
|
||||
def _compute_domain_keys(self):
|
||||
""" Return the list of context keys to use for caching ``_compute_domain``. """
|
||||
return super(IrRule, self)._compute_domain_keys() + ['website_id']
|
||||
|
||||
@@ -297,3 +297,6 @@ class View(models.Model):
|
||||
'url': '/website/pages',
|
||||
'target': 'self',
|
||||
}
|
||||
|
||||
def _read_template_keys(self):
|
||||
return super(View, self)._read_template_keys() + ['website_id']
|
||||
|
||||
@@ -694,18 +694,63 @@ class SeoMetadata(models.AbstractModel):
|
||||
website_meta_keywords = fields.Char("Website meta keywords", translate=True)
|
||||
|
||||
|
||||
class WebsiteMultiMixin(models.AbstractModel):
|
||||
|
||||
_name = 'website.multi.mixin'
|
||||
|
||||
website_id = fields.Many2one('website', string='Website', help='Restrict publishing to this website.')
|
||||
|
||||
@api.multi
|
||||
def can_access_from_current_website(self, website_id=False):
|
||||
can_access = True
|
||||
for record in self:
|
||||
if (website_id or record.website_id.id) not in (False, request.website.id):
|
||||
can_access = False
|
||||
continue
|
||||
return can_access
|
||||
|
||||
|
||||
class WebsitePublishedMixin(models.AbstractModel):
|
||||
|
||||
_name = "website.published.mixin"
|
||||
|
||||
website_published = fields.Boolean('Visible on current website',
|
||||
compute='_compute_website_published',
|
||||
inverse='_inverse_website_published',
|
||||
search='_search_website_published') # todo jov evaluate places where this is used, we can probably replace some with is_published
|
||||
website_published = fields.Boolean('Visible on current website', related='is_published')
|
||||
is_published = fields.Boolean('Is published')
|
||||
website_id = fields.Many2one('website', string='Website', help='Restrict publishing to this website.')
|
||||
website_url = fields.Char('Website URL', compute='_compute_website_url', help='The full URL to access the document through the website.')
|
||||
|
||||
@api.multi
|
||||
def _compute_website_url(self):
|
||||
for record in self:
|
||||
record.website_url = '#'
|
||||
|
||||
@api.multi
|
||||
def website_publish_button(self):
|
||||
self.ensure_one()
|
||||
if self.env.user.has_group('website.group_website_publisher') and self.website_url != '#':
|
||||
# Force website to land on record's website to publish/unpublish it
|
||||
if 'website_id' in self and self.env.user.has_group('website.group_multi_website'):
|
||||
self.website_id._force()
|
||||
return self.open_website_url()
|
||||
return self.write({'website_published': not self.website_published})
|
||||
|
||||
def open_website_url(self):
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'url': self.website_url,
|
||||
'target': 'self',
|
||||
}
|
||||
|
||||
|
||||
class WebsitePublishedMultiMixin(WebsitePublishedMixin):
|
||||
|
||||
_name = 'website.published.multi.mixin'
|
||||
_inherit = ['website.published.mixin', 'website.multi.mixin']
|
||||
|
||||
website_published = fields.Boolean(compute='_compute_website_published',
|
||||
inverse='_inverse_website_published',
|
||||
search='_search_website_published',
|
||||
related=False)
|
||||
|
||||
@api.multi
|
||||
@api.depends('is_published', 'website_id')
|
||||
def _compute_website_published(self):
|
||||
@@ -718,11 +763,8 @@ class WebsitePublishedMixin(models.AbstractModel):
|
||||
|
||||
@api.multi
|
||||
def _inverse_website_published(self):
|
||||
current_website_id = self._context.get('website_id')
|
||||
for record in self:
|
||||
record.is_published = record.website_published
|
||||
if record.website_published and current_website_id:
|
||||
record.write({'website_id': current_website_id})
|
||||
|
||||
def _search_website_published(self, operator, value):
|
||||
if not isinstance(value, bool) or operator not in ('=', '!='):
|
||||
@@ -740,30 +782,11 @@ class WebsitePublishedMixin(models.AbstractModel):
|
||||
else: # should be in the backend, return things that are published anywhere
|
||||
return is_published
|
||||
|
||||
@api.multi
|
||||
def _compute_website_url(self):
|
||||
for record in self:
|
||||
record.website_url = '#'
|
||||
|
||||
@api.multi
|
||||
def website_publish_button(self):
|
||||
self.ensure_one()
|
||||
if self.env.user.has_group('website.group_website_publisher') and self.website_url != '#':
|
||||
return self.open_website_url()
|
||||
return self.write({'website_published': not self.website_published})
|
||||
|
||||
def open_website_url(self):
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'url': self.website_url,
|
||||
'target': 'self',
|
||||
}
|
||||
|
||||
|
||||
class Page(models.Model):
|
||||
_name = 'website.page'
|
||||
_inherits = {'ir.ui.view': 'view_id'}
|
||||
_inherit = 'website.published.mixin'
|
||||
_inherit = 'website.published.multi.mixin'
|
||||
_description = 'Page'
|
||||
_order = 'website_id'
|
||||
|
||||
|
||||
@@ -112,6 +112,7 @@ var Dashboard = AbstractAction.extend(ControlPanelMixin, {
|
||||
return this._rpc({
|
||||
route: '/website/dashboard/set_ga_data',
|
||||
params: {
|
||||
'website_id': self.website_id,
|
||||
'ga_client_id': ga_client_id,
|
||||
'ga_analytics_key': ga_analytics_key,
|
||||
},
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
<field name="view_id" ref="view_website_tree"/>
|
||||
<field name="target">current</field>
|
||||
</record>
|
||||
|
||||
|
||||
<!-- website.redirect views -->
|
||||
<record id="website_redirect_form_view" model="ir.ui.view">
|
||||
<field name="name">website.redirect.form</field>
|
||||
|
||||
@@ -48,13 +48,14 @@ class WebsiteBlog(http.Controller):
|
||||
'/blog/page/<int:page>',
|
||||
], type='http', auth="public", website=True)
|
||||
def blogs(self, page=1, **post):
|
||||
domain = request.website.website_domain()
|
||||
Blog = request.env['blog.blog']
|
||||
blogs = Blog.search([], limit=2)
|
||||
blogs = Blog.search(domain, limit=2)
|
||||
if len(blogs) == 1:
|
||||
return werkzeug.utils.redirect('/blog/%s' % slug(blogs[0]), code=302)
|
||||
|
||||
BlogPost = request.env['blog.post']
|
||||
total = BlogPost.search([], count=True)
|
||||
total = BlogPost.search_count(domain)
|
||||
|
||||
pager = request.website.pager(
|
||||
url='/blog',
|
||||
@@ -62,7 +63,7 @@ class WebsiteBlog(http.Controller):
|
||||
page=page,
|
||||
step=self._blog_post_per_page,
|
||||
)
|
||||
posts = BlogPost.search([], offset=(page - 1) * self._blog_post_per_page, limit=self._blog_post_per_page)
|
||||
posts = BlogPost.search(domain, offset=(page - 1) * self._blog_post_per_page, limit=self._blog_post_per_page)
|
||||
blog_url = QueryURL('', ['blog', 'tag'])
|
||||
return request.render("website_blog.latest_blogs", {
|
||||
'posts': posts,
|
||||
@@ -71,10 +72,10 @@ class WebsiteBlog(http.Controller):
|
||||
})
|
||||
|
||||
@http.route([
|
||||
'/blog/<model("blog.blog"):blog>',
|
||||
'/blog/<model("blog.blog"):blog>/page/<int:page>',
|
||||
'/blog/<model("blog.blog"):blog>/tag/<string:tag>',
|
||||
'/blog/<model("blog.blog"):blog>/tag/<string:tag>/page/<int:page>',
|
||||
'''/blog/<model("blog.blog", "[('website_id', 'in', (False, current_website_id))]"):blog>''',
|
||||
'''/blog/<model("blog.blog"):blog>/page/<int:page>''',
|
||||
'''/blog/<model("blog.blog"):blog>/tag/<string:tag>''',
|
||||
'''/blog/<model("blog.blog"):blog>/tag/<string:tag>/page/<int:page>''',
|
||||
], type='http', auth="public", website=True)
|
||||
def blog(self, blog=None, tag=None, page=1, **opt):
|
||||
""" Prepare all values to display the blog.
|
||||
@@ -92,16 +93,19 @@ class WebsiteBlog(http.Controller):
|
||||
- 'date': date_begin optional parameter, used in archives navigation
|
||||
- 'blog_url': help object to create URLs
|
||||
"""
|
||||
if not blog.can_access_from_current_website():
|
||||
raise werkzeug.exceptions.NotFound()
|
||||
|
||||
date_begin, date_end, state = opt.get('date_begin'), opt.get('date_end'), opt.get('state')
|
||||
published_count, unpublished_count = 0, 0
|
||||
|
||||
domain = request.website.website_domain()
|
||||
|
||||
BlogPost = request.env['blog.post']
|
||||
|
||||
Blog = request.env['blog.blog']
|
||||
blogs = Blog.search([], order="create_date asc")
|
||||
blogs = Blog.search(domain, order="create_date asc")
|
||||
|
||||
# build the domain for blog post to display
|
||||
domain = []
|
||||
# retrocompatibility to accept tag as slug
|
||||
active_tag_ids = tag and [int(unslug(t)[1]) for t in tag.split(',')] or []
|
||||
if active_tag_ids:
|
||||
@@ -172,7 +176,7 @@ class WebsiteBlog(http.Controller):
|
||||
response = request.render("website_blog.blog_post_short", values)
|
||||
return response
|
||||
|
||||
@http.route(['/blog/<model("blog.blog"):blog>/feed'], type='http', auth="public")
|
||||
@http.route(['''/blog/<model("blog.blog", "[('website_id', 'in', (False, current_website_id))]"):blog>/feed'''], type='http', auth="public")
|
||||
def blog_feed(self, blog, limit='15'):
|
||||
v = {}
|
||||
v['blog'] = blog
|
||||
@@ -185,7 +189,7 @@ class WebsiteBlog(http.Controller):
|
||||
return r
|
||||
|
||||
@http.route([
|
||||
'''/blog/<model("blog.blog"):blog>/post/<model("blog.post", "[('blog_id','=',blog[0])]"):blog_post>''',
|
||||
'''/blog/<model("blog.blog", "[('website_id', 'in', (False, current_website_id))]"):blog>/post/<model("blog.post", "[('blog_id','=',blog[0])]"):blog_post>''',
|
||||
], type='http', auth="public", website=True)
|
||||
def blog_post(self, blog, blog_post, tag_id=None, page=1, enable_editor=None, **post):
|
||||
""" Prepare all values to display the blog.
|
||||
@@ -201,6 +205,9 @@ class WebsiteBlog(http.Controller):
|
||||
- 'nav_list': a dict [year][month] for archives navigation
|
||||
- 'next_post': next blog post, to direct the user towards the next interesting post
|
||||
"""
|
||||
if not blog.can_access_from_current_website():
|
||||
raise werkzeug.exceptions.NotFound()
|
||||
|
||||
BlogPost = request.env['blog.post']
|
||||
date_begin, date_end = post.get('date_begin'), post.get('date_end')
|
||||
|
||||
@@ -271,6 +278,12 @@ class WebsiteBlog(http.Controller):
|
||||
|
||||
@http.route('/blog/<int:blog_id>/post/new', type='http', auth="public", website=True)
|
||||
def blog_post_create(self, blog_id, **post):
|
||||
# Use sudo so this line prevents both editor and admin to access blog from another website
|
||||
# as browse() will return the record even if forbidden by security rules but editor won't
|
||||
# be able to access it
|
||||
if not request.env['blog.blog'].browse(blog_id).sudo().can_access_from_current_website():
|
||||
raise werkzeug.exceptions.NotFound()
|
||||
|
||||
new_blog_post = request.env['blog.post'].create({
|
||||
'blog_id': blog_id,
|
||||
'website_published': False,
|
||||
|
||||
@@ -15,7 +15,7 @@ from odoo.tools import html2plaintext
|
||||
class Blog(models.Model):
|
||||
_name = 'blog.blog'
|
||||
_description = 'Blogs'
|
||||
_inherit = ['mail.thread', 'website.seo.metadata']
|
||||
_inherit = ['mail.thread', 'website.seo.metadata', 'website.multi.mixin']
|
||||
_order = 'name'
|
||||
|
||||
name = fields.Char('Blog Name', required=True, translate=True)
|
||||
@@ -108,7 +108,7 @@ class BlogTag(models.Model):
|
||||
class BlogPost(models.Model):
|
||||
_name = "blog.post"
|
||||
_description = "Blog Post"
|
||||
_inherit = ['mail.thread', 'website.seo.metadata', 'website.published.mixin']
|
||||
_inherit = ['mail.thread', 'website.seo.metadata', 'website.published.multi.mixin']
|
||||
_order = 'id DESC'
|
||||
_mail_post_access = 'read'
|
||||
|
||||
@@ -168,6 +168,8 @@ class BlogPost(models.Model):
|
||||
visits = fields.Integer('No of Views', copy=False)
|
||||
ranking = fields.Float(compute='_compute_ranking', string='Ranking')
|
||||
|
||||
website_id = fields.Many2one(related='blog_id.website_id', readonly=True)
|
||||
|
||||
@api.multi
|
||||
@api.depends('content', 'teaser_manual')
|
||||
def _compute_teaser(self):
|
||||
|
||||
@@ -64,26 +64,26 @@
|
||||
</span>
|
||||
</h4>
|
||||
<div class="text-muted">
|
||||
<span class="fa fa-calendar mr8" role="img" aria-label="Post date" title="Post date"> <span t-field="post.post_date"/></span>
|
||||
<span class="fa fa-folder-open"> In
|
||||
<a class="mr8" t-attf-href="#{blog_url(blog=post.blog_id)}">
|
||||
<span t-field="post.blog_id"/>
|
||||
</a>
|
||||
</span>
|
||||
<span class="fa fa-calendar mr8" role="img" aria-label="Post date" title="Post date" />
|
||||
<span t-field="post.post_date"/>
|
||||
<span class="fa fa-folder-open" /> In
|
||||
<a class="mr8" t-attf-href="#{blog_url(blog=post.blog_id)}">
|
||||
<span t-field="post.blog_id"/>
|
||||
</a>
|
||||
</div>
|
||||
<div class="text-muted fa fa-tags">
|
||||
<div class="text-muted">
|
||||
<span class="fa fa-tags" role="img" aria-label="Tags" title="Tags"/>
|
||||
<span t-field="post.website_meta_keywords"/>
|
||||
<span t-if="editable and not post.website_meta_keywords" class="badge badge-danger">
|
||||
No keywords defined!
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-muted" t-if="len(post.message_ids) > 0">
|
||||
<span class="fa fa-comment-o">
|
||||
<a t-attf-href="#{blog_url('', ['blogpost'], blogpost=post)}#comments">
|
||||
<t t-if="len(post.message_ids) <= 1" ><t t-esc="len(post.message_ids)"/> comment</t>
|
||||
<t t-if="len(post.message_ids) > 1"><t t-esc="len(post.message_ids)"/> comments</t>
|
||||
</a>
|
||||
</span>
|
||||
<span class="fa fa-comment-o"/>
|
||||
<a t-attf-href="#{blog_url('', ['blogpost'], blogpost=post)}#comments">
|
||||
<t t-if="len(post.message_ids) <= 1" ><t t-esc="len(post.message_ids)"/> comment</t>
|
||||
<t t-if="len(post.message_ids) > 1"><t t-esc="len(post.message_ids)"/> comments</t>
|
||||
</a>
|
||||
</div>
|
||||
<div class="text-muted mb16" t-field="post.website_meta_description"/>
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Blogs">
|
||||
<field name="name"/>
|
||||
<field name="website_id" groups="website.group_multi_website"/>
|
||||
<field name="active" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
@@ -26,6 +27,7 @@
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="subtitle"/>
|
||||
<field name="website_id" groups="website.group_multi_website"/>
|
||||
</group>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
@@ -45,6 +47,7 @@
|
||||
<field name="name"/>
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="blog_id"/>
|
||||
<field name="website_id"/>
|
||||
<field name="visits"/>
|
||||
<field name="create_uid" invisible="1"/>
|
||||
<field name="write_uid"/>
|
||||
|
||||
@@ -28,7 +28,6 @@ class WebsiteEventController(http.Controller):
|
||||
searches.setdefault('type', 'all')
|
||||
searches.setdefault('country', 'all')
|
||||
|
||||
domain_search = {}
|
||||
|
||||
def sdn(date):
|
||||
return fields.Datetime.to_string(date.replace(hour=23, minute=59, second=59))
|
||||
@@ -64,6 +63,7 @@ class WebsiteEventController(http.Controller):
|
||||
]
|
||||
|
||||
# search domains
|
||||
domain_search = {'website_specific': request.website.website_domain()}
|
||||
current_date = None
|
||||
current_type = None
|
||||
current_country = None
|
||||
@@ -141,8 +141,11 @@ class WebsiteEventController(http.Controller):
|
||||
|
||||
return request.render("website_event.index", values)
|
||||
|
||||
@http.route(['/event/<model("event.event"):event>/page/<path:page>'], type='http', auth="public", website=True, sitemap=False)
|
||||
@http.route(['''/event/<model("event.event", "[('website_id', 'in', (False, current_website_id))]"):event>/page/<path:page>'''], type='http', auth="public", website=True, sitemap=False)
|
||||
def event_page(self, event, page, **post):
|
||||
if not event.can_access_from_current_website():
|
||||
raise werkzeug.exceptions.NotFound()
|
||||
|
||||
values = {
|
||||
'event': event,
|
||||
'main_object': event
|
||||
@@ -161,8 +164,11 @@ class WebsiteEventController(http.Controller):
|
||||
|
||||
return request.render(page, values)
|
||||
|
||||
@http.route(['/event/<model("event.event"):event>'], type='http', auth="public", website=True)
|
||||
@http.route(['''/event/<model("event.event", "[('website_id', 'in', (False, current_website_id))]"):event>'''], type='http', auth="public", website=True)
|
||||
def event(self, event, **post):
|
||||
if not event.can_access_from_current_website():
|
||||
raise werkzeug.exceptions.NotFound()
|
||||
|
||||
if event.menu_id and event.menu_id.child_id:
|
||||
target_url = event.menu_id.child_id[0].url
|
||||
else:
|
||||
@@ -171,8 +177,11 @@ class WebsiteEventController(http.Controller):
|
||||
target_url += '?enable_editor=1'
|
||||
return request.redirect(target_url)
|
||||
|
||||
@http.route(['/event/<model("event.event"):event>/register'], type='http', auth="public", website=True, sitemap=False)
|
||||
@http.route(['''/event/<model("event.event", "[('website_id', 'in', (False, current_website_id))]"):event>/register'''], type='http', auth="public", website=True, sitemap=False)
|
||||
def event_register(self, event, **post):
|
||||
if not event.can_access_from_current_website():
|
||||
raise werkzeug.exceptions.NotFound()
|
||||
|
||||
values = {
|
||||
'event': event,
|
||||
'main_object': event,
|
||||
@@ -195,6 +204,7 @@ class WebsiteEventController(http.Controller):
|
||||
'date_begin': fields.Date.to_string(date_begin),
|
||||
'date_end': fields.Date.to_string((date_begin + timedelta(days=(1)))),
|
||||
'seats_available': 1000,
|
||||
'website_id': request.website.id,
|
||||
}
|
||||
return request.env['event.event'].with_context(context or {}).create(vals)
|
||||
|
||||
@@ -210,11 +220,12 @@ class WebsiteEventController(http.Controller):
|
||||
country_code = request.session['geoip'].get('country_code')
|
||||
result = {'events': [], 'country': False}
|
||||
events = None
|
||||
domain = request.website.website_domain()
|
||||
if country_code:
|
||||
country = request.env['res.country'].search([('code', '=', country_code)], limit=1)
|
||||
events = Event.search(['|', ('address_id', '=', None), ('country_id.code', '=', country_code), ('date_begin', '>=', '%s 00:00:00' % fields.Date.today()), ('state', '=', 'confirm')], order="date_begin")
|
||||
events = Event.search(domain + ['|', ('address_id', '=', None), ('country_id.code', '=', country_code), ('date_begin', '>=', '%s 00:00:00' % fields.Date.today()), ('state', '=', 'confirm')], order="date_begin")
|
||||
if not events:
|
||||
events = Event.search([('date_begin', '>=', '%s 00:00:00' % fields.Date.today()), ('state', '=', 'confirm')], order="date_begin")
|
||||
events = Event.search(domain + [('date_begin', '>=', '%s 00:00:00' % fields.Date.today()), ('state', '=', 'confirm')], order="date_begin")
|
||||
for event in events:
|
||||
if country_code and event.country_id.code == country_code:
|
||||
result['country'] = country
|
||||
@@ -252,8 +263,11 @@ class WebsiteEventController(http.Controller):
|
||||
registration[key] = value
|
||||
return list(registrations.values())
|
||||
|
||||
@http.route(['/event/<model("event.event"):event>/registration/confirm'], type='http', auth="public", methods=['POST'], website=True)
|
||||
@http.route(['''/event/<model("event.event", "[('website_id', 'in', (False, current_website_id))]"):event>/registration/confirm'''], type='http', auth="public", methods=['POST'], website=True)
|
||||
def registration_confirm(self, event, **post):
|
||||
if not event.can_access_from_current_website():
|
||||
raise werkzeug.exceptions.NotFound()
|
||||
|
||||
Attendees = request.env['event.registration']
|
||||
registrations = self._process_registration_details(post)
|
||||
|
||||
@@ -270,7 +284,7 @@ class WebsiteEventController(http.Controller):
|
||||
'iCal_url': urls.get('iCal_url')
|
||||
})
|
||||
|
||||
@http.route(['/event/<model("event.event"):event>/ics'], type='http', auth="public", website=True)
|
||||
@http.route(['''/event/<model("event.event", "[('website_id', 'in', (False, current_website_id))]"):event>/ics'''], type='http', auth="public", website=True)
|
||||
def make_event_ics_file(self, event, **kwargs):
|
||||
if not event or not event.registration_ids:
|
||||
return request.not_found()
|
||||
|
||||
@@ -32,9 +32,9 @@ class EventType(models.Model):
|
||||
|
||||
class Event(models.Model):
|
||||
_name = 'event.event'
|
||||
_inherit = ['event.event', 'website.seo.metadata', 'website.published.mixin']
|
||||
_inherit = ['event.event', 'website.seo.metadata', 'website.published.multi.mixin']
|
||||
|
||||
website_published = fields.Boolean(track_visibility='onchange')
|
||||
is_published = fields.Boolean(track_visibility='onchange')
|
||||
|
||||
is_participating = fields.Boolean("Is Participating", compute="_compute_is_participating")
|
||||
|
||||
@@ -45,7 +45,7 @@ class Event(models.Model):
|
||||
|
||||
def _compute_is_participating(self):
|
||||
# we don't allow public user to see participating label
|
||||
if self.env.user != self.env.ref('base.public_user'):
|
||||
if self.env.user != self.env['website'].get_current_website().user_id:
|
||||
email = self.env.user.partner_id.email
|
||||
for event in self:
|
||||
domain = ['&', '|', ('email', '=', email), ('partner_id', '=', self.env.user.partner_id.id), ('event_id', '=', event.id)]
|
||||
@@ -120,9 +120,9 @@ class Event(models.Model):
|
||||
@api.multi
|
||||
def _track_subtype(self, init_values):
|
||||
self.ensure_one()
|
||||
if 'website_published' in init_values and self.website_published:
|
||||
if 'is_published' in init_values and self.is_published:
|
||||
return 'website_event.mt_event_published'
|
||||
elif 'website_published' in init_values and not self.website_published:
|
||||
elif 'is_published' in init_values and not self.is_published:
|
||||
return 'website_event.mt_event_unpublished'
|
||||
return super(Event, self)._track_subtype(init_values)
|
||||
|
||||
|
||||
@@ -30,6 +30,9 @@
|
||||
<field name="model">event.event</field>
|
||||
<field name="inherit_id" ref="event.view_event_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="organizer_id" position="before">
|
||||
<field name="website_id" groups="website.group_multi_website"/>
|
||||
</field>
|
||||
<button name="toggle_active" position="before">
|
||||
<field name="website_url" invisible="1"/>
|
||||
<button name="website_publish_button"
|
||||
@@ -57,4 +60,15 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="event_event_view_list_inherit_website" model="ir.ui.view">
|
||||
<field name="name">event.event.view.list.inherit.website</field>
|
||||
<field name="model">event.event</field>
|
||||
<field name="inherit_id" ref="event.view_event_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="name" position="after">
|
||||
<field name="website_id" groups="website.group_multi_website"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
||||
@@ -8,9 +8,9 @@ from odoo.http import request
|
||||
|
||||
class WebsiteEventSaleController(WebsiteEventController):
|
||||
|
||||
@http.route(['/event/<model("event.event"):event>/register'], type='http', auth="public", website=True)
|
||||
@http.route()
|
||||
def event_register(self, event, **post):
|
||||
event = event.with_context(pricelist=request.website.get_current_pricelist().id)
|
||||
event = event.with_context(pricelist=request.website.id)
|
||||
return super(WebsiteEventSaleController, self).event_register(event, **post)
|
||||
|
||||
def _process_tickets_details(self, data):
|
||||
@@ -25,7 +25,7 @@ class WebsiteEventSaleController(WebsiteEventController):
|
||||
tickets = request.env['event.event.ticket'].browse(tuple(ticket_post))
|
||||
return [{'id': ticket.id, 'name': ticket.name, 'quantity': ticket_post[ticket.id], 'price': ticket.price} for ticket in tickets if ticket_post[ticket.id]]
|
||||
|
||||
@http.route(['/event/<model("event.event"):event>/registration/confirm'], type='http', auth="public", methods=['POST'], website=True)
|
||||
@http.route()
|
||||
def registration_confirm(self, event, **post):
|
||||
order = request.website.sale_get_order(force_create=1)
|
||||
attendee_ids = set()
|
||||
|
||||
@@ -5,6 +5,7 @@ import babel
|
||||
import collections
|
||||
import datetime
|
||||
import pytz
|
||||
from werkzeug.exceptions import NotFound
|
||||
|
||||
from odoo import fields, http
|
||||
from odoo.http import request
|
||||
@@ -13,8 +14,11 @@ from odoo.tools import html_escape as escape, html2plaintext
|
||||
|
||||
class WebsiteEventTrackController(http.Controller):
|
||||
|
||||
@http.route(['''/event/<model("event.event"):event>/track/<model("event.track", "[('event_id','=',event[0])]"):track>'''], type='http', auth="public", website=True)
|
||||
@http.route(['''/event/<model("event.event", "[('website_id', 'in', (False, current_website_id))]"):event>/track/<model("event.track", "[('event_id','=',event[0])]"):track>'''], type='http', auth="public", website=True)
|
||||
def event_track_view(self, event, track, **post):
|
||||
if not event.can_access_from_current_website():
|
||||
raise NotFound()
|
||||
|
||||
track = track.sudo().with_context(tz=event.date_tz or 'UTC')
|
||||
values = {'track': track, 'event': track.event_id, 'main_object': track}
|
||||
return request.render("website_event_track.track_view", values)
|
||||
@@ -65,8 +69,11 @@ class WebsiteEventTrackController(http.Controller):
|
||||
'dates': dates
|
||||
}
|
||||
|
||||
@http.route(['''/event/<model("event.event"):event>/agenda'''], type='http', auth="public", website=True, sitemap=False)
|
||||
@http.route(['''/event/<model("event.event", "[('website_id', 'in', (False, current_website_id))]"):event>/agenda'''], type='http', auth="public", website=True, sitemap=False)
|
||||
def event_agenda(self, event, tag=None, **post):
|
||||
if not event.can_access_from_current_website():
|
||||
raise NotFound()
|
||||
|
||||
event = event.with_context(tz=event.date_tz or 'UTC')
|
||||
local_tz = pytz.timezone(event.date_tz or 'UTC')
|
||||
days_tracks = collections.defaultdict(lambda: [])
|
||||
@@ -90,10 +97,13 @@ class WebsiteEventTrackController(http.Controller):
|
||||
})
|
||||
|
||||
@http.route([
|
||||
'''/event/<model("event.event"):event>/track''',
|
||||
'''/event/<model("event.event"):event>/track/tag/<model("event.track.tag"):tag>'''
|
||||
'''/event/<model("event.event", "[('website_id', 'in', (False, current_website_id))]"):event>/track''',
|
||||
'''/event/<model("event.event", "[('website_id', 'in', (False, current_website_id))]"):event>/track/tag/<model("event.track.tag"):tag>'''
|
||||
], type='http', auth="public", website=True, sitemap=False)
|
||||
def event_tracks(self, event, tag=None, **post):
|
||||
if not event.can_access_from_current_website():
|
||||
raise NotFound()
|
||||
|
||||
event = event.with_context(tz=event.date_tz or 'UTC')
|
||||
searches = {}
|
||||
if tag:
|
||||
@@ -112,12 +122,18 @@ class WebsiteEventTrackController(http.Controller):
|
||||
}
|
||||
return request.render("website_event_track.tracks", values)
|
||||
|
||||
@http.route(['''/event/<model("event.event"):event>/track_proposal'''], type='http', auth="public", website=True, sitemap=False)
|
||||
@http.route(['''/event/<model("event.event", "[('website_id', 'in', (False, current_website_id))]"):event>/track_proposal'''], type='http', auth="public", website=True, sitemap=False)
|
||||
def event_track_proposal(self, event, **post):
|
||||
if not event.can_access_from_current_website():
|
||||
raise NotFound()
|
||||
|
||||
return request.render("website_event_track.event_track_proposal", {'event': event})
|
||||
|
||||
@http.route(['/event/<model("event.event"):event>/track_proposal/post'], type='http', auth="public", methods=['POST'], website=True)
|
||||
@http.route(['''/event/<model("event.event", "[('website_id', 'in', (False, current_website_id))]"):event>/track_proposal/post'''], type='http', auth="public", methods=['POST'], website=True)
|
||||
def event_track_proposal_post(self, event, **post):
|
||||
if not event.can_access_from_current_website():
|
||||
raise NotFound()
|
||||
|
||||
tags = []
|
||||
for tag in event.allowed_track_tag_ids:
|
||||
if post.get('tag_' + str(tag.id)):
|
||||
|
||||
@@ -81,7 +81,8 @@ class WebsiteForum(http.Controller):
|
||||
|
||||
@http.route(['/forum'], type='http', auth="public", website=True)
|
||||
def forum(self, **kwargs):
|
||||
forums = request.env['forum.forum'].search([])
|
||||
domain = request.website.get_current_website().website_domain()
|
||||
forums = request.env['forum.forum'].search(domain)
|
||||
return request.render("website_forum.forum_all", {'forums': forums})
|
||||
|
||||
@http.route('/forum/new', type='json', auth="user", methods=['POST'], website=True)
|
||||
@@ -104,6 +105,7 @@ class WebsiteForum(http.Controller):
|
||||
def sitemap_forum(env, rule, qs):
|
||||
Forum = env['forum.forum']
|
||||
dom = sitemap_qs2dom(qs, '/forum', Forum._rec_name)
|
||||
dom += env['website'].get_current_website().website_domain()
|
||||
for f in Forum.search(dom):
|
||||
loc = '/forum/%s' % slug(f)
|
||||
if not qs or qs.lower() in loc:
|
||||
@@ -115,6 +117,9 @@ class WebsiteForum(http.Controller):
|
||||
'''/forum/<model("forum.forum"):forum>/tag/<model("forum.tag"):tag>/questions/page/<int:page>''',
|
||||
], type='http', auth="public", website=True, sitemap=sitemap_forum)
|
||||
def questions(self, forum, tag=None, page=1, filters='all', sorting=None, search='', post_type=None, **post):
|
||||
if not forum.can_access_from_current_website():
|
||||
raise werkzeug.exceptions.NotFound()
|
||||
|
||||
Post = request.env['forum.post']
|
||||
|
||||
domain = [('forum_id', '=', forum.id), ('parent_id', '=', False), ('state', '=', 'active')]
|
||||
@@ -174,7 +179,7 @@ class WebsiteForum(http.Controller):
|
||||
})
|
||||
return request.render("website_forum.forum_index", values)
|
||||
|
||||
@http.route(['/forum/<model("forum.forum"):forum>/faq'], type='http', auth="public", website=True)
|
||||
@http.route(['''/forum/<model("forum.forum", "[('website_id', 'in', (False, current_website_id))]"):forum>/faq'''], type='http', auth="public", website=True)
|
||||
def forum_faq(self, forum, **post):
|
||||
values = self._prepare_forum_values(forum=forum, searches=dict(), header={'is_guidelines': True}, **post)
|
||||
return request.render("website_forum.faq", values)
|
||||
@@ -232,7 +237,7 @@ class WebsiteForum(http.Controller):
|
||||
except IOError:
|
||||
return False
|
||||
|
||||
@http.route(['''/forum/<model("forum.forum"):forum>/question/<model("forum.post", "[('forum_id','=',forum[0]),('parent_id','=',False),('can_view', '=', True)]"):question>'''], type='http', auth="public", website=True)
|
||||
@http.route(['''/forum/<model("forum.forum", "[('website_id', 'in', (False, current_website_id))]"):forum>/question/<model("forum.post", "[('forum_id','=',forum[0]),('parent_id','=',False),('can_view', '=', True)]"):question>'''], type='http', auth="public", website=True)
|
||||
def question(self, forum, question, **post):
|
||||
if not forum.active:
|
||||
return request.render("website_forum.header", {'forum': forum})
|
||||
@@ -548,8 +553,8 @@ class WebsiteForum(http.Controller):
|
||||
# User
|
||||
# --------------------------------------------------
|
||||
|
||||
@http.route(['/forum/<model("forum.forum"):forum>/users',
|
||||
'/forum/<model("forum.forum"):forum>/users/page/<int:page>'],
|
||||
@http.route(['''/forum/<model("forum.forum", "[('website_id', 'in', (False, current_website_id))]"):forum>/users''',
|
||||
'''/forum/<model("forum.forum"):forum>/users/page/<int:page>'''],
|
||||
type='http', auth="public", website=True)
|
||||
def users(self, forum, page=1, **searches):
|
||||
User = request.env['res.users']
|
||||
@@ -733,7 +738,7 @@ class WebsiteForum(http.Controller):
|
||||
# Badges
|
||||
# --------------------------------------------------
|
||||
|
||||
@http.route('/forum/<model("forum.forum"):forum>/badge', type='http', auth="public", website=True)
|
||||
@http.route('''/forum/<model("forum.forum", "[('website_id', 'in', (False, current_website_id))]"):forum>/badge''', type='http', auth="public", website=True)
|
||||
def badges(self, forum, **searches):
|
||||
Badge = request.env['gamification.badge']
|
||||
badges = Badge.sudo().search([('challenge_ids.category', '=', 'forum')])
|
||||
|
||||
@@ -25,7 +25,7 @@ class KarmaError(Forbidden):
|
||||
class Forum(models.Model):
|
||||
_name = 'forum.forum'
|
||||
_description = 'Forum'
|
||||
_inherit = ['mail.thread', 'website.seo.metadata']
|
||||
_inherit = ['mail.thread', 'website.seo.metadata', 'website.multi.mixin']
|
||||
|
||||
@api.model_cr
|
||||
def init(self):
|
||||
@@ -208,6 +208,7 @@ class Post(models.Model):
|
||||
('discussion', 'Discussion')],
|
||||
string='Type', default='question', required=True)
|
||||
website_message_ids = fields.One2many(domain=lambda self: [('model', '=', self._name), ('message_type', 'in', ['email', 'comment'])])
|
||||
website_id = fields.Many2one(related='forum_id.website_id', readonly=True)
|
||||
|
||||
# history
|
||||
create_date = fields.Datetime('Asked on', index=True, readonly=True)
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Forums">
|
||||
<field name="name"/>
|
||||
<field name="website_id" groups="website.group_multi_website"/>
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="allow_question"/>
|
||||
<field name="allow_link"/>
|
||||
@@ -39,6 +40,9 @@
|
||||
<field name="name"/>
|
||||
</h1>
|
||||
<field name="description"/>
|
||||
<group>
|
||||
<field name="website_id" groups="website.group_multi_website"/>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Options">
|
||||
<group string="Post Types">
|
||||
@@ -138,6 +142,7 @@
|
||||
<field name="name"/>
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="forum_id"/>
|
||||
<field name="website_id" groups="website.group_multi_website"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
@@ -160,6 +165,7 @@
|
||||
<group>
|
||||
<group>
|
||||
<field name="forum_id"/>
|
||||
<field name="website_id" groups="website.group_multi_website"/>
|
||||
<field name="parent_id"/>
|
||||
</group><group>
|
||||
<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color'}"/>
|
||||
|
||||
@@ -5,7 +5,7 @@ from odoo import api, fields, models
|
||||
|
||||
class HrEmployee(models.Model):
|
||||
_name = 'hr.employee'
|
||||
_inherit = ['hr.employee', 'website.published.mixin']
|
||||
_inherit = ['hr.employee', 'website.published.multi.mixin']
|
||||
|
||||
public_info = fields.Char(string='Public Info')
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ class Applicant(models.Model):
|
||||
class Job(models.Model):
|
||||
|
||||
_name = 'hr.job'
|
||||
_inherit = ['hr.job', 'website.seo.metadata', 'website.published.mixin']
|
||||
_inherit = ['hr.job', 'website.seo.metadata', 'website.published.multi.mixin']
|
||||
|
||||
def _get_default_website_description(self):
|
||||
default_description = self.env["ir.model.data"].xmlid_to_object("website_hr_recruitment.default_website_description")
|
||||
|
||||
@@ -206,12 +206,14 @@ class WebsiteSale(http.Controller):
|
||||
return domain
|
||||
|
||||
@http.route([
|
||||
'/shop',
|
||||
'/shop/page/<int:page>',
|
||||
'/shop/category/<model("product.public.category"):category>',
|
||||
'/shop/category/<model("product.public.category"):category>/page/<int:page>'
|
||||
'''/shop''',
|
||||
'''/shop/page/<int:page>''',
|
||||
'''/shop/category/<model("product.public.category", "[('website_id', 'in', (False, current_website_id))]"):category>''',
|
||||
'''/shop/category/<model("product.public.category", "[('website_id', 'in', (False, current_website_id))]"):category>/page/<int:page>'''
|
||||
], type='http', auth="public", website=True)
|
||||
def shop(self, page=0, category=None, search='', ppg=False, **post):
|
||||
if category and not category.can_access_from_current_website():
|
||||
raise NotFound()
|
||||
if ppg:
|
||||
try:
|
||||
ppg = int(ppg)
|
||||
@@ -245,7 +247,7 @@ class WebsiteSale(http.Controller):
|
||||
if attrib_list:
|
||||
post['attrib'] = attrib_list
|
||||
|
||||
categs = request.env['product.public.category'].search([('parent_id', '=', False), ('website_published', '=', True)])
|
||||
categs = request.env['product.public.category'].search([('parent_id', '=', False)] + request.website.website_domain())
|
||||
Product = request.env['product.template']
|
||||
|
||||
parent_category_ids = []
|
||||
@@ -292,6 +294,9 @@ class WebsiteSale(http.Controller):
|
||||
|
||||
@http.route(['/shop/product/<model("product.template"):product>'], type='http', auth="public", website=True)
|
||||
def product(self, product, category='', search='', **kwargs):
|
||||
if not product.can_access_from_current_website():
|
||||
raise NotFound()
|
||||
|
||||
product_context = dict(request.env.context,
|
||||
active_id=product.id,
|
||||
partner=request.env.user.partner_id)
|
||||
@@ -993,7 +998,8 @@ class WebsiteSale(http.Controller):
|
||||
def add_product(self, name=None, category=0, **post):
|
||||
product = request.env['product.product'].create({
|
||||
'name': name or _("New Product"),
|
||||
'public_categ_ids': category
|
||||
'public_categ_ids': category,
|
||||
'website_id': request.website.id,
|
||||
})
|
||||
return "/shop/product/%s?enable_editor=1" % slug(product.product_tmpl_id)
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ class ProductPricelist(models.Model):
|
||||
|
||||
class ProductPublicCategory(models.Model):
|
||||
_name = "product.public.category"
|
||||
_inherit = ["website.seo.metadata", "website.published.mixin"]
|
||||
_inherit = ["website.seo.metadata", "website.published.multi.mixin"]
|
||||
_description = "Website Product Category"
|
||||
_order = "sequence, name"
|
||||
|
||||
@@ -105,7 +105,7 @@ class ProductPublicCategory(models.Model):
|
||||
|
||||
|
||||
class ProductTemplate(models.Model):
|
||||
_inherit = ["product.template", "website.seo.metadata", 'website.published.mixin', 'rating.mixin']
|
||||
_inherit = ["product.template", "website.seo.metadata", 'website.published.multi.mixin', 'rating.mixin']
|
||||
_order = 'website_published desc, website_sequence desc, name'
|
||||
_name = 'product.template'
|
||||
_mail_post_access = 'read'
|
||||
@@ -180,6 +180,8 @@ class ProductTemplate(models.Model):
|
||||
class Product(models.Model):
|
||||
_inherit = "product.product"
|
||||
|
||||
website_id = fields.Many2one(related='product_tmpl_id.website_id')
|
||||
|
||||
website_price = fields.Float('Website price', compute='_website_price', digits=dp.get_precision('Product Price'))
|
||||
website_public_price = fields.Float('Website public price', compute='_website_price', digits=dp.get_precision('Product Price'))
|
||||
website_price_difference = fields.Boolean('Website price difference', compute='_website_price')
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
# -*- 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
|
||||
|
||||
|
||||
class ResPartner(models.Model):
|
||||
_inherit = 'res.partner'
|
||||
|
||||
last_website_so_id = fields.Many2one('sale.order', string='Last Online Sales Order', copy=False)
|
||||
last_website_so_id = fields.Many2one('sale.order', compute='_compute_last_website_so_id', string='Last Online Sales Order')
|
||||
|
||||
@api.multi
|
||||
def _compute_last_website_so_id(self):
|
||||
SaleOrder = self.env['sale.order']
|
||||
for partner in self:
|
||||
current_website_so = SaleOrder.search([('partner_id', '=', partner.id)]).filtered(lambda so: so.order_line.mapped('product_id.product_tmpl_id').can_access_from_current_website())
|
||||
partner.last_website_so_id = current_website_so and current_website_so[0]
|
||||
|
||||
@@ -145,7 +145,7 @@ class Website(models.Model):
|
||||
|
||||
@api.multi
|
||||
def sale_product_domain(self):
|
||||
return [("sale_ok", "=", True)]
|
||||
return [("sale_ok", "=", True)] + self.get_current_website().website_domain()
|
||||
|
||||
@api.model
|
||||
def sale_get_payment_term(self, partner):
|
||||
|
||||
@@ -12,6 +12,13 @@
|
||||
<field name="perm_unlink" eval="False"/>
|
||||
</record>
|
||||
|
||||
<record id="product_public_category_public" model="ir.rule">
|
||||
<field name="name">Public product category public</field>
|
||||
<field name="model_id" ref="model_product_public_category"/>
|
||||
<field name="domain_force">[('website_published', '=', True)]</field>
|
||||
<field name="groups" eval="[(4, ref('base.group_public')), (4, ref('base.group_portal'))]"/>
|
||||
</record>
|
||||
|
||||
<record id="product_supplierinfo_public" model="ir.rule">
|
||||
<field name="name">Public product supplierinfo</field>
|
||||
<field name="model_id" ref="product.model_product_supplierinfo"/>
|
||||
|
||||
@@ -68,6 +68,28 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="product_product_website_tree_view">
|
||||
<field name="name">product.product.website.tree</field>
|
||||
<field name="model">product.product</field>
|
||||
<field name="inherit_id" ref="product.product_product_tree_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="name" position="after">
|
||||
<field name="website_id" groups="website.group_multi_website"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="product_template_website_tree_view">
|
||||
<field name="name">product.template.website.tree</field>
|
||||
<field name="model">product.template</field>
|
||||
<field name="inherit_id" ref="product.product_template_tree_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="name" position="after">
|
||||
<field name="website_id" groups="website.group_multi_website"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="product_template_form_view">
|
||||
<field name="name">product.template.product.website.form</field>
|
||||
<field name="model">product.template</field>
|
||||
@@ -85,6 +107,7 @@
|
||||
<group name="shop">
|
||||
<group string="Shop">
|
||||
<field name="website_url" invisible="1"/>
|
||||
<field name="website_id" groups="website.group_multi_website"/>
|
||||
<field name="public_categ_ids" widget="many2many_tags" string="Categories"/>
|
||||
<field name="alternative_product_ids" widget="many2many_tags"/>
|
||||
<field name="accessory_product_ids" widget="many2many_tags"/>
|
||||
@@ -182,6 +205,7 @@
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="parent_id"/>
|
||||
<field name="website_id" groups="website.group_multi_website"/>
|
||||
<field name="sequence"/>
|
||||
</group>
|
||||
</div>
|
||||
@@ -197,6 +221,7 @@
|
||||
<tree string="Product Public Categories">
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="display_name"/>
|
||||
<field name="website_id" groups="website.group_multi_website"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -271,7 +271,9 @@
|
||||
<!-- Add to cart button-->
|
||||
<template id="categories_recursive" name="Category list">
|
||||
<li class="nav-item">
|
||||
<a t-att-href="keep('/shop/category/' + slug(c), category=0)" t-field="c.name" t-attf-class="nav-link#{' active' if c.id == int(category or 0) else ''}"></a>
|
||||
<a t-att-href="keep('/shop/category/' + slug(c), category=0)" t-attf-class="nav-link#{' active' if c.id == int(category or 0) else ''}">
|
||||
<span t-field="c.name"/> <span t-if="not c.website_published" class="text-danger">(Unpublished)</span>
|
||||
</a>
|
||||
<ul t-if="c.child_id" class="nav nav-pills flex-column nav-hierarchy">
|
||||
<t t-foreach="c.child_id" t-as="c">
|
||||
<t t-call="website_sale.categories_recursive" />
|
||||
|
||||
@@ -6,7 +6,7 @@ from odoo import fields, models
|
||||
|
||||
class DeliveryCarrier(models.Model):
|
||||
_name = 'delivery.carrier'
|
||||
_inherit = ['delivery.carrier', 'website.published.mixin']
|
||||
_inherit = ['delivery.carrier', 'website.published.multi.mixin']
|
||||
|
||||
website_description = fields.Text(related='product_id.description_sale', string='Description for Online Quotations')
|
||||
website_published = fields.Boolean(default=False)
|
||||
|
||||
@@ -57,7 +57,8 @@ class WebsiteSlides(http.Controller):
|
||||
""" Returns a list of available channels: if only one is available,
|
||||
redirects directly to its slides
|
||||
"""
|
||||
channels = request.env['slide.channel'].search([], order='sequence, id')
|
||||
domain = request.website.website_domain()
|
||||
channels = request.env['slide.channel'].search(domain, order='sequence, id')
|
||||
if not channels:
|
||||
return request.render("website_slides.channel_not_found")
|
||||
elif len(channels) == 1:
|
||||
@@ -71,6 +72,7 @@ class WebsiteSlides(http.Controller):
|
||||
def sitemap_slide(env, rule, qs):
|
||||
Channel = env['slide.channel']
|
||||
dom = sitemap_qs2dom(qs=qs, route='/slides/', field=Channel._rec_name)
|
||||
dom += env['website'].get_current_website().website_domain()
|
||||
for channel in Channel.search(dom):
|
||||
loc = '/slides/%s' % slug(channel)
|
||||
if not qs or qs.lower() in loc:
|
||||
@@ -93,6 +95,9 @@ class WebsiteSlides(http.Controller):
|
||||
'''/slides/<model("slide.channel"):channel>/category/<model("slide.category"):category>/<string:slide_type>/page/<int:page>'''],
|
||||
type='http', auth="public", website=True, sitemap=sitemap_slide)
|
||||
def channel(self, channel, category=None, tag=None, page=1, slide_type=None, sorting='creation', search=None, **kw):
|
||||
if not channel.can_access_from_current_website():
|
||||
raise werkzeug.exceptions.NotFound()
|
||||
|
||||
user = request.env.user
|
||||
Slide = request.env['slide.slide']
|
||||
domain = [('channel_id', '=', channel.id)]
|
||||
@@ -164,8 +169,11 @@ class WebsiteSlides(http.Controller):
|
||||
# SLIDE.SLIDE CONTOLLERS
|
||||
# --------------------------------------------------
|
||||
|
||||
@http.route('''/slides/slide/<model("slide.slide", "[('channel_id.can_see', '=', True)]"):slide>''', type='http', auth="public", website=True)
|
||||
@http.route('''/slides/slide/<model("slide.slide", "[('channel_id.can_see', '=', True), ('website_id', 'in', (False, current_website_id))]"):slide>''', type='http', auth="public", website=True)
|
||||
def slide_view(self, slide, **kwargs):
|
||||
if not slide.channel_id.can_access_from_current_website():
|
||||
raise werkzeug.exceptions.NotFound()
|
||||
|
||||
values = self._get_slide_detail(slide)
|
||||
if not values.get('private'):
|
||||
self._set_viewed_slide(slide, 'slide')
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
|
||||
<record model="ir.config_parameter" id="google_app_key">
|
||||
<field name="key">website_slides.google_app_key</field>
|
||||
<field name="value">AIzaSyDOWlmDW-7DbLmOR9ZsT5AOEXf4n6TFwQA</field>
|
||||
<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">
|
||||
|
||||
@@ -2,3 +2,4 @@
|
||||
|
||||
from . import slides
|
||||
from . import res_config_settings
|
||||
from . import website
|
||||
|
||||
@@ -7,4 +7,4 @@ from odoo import api, fields, models
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
_inherit = "res.config.settings"
|
||||
|
||||
website_slide_google_app_key = fields.Char(string='Google Doc Key', config_parameter='website_slides.google_app_key')
|
||||
website_slide_google_app_key = fields.Char(related='website_id.website_slide_google_app_key')
|
||||
|
||||
@@ -26,7 +26,7 @@ class Channel(models.Model):
|
||||
channels. """
|
||||
_name = 'slide.channel'
|
||||
_description = 'Channel for Slides'
|
||||
_inherit = ['mail.thread', 'website.seo.metadata', 'website.published.mixin']
|
||||
_inherit = ['mail.thread', 'website.seo.metadata', 'website.published.multi.mixin']
|
||||
_order = 'sequence, id'
|
||||
_order_by_strategy = {
|
||||
'most_viewed': 'total_views desc',
|
||||
@@ -330,6 +330,7 @@ class Slide(models.Model):
|
||||
self[key] = value
|
||||
|
||||
# website
|
||||
website_id = fields.Many2one(related='channel_id.website_id', readonly=True)
|
||||
date_published = fields.Datetime('Publish Date')
|
||||
likes = fields.Integer('Likes')
|
||||
dislikes = fields.Integer('Dislikes')
|
||||
@@ -469,14 +470,17 @@ class Slide(models.Model):
|
||||
return groups
|
||||
|
||||
def get_related_slides(self, limit=20):
|
||||
domain = [('website_published', '=', True), ('channel_id.visibility', '!=', 'private'), ('id', '!=', self.id)]
|
||||
domain = request.website.website_domain()
|
||||
domain += [('website_published', '=', True), ('channel_id.visibility', '!=', 'private'), ('id', '!=', self.id)]
|
||||
if self.category_id:
|
||||
domain += [('category_id', '=', self.category_id.id)]
|
||||
for record in self.search(domain, limit=limit):
|
||||
yield record
|
||||
|
||||
def get_most_viewed_slides(self, limit=20):
|
||||
for record in self.search([('website_published', '=', True), ('channel_id.visibility', '!=', 'private'), ('id', '!=', self.id)], limit=limit, order='total_views desc'):
|
||||
domain = request.website.website_domain()
|
||||
domain += [('website_published', '=', True), ('channel_id.visibility', '!=', 'private'), ('id', '!=', self.id)]
|
||||
for record in self.search(domain, limit=limit, order='total_views desc'):
|
||||
yield record
|
||||
|
||||
def _post_publication(self):
|
||||
@@ -542,7 +546,7 @@ class Slide(models.Model):
|
||||
return {'error': _('Unknown document')}
|
||||
|
||||
def _parse_youtube_document(self, document_id, only_preview_fields):
|
||||
key = self.env['ir.config_parameter'].sudo().get_param('website_slides.google_app_key')
|
||||
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')
|
||||
if fetch_res.get('error'):
|
||||
return fetch_res
|
||||
@@ -592,7 +596,7 @@ class Slide(models.Model):
|
||||
if access_token:
|
||||
params['access_token'] = access_token
|
||||
if not params.get('access_token'):
|
||||
params['key'] = self.env['ir.config_parameter'].sudo().get_param('website_slides.google_app_key')
|
||||
params['key'] = self.env['website'].get_current_website().website_slide_google_app_key
|
||||
|
||||
fetch_res = self._fetch_data('https://www.googleapis.com/drive/v2/files/%s' % document_id, params, "json")
|
||||
if fetch_res.get('error'):
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class Website(models.Model):
|
||||
_inherit = "website"
|
||||
|
||||
website_slide_google_app_key = fields.Char('Google Doc Key')
|
||||
@@ -60,6 +60,7 @@
|
||||
<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>
|
||||
@@ -108,6 +109,7 @@
|
||||
<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"/>
|
||||
@@ -198,7 +200,7 @@
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<!-- SLIDE.CHANNEL VIEWS -->
|
||||
<record model="ir.ui.view" id="view_slide_channel_form">
|
||||
<field name="name">slide.channel.form</field>
|
||||
@@ -229,6 +231,7 @@
|
||||
<page string="General">
|
||||
<group>
|
||||
<group>
|
||||
<field name="website_id" 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')]}"/>
|
||||
@@ -269,6 +272,7 @@
|
||||
<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"/>
|
||||
@@ -297,7 +301,7 @@
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<menuitem name="Slides"
|
||||
id="menu_website_slides_root"
|
||||
sequence="32"
|
||||
@@ -310,13 +314,13 @@
|
||||
sequence="140"
|
||||
parent="website.menu_website_global_configuration"
|
||||
groups="website.group_website_designer"/>
|
||||
|
||||
<menuitem action="action_slide_channels" id="menu_action_slide_channels_global"
|
||||
|
||||
<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>
|
||||
|
||||
@@ -6,6 +6,7 @@ from collections import defaultdict
|
||||
from odoo import api, fields, models, tools, SUPERUSER_ID, _
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.osv import expression
|
||||
from odoo.tools import config
|
||||
from odoo.tools.safe_eval import safe_eval
|
||||
|
||||
|
||||
@@ -60,8 +61,16 @@ class IrRule(models.Model):
|
||||
if any(rule.model_id.model == self._name for rule in self):
|
||||
raise ValidationError(_('Rules can not be applied on the Record Rules model.'))
|
||||
|
||||
def _compute_domain_keys(self):
|
||||
""" Return the list of context keys to use for caching ``_compute_domain``. """
|
||||
return []
|
||||
|
||||
@api.model
|
||||
@tools.ormcache('self._uid', 'model_name', 'mode')
|
||||
@tools.conditional(
|
||||
'xml' not in config['dev_mode'],
|
||||
tools.ormcache('self._uid', 'model_name', 'mode',
|
||||
'tuple(self._context.get(k) for k in self._compute_domain_keys())'),
|
||||
)
|
||||
def _compute_domain(self, model_name, mode="read"):
|
||||
if mode not in self._MODES:
|
||||
raise ValueError('Invalid mode: %r' % (mode,))
|
||||
|
||||
@@ -1095,7 +1095,7 @@ actual arch.
|
||||
|
||||
def _read_template_keys(self):
|
||||
""" Return the list of context keys to use for caching ``_read_template``. """
|
||||
return ['lang', 'inherit_branding', 'editable', 'translatable', 'edit_translations', 'website_id']
|
||||
return ['lang', 'inherit_branding', 'editable', 'translatable', 'edit_translations']
|
||||
|
||||
# apply ormcache_context decorator unless in dev mode...
|
||||
@api.model
|
||||
|
||||
Reference in New Issue
Block a user