[MERGE] Merge parents
bzr revid: rim@openerp.com-20131206161238-orblyjhemcw5cjgw bzr revid: rim@openerp.com-20131209074455-tyhl2dkv8id2my1n
This commit is contained in:
@@ -42,12 +42,11 @@ class mail_compose_message(osv.TransientModel):
|
||||
_inherit = 'mail.compose.message'
|
||||
|
||||
def default_get(self, cr, uid, fields, context=None):
|
||||
"""
|
||||
"""
|
||||
""" Override to pre-fill the data when having a template in single-email mode """
|
||||
if context is None:
|
||||
context = {}
|
||||
res = super(mail_compose_message, self).default_get(cr, uid, fields, context=context)
|
||||
if context.get('default_template_id'):
|
||||
if res.get('composition_mode') != 'mass_mail' and context.get('default_template_id'):
|
||||
res.update(
|
||||
self.onchange_template_id(
|
||||
cr, uid, [], context['default_template_id'], res.get('composition_mode'),
|
||||
|
||||
@@ -23,7 +23,7 @@ from datetime import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from openerp.addons.hr_holidays.tests.common import TestHrHolidaysBase
|
||||
from openerp.exceptions import Warning
|
||||
from openerp.exceptions import Warning, AccessError
|
||||
from openerp.osv.orm import except_orm
|
||||
from openerp.tools import mute_logger
|
||||
|
||||
@@ -46,7 +46,7 @@ class TestHolidaysFlow(TestHrHolidaysBase):
|
||||
'hr_holidays: wrong type days computation')
|
||||
|
||||
# HrUser creates some holiday statuses -> crash because only HrManagers should do this
|
||||
with self.assertRaises(except_orm):
|
||||
with self.assertRaises(AccessError):
|
||||
self.holidays_status_dummy = self.hr_holidays_status.create(cr, self.user_hruser_id, {
|
||||
'name': 'UserCheats',
|
||||
'limit': True,
|
||||
|
||||
@@ -34,7 +34,6 @@ class mail_group(osv.Model):
|
||||
_mail_flat_thread = False
|
||||
_inherit = ['mail.thread']
|
||||
_inherits = {'mail.alias': 'alias_id'}
|
||||
_mail_post_access = 'read'
|
||||
|
||||
def _get_image(self, cr, uid, ids, name, args, context=None):
|
||||
result = dict.fromkeys(ids, False)
|
||||
|
||||
@@ -165,7 +165,7 @@ class mail_thread(osv.AbstractModel):
|
||||
|
||||
def read_followers_data(self, cr, uid, follower_ids, context=None):
|
||||
result = []
|
||||
technical_group = self.pool.get('ir.model.data').get_object(cr, uid, 'base', 'group_no_one')
|
||||
technical_group = self.pool.get('ir.model.data').get_object(cr, uid, 'base', 'group_no_one', context=context, check_existence_and_raise=True)
|
||||
for follower in self.pool.get('res.partner').browse(cr, uid, follower_ids, context=context):
|
||||
is_editable = uid in map(lambda x: x.id, technical_group.users)
|
||||
is_uid = uid in map(lambda x: x.id, follower.user_ids)
|
||||
@@ -496,9 +496,9 @@ class mail_thread(osv.AbstractModel):
|
||||
|
||||
posted = False
|
||||
for subtype in subtypes:
|
||||
subtype_rec = self.pool.get('ir.model.data').get_object(cr, uid, subtype.split('.')[0], subtype.split('.')[1], context=context)
|
||||
subtype_rec = self.pool.get('ir.model.data').get_object(cr, uid, subtype.split('.')[0], subtype.split('.')[1], context=context, check_existence_and_raise=False)
|
||||
if not subtype_rec.exists():
|
||||
_logger.debug('subtype %s not found, giving error "%s"' % (subtype, e))
|
||||
_logger.debug('subtype %s not found' % subtype)
|
||||
continue
|
||||
message = format_message(subtype_rec.description if subtype_rec.description else subtype_rec.name, tracked_values)
|
||||
self.message_post(cr, uid, browse_record.id, body=message, subtype=subtype, context=context)
|
||||
|
||||
@@ -725,6 +725,7 @@ openerp.mail = function (session) {
|
||||
var values = {
|
||||
'body': this.$('textarea').val(),
|
||||
'subject': false,
|
||||
'parent_id': this.context.default_parent_id,
|
||||
'attachment_ids': _.map(this.attachment_ids, function (file) {return file.id;}),
|
||||
'partner_ids': partner_ids,
|
||||
'context': _.extend(this.parent_thread.context, {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Business Applications
|
||||
# Copyright (c) 20123TODAY OpenERP S.A. <http://www.openerp.com>
|
||||
# Copyright (c) 2012-TODAY OpenERP S.A. <http://openerp.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
##############################################################################
|
||||
|
||||
from openerp.addons.mail.tests.common import TestMail
|
||||
from openerp.exceptions import AccessError
|
||||
from openerp.osv.orm import except_orm
|
||||
from openerp.tools import mute_logger
|
||||
|
||||
@@ -40,7 +41,7 @@ class TestMailGroup(TestMail):
|
||||
self.mail_group.read(cr, user_employee_id, [self.group_pigs_id])
|
||||
|
||||
# Do: Bert creates a group -> ko, no access rights
|
||||
with self.assertRaises(except_orm):
|
||||
with self.assertRaises(AccessError):
|
||||
self.mail_group.create(cr, user_noone_id, {'name': 'Test'})
|
||||
# Do: Raoul creates a restricted group -> ok
|
||||
new_group_id = self.mail_group.create(cr, user_employee_id, {'name': 'Test'})
|
||||
@@ -58,7 +59,7 @@ class TestMailGroup(TestMail):
|
||||
# Do: Raoul write on Jobs -> ok
|
||||
self.mail_group.write(cr, user_employee_id, [self.group_priv_id], {'name': 'modified'})
|
||||
# Do: Bert cannot write on Private -> ko (read but no write)
|
||||
with self.assertRaises(except_orm):
|
||||
with self.assertRaises(AccessError):
|
||||
self.mail_group.write(cr, user_noone_id, [self.group_priv_id], {'name': 're-modified'})
|
||||
# Test: Bert cannot unlink the group
|
||||
with self.assertRaises(except_orm):
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
##############################################################################
|
||||
|
||||
from openerp.addons.mail.tests.common import TestMail
|
||||
from openerp.exceptions import AccessError
|
||||
from openerp.osv.orm import except_orm
|
||||
from openerp.tools import mute_logger
|
||||
|
||||
@@ -258,13 +259,13 @@ class TestMailMessage(TestMail):
|
||||
# ----------------------------------------
|
||||
|
||||
# Do: Bert creates a message on Pigs -> ko, no creation rights
|
||||
self.assertRaises(except_orm, self.mail_message.create,
|
||||
self.assertRaises(AccessError, self.mail_message.create,
|
||||
cr, user_bert_id, {'model': 'mail.group', 'res_id': self.group_pigs_id, 'body': 'Test'})
|
||||
# Do: Bert create a message on Jobs -> ko, no creation rights
|
||||
self.assertRaises(except_orm, self.mail_message.create,
|
||||
self.assertRaises(AccessError, self.mail_message.create,
|
||||
cr, user_bert_id, {'model': 'mail.group', 'res_id': self.group_jobs_id, 'body': 'Test'})
|
||||
# Do: Bert create a private message -> ko, no creation rights
|
||||
self.assertRaises(except_orm, self.mail_message.create,
|
||||
self.assertRaises(AccessError, self.mail_message.create,
|
||||
cr, user_bert_id, {'body': 'Test'})
|
||||
|
||||
# Do: Raoul creates a message on Jobs -> ok, write access to the related document
|
||||
@@ -410,15 +411,15 @@ class TestMailMessage(TestMail):
|
||||
for message in bert_jobs.message_ids:
|
||||
trigger_read = message.subject
|
||||
for partner in bert_jobs.message_follower_ids:
|
||||
with self.assertRaises(except_orm):
|
||||
with self.assertRaises(AccessError):
|
||||
trigger_read = partner.name
|
||||
# Do: Bert comments Jobs, ko because no creation right
|
||||
self.assertRaises(except_orm,
|
||||
self.assertRaises(AccessError,
|
||||
self.mail_group.message_post,
|
||||
cr, user_bert_id, self.group_jobs_id, body='I love Pigs')
|
||||
|
||||
# Do: Bert writes on its own profile, ko because no message create access
|
||||
with self.assertRaises(except_orm):
|
||||
with self.assertRaises(AccessError):
|
||||
self.res_users.message_post(cr, user_bert_id, user_bert_id, body='I love Bert')
|
||||
self.res_partner.message_post(cr, user_bert_id, partner_bert_id, body='I love Bert')
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ class publisher_warranty_contract(osv.osv):
|
||||
# old behavior based on res.log; now on mail.message, that is not necessarily installed
|
||||
IMD = self.pool['ir.model.data']
|
||||
user = self.pool['res.users'].browse(cr, SUPERUSER_ID, SUPERUSER_ID)
|
||||
poster = IMD.get_object(cr, SUPERUSER_ID, 'mail', 'group_all_employees')
|
||||
poster = IMD.get_object(cr, SUPERUSER_ID, 'mail', 'group_all_employees', context=context, check_existence_and_raise=False)
|
||||
if not poster.exists():
|
||||
if not user.exists():
|
||||
return True
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
##############################################################################
|
||||
|
||||
from openerp.addons.mail.tests.common import TestMail
|
||||
from openerp.exceptions import AccessError
|
||||
from openerp.osv.orm import except_orm
|
||||
from openerp.tools.misc import mute_logger
|
||||
|
||||
@@ -63,7 +64,7 @@ class test_portal(TestMail):
|
||||
trigger_read = chell_pigs.name
|
||||
|
||||
# Do: Chell posts a message on Pigs, crash because can not write on group or is not in the followers
|
||||
with self.assertRaises(except_orm):
|
||||
with self.assertRaises(AccessError):
|
||||
self.mail_group.message_post(cr, self.user_chell_id, self.group_pigs_id, body='Message')
|
||||
|
||||
# Do: Chell is added into Pigs followers and browse it -> ok for messages, ko for partners (no read permission)
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
##############################################################################
|
||||
|
||||
from openerp.addons.project.tests.test_project_base import TestProjectBase
|
||||
from openerp.exceptions import AccessError
|
||||
from openerp.osv.orm import except_orm
|
||||
from openerp.tools import mute_logger
|
||||
|
||||
@@ -93,13 +94,13 @@ class TestPortalProject(TestPortalProjectBase):
|
||||
self.project_task.write(cr, self.user_projectuser_id, task_ids, {'description': 'TestDescription'})
|
||||
|
||||
# Do: Bert reads project -> crash, no group
|
||||
self.assertRaises(except_orm, self.project_project.read, cr, self.user_none_id, pigs_id, ['name'])
|
||||
self.assertRaises(AccessError, self.project_project.read, cr, self.user_none_id, pigs_id, ['name'])
|
||||
# Test: no project task visible
|
||||
self.assertRaises(except_orm, self.project_task.search, cr, self.user_none_id, [('project_id', '=', pigs_id)])
|
||||
self.assertRaises(AccessError, self.project_task.search, cr, self.user_none_id, [('project_id', '=', pigs_id)])
|
||||
# Test: no project task readable
|
||||
self.assertRaises(except_orm, self.project_task.read, cr, self.user_none_id, task_ids, ['name'])
|
||||
self.assertRaises(AccessError, self.project_task.read, cr, self.user_none_id, task_ids, ['name'])
|
||||
# Test: no project task writable
|
||||
self.assertRaises(except_orm, self.project_task.write, cr, self.user_none_id, task_ids, {'description': 'TestDescription'})
|
||||
self.assertRaises(AccessError, self.project_task.write, cr, self.user_none_id, task_ids, {'description': 'TestDescription'})
|
||||
|
||||
# Do: Chell reads project -> ok (portal ok public)
|
||||
self.project_project.read(cr, self.user_portal_id, pigs_id, ['name'])
|
||||
@@ -110,7 +111,7 @@ class TestPortalProject(TestPortalProjectBase):
|
||||
# Test: all project tasks readable
|
||||
self.project_task.read(cr, self.user_portal_id, task_ids, ['name'])
|
||||
# Test: no project task writable
|
||||
self.assertRaises(except_orm, self.project_task.write, cr, self.user_portal_id, task_ids, {'description': 'TestDescription'})
|
||||
self.assertRaises(AccessError, self.project_task.write, cr, self.user_portal_id, task_ids, {'description': 'TestDescription'})
|
||||
|
||||
# Do: Donovan reads project -> ok (public)
|
||||
self.project_project.read(cr, self.user_public_id, pigs_id, ['name'])
|
||||
@@ -121,7 +122,7 @@ class TestPortalProject(TestPortalProjectBase):
|
||||
# Test: all project tasks readable
|
||||
self.project_task.read(cr, self.user_public_id, task_ids, ['name'])
|
||||
# Test: no project task writable
|
||||
self.assertRaises(except_orm, self.project_task.write, cr, self.user_public_id, task_ids, {'description': 'TestDescription'})
|
||||
self.assertRaises(AccessError, self.project_task.write, cr, self.user_public_id, task_ids, {'description': 'TestDescription'})
|
||||
|
||||
# ----------------------------------------
|
||||
# CASE2: portal project
|
||||
@@ -136,9 +137,9 @@ class TestPortalProject(TestPortalProjectBase):
|
||||
'access rights: project user cannot see all tasks of a portal project')
|
||||
|
||||
# Do: Bert reads project -> crash, no group
|
||||
self.assertRaises(except_orm, self.project_project.read, cr, self.user_none_id, pigs_id, ['name'])
|
||||
self.assertRaises(AccessError, self.project_project.read, cr, self.user_none_id, pigs_id, ['name'])
|
||||
# Test: no project task searchable
|
||||
self.assertRaises(except_orm, self.project_task.search, cr, self.user_none_id, [('project_id', '=', pigs_id)])
|
||||
self.assertRaises(AccessError, self.project_task.search, cr, self.user_none_id, [('project_id', '=', pigs_id)])
|
||||
|
||||
# Data: task follower
|
||||
self.project_task.message_subscribe_users(cr, self.user_projectuser_id, [self.task_1_id, self.task_3_id], [self.user_portal_id])
|
||||
@@ -174,7 +175,7 @@ class TestPortalProject(TestPortalProjectBase):
|
||||
'access rights: project user cannot see all tasks of an employees project')
|
||||
|
||||
# Do: Bert reads project -> crash, no group
|
||||
self.assertRaises(except_orm, self.project_project.read, cr, self.user_none_id, pigs_id, ['name'])
|
||||
self.assertRaises(AccessError, self.project_project.read, cr, self.user_none_id, pigs_id, ['name'])
|
||||
|
||||
# Do: Chell reads project -> ko (portal ko employee)
|
||||
self.assertRaises(except_orm, self.project_project.read, cr, self.user_portal_id, pigs_id, ['name'])
|
||||
@@ -202,7 +203,7 @@ class TestPortalProject(TestPortalProjectBase):
|
||||
'access rights: employee user should not see tasks of a not-followed followers project, only assigned')
|
||||
|
||||
# Do: Bert reads project -> crash, no group
|
||||
self.assertRaises(except_orm, self.project_project.read, cr, self.user_none_id, pigs_id, ['name'])
|
||||
self.assertRaises(AccessError, self.project_project.read, cr, self.user_none_id, pigs_id, ['name'])
|
||||
|
||||
# Do: Chell reads project -> ko (portal ko employee)
|
||||
self.assertRaises(except_orm, self.project_project.read, cr, self.user_portal_id, pigs_id, ['name'])
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
##############################################################################
|
||||
|
||||
from openerp.addons.portal_project.tests.test_access_rights import TestPortalProjectBase
|
||||
from openerp.exceptions import AccessError
|
||||
from openerp.osv.orm import except_orm
|
||||
from openerp.tools import mute_logger
|
||||
|
||||
@@ -71,11 +72,11 @@ class TestPortalIssue(TestPortalProjectBase):
|
||||
|
||||
# Do: Bert reads project -> crash, no group
|
||||
# Test: no project issue visible
|
||||
self.assertRaises(except_orm, self.project_issue.search, cr, self.user_none_id, [('project_id', '=', pigs_id)])
|
||||
self.assertRaises(AccessError, self.project_issue.search, cr, self.user_none_id, [('project_id', '=', pigs_id)])
|
||||
# Test: no project issue readable
|
||||
self.assertRaises(except_orm, self.project_issue.read, cr, self.user_none_id, issue_ids, ['name'])
|
||||
self.assertRaises(AccessError, self.project_issue.read, cr, self.user_none_id, issue_ids, ['name'])
|
||||
# Test: no project issue writable
|
||||
self.assertRaises(except_orm, self.project_issue.write, cr, self.user_none_id, issue_ids, {'description': 'TestDescription'})
|
||||
self.assertRaises(AccessError, self.project_issue.write, cr, self.user_none_id, issue_ids, {'description': 'TestDescription'})
|
||||
|
||||
# Do: Chell reads project -> ok (portal ok public)
|
||||
# Test: all project issues visible
|
||||
@@ -85,7 +86,7 @@ class TestPortalIssue(TestPortalProjectBase):
|
||||
# Test: all project issues readable
|
||||
self.project_issue.read(cr, self.user_portal_id, issue_ids, ['name'])
|
||||
# Test: no project issue writable
|
||||
self.assertRaises(except_orm, self.project_issue.write, cr, self.user_portal_id, issue_ids, {'description': 'TestDescription'})
|
||||
self.assertRaises(AccessError, self.project_issue.write, cr, self.user_portal_id, issue_ids, {'description': 'TestDescription'})
|
||||
|
||||
# Do: Donovan reads project -> ok (public ok public)
|
||||
# Test: all project issues visible
|
||||
@@ -106,7 +107,7 @@ class TestPortalIssue(TestPortalProjectBase):
|
||||
|
||||
# Do: Bert reads project -> crash, no group
|
||||
# Test: no project issue searchable
|
||||
self.assertRaises(except_orm, self.project_issue.search, cr, self.user_none_id, [('project_id', '=', pigs_id)])
|
||||
self.assertRaises(AccessError, self.project_issue.search, cr, self.user_none_id, [('project_id', '=', pigs_id)])
|
||||
|
||||
# Data: issue follower
|
||||
self.project_issue.message_subscribe_users(cr, self.user_projectuser_id, [self.issue_1_id, self.issue_3_id], [self.user_portal_id])
|
||||
|
||||
@@ -26,7 +26,10 @@
|
||||
'author': 'OpenERP SA',
|
||||
'category': 'Sales Management',
|
||||
'depends': ['base', 'process', 'decimal_precision', 'mail'],
|
||||
'demo': ['product_demo.xml', 'product_image_demo.xml'],
|
||||
'demo': [
|
||||
'product_demo.xml',
|
||||
'product_image_demo.xml',
|
||||
],
|
||||
'description': """
|
||||
This is the base module for managing products and pricelists in OpenERP.
|
||||
========================================================================
|
||||
|
||||
+113
-145
@@ -146,7 +146,6 @@ class product_pricelist(osv.osv):
|
||||
"currency_id": _get_currency
|
||||
}
|
||||
|
||||
#def price_get_multi(self, cr, uid, product_ids, context=None):
|
||||
def price_get_multi(self, cr, uid, pricelist_ids, products_by_qty_by_partner, context=None):
|
||||
"""multi products 'price_get'.
|
||||
@param pricelist_ids:
|
||||
@@ -156,23 +155,21 @@ class product_pricelist(osv.osv):
|
||||
'date': Date of the pricelist (%Y-%m-%d),}
|
||||
@return: a dict of dict with product_id as key and a dict 'price by pricelist' as value
|
||||
"""
|
||||
if not pricelist_ids:
|
||||
pricelist_ids = self.pool.get('product.pricelist').search(cr, uid, [], context=context)
|
||||
results = {}
|
||||
for pricelist in self.browse(cr, uid, pricelist_ids, context=context):
|
||||
subres = self._price_get_multi(cr, uid, pricelist, products_by_qty_by_partner, context=context)
|
||||
for product_id,price in subres.items():
|
||||
results.setdefault(product_id, {})
|
||||
results[product_id][pricelist.id] = price
|
||||
return results
|
||||
|
||||
def _create_parent_category_list(id, lst):
|
||||
if not id:
|
||||
return []
|
||||
parent = product_category_tree.get(id)
|
||||
if parent:
|
||||
lst.append(parent)
|
||||
return _create_parent_category_list(parent, lst)
|
||||
else:
|
||||
return lst
|
||||
# _create_parent_category_list
|
||||
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
def _price_get_multi(self, cr, uid, pricelist, products_by_qty_by_partner, context=None):
|
||||
context = context or {}
|
||||
date = context.get('date') or time.strftime('%Y-%m-%d')
|
||||
|
||||
products = map(lambda x: x[0], products_by_qty_by_partner)
|
||||
currency_obj = self.pool.get('res.currency')
|
||||
product_obj = self.pool.get('product.product')
|
||||
product_category_obj = self.pool.get('product.category')
|
||||
@@ -180,154 +177,125 @@ class product_pricelist(osv.osv):
|
||||
supplierinfo_obj = self.pool.get('product.supplierinfo')
|
||||
price_type_obj = self.pool.get('product.price.type')
|
||||
|
||||
# product.pricelist.version:
|
||||
if not pricelist_ids:
|
||||
pricelist_ids = self.pool.get('product.pricelist').search(cr, uid, [], context=context)
|
||||
version = False
|
||||
for v in pricelist.version_id:
|
||||
if ((v.date_start is False) or (v.date_start <= date)) and ((v.date_end is False) or (v.date_end >= date)):
|
||||
version = v
|
||||
break
|
||||
|
||||
pricelist_version_ids = self.pool.get('product.pricelist.version').search(cr, uid, [
|
||||
('pricelist_id', 'in', pricelist_ids),
|
||||
'|',
|
||||
('date_start', '=', False),
|
||||
('date_start', '<=', date),
|
||||
'|',
|
||||
('date_end', '=', False),
|
||||
('date_end', '>=', date),
|
||||
])
|
||||
if len(pricelist_ids) != len(pricelist_version_ids):
|
||||
raise osv.except_osv(_('Warning!'), _("At least one pricelist has no active version !\nPlease create or activate one."))
|
||||
categ_ids = {}
|
||||
for p in products:
|
||||
categ = p.categ_id
|
||||
while categ:
|
||||
categ_ids[categ.id] = True
|
||||
categ = categ.parent_id
|
||||
categ_ids = categ_ids.keys()
|
||||
|
||||
# product.product:
|
||||
product_ids = [i[0] for i in products_by_qty_by_partner]
|
||||
#products = dict([(item['id'], item) for item in product_obj.read(cr, uid, product_ids, ['categ_id', 'product_tmpl_id', 'uos_id', 'uom_id'])])
|
||||
products = product_obj.browse(cr, uid, product_ids, context=context)
|
||||
products_dict = dict([(item.id, item) for item in products])
|
||||
prod_ids = [x.id for x in products]
|
||||
prod_tmpl_ids = [x.product_tmpl_id.id for x in products]
|
||||
|
||||
# product.category:
|
||||
product_category_ids = product_category_obj.search(cr, uid, [])
|
||||
product_categories = product_category_obj.read(cr, uid, product_category_ids, ['parent_id'])
|
||||
product_category_tree = dict([(item['id'], item['parent_id'][0]) for item in product_categories if item['parent_id']])
|
||||
# Load all rules
|
||||
cr.execute(
|
||||
'SELECT i.id '
|
||||
'FROM product_pricelist_item AS i '
|
||||
'WHERE (product_tmpl_id IS NULL OR product_tmpl_id = any(%s)) '
|
||||
'AND (product_id IS NULL OR (product_id = any(%s))) '
|
||||
'AND ((categ_id IS NULL) OR (categ_id = any(%s))) '
|
||||
'AND (price_version_id = %s) '
|
||||
'ORDER BY sequence, min_quantity desc',
|
||||
(prod_tmpl_ids, prod_ids, categ_ids, version.id))
|
||||
|
||||
item_ids = [x[0] for x in cr.fetchall()]
|
||||
items = self.pool.get('product.pricelist.item').browse(cr, uid, item_ids, context=context)
|
||||
|
||||
price_types = {}
|
||||
|
||||
results = {}
|
||||
for product_id, qty, partner in products_by_qty_by_partner:
|
||||
for pricelist_id in pricelist_ids:
|
||||
price = False
|
||||
|
||||
tmpl_id = products_dict[product_id].product_tmpl_id and products_dict[product_id].product_tmpl_id.id or False
|
||||
|
||||
categ_id = products_dict[product_id].categ_id and products_dict[product_id].categ_id.id or False
|
||||
categ_ids = _create_parent_category_list(categ_id, [categ_id])
|
||||
if categ_ids:
|
||||
categ_where = '(categ_id IN (' + ','.join(map(str, categ_ids)) + '))'
|
||||
else:
|
||||
categ_where = '(categ_id IS NULL)'
|
||||
|
||||
if partner:
|
||||
partner_where = 'base <> -2 OR %s IN (SELECT name FROM product_supplierinfo WHERE product_id = %s) '
|
||||
partner_args = (partner, tmpl_id)
|
||||
else:
|
||||
partner_where = 'base <> -2 '
|
||||
partner_args = ()
|
||||
|
||||
cr.execute(
|
||||
'SELECT i.*, pl.currency_id '
|
||||
'FROM product_pricelist_item AS i, '
|
||||
'product_pricelist_version AS v, product_pricelist AS pl '
|
||||
'WHERE (product_tmpl_id IS NULL OR product_tmpl_id = %s) '
|
||||
'AND (product_id IS NULL OR product_id = %s) '
|
||||
'AND (' + categ_where + ' OR (categ_id IS NULL)) '
|
||||
'AND (' + partner_where + ') '
|
||||
'AND price_version_id = %s '
|
||||
'AND (min_quantity IS NULL OR min_quantity <= %s) '
|
||||
'AND i.price_version_id = v.id AND v.pricelist_id = pl.id '
|
||||
'ORDER BY sequence',
|
||||
(tmpl_id, product_id) + partner_args + (pricelist_version_ids[0], qty))
|
||||
res1 = cr.dictfetchall()
|
||||
uom_price_already_computed = False
|
||||
for res in res1:
|
||||
if res:
|
||||
if res['base'] == -1:
|
||||
if not res['base_pricelist_id']:
|
||||
price = 0.0
|
||||
else:
|
||||
price_tmp = self.price_get(cr, uid,
|
||||
[res['base_pricelist_id']], product_id,
|
||||
qty, context=context)[res['base_pricelist_id']]
|
||||
ptype_src = self.browse(cr, uid, res['base_pricelist_id']).currency_id.id
|
||||
uom_price_already_computed = True
|
||||
price = currency_obj.compute(cr, uid,
|
||||
ptype_src, res['currency_id'],
|
||||
price_tmp, round=False,
|
||||
context=context)
|
||||
elif res['base'] == -2:
|
||||
# this section could be improved by moving the queries outside the loop:
|
||||
where = []
|
||||
if partner:
|
||||
where = [('name', '=', partner) ]
|
||||
sinfo = supplierinfo_obj.search(cr, uid,
|
||||
[('product_id', '=', tmpl_id)] + where)
|
||||
price = 0.0
|
||||
if sinfo:
|
||||
qty_in_product_uom = qty
|
||||
product_default_uom = product_obj.read(cr, uid, [product_id], ['uom_id'])[0]['uom_id'][0]
|
||||
supplier = supplierinfo_obj.browse(cr, uid, sinfo, context=context)[0]
|
||||
seller_uom = supplier.product_uom and supplier.product_uom.id or False
|
||||
if seller_uom and product_default_uom and product_default_uom != seller_uom:
|
||||
uom_price_already_computed = True
|
||||
qty_in_product_uom = product_uom_obj._compute_qty(cr, uid, product_default_uom, qty, to_uom_id=seller_uom)
|
||||
cr.execute('SELECT * ' \
|
||||
'FROM pricelist_partnerinfo ' \
|
||||
'WHERE suppinfo_id IN %s' \
|
||||
'AND min_quantity <= %s ' \
|
||||
'ORDER BY min_quantity DESC LIMIT 1', (tuple(sinfo),qty_in_product_uom,))
|
||||
res2 = cr.dictfetchone()
|
||||
if res2:
|
||||
price = res2['price']
|
||||
else:
|
||||
price_type = price_type_obj.browse(cr, uid, int(res['base']))
|
||||
uom_price_already_computed = True
|
||||
price = currency_obj.compute(cr, uid,
|
||||
price_type.currency_id.id, res['currency_id'],
|
||||
product_obj.price_get(cr, uid, [product_id],
|
||||
price_type.field, context=context)[product_id], round=False, context=context)
|
||||
|
||||
if price is not False:
|
||||
price_limit = price
|
||||
price = price * (1.0+(res['price_discount'] or 0.0))
|
||||
price = rounding(price, res['price_round']) #TOFIX: rounding with tools.float_rouding
|
||||
price += (res['price_surcharge'] or 0.0)
|
||||
if res['price_min_margin']:
|
||||
price = max(price, price_limit+res['price_min_margin'])
|
||||
if res['price_max_margin']:
|
||||
price = min(price, price_limit+res['price_max_margin'])
|
||||
for product, qty, partner in products_by_qty_by_partner:
|
||||
uom_price_already_computed = False
|
||||
results[product.id] = 0.0
|
||||
price = False
|
||||
for rule in items:
|
||||
if rule.min_quantity and qty<rule.min_quantity:
|
||||
continue
|
||||
if rule.product_tmpl_id and product.product_tmpl_id.id<>rule.product_tmpl_id.id:
|
||||
continue
|
||||
if rule.product_id and product.id<>rule.product_id.id:
|
||||
continue
|
||||
if rule.categ_id:
|
||||
cat = product.categ_id
|
||||
while cat:
|
||||
if cat.id == rule.categ_id.id:
|
||||
break
|
||||
cat = cat.parent_id
|
||||
if not cat:
|
||||
continue
|
||||
|
||||
else:
|
||||
# False means no valid line found ! But we may not raise an
|
||||
# exception here because it breaks the search
|
||||
price = False
|
||||
if rule.base == -1:
|
||||
if rule.base_pricelist_id:
|
||||
price_tmp = self._price_get_multi(cr, uid,
|
||||
rule.base_pricelist_id, [(product,
|
||||
qty, False)], context=context)[product.id]
|
||||
ptype_src = rule.base_pricelist_id.currency_id.id
|
||||
uom_price_already_computed = True
|
||||
price = currency_obj.compute(cr, uid,
|
||||
ptype_src, pricelist.currency_id.id,
|
||||
price_tmp, round=False,
|
||||
context=context)
|
||||
elif rule.base == -2:
|
||||
for seller in product.seller_ids:
|
||||
if (not partner) or (seller.name.id<>partner):
|
||||
continue
|
||||
product_default_uom = product.uom_id.id
|
||||
seller_uom = seller.product_uom and seller.product_uom.id or False
|
||||
if seller_uom and product_default_uom and product_default_uom != seller_uom:
|
||||
uom_price_already_computed = True
|
||||
qty = product_uom_obj._compute_qty(cr, uid, product_default_uom, qty, to_uom_id=seller_uom)
|
||||
for line in seller.pricelist_ids:
|
||||
if line.min_quantity <= qty:
|
||||
price = line.price
|
||||
|
||||
if price:
|
||||
results['item_id'] = res['id']
|
||||
if 'uom' in context and not uom_price_already_computed:
|
||||
product = products_dict[product_id]
|
||||
uom = product.uos_id or product.uom_id
|
||||
price = product_uom_obj._compute_price(cr, uid, uom.id, price, context['uom'])
|
||||
|
||||
if results.get(product_id):
|
||||
results[product_id][pricelist_id] = price
|
||||
else:
|
||||
results[product_id] = {pricelist_id: price}
|
||||
if rule.base not in price_types:
|
||||
price_types[rule.base] = price_type_obj.browse(cr, uid, int(rule.base))
|
||||
price_type = price_types[rule.base]
|
||||
|
||||
uom_price_already_computed = True
|
||||
price = currency_obj.compute(cr, uid,
|
||||
price_type.currency_id.id, pricelist.currency_id.id,
|
||||
product_obj._price_get(cr, uid, [product],
|
||||
price_type.field, context=context)[product.id], round=False, context=context)
|
||||
|
||||
if price is not False:
|
||||
price_limit = price
|
||||
price = price * (1.0+(rule.price_discount or 0.0))
|
||||
price = rounding(price, rule.price_round) #TOFIX: rounding with tools.float_rouding
|
||||
price += (rule.price_surcharge or 0.0)
|
||||
if rule.price_min_margin:
|
||||
price = max(price, price_limit+rule.price_min_margin)
|
||||
if rule.price_max_margin:
|
||||
price = min(price, price_limit+rule.price_max_margin)
|
||||
break
|
||||
|
||||
if price:
|
||||
# FP Note: what's this?
|
||||
# results['item_id'] = res.id
|
||||
if 'uom' in context and not uom_price_already_computed:
|
||||
product = products_dict[product.id]
|
||||
uom = product.uos_id or product.uom_id
|
||||
price = product_uom_obj._compute_price(cr, uid, uom.id, price, context['uom'])
|
||||
|
||||
results[product.id] = price
|
||||
return results
|
||||
|
||||
def price_get(self, cr, uid, ids, prod_id, qty, partner=None, context=None):
|
||||
res_multi = self.price_get_multi(cr, uid, pricelist_ids=ids, products_by_qty_by_partner=[(prod_id, qty, partner)], context=context)
|
||||
product = self.pool.get('product.product').browse(cr, uid, prod_id, context=context)
|
||||
res_multi = self.price_get_multi(cr, uid, pricelist_ids=ids, products_by_qty_by_partner=[(product, qty, partner)], context=context)
|
||||
res = res_multi[prod_id]
|
||||
res.update({'item_id': {ids[-1]: res_multi.get('item_id', ids[-1])}})
|
||||
return res
|
||||
|
||||
|
||||
|
||||
class product_pricelist_version(osv.osv):
|
||||
_name = "product.pricelist.version"
|
||||
_description = "Pricelist Version"
|
||||
|
||||
@@ -517,6 +517,7 @@ class product_product(osv.osv):
|
||||
return res
|
||||
|
||||
def _product_price(self, cr, uid, ids, name, arg, context=None):
|
||||
plobj = self.pool.get('product.pricelist')
|
||||
res = {}
|
||||
if context is None:
|
||||
context = {}
|
||||
@@ -526,18 +527,16 @@ class product_product(osv.osv):
|
||||
if pricelist:
|
||||
# Support context pricelists specified as display_name or ID for compatibility
|
||||
if isinstance(pricelist, basestring):
|
||||
pricelist_ids = self.pool.get('product.pricelist').name_search(
|
||||
pricelist_ids = plobj.name_search(
|
||||
cr, uid, pricelist, operator='=', context=context, limit=1)
|
||||
pricelist = pricelist_ids[0][0] if pricelist_ids else pricelist
|
||||
|
||||
qtys = map(lambda x: (x, quantity, partner), ids)
|
||||
price = self.pool.get('product.pricelist').price_get_multi(cr,uid, [pricelist],
|
||||
qtys, context=context)
|
||||
products = self.browse(cr, uid, ids, context=context)
|
||||
qtys = map(lambda x: (x, quantity, partner), products)
|
||||
pl = plobj.browse(cr, uid, pricelist, context=context)
|
||||
price = plobj._price_get_multi(cr,uid, pl, qtys, context=context)
|
||||
for id in ids:
|
||||
try:
|
||||
res[id] = price[id][pricelist]
|
||||
except:
|
||||
price = 0.0
|
||||
res[id] = price.get(id, 0.0)
|
||||
for id in ids:
|
||||
res.setdefault(id, 0.0)
|
||||
return res
|
||||
@@ -790,6 +789,10 @@ class product_product(osv.osv):
|
||||
# Could be overrided for variants matrices prices
|
||||
#
|
||||
def price_get(self, cr, uid, ids, ptype='list_price', context=None):
|
||||
products = self.browse(cr, uid, ids, context=context)
|
||||
return self._price_get(cr, uid, products, ptype=ptype, context=context)
|
||||
|
||||
def _price_get(self, cr, uid, products, ptype='list_price', context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
@@ -800,7 +803,7 @@ class product_product(osv.osv):
|
||||
|
||||
res = {}
|
||||
product_uom_obj = self.pool.get('product.uom')
|
||||
for product in self.browse(cr, uid, ids, context=context):
|
||||
for product in products:
|
||||
res[product.id] = product[ptype] or 0.0
|
||||
if ptype == 'list_price':
|
||||
res[product.id] = (res[product.id] * (product.price_margin or 1.0)) + \
|
||||
|
||||
@@ -149,6 +149,7 @@ parameter) will see those record just disappear.
|
||||
<record id="item0" model="product.pricelist.item">
|
||||
<field name="price_version_id" ref="ver0"/>
|
||||
<field name="base" ref="list_price"/>
|
||||
<field name="sequence">1000</field>
|
||||
<field name="name">Default Public Pricelist Line</field>
|
||||
</record>
|
||||
|
||||
|
||||
@@ -6,19 +6,19 @@
|
||||
!python {model: product.product}: |
|
||||
context.update({'pricelist': ref("customer_pricelist"), 'quantity':1})
|
||||
product = self.browse(cr, uid, ref("product_product_4"), context=context)
|
||||
assert product.price == (product.lst_price-product.lst_price*(0.10)), "Sell price is not correspond."
|
||||
assert product.price == (product.lst_price-product.lst_price*(0.10)), "Wrong sale price."
|
||||
-
|
||||
I check sale price of Laptop.
|
||||
-
|
||||
!python {model: product.product}: |
|
||||
product = self.browse(cr, uid, ref("product_product_25"), context=context)
|
||||
assert product.price == product.lst_price + 1, "Sell price is not correspond."
|
||||
assert product.price == product.lst_price + 1, "Wrong sale price."
|
||||
-
|
||||
I check sale price of IT component.
|
||||
-
|
||||
!python {model: product.product}: |
|
||||
product = self.browse(cr, uid, ref("product_product_7"), context=context)
|
||||
assert product.price == product.lst_price, "Sell price is not correspond."
|
||||
assert product.price == product.lst_price, "Wrong sale price."
|
||||
|
||||
-
|
||||
I check sale price of IT component if more than 3 Unit.
|
||||
@@ -26,14 +26,14 @@
|
||||
!python {model: product.product}: |
|
||||
context.update({'quantity':5})
|
||||
product = self.browse(cr, uid, ref("product_product_26"), context=context)
|
||||
assert product.price == product.lst_price-product.lst_price*(0.05), "Sell price is not correspond."
|
||||
assert product.price == product.lst_price-product.lst_price*(0.05), "Wrong sale price."
|
||||
-
|
||||
I check sale price of LCD Monitor.
|
||||
-
|
||||
!python {model: product.product}: |
|
||||
context.update({'quantity':1})
|
||||
product = self.browse(cr, uid, ref("product_product_6"), context=context)
|
||||
assert product.price == product.lst_price, "Sell price is not correspond."
|
||||
assert product.price == product.lst_price, "Wrong sale price."
|
||||
|
||||
-
|
||||
I check sale price of LCD Monitor on end of year.
|
||||
@@ -41,7 +41,7 @@
|
||||
!python {model: product.product}: |
|
||||
context.update({'quantity':1, 'date': '2011-12-31'})
|
||||
product = self.browse(cr, uid, ref("product_product_6"), context=context)
|
||||
assert product.price == product.lst_price-product.lst_price*(0.30), "Sell price is not correspond."
|
||||
assert product.price == product.lst_price-product.lst_price*(0.30), "Wrong sale price."
|
||||
|
||||
-
|
||||
I check cost price of LCD Monitor.
|
||||
@@ -49,14 +49,14 @@
|
||||
!python {model: product.product}: |
|
||||
context.update({'quantity':1, 'date': False, 'partner': ref('base.res_partner_4'), 'pricelist': ref("supplier_pricelist")})
|
||||
product = self.browse(cr, uid, ref("product_product_6"), context=context)
|
||||
assert product.price == 792, "cost price is not correspond."
|
||||
assert product.price == 792, "wrong cost price."
|
||||
-
|
||||
I check cost price of LCD Monitor if more than 3 Unit.
|
||||
-
|
||||
!python {model: product.product}: |
|
||||
context.update({'quantity':3})
|
||||
product = self.browse(cr, uid, ref("product_product_6"), context=context)
|
||||
assert product.price == 787, "cost price is not correspond."
|
||||
assert product.price == 787, "wrong cost price."
|
||||
|
||||
-
|
||||
I print the sale prices report.
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
##############################################################################
|
||||
|
||||
from openerp.addons.project.tests.test_project_base import TestProjectBase
|
||||
from openerp.osv.orm import except_orm
|
||||
from openerp.exceptions import AccessError
|
||||
from openerp.tools import mute_logger
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ class TestProjectFlow(TestProjectBase):
|
||||
cr, uid, user_projectuser_id, user_projectmanager_id, project_pigs_id = self.cr, self.uid, self.user_projectuser_id, self.user_projectmanager_id, self.project_pigs_id
|
||||
|
||||
# ProjectUser: set project as template -> raise
|
||||
self.assertRaises(except_orm, self.project_project.set_template, cr, user_projectuser_id, [project_pigs_id])
|
||||
self.assertRaises(AccessError, self.project_project.set_template, cr, user_projectuser_id, [project_pigs_id])
|
||||
|
||||
# Other tests are done using a ProjectManager
|
||||
project = self.project_project.browse(cr, user_projectmanager_id, project_pigs_id)
|
||||
|
||||
@@ -21,6 +21,7 @@ from lxml import etree, html
|
||||
from PIL import Image as I
|
||||
import openerp.modules
|
||||
|
||||
import openerp
|
||||
from openerp.osv import orm, fields
|
||||
from openerp.tools import ustr, DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
|
||||
from openerp.addons.web.http import request
|
||||
@@ -197,6 +198,7 @@ class ManyToOne(orm.AbstractModel):
|
||||
matches = self.pool[column._obj].name_search(
|
||||
cr, uid, name=element.text_content().strip(), context=context)
|
||||
# FIXME: no match? More than 1 match?
|
||||
print element.text_content().strip()
|
||||
assert len(matches) == 1
|
||||
return matches[0][0]
|
||||
|
||||
@@ -349,3 +351,43 @@ class RelativeDatetime(orm.AbstractModel):
|
||||
]
|
||||
|
||||
# get formatting from ir.qweb.field.relative but edition/save from datetime
|
||||
|
||||
|
||||
class Contact(orm.AbstractModel):
|
||||
_name = 'website.qweb.field.contact'
|
||||
_inherit = ['website.qweb.field', 'website.qweb.field.many2one']
|
||||
|
||||
def from_html(self, cr, uid, model, column, element, context=None):
|
||||
# FIXME: this behavior is really weird, what if the user wanted to edit the name of the related thingy? Should m2os really be editable without a widget?
|
||||
divs = element.xpath(".//div")
|
||||
for div in divs:
|
||||
if div != divs[0]:
|
||||
div.getparent().remove(div)
|
||||
return super(Contact, self).from_html(cr, uid, model, column, element, context=context)
|
||||
|
||||
def record_to_html(self, cr, uid, field_name, record, column, options=None, context=None):
|
||||
opf = options.get('fields') or ["name", "address", "phone", "mobile", "fax", "email"]
|
||||
|
||||
if not getattr(record, field_name):
|
||||
return None
|
||||
|
||||
id = getattr(record, field_name).id
|
||||
field_browse = self.pool[column._obj].browse(cr, openerp.SUPERUSER_ID, id, context={"show_address": True})
|
||||
value = werkzeug.utils.escape( field_browse.name_get()[0][1] )
|
||||
|
||||
IMD = self.pool["ir.model.data"]
|
||||
model, id = IMD.get_object_reference(cr, uid, "website", "contact")
|
||||
view = self.pool["ir.ui.view"].browse(cr, uid, id, context=context)
|
||||
|
||||
html = view.render({
|
||||
'name': value.split("\n")[0],
|
||||
'address': werkzeug.utils.escape("\n".join(value.split("\n")[1:])),
|
||||
'phone': field_browse.phone,
|
||||
'mobile': field_browse.mobile,
|
||||
'fax': field_browse.fax,
|
||||
'email': field_browse.email,
|
||||
'fields': opf,
|
||||
'options': options
|
||||
}, engine='website.qweb', context=context)
|
||||
|
||||
return html
|
||||
|
||||
@@ -496,3 +496,10 @@ table.well tr td {
|
||||
.oe_demo div a {
|
||||
color: white;
|
||||
}
|
||||
|
||||
address .fa.fa-mobile-phone {
|
||||
margin: 0 3px 0 2px;
|
||||
}
|
||||
address .fa.fa-file-text-o {
|
||||
margin-right: 1px;
|
||||
}
|
||||
|
||||
@@ -372,3 +372,9 @@ table.well tr
|
||||
font-weight: bold
|
||||
a
|
||||
color: white
|
||||
|
||||
address
|
||||
.fa.fa-mobile-phone
|
||||
margin: 0 3px 0 2px
|
||||
.fa.fa-file-text-o
|
||||
margin-right: 1px
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
self.snippets.$button.removeClass("hidden");
|
||||
website.snippet.stop_animation();
|
||||
website.snippet.start_animation();
|
||||
self.trigger('tour:editor_bar_loaded');
|
||||
});
|
||||
|
||||
return this._super.apply(this, arguments);
|
||||
@@ -207,17 +206,17 @@
|
||||
self.make_active($target);
|
||||
});
|
||||
},
|
||||
snippet_blur: function ($snipped_id) {
|
||||
if ($snipped_id) {
|
||||
if ($snipped_id.data("snippet-editor")) {
|
||||
$snipped_id.data("snippet-editor").onBlur();
|
||||
snippet_blur: function ($snippet) {
|
||||
if ($snippet) {
|
||||
if ($snippet.data("snippet-editor")) {
|
||||
$snippet.data("snippet-editor").onBlur();
|
||||
}
|
||||
}
|
||||
},
|
||||
snippet_focus: function ($snipped_id) {
|
||||
if ($snipped_id) {
|
||||
if ($snipped_id.data("snippet-editor")) {
|
||||
$snipped_id.data("snippet-editor").onFocus();
|
||||
snippet_focus: function ($snippet) {
|
||||
if ($snippet) {
|
||||
if ($snippet.data("snippet-editor")) {
|
||||
$snippet.data("snippet-editor").onFocus();
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -230,31 +229,32 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
make_active: function ($snipped_id) {
|
||||
if ($snipped_id && this.$active_snipped_id && this.$active_snipped_id.get(0) === $snipped_id.get(0)) {
|
||||
make_active: function ($snippet) {
|
||||
if ($snippet && this.$active_snipped_id && this.$active_snipped_id.get(0) === $snippet.get(0)) {
|
||||
return;
|
||||
}
|
||||
if (this.$active_snipped_id) {
|
||||
this.snippet_blur(this.$active_snipped_id);
|
||||
this.$active_snipped_id = false;
|
||||
}
|
||||
if ($snipped_id && $snipped_id.length) {
|
||||
if(_.indexOf(this.snippets, $snipped_id.get(0)) === -1) {
|
||||
this.snippets.push($snipped_id.get(0));
|
||||
if ($snippet && $snippet.length) {
|
||||
if(_.indexOf(this.snippets, $snippet.get(0)) === -1) {
|
||||
this.snippets.push($snippet.get(0));
|
||||
}
|
||||
this.$active_snipped_id = $snipped_id;
|
||||
this.$active_snipped_id = $snippet;
|
||||
this.create_overlay(this.$active_snipped_id);
|
||||
this.snippet_focus($snipped_id);
|
||||
this.snippet_focus($snippet);
|
||||
}
|
||||
$("#oe_snippets").trigger('snippet-activated', $snippet);
|
||||
},
|
||||
create_overlay: function ($snipped_id) {
|
||||
if (typeof $snipped_id.data("snippet-editor") === 'undefined') {
|
||||
var $targets = this.activate_overlay_zones($snipped_id);
|
||||
create_overlay: function ($snippet) {
|
||||
if (typeof $snippet.data("snippet-editor") === 'undefined') {
|
||||
var $targets = this.activate_overlay_zones($snippet);
|
||||
if (!$targets.length) return;
|
||||
var editor = website.snippet.editorRegistry[$snipped_id.data("snippet-id")] || website.snippet.editorRegistry.resize;
|
||||
$snipped_id.data("snippet-editor", new editor(this, $snipped_id));
|
||||
var editor = website.snippet.editorRegistry[$snippet.data("snippet-id")] || website.snippet.editorRegistry.resize;
|
||||
$snippet.data("snippet-editor", new editor(this, $snippet));
|
||||
}
|
||||
this.cover_target($snipped_id.data('overlay'), $snipped_id);
|
||||
this.cover_target($snippet.data('overlay'), $snippet);
|
||||
},
|
||||
|
||||
path_eval: function(path){
|
||||
@@ -305,6 +305,7 @@
|
||||
|
||||
$toInsert = $snippet.find('.oe_snippet_body').clone();
|
||||
$toInsert.removeClass('oe_snippet_body');
|
||||
$toInsert.data('src-snippet-id', snipped_id);
|
||||
if (!$toInsert.data('snippet-id')) {
|
||||
$toInsert.attr('data-snippet-id', snipped_id);
|
||||
} else {
|
||||
@@ -381,7 +382,10 @@
|
||||
snippet.drop_and_build_snippet($target);
|
||||
}
|
||||
}
|
||||
setTimeout(function () {self.make_active($target);},0);
|
||||
setTimeout(function () {
|
||||
$("#oe_snippets").trigger('snippet-dropped', $target);
|
||||
self.make_active($target);
|
||||
},0);
|
||||
} else {
|
||||
$toInsert.remove();
|
||||
if (self.$modal.find('input:not(:checked)').length) {
|
||||
@@ -869,7 +873,7 @@
|
||||
var editor = new Editor(self, self.$target, val['snippet-style-id']);
|
||||
$ul.prepend(editor.$el);
|
||||
});
|
||||
|
||||
|
||||
if ($ul.find("li").length) {
|
||||
$styles.removeClass("hidden");
|
||||
}
|
||||
@@ -1149,7 +1153,7 @@
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
website.snippet.editorRegistry.slider = website.snippet.editorRegistry.resize.extend({
|
||||
drop_and_build_snippet: function() {
|
||||
var id = $(".carousel").length;
|
||||
@@ -1224,7 +1228,7 @@
|
||||
var new_index = 0;
|
||||
var cycle = this.$inner.find('.item').length - 1;
|
||||
var index = this.$inner.find('.item.active').index();
|
||||
|
||||
|
||||
if (cycle > 0) {
|
||||
this.remove_process = true;
|
||||
var $el = this.$inner.find('.item.active');
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
var tests = {};
|
||||
var droptest = function () {
|
||||
var errors = [];
|
||||
for (var snippet_id in tests) {
|
||||
if (!tests[snippet_id]['activated'] || !tests[snippet_id]['dropped']){
|
||||
console.log("Can't dropped or activated snippet: " + snippet_id);
|
||||
}
|
||||
}
|
||||
if (errors.length) {
|
||||
console.log(tests);
|
||||
throw new Error("Can't dropped or activated at least one snippet");
|
||||
}
|
||||
$("#oe_snippets").off('snippet-activated snippet-dropped');
|
||||
};
|
||||
var droptesttime = setTimeout(droptest,0);
|
||||
|
||||
$("#oe_snippets").off('snippet-activated snippet-dropped')
|
||||
.on('snippet-activated', function (event, dom) {
|
||||
tests[$(dom).data('src-snippet-id')]['activated'] = true;
|
||||
clearTimeout(droptesttime);
|
||||
droptesttime = setTimeout(droptest,0);
|
||||
})
|
||||
.on('snippet-dropped', function (event, dom, src_snipped_id) {
|
||||
tests[$(dom).data('src-snippet-id')]['dropped'] = true;
|
||||
clearTimeout(droptesttime);
|
||||
droptesttime = setTimeout(droptest,0);
|
||||
});
|
||||
var $thumbnails = $('#oe_snippets div.oe_snippet[data-snippet-id] .oe_snippet_thumbnail');
|
||||
$thumbnails.each(function () {
|
||||
var $thumbnail = $(this);
|
||||
tests[$thumbnail.parent().data('snippet-id')] = {};
|
||||
var position = $thumbnail.position();
|
||||
$thumbnail.trigger( $.Event( "mousedown", { which: 1, pageX: position.left, pageY: position.top } ) );
|
||||
$thumbnail.trigger( $.Event( "mousemove", { which: 1, pageX: position.left+100, pageY: position.top+100 } ) );
|
||||
$first_drop = $(".oe_drop_zone").first();
|
||||
position = $first_drop.position();
|
||||
$first_drop.trigger( $.Event( "mouseup", { which: 1, pageX: position.left+20, pageY: position.top+20 } ) );
|
||||
clearTimeout(droptesttime);
|
||||
droptesttime = setTimeout(droptest,0);
|
||||
});
|
||||
@@ -11,7 +11,7 @@
|
||||
});
|
||||
|
||||
website.BannerTour = website.Tour.extend({
|
||||
id: 'banner-tutorial',
|
||||
id: 'banner',
|
||||
name: "Insert a banner",
|
||||
startPath: '/page/website.homepage',
|
||||
init: function (editor) {
|
||||
@@ -19,11 +19,10 @@
|
||||
self.steps = [
|
||||
{
|
||||
stepId: 'welcome',
|
||||
orphan: true,
|
||||
backdrop: true,
|
||||
title: "Welcome to your website!",
|
||||
content: "This tutorial will guide you to build your home page. We will start by adding a banner.",
|
||||
template: self.popover({ next: "Start Tutorial", end: "Skip It" }),
|
||||
backdrop: true,
|
||||
},
|
||||
{
|
||||
stepId: 'edit-page',
|
||||
@@ -31,8 +30,10 @@
|
||||
placement: 'bottom',
|
||||
title: "Edit this page",
|
||||
content: "Every page of your website can be modified through the <i>Edit</i> button.",
|
||||
triggers: function () {
|
||||
editor.on('tour:editor_bar_loaded', self, self.moveToNextStep);
|
||||
trigger: {
|
||||
emitter: editor,
|
||||
type: 'openerp',
|
||||
id: 'rte:ready',
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -41,11 +42,7 @@
|
||||
placement: 'bottom',
|
||||
title: "Insert building blocks",
|
||||
content: "To add content in a page, you can insert building blocks.",
|
||||
triggers: function () {
|
||||
$('button[data-action=snippet]').one('click', function () {
|
||||
self.moveToNextStep();
|
||||
});
|
||||
}
|
||||
trigger: 'click',
|
||||
},
|
||||
{
|
||||
stepId: 'drag-banner',
|
||||
@@ -53,9 +50,7 @@
|
||||
placement: 'bottom',
|
||||
title: "Drag & Drop a Banner",
|
||||
content: "Drag the Banner block and drop it in your page.",
|
||||
triggers: function () {
|
||||
self.onSnippetDraggedAdvance();
|
||||
},
|
||||
trigger: 'drag',
|
||||
},
|
||||
{
|
||||
stepId: 'edit-title',
|
||||
@@ -64,7 +59,7 @@
|
||||
title: "Customize banner's text",
|
||||
content: "Click in the text and start editing it. Click continue once it's done.",
|
||||
template: self.popover({ next: "Continue" }),
|
||||
triggers: function () {
|
||||
onStart: function () {
|
||||
var $banner = $("#wrap [data-snippet-id=carousel]:first");
|
||||
if ($banner.length) {
|
||||
$banner.click();
|
||||
@@ -85,11 +80,7 @@
|
||||
placement: 'bottom',
|
||||
title: "Add Another Block",
|
||||
content: "Let's add another building block to your page.",
|
||||
triggers: function () {
|
||||
$('button[data-action=snippet]').one('click', function () {
|
||||
self.moveToNextStep();
|
||||
});
|
||||
}
|
||||
trigger: 'click',
|
||||
},
|
||||
{
|
||||
stepId: 'drag-three-columns',
|
||||
@@ -97,9 +88,7 @@
|
||||
placement: 'bottom',
|
||||
title: "Drag & Drop a Block",
|
||||
content: "Drag the <em>'3 Columns'</em> block and drop it below the banner.",
|
||||
triggers: function () {
|
||||
self.onSnippetDraggedAdvance();
|
||||
},
|
||||
trigger: 'drag',
|
||||
},
|
||||
{
|
||||
stepId: 'activate-text-block-title',
|
||||
@@ -107,31 +96,28 @@
|
||||
placement: 'top',
|
||||
title: "Edit an Area",
|
||||
content: "Select any area of the page to modify it. Click on this subtitle.",
|
||||
triggers: function () {
|
||||
$('#wrap [data-snippet-id=three-columns] .text-center[data-snippet-id=colmd]').one('click', function () {
|
||||
self.moveToNextStep();
|
||||
});
|
||||
trigger: {
|
||||
id: 'snippet-activated'
|
||||
},
|
||||
},
|
||||
{
|
||||
stepId: 'remove-text-block-title',
|
||||
element: '.ui-draggable.oe_active .oe_snippet_remove',
|
||||
placement: 'top',
|
||||
reflex: true,
|
||||
title: "Delete the Title",
|
||||
content: "From this toolbar you can move, duplicate or delete the selected zone. Click on the trash can to delete the title.",
|
||||
trigger: 'click',
|
||||
},
|
||||
{
|
||||
stepId: 'save-changes',
|
||||
element: 'button[data-action=save]',
|
||||
placement: 'right',
|
||||
reflex: true,
|
||||
title: "Save your modifications",
|
||||
content: "Publish your page by clicking on the <em>'Save'</em> button.",
|
||||
trigger: 'click',
|
||||
},
|
||||
{
|
||||
stepId: 'part-2',
|
||||
orphan: true,
|
||||
title: "Congratulation!",
|
||||
content: "Your homepage has been updated.",
|
||||
template: self.popover({ next: "Continue" }),
|
||||
@@ -142,35 +128,31 @@
|
||||
placement: 'bottom',
|
||||
title: "Test Your Mobile Version",
|
||||
content: "Let's check how your homepage looks like on mobile devices.",
|
||||
triggers: function () {
|
||||
$(document).one('shown.bs.modal', function () {
|
||||
self.moveToNextStep();
|
||||
});
|
||||
}
|
||||
trigger: {
|
||||
emitter: $(document),
|
||||
id: 'shown.bs.modal',
|
||||
},
|
||||
},
|
||||
{
|
||||
stepId: 'show-mobile-close',
|
||||
element: 'button[data-dismiss=modal]',
|
||||
placement: 'right',
|
||||
reflex: true,
|
||||
title: "Close Mobile Preview",
|
||||
content: "Scroll in the mobile preview to test the rendering. Once it's ok, close this dialog.",
|
||||
trigger: 'click',
|
||||
},
|
||||
{
|
||||
stepId: 'show-tutorials',
|
||||
element: '#help-menu-button',
|
||||
placement: 'left',
|
||||
reflex: true,
|
||||
title: "More Tutorials",
|
||||
content: "Get more tutorials through this <em>'Help'</em> menu or click on the left <em>'Edit'</em> button to continue modifying this page.",
|
||||
template: self.popover({ end: "Close Tutorial" }),
|
||||
trigger: 'click',
|
||||
}
|
||||
];
|
||||
return this._super();
|
||||
},
|
||||
resume: function () {
|
||||
return this.isCurrentStep('part-2') && this._super();
|
||||
},
|
||||
});
|
||||
|
||||
}());
|
||||
|
||||
@@ -14,49 +14,76 @@
|
||||
storage: this.tourStorage,
|
||||
keyboard: false,
|
||||
template: this.popover(),
|
||||
onHide: function () {
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
});
|
||||
this.registerSteps();
|
||||
},
|
||||
registerSteps: function () {
|
||||
var self = this;
|
||||
this.tour.addSteps(_.map(this.steps, function (step) {
|
||||
step.title = openerp.qweb.render('website.tour_popover_title', { title: step.title });
|
||||
if (step.modal) {
|
||||
step.onShow = function () {
|
||||
var $doc = $(document);
|
||||
function onStop () {
|
||||
if (step.modal.stopOnClose) {
|
||||
self.stop();
|
||||
}
|
||||
}
|
||||
$doc.on('hide.bs.modal', onStop);
|
||||
$doc.one('shown.bs.modal', function () {
|
||||
$('.modal button.btn-primary').one('click', function () {
|
||||
$doc.off('hide.bs.modal', onStop);
|
||||
self.moveToStep(step.modal.afterSubmit);
|
||||
step.title = openerp.qweb.render('website.tour_popover_title', { title: step.title });
|
||||
if (!step.element) {
|
||||
step.orphan = true;
|
||||
}
|
||||
if (step.trigger) {
|
||||
if (step.trigger === 'click') {
|
||||
step.triggers = function (callback) {
|
||||
$(step.element).one('click', function () {
|
||||
(callback || self.moveToNextStep).apply(self);
|
||||
});
|
||||
self.moveToNextStep();
|
||||
});
|
||||
};
|
||||
} else if (step.trigger === 'drag') {
|
||||
step.triggers = function (callback) {
|
||||
self.onSnippetDragged(callback || self.moveToNextStep);
|
||||
};
|
||||
} else if (step.trigger && step.trigger.id) {
|
||||
if (step.trigger.emitter && step.trigger.type === 'openerp') {
|
||||
step.triggers = function (callback) {
|
||||
step.trigger.emitter.on(step.trigger.id, self, function customHandler () {
|
||||
step.trigger.emitter.off(step.trigger.id, customHandler);
|
||||
(callback || self.moveToNextStep).apply(self, arguments);
|
||||
});
|
||||
};
|
||||
} else {
|
||||
step.triggers = function (callback) {
|
||||
var emitter = step.trigger.emitter || $(step.element);
|
||||
emitter.one(step.trigger.id, function customHandler () {
|
||||
(callback || self.moveToNextStep).apply(self, arguments);
|
||||
});
|
||||
};
|
||||
}
|
||||
} else if (step.trigger.modal) {
|
||||
step.triggers = function (callback) {
|
||||
var $doc = $(document);
|
||||
function onStop () {
|
||||
if (step.trigger.modal.stopOnClose) {
|
||||
self.stop();
|
||||
}
|
||||
}
|
||||
$doc.on('hide.bs.modal', onStop);
|
||||
$doc.one('shown.bs.modal', function () {
|
||||
$('.modal button.btn-primary').one('click', function () {
|
||||
$doc.off('hide.bs.modal', onStop);
|
||||
self.moveToStep(step.trigger.modal.afterSubmit);
|
||||
});
|
||||
(callback || self.moveToNextStep).apply(self);
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
step.onShow = (function () {
|
||||
var executed = false;
|
||||
return function () {
|
||||
if (!executed) {
|
||||
_.isFunction(step.onStart) && step.onStart();
|
||||
_.isFunction(step.triggers) && step.triggers();
|
||||
executed = true;
|
||||
}
|
||||
};
|
||||
} else if (step.triggers) {
|
||||
step.onShow = (function () {
|
||||
var executed = false;
|
||||
return function () {
|
||||
if (!executed) {
|
||||
step.triggers();
|
||||
executed = true;
|
||||
}
|
||||
};
|
||||
}());
|
||||
} else if (step.reflex) {
|
||||
step.onShow = function () {
|
||||
$(step.element).one('click', function () {
|
||||
self.moveToNextStep();
|
||||
});
|
||||
};
|
||||
delete step.reflex;
|
||||
}
|
||||
return step;
|
||||
}());
|
||||
return step;
|
||||
}));
|
||||
},
|
||||
reset: function () {
|
||||
@@ -108,7 +135,7 @@
|
||||
redirect: function (url) {
|
||||
url = url || new website.UrlParser(window.location.href);
|
||||
var path = (this.startPath && url.pathname !== this.startPath) ? this.startPath : url.pathname;
|
||||
var search = url.addToSearch(this.id + "=true");
|
||||
var search = url.activateTutorial(this.id);
|
||||
var newUrl = path + search;
|
||||
window.location.replace(newUrl);
|
||||
},
|
||||
@@ -122,8 +149,7 @@
|
||||
trigger: function (url) {
|
||||
// Override if necessary
|
||||
url = url || new website.UrlParser(window.location.href);
|
||||
var urlTrigger = this.id + "=true";
|
||||
return url.search.indexOf(urlTrigger) >= 0;
|
||||
return url.isActive(this.id);
|
||||
},
|
||||
testUrl: function (pattern) {
|
||||
var url = new website.UrlParser(window.location.href);
|
||||
@@ -132,21 +158,22 @@
|
||||
popover: function (options) {
|
||||
return openerp.qweb.render('website.tour_popover', options);
|
||||
},
|
||||
onSnippetDraggedAdvance: function (snippetId, stepId) {
|
||||
onSnippetDragged: function (callback) {
|
||||
var self = this;
|
||||
function beginDrag () {
|
||||
$('.popover.tour').remove();
|
||||
function advance () {
|
||||
if (stepId) {
|
||||
self.moveToStep(stepId);
|
||||
} else {
|
||||
self.moveToNextStep();
|
||||
if (_.isFunction(callback)) {
|
||||
callback.call(self);
|
||||
}
|
||||
}
|
||||
$(document.body).one('mouseup', advance);
|
||||
}
|
||||
$('#website-top-navbar [data-snippet-id].ui-draggable').one('mousedown', beginDrag);
|
||||
},
|
||||
onSnippetDraggedAdvance: function () {
|
||||
onSnippetDragged(self.moveToNextStep);
|
||||
},
|
||||
});
|
||||
|
||||
website.UrlParser = openerp.Class.extend({
|
||||
@@ -162,21 +189,35 @@
|
||||
this.origin = a.origin;
|
||||
this.search = a.search;
|
||||
this.hash = a.hash;
|
||||
this.addToSearch= function (query) {
|
||||
function generateTrigger (id) {
|
||||
return "tutorial."+id+"=true";
|
||||
}
|
||||
this.activateTutorial = function (id) {
|
||||
var urlTrigger = generateTrigger(id);
|
||||
var querystring = _.filter(this.search.split('?'), function (str) {
|
||||
return str;
|
||||
});
|
||||
if (querystring.length > 0) {
|
||||
var queries = querystring[0].split("&");
|
||||
queries.push(query);
|
||||
return "?" + _.uniq(queries).join("&");
|
||||
var queries = _.filter(querystring[0].split("&"), function (query) {
|
||||
return query.indexOf("tutorial.") < 0
|
||||
});
|
||||
queries.push(urlTrigger);
|
||||
return "?"+_.uniq(queries).join("&");
|
||||
} else {
|
||||
return "?"+query;
|
||||
return "?"+urlTrigger;
|
||||
}
|
||||
};
|
||||
this.isActive = function (id) {
|
||||
var urlTrigger = generateTrigger(id);
|
||||
return this.search.indexOf(urlTrigger) >= 0;
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
var TestConsole = website.TestConsole = {
|
||||
tests: []
|
||||
};
|
||||
|
||||
website.EditorBar.include({
|
||||
tours: [],
|
||||
start: function () {
|
||||
@@ -202,6 +243,24 @@
|
||||
},
|
||||
registerTour: function (tour) {
|
||||
this.tours.push(tour);
|
||||
TestConsole.tests.push({
|
||||
id: tour.id,
|
||||
run: function runTest () {
|
||||
var actionSteps = _.filter(tour.steps, function (step) {
|
||||
return step.triggers;
|
||||
});
|
||||
var currentIndex = 0;
|
||||
function executeStep (step) {
|
||||
var $element = $(step.element);
|
||||
step.triggers(function () {
|
||||
currentIndex = currentIndex + 1;
|
||||
executeStep(actionSteps[currentIndex]);
|
||||
});
|
||||
$element.click();
|
||||
}
|
||||
executeStep(actionSteps[currentIndex]);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -705,5 +705,20 @@ Sitemap: <t t-esc="url_root"/>sitemap.xml
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="contact">
|
||||
<address t-ignore="true">
|
||||
<div t-att-class='"" if "name" in fields else "css_non_editable_mode_hidden"'><span t-esc="name"/></div>
|
||||
<div class='css_editable_mode_hidden'>
|
||||
<div t-if="address and 'address' in fields">
|
||||
<i class='fa fa-map-marker'/>
|
||||
<span t-raw="address.replace('\n', options.get('no_tag_br') and ', ' or '<br/> &nbsp; &nbsp; ')"/>
|
||||
</div>
|
||||
<div t-if="phone and 'phone' in fields"><i class='fa fa-phone'/> <span t-esc="phone"/></div>
|
||||
<div t-if="mobile and 'mobile' in fields"><i class='fa fa-mobile-phone'/> <span t-esc="mobile"/></div>
|
||||
<div t-if="fax and 'fax' in fields"><i class='fa fa-file-text-o'/> <span t-esc="fax"/></div>
|
||||
<div t-if="email and 'email' in fields"><i class='fa fa-envelope'/> <span t-esc="email"/></div>
|
||||
</div>
|
||||
</address>
|
||||
</template>
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
@@ -154,7 +154,7 @@ class WebsiteBlog(http.Controller):
|
||||
@website.route([
|
||||
'/blogpost/<model("blog.post"):blog_post>/',
|
||||
], type='http', auth="public", multilang=True)
|
||||
def blog_post(self, blog_post=None, tag=None, date=None, page=1, enable_editor=None):
|
||||
def blog_post(self, blog_post, tag=None, date=None, page=1, enable_editor=None, **post):
|
||||
""" Prepare all values to display the blog.
|
||||
|
||||
:param blog_post: blog post currently browsed. If not set, the user is
|
||||
|
||||
@@ -11,26 +11,25 @@
|
||||
});
|
||||
|
||||
website.BlogTour = website.Tour.extend({
|
||||
id: 'blog-tutorial',
|
||||
id: 'blog',
|
||||
name: "Create a blog post",
|
||||
init: function (editor) {
|
||||
var self = this;
|
||||
self.steps = [
|
||||
{
|
||||
stepId: 'welcome-blog',
|
||||
orphan: true,
|
||||
backdrop: true,
|
||||
title: "New Blog Post",
|
||||
content: "Let's go through the first steps to write beautiful blog posts.",
|
||||
template: self.popover({ next: "Start Tutorial", end: "Skip" }),
|
||||
backdrop: true,
|
||||
},
|
||||
{
|
||||
stepId: 'content-menu',
|
||||
element: '#content-menu-button',
|
||||
placement: 'left',
|
||||
reflex: true,
|
||||
title: "Add Content",
|
||||
content: "Create new pages, blogs, menu items and products through the <em>'Content'</em> menu.",
|
||||
trigger: 'click',
|
||||
},
|
||||
{
|
||||
stepId: 'new-post-entry',
|
||||
@@ -38,9 +37,11 @@
|
||||
placement: 'left',
|
||||
title: "New Blog Post",
|
||||
content: "Select this entry to create a new blog post.",
|
||||
modal: {
|
||||
stopOnClose: true,
|
||||
afterSubmit: 'post-page',
|
||||
trigger: {
|
||||
modal: {
|
||||
stopOnClose: true,
|
||||
afterSubmit: 'post-page',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -49,15 +50,12 @@
|
||||
placement: 'right',
|
||||
title: "Which Blog?",
|
||||
content: "Blog posts are organized in multiple categories (news, job offers, events, etc). Select <em>News</em> and click <em>Continue</em>.",
|
||||
triggers: function () {
|
||||
function newsSelected () {
|
||||
var $this = $(this);
|
||||
if ($this.find('[value='+$this.val()+']').text().toLowerCase() === 'news') {
|
||||
self.moveToNextStep();
|
||||
$('.modal select').off('change', newsSelected);
|
||||
}
|
||||
}
|
||||
$('.modal select').on('change', newsSelected);
|
||||
trigger: {
|
||||
emitter: $('.modal select'),
|
||||
id: 'change',
|
||||
predicate: function ($el, value) {
|
||||
return $el.find('[value='+value+']').text().toLowerCase() === 'news';
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -69,7 +67,6 @@
|
||||
},
|
||||
{
|
||||
stepId: 'post-page',
|
||||
orphan: true,
|
||||
title: "Blog Post Created",
|
||||
content: "This is your new blog post. We will edit your pages inline. What You See Is What You Get. No need for a complex backend.",
|
||||
template: self.popover({ next: "Continue" }),
|
||||
@@ -88,11 +85,7 @@
|
||||
placement: 'bottom',
|
||||
title: "Layout Your Blog Post",
|
||||
content: "Use well designed building blocks to structure the content of your blog. Click 'Insert Blocks' to add new content.",
|
||||
triggers: function () {
|
||||
$('button[data-action=snippet]').one('click', function () {
|
||||
self.moveToNextStep();
|
||||
});
|
||||
},
|
||||
trigger: 'click',
|
||||
},
|
||||
{
|
||||
stepId: 'drag-image-text',
|
||||
@@ -100,9 +93,7 @@
|
||||
placement: 'bottom',
|
||||
title: "Drag & Drop a Block",
|
||||
content: "Drag the <em>'Image-Text'</em> block and drop it in your page.",
|
||||
triggers: function () {
|
||||
self.onSnippetDraggedAdvance();
|
||||
},
|
||||
trigger: 'drag',
|
||||
},
|
||||
{
|
||||
stepId: 'add-text-block',
|
||||
@@ -110,11 +101,7 @@
|
||||
placement: 'bottom',
|
||||
title: "Add Another Block",
|
||||
content: "Let's add another block to your post.",
|
||||
triggers: function () {
|
||||
$('button[data-action=snippet]').on('click', function () {
|
||||
self.moveToNextStep();
|
||||
});
|
||||
}
|
||||
trigger: 'click',
|
||||
},
|
||||
{
|
||||
stepId: 'drag-text-block',
|
||||
@@ -122,9 +109,7 @@
|
||||
placement: 'bottom',
|
||||
title: "Drag & Drop a block",
|
||||
content: "Drag the <em>'Text Block'</em> block and drop it below the image block.",
|
||||
triggers: function () {
|
||||
self.onSnippetDraggedAdvance();
|
||||
},
|
||||
trigger: 'drag',
|
||||
},
|
||||
{
|
||||
stepId: 'activate-text-block-title',
|
||||
@@ -132,19 +117,15 @@
|
||||
placement: 'top',
|
||||
title: "Edit an Area",
|
||||
content: "Select any area of the page to modify it. Click on this subtitle.",
|
||||
triggers: function () {
|
||||
$('#wrap [data-snippet-id=text-block] .text-center[data-snippet-id=colmd]').one('click', function () {
|
||||
self.moveToNextStep();
|
||||
});
|
||||
},
|
||||
trigger: 'click',
|
||||
},
|
||||
{
|
||||
stepId: 'remove-text-block-title',
|
||||
element: '.oe_snippet_remove:last',
|
||||
placement: 'top',
|
||||
reflex: true,
|
||||
title: "Delete the Title",
|
||||
content: "From this toolbar you can move, duplicate or delete the selected zone. Click on the cross to delete the title.",
|
||||
trigger: 'click',
|
||||
},
|
||||
{
|
||||
stepId: 'save-changes',
|
||||
@@ -152,35 +133,26 @@
|
||||
placement: 'right',
|
||||
title: "Save Your Blog",
|
||||
content: "Click the <em>Save</em> button to record changes on the page.",
|
||||
triggers: function () {
|
||||
var $publish = $('button.js_publish_btn');
|
||||
if ($publish.length > 0 && $publish.is(":visible")) {
|
||||
self.moveToNextStep();
|
||||
}
|
||||
},
|
||||
trigger: 'click',
|
||||
},
|
||||
{
|
||||
stepId: 'publish-post',
|
||||
element: 'button.js_publish_btn',
|
||||
placement: 'top',
|
||||
reflex: true,
|
||||
title: "Publish Your Post",
|
||||
content: "Your blog post is not yet published. You can update this draft version and publish it once you are ready.",
|
||||
trigger: 'click',
|
||||
},
|
||||
{
|
||||
stepId: 'end-tutorial',
|
||||
orphan: true,
|
||||
backdrop: true,
|
||||
title: "Thanks!",
|
||||
content: "This tutorial is finished. To discover more features, improve the content of this page and try the <em>Promote</em> button in the top right menu.",
|
||||
template: self.popover({ end: "Close Tutorial" }),
|
||||
backdrop: true,
|
||||
},
|
||||
];
|
||||
return this._super();
|
||||
},
|
||||
resume: function () {
|
||||
return (this.isCurrentStep('post-page') || this.isCurrentStep('save-changes')) && this._super();
|
||||
},
|
||||
trigger: function () {
|
||||
return (this.resume() && this.testUrl(/^\/blogpost\/[0-9]+\//)) || this._super();
|
||||
},
|
||||
|
||||
@@ -11,26 +11,25 @@
|
||||
});
|
||||
|
||||
website.EventTour = website.Tour.extend({
|
||||
id: 'event-tutorial',
|
||||
id: 'event',
|
||||
name: "Create an event",
|
||||
init: function (editor) {
|
||||
var self = this;
|
||||
self.steps = [
|
||||
{
|
||||
stepId: 'welcome-event',
|
||||
orphan: true,
|
||||
backdrop: true,
|
||||
title: "Create an Event",
|
||||
content: "Let's go through the firsts step to publish a new event.",
|
||||
template: self.popover({ next: "Start Tutorial", end: "Skip It" }),
|
||||
backdrop: true,
|
||||
},
|
||||
{
|
||||
stepId: 'content-menu',
|
||||
element: '#content-menu-button',
|
||||
placement: 'left',
|
||||
reflex: true,
|
||||
title: "Add Content",
|
||||
content: "The <em>Content</em> menu allows to create new pages, events, menus, etc.",
|
||||
trigger: 'click',
|
||||
},
|
||||
{
|
||||
stepId: 'new-post-entry',
|
||||
@@ -38,9 +37,11 @@
|
||||
placement: 'left',
|
||||
title: "New Event",
|
||||
content: "Click here to create a new event.",
|
||||
modal: {
|
||||
stopOnClose: true,
|
||||
afterSubmit: 'event-page',
|
||||
trigger: {
|
||||
modal: {
|
||||
stopOnClose: true,
|
||||
afterSubmit: 'event-page',
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -52,7 +53,6 @@
|
||||
},
|
||||
{
|
||||
stepId: 'event-page',
|
||||
orphan: true,
|
||||
title: "New Event Created",
|
||||
content: "This is your new event page. We will edit the event presentation page.",
|
||||
template: self.popover({ next: "OK" }),
|
||||
@@ -71,11 +71,7 @@
|
||||
placement: 'bottom',
|
||||
title: "Layout your event",
|
||||
content: "Insert blocks like 'Banner' to layout the body of your event.",
|
||||
triggers: function () {
|
||||
$('button[data-action=snippet]').one('click', function () {
|
||||
self.moveToNextStep();
|
||||
});
|
||||
},
|
||||
trigger: 'click',
|
||||
},
|
||||
{
|
||||
stepId: 'drag-banner',
|
||||
@@ -83,9 +79,7 @@
|
||||
placement: 'bottom',
|
||||
title: "Drag & Drop a block",
|
||||
content: "Drag the 'Banner' block and drop it in your page.",
|
||||
triggers: function () {
|
||||
self.onSnippetDraggedAdvance();
|
||||
},
|
||||
trigger: 'drag',
|
||||
},
|
||||
{
|
||||
stepId: 'add-text-block',
|
||||
@@ -93,11 +87,7 @@
|
||||
placement: 'bottom',
|
||||
title: "Layout your event",
|
||||
content: "Insert another block to your event.",
|
||||
triggers: function () {
|
||||
$('button[data-action=snippet]').one('click', function () {
|
||||
self.moveToNextStep();
|
||||
});
|
||||
},
|
||||
trigger: 'click',
|
||||
},
|
||||
{
|
||||
stepId: 'drag-text-block',
|
||||
@@ -105,12 +95,7 @@
|
||||
placement: 'bottom',
|
||||
title: "Drag & Drop a block",
|
||||
content: "Drag the 'Text Block' block below the banner.",
|
||||
triggers: function () {
|
||||
self.onSnippetDraggedAdvance();
|
||||
},
|
||||
onHide: function () {
|
||||
window.scrollTo(0, 0);
|
||||
},
|
||||
trigger: 'drag',
|
||||
},
|
||||
{
|
||||
stepId: 'add-three-columns',
|
||||
@@ -118,11 +103,7 @@
|
||||
placement: 'bottom',
|
||||
title: "Layout your event",
|
||||
content: "Insert a last block to your event.",
|
||||
triggers: function () {
|
||||
$('button[data-action=snippet]').one('click', function () {
|
||||
self.moveToNextStep();
|
||||
});
|
||||
},
|
||||
trigger: 'click',
|
||||
},
|
||||
{
|
||||
stepId: 'drag-three-columns',
|
||||
@@ -130,12 +111,7 @@
|
||||
placement: 'bottom',
|
||||
title: "Drag & Drop a block",
|
||||
content: "Drag the 'Three Columns' block at the bottom.",
|
||||
triggers: function () {
|
||||
self.onSnippetDraggedAdvance();
|
||||
},
|
||||
onHide: function () {
|
||||
window.scrollTo(0, 0);
|
||||
},
|
||||
trigger: 'drag',
|
||||
},
|
||||
{
|
||||
stepId: 'save-changes',
|
||||
@@ -143,43 +119,35 @@
|
||||
placement: 'right',
|
||||
title: "Save your modifications",
|
||||
content: "Once you click on save, your event is updated.",
|
||||
triggers: function () {
|
||||
var $publish = $('button.js_publish_btn');
|
||||
if ($publish.length > 0 && $publish.is(":visible")) {
|
||||
self.moveToNextStep();
|
||||
}
|
||||
},
|
||||
trigger: 'click',
|
||||
},
|
||||
{
|
||||
stepId: 'publish-event',
|
||||
element: 'button.js_publish_btn',
|
||||
placement: 'top',
|
||||
reflex: true,
|
||||
title: "Publish your event",
|
||||
content: "Click to publish your event.",
|
||||
trigger: 'click',
|
||||
},
|
||||
{
|
||||
stepId: 'customize-event',
|
||||
element: '.js_publish_management button:last',
|
||||
placement: 'left',
|
||||
reflex: true,
|
||||
title: "Customize your event",
|
||||
content: "Click here to customize your event further.",
|
||||
trigger: 'click',
|
||||
},
|
||||
{
|
||||
stepId: 'edit-event-backend',
|
||||
element: '.js_publish_management ul>li>a',
|
||||
placement: 'left',
|
||||
reflex: true,
|
||||
title: "Customize your event",
|
||||
content: "Click here to edit your event in the backend.",
|
||||
trigger: 'click',
|
||||
},
|
||||
];
|
||||
return this._super();
|
||||
},
|
||||
resume: function () {
|
||||
return (this.isCurrentStep('event-page') || this.isCurrentStep('save-changes')) && this._super();
|
||||
},
|
||||
trigger: function () {
|
||||
return (this.resume() && this.testUrl(/^\/event\/[0-9]+\/register/)) || this._super();
|
||||
},
|
||||
|
||||
@@ -336,19 +336,10 @@
|
||||
<a t-att-href="event.google_map_link()" target="_BLANK">
|
||||
<img t-att-src="event.google_map_img()" width="100%%"/>
|
||||
</a>
|
||||
<address class="mt16 mb8" t-field="event.address_id"> <!-- TODO: use widget contact -->
|
||||
<!-- <strong t-field="event.address_id"/>
|
||||
<p t-field="event.address_id"/>
|
||||
<div t-if="event.address_id.phone">
|
||||
<span class="fa fa-phone"/> <span t-field="event.address_id.phone"/>
|
||||
</div>
|
||||
<div t-if="event.address_id.email">
|
||||
<span class="fa fa-envelope"/>
|
||||
<a t-att-href="'mailto:'+event.address_id.email">
|
||||
<span t-field="event.address_id.email"/>
|
||||
</a>
|
||||
</div> -->
|
||||
</address>
|
||||
<div class="mt16 mb8" t-field="event.address_id" t-field-options='{
|
||||
"widget": "contact",
|
||||
"fields": ["address", "phone", "mobile", "fax", "email"]
|
||||
}'/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -366,15 +357,10 @@
|
||||
<div class="panel-heading">
|
||||
<h4>Organizer</h4>
|
||||
</div>
|
||||
<div class="panel-body" t-field="event.organizer_id"> <!-- TODO: use widget contact -->
|
||||
<!-- <p><strong t-field="event.organizer_id.display_name"/></p>
|
||||
<div t-if="event.phone">
|
||||
<span class="fa fa-phone"/> <span t-field="event.phone"/>
|
||||
</div>
|
||||
<div t-if="event.email">
|
||||
<i class="fa fa-envelope"/> <span t-field="event.email"/>
|
||||
</div> -->
|
||||
</div>
|
||||
<div class="panel-body" t-field="event.organizer_id" t-field-options='{
|
||||
"widget": "contact",
|
||||
"fields": ["name", "phone", "mobile", "fax", "email"]
|
||||
}'/>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default">
|
||||
|
||||
@@ -17,7 +17,11 @@ class website_hr_recruitment(http.Controller):
|
||||
'/jobs/office/<model("res.partner"):office>'
|
||||
], type='http', auth="public", multilang=True)
|
||||
def jobs(self, department=None, office=None):
|
||||
jobs = self._browse_jobs([])
|
||||
JobsObj = request.registry['hr.job']
|
||||
jobpost_ids = JobsObj.search(request.cr, request.uid, [],
|
||||
order="website_published desc,no_of_recruitment desc",
|
||||
context=request.context)
|
||||
jobs = JobsObj.browse(request.cr, request.uid, jobpost_ids, context=dict(request.context, show_address=True, no_tag_br=True))
|
||||
|
||||
departments = set()
|
||||
offices = set()
|
||||
@@ -28,12 +32,12 @@ class website_hr_recruitment(http.Controller):
|
||||
offices.add(job.address_id)
|
||||
|
||||
if department or office:
|
||||
domain = []
|
||||
if department:
|
||||
domain.append(('department_id','=', department.id))
|
||||
if office:
|
||||
domain.append(('address_id','=', office.id))
|
||||
jobs = self._browse_jobs(domain)
|
||||
_jobs = []
|
||||
for job in jobs:
|
||||
if (department and job.department_id.id == department.id) or \
|
||||
(office and job.address_id.id == office.id):
|
||||
_jobs.append(job)
|
||||
jobs = _jobs
|
||||
|
||||
return request.website.render("website_hr_recruitment.index", {
|
||||
'jobs': jobs,
|
||||
@@ -97,11 +101,3 @@ class website_hr_recruitment(http.Controller):
|
||||
}, context=request.context)
|
||||
|
||||
return request.redirect("/job/detail/%s/?enable_editor=1" % job_id)
|
||||
|
||||
def _browse_jobs(self, domain):
|
||||
Jobs = request.registry['hr.job']
|
||||
jobpost_ids = Jobs.search(request.cr, request.uid, domain,
|
||||
order="website_published desc,no_of_recruitment desc",
|
||||
context=request.context)
|
||||
jobs = Jobs.browse(request.cr, request.uid, jobpost_ids, context=request.context)
|
||||
return jobs
|
||||
|
||||
@@ -67,13 +67,12 @@
|
||||
</small>
|
||||
</h3>
|
||||
|
||||
<div t-if="job.address_id">
|
||||
<i class="fa fa-map-marker"/>
|
||||
<span t-field="job.address_id.city"/>
|
||||
<span t-if="job.address_id.state_id" t-field="job.address_id.state_id"/>
|
||||
, <span t-field="job.address_id.country_id.name"/>
|
||||
<span t-if="not job.website_published" class="label label-danger">not published</span>
|
||||
</div>
|
||||
<span t-field="job.address_id" t-field-options='{
|
||||
"widget": "contact",
|
||||
"fields": ["address"],
|
||||
"no_tag_br": true
|
||||
}'/>
|
||||
<span t-if="not job.website_published" class="label label-danger">not published</span>
|
||||
<div class="text-muted">
|
||||
<i class="fa fa-clock-o"/> <span t-field="job.write_date"/>
|
||||
</div>
|
||||
@@ -112,9 +111,11 @@
|
||||
|
||||
<div class="oe_structure" style="clear:both;">
|
||||
<h1 class="text-center" t-field="job.name"/>
|
||||
<h5 class="text-center">
|
||||
<i class="fa fa-map-marker"/> <span t-field="job.address_id.city"/> <span t-if="job.address_id.state_id" t-field="job.address_id.state_id.name"/>, <span t-field="job.address_id.country_id.name"/>
|
||||
</h5>
|
||||
<h5 class="text-center" t-field="job.address_id" t-field-options='{
|
||||
"widget": "contact",
|
||||
"fields": ["address"],
|
||||
"no_tag_br": true
|
||||
}'/>
|
||||
<h5 class="text-center text-muted">
|
||||
<i class="fa fa-clock-o"/> <span t-field="job.write_date"/>
|
||||
</h5>
|
||||
|
||||
@@ -11,26 +11,25 @@
|
||||
});
|
||||
|
||||
website.EditorShopTour = website.Tour.extend({
|
||||
id: 'shop-tutorial',
|
||||
id: 'shop',
|
||||
name: "Create a product",
|
||||
init: function (editor) {
|
||||
var self = this;
|
||||
self.steps = [
|
||||
{
|
||||
stepId: 'welcome-shop',
|
||||
orphan: true,
|
||||
backdrop: true,
|
||||
title: "Welcome to your shop",
|
||||
content: "You successfully installed the e-commerce. This guide will help you to create your product and promote your sales.",
|
||||
template: self.popover({ next: "Start Tutorial", end: "Skip It" }),
|
||||
backdrop: true,
|
||||
},
|
||||
{
|
||||
stepId: 'content-menu',
|
||||
element: '#content-menu-button',
|
||||
placement: 'left',
|
||||
reflex: true,
|
||||
title: "Create your first product",
|
||||
content: "Click here to add a new product.",
|
||||
trigger: 'click',
|
||||
},
|
||||
{
|
||||
stepId: 'edit-entry',
|
||||
@@ -38,9 +37,11 @@
|
||||
placement: 'left',
|
||||
title: "Create a new product",
|
||||
content: "Select 'New Product' to create it and manage its properties to boost your sales.",
|
||||
modal: {
|
||||
stopOnClose: true,
|
||||
afterSubmit: 'product-page',
|
||||
trigger: {
|
||||
modal: {
|
||||
stopOnClose: true,
|
||||
afterSubmit: 'product-page',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -49,15 +50,13 @@
|
||||
placement: 'right',
|
||||
title: "Choose name",
|
||||
content: "Enter a name for your new product then click 'Continue'.",
|
||||
|
||||
},
|
||||
{
|
||||
stepId: 'product-page',
|
||||
orphan: true,
|
||||
backdrop: true,
|
||||
title: "New product created",
|
||||
content: "This page contains all the information related to the new product.",
|
||||
template: self.popover({ next: "OK" }),
|
||||
backdrop: true,
|
||||
},
|
||||
{
|
||||
stepId: 'edit-price',
|
||||
@@ -75,7 +74,7 @@
|
||||
content: "Click here to set an image describing your product.",
|
||||
triggers: function () {
|
||||
function registerClick () {
|
||||
$('button.image-edit-button').one('click', function () {
|
||||
$('button.hover-edition-button').one('click', function () {
|
||||
$('#wrap img.img:first').off('hover', registerClick);
|
||||
self.moveToNextStep();
|
||||
});
|
||||
@@ -110,11 +109,7 @@
|
||||
placement: 'bottom',
|
||||
title: "Describe the product for your audience",
|
||||
content: "Insert blocks like text-image, or gallery to fully describe the product and make your visitors want to buy this product.",
|
||||
triggers: function () {
|
||||
$('button[data-action=snippet]').one('click', function () {
|
||||
self.moveToNextStep();
|
||||
});
|
||||
},
|
||||
trigger: 'click',
|
||||
},
|
||||
{
|
||||
stepId: 'drag-big-picture',
|
||||
@@ -122,9 +117,7 @@
|
||||
placement: 'bottom',
|
||||
title: "Drag & Drop a block",
|
||||
content: "Drag the 'Big Picture' block and drop it in your page.",
|
||||
triggers: function () {
|
||||
self.onSnippetDraggedAdvance();
|
||||
},
|
||||
trigger: 'drag',
|
||||
},
|
||||
{
|
||||
stepId: 'save-changes',
|
||||
@@ -132,28 +125,20 @@
|
||||
placement: 'right',
|
||||
title: "Save your modifications",
|
||||
content: "Once you click on save, your product is updated.",
|
||||
triggers: function () {
|
||||
var $publish = $('button.js_publish_btn');
|
||||
if ($publish.length > 0 && $publish.is(":visible")) {
|
||||
self.moveToNextStep();
|
||||
}
|
||||
},
|
||||
trigger: 'click',
|
||||
|
||||
},
|
||||
{
|
||||
stepId: 'publish-product',
|
||||
element: 'button.js_publish_btn',
|
||||
placement: 'top',
|
||||
reflex: true,
|
||||
title: "Publish your product",
|
||||
content: "Click to publish your product so your customers can see it.",
|
||||
trigger: 'click',
|
||||
},
|
||||
];
|
||||
return this._super();
|
||||
},
|
||||
resume: function () {
|
||||
return (this.isCurrentStep('product-page') || this.isCurrentStep('save-changes')) && this._super();
|
||||
},
|
||||
trigger: function () {
|
||||
return (this.resume() && this.testUrl(/^\/shop\/product\/[0-9]+\//)) || this._super();
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user