[MERGE] Merge parents

bzr revid: rim@openerp.com-20131206161238-orblyjhemcw5cjgw
bzr revid: rim@openerp.com-20131209074455-tyhl2dkv8id2my1n
This commit is contained in:
Richard Mathot (OpenERP)
2013-12-09 08:44:55 +01:00
33 changed files with 533 additions and 491 deletions
@@ -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,
-1
View File
@@ -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)
+3 -3
View File
@@ -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)
+1
View File
@@ -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, {
+1 -1
View File
@@ -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
+3 -2
View File
@@ -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):
+7 -6
View File
@@ -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')
+1 -1
View File
@@ -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
+2 -1
View File
@@ -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])
+4 -1
View File
@@ -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
View File
@@ -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"
+12 -9
View File
@@ -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)) + \
+1
View File
@@ -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>
+8 -8
View File
@@ -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.
+2 -2
View File
@@ -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)
+42
View File
@@ -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();
},
});
}());
+107 -48
View File
@@ -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 '&lt;br/&gt; &amp;nbsp; &amp;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>
+1 -1
View File
@@ -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();
},
+8 -22
View File
@@ -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();
},