[MERGE] forward port of branch 8.0 up to 8ac8281

This commit is contained in:
Christophe Simonis
2015-04-13 12:07:58 +02:00
83 changed files with 597 additions and 255 deletions
+1 -2
View File
@@ -685,8 +685,7 @@ Paulius Sladkevičius <komsas@gmail.com> Paulius Sladkevičius <paulius@hacbee.c
Paulius Sladkevičius <komsas@gmail.com> Paulius Sladkevičius <paulius@hbee.eu>
Paulius Sladkevičius <komsas@gmail.com> Paulius Sladkevičius <paulius@inovera.lt>
Paulius Sladkevičius <komsas@gmail.com> paulius <>
Pedro Manuel Baeza <pedro.baeza@serviciosbaeza.com>
Pedro Manuel Baeza <pedro.baeza@serviciosbaeza.com> <pedro.baeza@gmail.com>
Pedro Manuel Baeza <pedro.baeza@gmail.com> <pedro.baeza@serviciosbaeza.com>
Peter Langenberg <pl@agaplan.eu>
Peter Langenberg <pl@agaplan.eu> Peter Langenberg <>
Philu <philubert@gmail.com> philubert@gmail.com <>
+1 -1
View File
@@ -840,7 +840,7 @@ class account_invoice(models.Model):
# create one move line for the total and possibly adjust the other lines amount
total, total_currency, iml = inv.with_context(ctx).compute_invoice_totals(company_currency, ref, iml)
name = inv.name or inv.supplier_invoice_number or '/'
name = inv.supplier_invoice_number or inv.name or '/'
totlines = []
if inv.payment_term:
totlines = inv.with_context(ctx).payment_term.compute(total, date_invoice)[0]
+8 -7
View File
@@ -251,13 +251,14 @@ class res_partner(osv.osv):
def _journal_item_count(self, cr, uid, ids, field_name, arg, context=None):
MoveLine = self.pool('account.move.line')
AnalyticAccount = self.pool('account.analytic.account')
return {
partner_id: {
'journal_item_count': MoveLine.search_count(cr, uid, [('partner_id', '=', partner_id)], context=context),
'contracts_count': AnalyticAccount.search_count(cr, uid, [('partner_id', 'child_of', partner_id)], context=context)
}
for partner_id in ids
}
results = {}
for partner_id in ids:
results[partner_id] = {}
if 'contracts_count' in field_name:
results[partner_id]['contracts_count'] = AnalyticAccount.search_count(cr, uid, [('partner_id', '=', partner_id)], context=context)
if 'journal_item_count' in field_name:
results[partner_id]['journal_item_count'] = MoveLine.search_count(cr, uid, [('partner_id', 'child_of', partner_id)], context=context)
return results
def has_something_to_reconcile(self, cr, uid, partner_id, context=None):
'''
-15
View File
@@ -28,21 +28,6 @@
</field>
</record>
<record id="product_template_search_view" model="ir.ui.view">
<field name="name">product.template.search</field>
<field name="model">product.template</field>
<field name="mode">primary</field>
<field name="inherit_id" ref="product.product_template_search_view"/>
<field name="arch" type="xml">
<field name="product_variant_ids" position="after">
<field name="categ_id"/>
</field>
<xpath expr="//group[@string='Group by...']" position="inside">
<filter string='Category' icon="terp-stock_symbol-selection" domain="[]" context="{'group_by' : 'categ_id'}"/>
</xpath>
</field>
</record>
<record id="view_category_property_form" model="ir.ui.view">
<field name="name">product.category.property.form.inherit</field>
<field name="model">product.category</field>
@@ -1317,13 +1317,13 @@ openerp.account = function (instance) {
if (balance_type === "equal") {
displayValidState(true);
} else if (balance_type === "greater") {
createOpenBalance("Create Write-off");
createOpenBalance(_t("Create Write-off"));
} else if (balance_type === "lower") {
if (self.st_line.has_no_partner) {
createOpenBalance("Choose counterpart");
createOpenBalance(_t("Choose counterpart"));
} else {
displayValidState(false, "Keep open");
createOpenBalance("Open balance");
createOpenBalance(_t("Open balance"));
}
}
@@ -96,7 +96,7 @@
<span t-esc="formatLang(sum_balance_account(childrenaccount), digits=get_digits(dp='Account'), currency_obj=res_company.currency_id)"/>
</td>
<td class="text-right" t-if="data['form']['amount_currency']">
<span t-esc="formatLang(sum_currency_amount_account(childrenaccount), digits=get_digits(dp='Account'), currency_obj=res_company.currency_id) if sum_currency_amount_account(childrenaccount) > 0.00 else ''"/>
<span t-esc="formatLang(sum_currency_amount_account(childrenaccount), digits=get_digits(dp='Account')) if sum_currency_amount_account(childrenaccount) > 0.00 else ''"/>
</td>
</tr>
<tr t-foreach="lines(childrenaccount)" t-as="line">
@@ -117,7 +117,7 @@
<span t-esc="formatLang(line['progress'], digits=get_digits(dp='Account'), currency_obj=res_company.currency_id)"/>
</td>
<td class="text-right" t-if="data['form']['amount_currency']">
<span t-esc="formatLang(line['amount_currency'], currency_obj=res_company.currency_id) if line['amount_currency'] > 0.00 else ''"/>
<span t-esc="formatLang(line['amount_currency'], digits=get_digits(dp='Account')) if line['amount_currency'] > 0.00 else ''"/>
<span t-esc="line['currency_code'] if line['amount_currency'] > 0.00 else ''"/>
</td>
</tr>
@@ -63,6 +63,8 @@ class account_fiscalyear_close(osv.osv_memory):
raise UserError(_('The entries to reconcile should belong to the same company.'))
r_id = self.pool.get('account.move.reconcile').create(cr, uid, {'type': 'auto', 'opening_reconciliation': True})
cr.execute('update account_move_line set reconcile_id = %s where id in %s',(r_id, tuple(ids),))
# reconcile_ref deptends from reconcile_id but was not recomputed
obj_acc_move_line._store_set_values(cr, uid, ids, ['reconcile_ref'], context=context)
obj_acc_move_line.invalidate_cache(cr, uid, ['reconcile_id'], ids, context=context)
return r_id
@@ -79,5 +79,7 @@ class account_statement_from_invoice_lines(osv.osv_memory):
'statement_id': statement_id,
'ref': line.ref,
'date': statement.date,
'amount_currency': line.amount_currency,
'currency_id': line.currency_id.id,
}, context=context)
return {'type': 'ir.actions.act_window_close'}
@@ -7,7 +7,10 @@
<field name="arch" type="xml">
<form string="Import Entries">
<separator string="Payable and Receivables"/>
<field height="300" width="700" name="line_ids" domain="[('account_id.type','in',['receivable','payable']), ('reconcile_id','=',False), ('state', '=', 'valid')]"/>
<field height="300" width="700"
name="line_ids"
domain="[('account_id.type','in',['receivable','payable']), ('reconcile_id','=',False), ('state', '=', 'valid')]"
context="{'currency':True}" />
<footer>
<button string="Ok" name="populate_statement" type="object" class="oe_highlight"/>
or
+2
View File
@@ -5,6 +5,8 @@ from passlib.context import CryptContext
import openerp
from openerp.osv import fields, osv
openerp.addons.base.res.res_users.USER_PRIVATE_FIELDS.append('password_crypt')
_logger = logging.getLogger(__name__)
default_crypt_context = CryptContext(
+9 -9
View File
@@ -101,11 +101,11 @@
<table>
<tr>
<td>
<div style="border-top-left-radius:3px;border-top-right-radius:3px;font-size:12px;border-collapse:separate;text-align:center;font-weight:bold;color:#ffffff;width:130px;min-height: 18px;border-color:#ffffff;background:#8a89ba;padding-top: 4px;">${object.event_id.get_interval(object.event_id.start, 'dayname')}</div>
<div style="border-top-left-radius:3px;border-top-right-radius:3px;font-size:12px;border-collapse:separate;text-align:center;font-weight:bold;color:#ffffff;width:130px;min-height: 18px;border-color:#ffffff;background:#8a89ba;padding-top: 4px;">${object.event_id.get_interval(object.event_id.start, 'dayname', tz=object.partner_id.tz)}</div>
<div style="font-size:48px;min-height:auto;font-weight:bold;text-align:center;color: #5F5F5F;background-color: #E1E2F8;width: 130px;">
${object.event_id.get_interval(object.event_id.start,'day')}
${object.event_id.get_interval(object.event_id.start,'day', tz=object.partner_id.tz)}
</div>
<div style='font-size:12px;text-align:center;font-weight:bold;color:#ffffff;background-color:#8a89ba'>${object.event_id.get_interval(object.event_id.start, 'month')}</div>
<div style='font-size:12px;text-align:center;font-weight:bold;color:#ffffff;background-color:#8a89ba'>${object.event_id.get_interval(object.event_id.start, 'month', tz=object.partner_id.tz)}</div>
<div style="border-collapse:separate;color:#8a89ba;text-align:center;width: 128px;font-size:12px;border-bottom-right-radius:3px;font-weight:bold;border:1px solid;border-bottom-left-radius:3px;">${not object.event_id.allday and object.event_id.get_interval(object.event_id.start, 'time', tz=object.partner_id.tz) or ''}</div>
</td>
<td>
@@ -226,11 +226,11 @@
<table>
<tr>
<td>
<div style="border-top-left-radius:3px;border-top-right-radius:3px;font-size:12px;border-collapse:separate;text-align:center;font-weight:bold;color:#ffffff;width:130px;min-height: 18px;border-color:#ffffff;background:#8a89ba;padding-top: 4px;">${object.event_id.get_interval(object.event_id.start, 'dayname')}</div>
<div style="border-top-left-radius:3px;border-top-right-radius:3px;font-size:12px;border-collapse:separate;text-align:center;font-weight:bold;color:#ffffff;width:130px;min-height: 18px;border-color:#ffffff;background:#8a89ba;padding-top: 4px;">${object.event_id.get_interval(object.event_id.start, 'dayname', tz=object.partner_id.tz)}</div>
<div style="font-size:48px;min-height:auto;font-weight:bold;text-align:center;color: #5F5F5F;background-color: #E1E2F8;width: 130px;">
${object.event_id.get_interval(object.event_id.start,'day')}
${object.event_id.get_interval(object.event_id.start,'day', tz=object.partner_id.tz)}
</div>
<div style='font-size:12px;text-align:center;font-weight:bold;color:#ffffff;background-color:#8a89ba'>${object.event_id.get_interval(object.event_id.start, 'month')}</div>
<div style='font-size:12px;text-align:center;font-weight:bold;color:#ffffff;background-color:#8a89ba'>${object.event_id.get_interval(object.event_id.start, 'month', tz=object.partner_id.tz)}</div>
<div style="border-collapse:separate;color:#8a89ba;text-align:center;width: 128px;font-size:12px;border-bottom-right-radius:3px;font-weight:bold;border:1px solid;border-bottom-left-radius:3px;">${not object.event_id.allday and object.event_id.get_interval(object.event_id.start, 'time', tz=object.partner_id.tz) or ''}</div>
</td>
<td>
@@ -350,11 +350,11 @@
<table>
<tr>
<td>
<div style="border-top-left-radius:3px;border-top-right-radius:3px;font-size:12px;border-collapse:separate;text-align:center;font-weight:bold;color:#ffffff;width:130px;min-height: 18px;border-color:#ffffff;background:#8a89ba;padding-top: 4px;">${object.event_id.get_interval(object.event_id.start, 'dayname')}</div>
<div style="border-top-left-radius:3px;border-top-right-radius:3px;font-size:12px;border-collapse:separate;text-align:center;font-weight:bold;color:#ffffff;width:130px;min-height: 18px;border-color:#ffffff;background:#8a89ba;padding-top: 4px;">${object.event_id.get_interval(object.event_id.start, 'dayname', tz=object.partner_id.tz)}</div>
<div style="font-size:48px;min-height:auto;font-weight:bold;text-align:center;color: #5F5F5F;background-color: #E1E2F8;width: 130px;">
${object.event_id.get_interval(object.event_id.start,'day')}
${object.event_id.get_interval(object.event_id.start,'day', tz=object.partner_id.tz)}
</div>
<div style='font-size:12px;text-align:center;font-weight:bold;color:#ffffff;background-color:#8a89ba'>${object.event_id.get_interval(object.event_id.start, 'month')}</div>
<div style='font-size:12px;text-align:center;font-weight:bold;color:#ffffff;background-color:#8a89ba'>${object.event_id.get_interval(object.event_id.start, 'month', tz=object.partner_id.tz)}</div>
<div style="border-collapse:separate;color:#8a89ba;text-align:center;width: 128px;font-size:12px;border-bottom-right-radius:3px;font-weight:bold;border:1px solid;border-bottom-left-radius:3px;">${not object.event_id.allday and object.event_id.get_interval(object.event_id.start, 'time', tz=object.partner_id.tz) or ''}</div>
</td>
<td>
+5 -4
View File
@@ -35,8 +35,9 @@ class stock_picking(osv.osv):
total_weight = total_weight_net = 0.00
for move in picking.move_lines:
total_weight += move.weight
total_weight_net += move.weight_net
if move.state != 'cancel':
total_weight += move.weight
total_weight_net += move.weight_net
res[picking.id] = {
'weight': total_weight,
@@ -58,12 +59,12 @@ class stock_picking(osv.osv):
'weight': fields.function(_cal_weight, type='float', string='Weight', digits_compute= dp.get_precision('Stock Weight'), multi='_cal_weight',
store={
'stock.picking': (lambda self, cr, uid, ids, c={}: ids, ['move_lines'], 40),
'stock.move': (_get_picking_line, ['picking_id', 'product_id','product_uom_qty','product_uom'], 40),
'stock.move': (_get_picking_line, ['state', 'picking_id', 'product_id','product_uom_qty','product_uom'], 40),
}),
'weight_net': fields.function(_cal_weight, type='float', string='Net Weight', digits_compute= dp.get_precision('Stock Weight'), multi='_cal_weight',
store={
'stock.picking': (lambda self, cr, uid, ids, c={}: ids, ['move_lines'], 40),
'stock.move': (_get_picking_line, ['picking_id', 'product_id','product_uom_qty','product_uom'], 40),
'stock.move': (_get_picking_line, ['state', 'picking_id', 'product_id','product_uom_qty','product_uom'], 40),
}),
'carrier_tracking_ref': fields.char('Carrier Tracking Ref', copy=False),
'number_of_packages': fields.integer('Number of Packages', copy=False),
+5 -4
View File
@@ -37,7 +37,10 @@ MAX_VISIBILITY_RANKING = 3
def start_end_date_for_period(period, default_start_date=False, default_end_date=False):
"""Return the start and end date for a goal period based on today
:return: (start_date, end_date), datetime.date objects, False if the period is
:param str default_start_date: string date in DEFAULT_SERVER_DATE_FORMAT format
:param str default_end_date: string date in DEFAULT_SERVER_DATE_FORMAT format
:return: (start_date, end_date), dates in string format, False if the period is
not defined or unknown"""
today = date.today()
if period == 'daily':
@@ -58,11 +61,9 @@ def start_end_date_for_period(period, default_start_date=False, default_end_date
start_date = default_start_date # for manual goal, start each time
end_date = default_end_date
if start_date and end_date:
return (datetime.strftime(start_date, DF), datetime.strftime(end_date, DF))
else:
return (start_date, end_date)
return (datetime.strftime(start_date, DF), datetime.strftime(end_date, DF))
class gamification_challenge(osv.Model):
"""Gamification challenge
+14 -15
View File
@@ -16,37 +16,36 @@
<div attrs="{'invisible':[('google_cal_sync','=',False)]}">
<br/><h2>To setup the signin process with Google, first you have to perform the following steps</h2>
<ul>
<li> Connect on your google account and go to <a href='https://cloud.google.com/console' target='_blank'>https://cloud.google.com/console</a> </li>
<li> Connect on your google account and go to <a href='https://console.developers.google.com/' target='_blank'>https://console.developers.google.com/</a> </li>
<li>
Click on <b>"Create Project"</b> and enter a project name and change your id if you want. Don't forget to accept the Terms of Services
Click on <b>'Create Project'</b> and enter a project name and change your id if you want. Don't forget to accept the Terms of Services
<br/><br/><img src='/google_calendar/static/src/img/setup_01.png' class='calendar_img_tuto'/>
<br/><img src='/google_calendar/static/src/img/setup_02.png' class='calendar_img_tuto'/>
</li>
<li> In the menu on left side, select the sub menu APIs (from menu APIs and auth) and activate <b>"Calendar API"</b> by clicking on button "OFF".
<br/> You will need to accept again the "Google APIs Terms of services" and "Calendar API Terms of service"
<br/> When it's done, check that the button of <b>"Calendar API"</b> is well in green and with text "ON"
<li> In the menu on left side, select the sub menu APIs (from menu APIs and auth) and click on <b>'Calendar API'</b>.
<br/> Activate the Calendar API by clicking on the blue button <b>'Enable API'</b>.
<br/> When it's done, the Calendar API overview will be available
<br/><br/> <img src='/google_calendar/static/src/img/setup_03.png' class='calendar_img_tuto'/>
<br/> <img src='/google_calendar/static/src/img/setup_04.png' class='calendar_img_tuto'/>
<br/> <img src='/google_calendar/static/src/img/setup_05.png' class='calendar_img_tuto'/>
</li>
<li>
In the menu on left side, select the sub menu <b>'Credentials'</b> (from menu APIs and auth) and click on button <b>'Create New Client ID'</b>
In the menu on left side, select the sub menu <b>'Credentials'</b> (from menu APIs and auth) and click on button <b>'Create new Client ID'</b>
<br/><br/> <img src='/google_calendar/static/src/img/setup_06.png' class='calendar_img_tuto'/>
</li>
<li> Fill in the Name of application and check that the platform is well on <b>'Web Application'</b>.<br/>
<br/> You should now configure the allowed pages on which you will be redirected. To do it, you need to complete the field <b>"Redirect RI"</b>
and set as value (your own domain followed by <i>"/google_account/authentication"</i>) :
<li> Check that the Application type is set on <b>'Web Application'</b>, then click on <b>'Configure consent screen'</b>.
<br/> Specify an email address and a product name, then save.
<br/><br/> <img src='/google_calendar/static/src/img/setup_07.png' class='calendar_img_tuto'/>
<br/><img src='/google_calendar/static/src/img/setup_09.png' class='calendar_img_tuto'/>
<br/> You should now configure the allowed pages on which you will be redirected. To do it, you need to complete the field <b>"Authorized redirect URI"</b>
and set as value (your own domain followed by <i>'/google_account/authentication'</i>):
<br/>==> <b><field name="server_uri" readonly="1" style='display:inline'/></b>
<br/> You can now click on <b>'Create Client ID'</b>
<br/><br/> <img src='/google_calendar/static/src/img/setup_07.png' class='calendar_img_tuto'/>
<br/><br/> <img src='/google_calendar/static/src/img/setup_10.png' class='calendar_img_tuto'/>
</li>
<li>Once done, you will have the both informations (<b>Client ID</b> and <b>Client Secret</b>) that you need to insert in the 2 fields below !
<li>Once done, you will have the both informations (<b>Client ID</b> and <b>Client Secret</b>) that you need to insert in the 2 fields below!
<br/><br/> <img src='/google_calendar/static/src/img/setup_08.png' class='calendar_img_tuto'/>
</li>
<li>
To finish, you just need to create a "consent screen" by clicking on <b>"Consent Screen"</b> in the left menu.
<br/><br/> <img src='/google_calendar/static/src/img/setup_09.png' class='calendar_img_tuto'/>
</li>
<a href="#" class="oe_link">Return at Top</a>
</ul>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

+1 -1
View File
@@ -6,7 +6,7 @@
<record id="view_partner_inv_comm_type_form" model="ir.ui.view">
<field name="name">res.partner.inv_comm_type.form.inherit</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="inherit_id" ref="account.view_partner_property_form"/>
<field name="arch" type="xml">
<field name="last_reconciliation_date" position="after">
<field name="out_inv_comm_type"/>
+2 -1
View File
@@ -601,7 +601,8 @@ class mail_message(osv.Model):
def _find_allowed_model_wise(self, cr, uid, doc_model, doc_dict, context=None):
doc_ids = doc_dict.keys()
allowed_doc_ids = self.pool[doc_model].search(cr, uid, [('id', 'in', doc_ids)], context=context)
ctx = dict(context or {}, active_test=False)
allowed_doc_ids = self.pool[doc_model].search(cr, uid, [('id', 'in', doc_ids)], context=ctx)
return set([message_id for allowed_doc_id in allowed_doc_ids for message_id in doc_dict[allowed_doc_id]])
def _find_allowed_doc_ids(self, cr, uid, model_ids, context=None):
+1
View File
@@ -590,6 +590,7 @@ class mail_thread(osv.AbstractModel):
ir_attachment_obj.unlink(cr, uid, attach_ids, context=context)
return True
@api.cr_uid_ids_context
def check_mail_message_access(self, cr, uid, mids, operation, model_obj=None, context=None):
""" mail.message check permission rules for related document. This method is
meant to be inherited in order to implement addons-specific behavior.
+1
View File
@@ -206,6 +206,7 @@ class mrp_bom(osv.osv):
}
_order = "sequence"
def _bom_find(self, cr, uid, product_tmpl_id=None, product_id=None, properties=None, context=None):
""" Finds BoM for particular product and product uom.
@param product_tmpl_id: Selected product.
+2 -2
View File
@@ -220,7 +220,7 @@ class mrp_production(osv.osv):
workcenter_line.signal_workflow('button_done')
return super(mrp_production,self).action_production_end(cr, uid, ids, context=context)
def action_in_production(self, cr, uid, ids):
def action_in_production(self, cr, uid, ids, context=None):
""" Changes state to In Production and writes starting date.
@return: True
"""
@@ -228,7 +228,7 @@ class mrp_production(osv.osv):
for prod in self.browse(cr, uid, ids):
if prod.workcenter_lines:
workcenter_pool.signal_workflow(cr, uid, [prod.workcenter_lines[0].id], 'button_start_working')
return super(mrp_production,self).action_in_production(cr, uid, ids)
return super(mrp_production,self).action_in_production(cr, uid, ids, context=context)
def action_cancel(self, cr, uid, ids, context=None):
""" Cancels work order if production order is canceled.
+1 -1
View File
@@ -154,7 +154,7 @@ class PaymentAcquirerOgone(osv.Model):
'OWNERZIP': partner_values['zip'],
'OWNERADDRESS': partner_values['address'],
'OWNERTOWN': partner_values['city'],
'OWNERCTY': partner_values['country'] and partner_values['country'].name or '',
'OWNERCTY': partner_values['country'] and partner_values['country'].code or '',
'OWNERTELNO': partner_values['phone'],
'ACCEPTURL': '%s' % urlparse.urljoin(base_url, OgoneController._accept_url),
'DECLINEURL': '%s' % urlparse.urljoin(base_url, OgoneController._decline_url),
+1
View File
@@ -998,6 +998,7 @@ class pos_order(osv.osv):
'invoice_id': inv_id,
'product_id': line.product_id.id,
'quantity': line.qty,
'account_analytic_id': self._prepare_analytic_account(cr, uid, line, context=context),
}
inv_name = product_obj.name_get(cr, uid, [line.product_id.id], context=context)[0][1]
inv_line.update(inv_line_ref.product_id_change(cr, uid, [],
+1 -1
View File
@@ -807,7 +807,7 @@ class product_template(osv.osv):
# re-apply product.template order + name_get
return super(product_template, self).name_search(
cr, user, '', args=[('id', 'in', template_ids)],
operator=operator, context=context, limit=limit)
operator='ilike', context=context, limit=limit)
class product_product(osv.osv):
_name = "product.product"
@@ -22,6 +22,7 @@
<field name="arch" type="xml">
<graph string="Tasks Analysis" type="pivot">
<field name="project_id" type="row"/>
<field name="user_id" type="col"/>
<field name="nbr" type="measure"/>
</graph>
</field>
+2 -1
View File
@@ -47,7 +47,8 @@ class project_configuration(osv.osv_memory):
'module_rating_project': fields.boolean('Allow customers to rate provided services',
help="This allows customers to give rating on provided services"),
'time_unit': fields.many2one('product.uom', 'Working time unit', required=True,
help="""This will set the unit of measure used in projects and tasks."""),
help='This will set the unit of measure used in projects and tasks.\n'
'Changing the unit will only impact new entries.'),
'module_project_issue_sheet': fields.boolean("Invoice working time on issues",
help='Provides timesheet support for the issues/bugs management in project.\n'
'-This installs the module project_issue_sheet.'),
+1 -1
View File
@@ -834,7 +834,7 @@ class purchase_order(osv.osv):
picking_vals = {
'picking_type_id': order.picking_type_id.id,
'partner_id': order.partner_id.id,
'date': max([l.date_planned for l in order.order_line]),
'date': order.date_order,
'origin': order.name
}
picking_id = self.pool.get('stock.picking').create(cr, uid, picking_vals, context=context)
+1 -1
View File
@@ -2,7 +2,7 @@
Set a product as using average price.
-
!record {model: product.product, id: product_variable_icecream}:
default_code: AVG
default_code: VARAVG
name: Variable Ice Cream
type: product
categ_id: product.product_category_1
+1 -1
View File
@@ -2,7 +2,7 @@
Set a product as using fifo price
-
!record {model: product.product, id: product_fiforet_icecream}:
default_code: FIFO
default_code: FIFORET
name: FIFO Ice Cream
type: product
standard_price: 0.0
+1
View File
@@ -45,6 +45,7 @@ class sale_report(osv.osv):
'nbr': fields.integer('# of Lines', readonly=True), # TDE FIXME master: rename into nbr_lines
'state': fields.selection([
('draft', 'Quotation'),
('sent', 'Quotation Sent'),
('waiting_date', 'Waiting Schedule'),
('manual', 'Manual In Progress'),
('progress', 'In Progress'),
+1 -1
View File
@@ -780,7 +780,7 @@ class sale_order(osv.osv):
order_line.append([4, line_id])
else:
order_line.append(line)
return {'value': {'order_line': order_line}}
return {'value': {'order_line': order_line, 'amount_untaxed': False, 'amount_tax': False, 'amount_total': False}}
def test_procurements_done(self, cr, uid, ids, context=None):
for sale in self.browse(cr, uid, ids, context=context):
+1 -1
View File
@@ -37,7 +37,7 @@ class sale_order_line(osv.osv):
to_cur = self.pool.get('product.pricelist').browse(cr, uid, [pricelist])[0].currency_id.id
if product:
product = self.pool['product.product'].browse(cr, uid, product, context=context)
purchase_price = product.standard_price
purchase_price = product.price_get(ptype='standard_price', context=dict(context, currency_id=to_cur))[product.id]
to_uom = res.get('product_uom', uom)
if to_uom != product.uom_id.id:
purchase_price = self.pool['product.uom']._compute_price(cr, uid, product.uom_id.id, purchase_price, to_uom)
+1
View File
@@ -30,6 +30,7 @@ class sale_report(osv.osv):
'warehouse_id': fields.many2one('stock.warehouse', 'Warehouse',readonly=True),
'state': fields.selection([
('draft', 'Quotation'),
('sent', 'Quotation Sent'),
('waiting_date', 'Waiting Schedule'),
('manual', 'Manual In Progress'),
('progress', 'In Progress'),
+15 -14
View File
@@ -138,20 +138,21 @@ class product_product(osv.osv):
domain_move_in += self._get_domain_dates(cr, uid, ids, context=context) + [('state', 'not in', ('done', 'cancel', 'draft'))] + domain_products
domain_move_out += self._get_domain_dates(cr, uid, ids, context=context) + [('state', 'not in', ('done', 'cancel', 'draft'))] + domain_products
domain_quant += domain_products
if context.get('lot_id') or context.get('owner_id') or context.get('package_id'):
if context.get('lot_id'):
domain_quant.append(('lot_id', '=', context['lot_id']))
if context.get('owner_id'):
domain_quant.append(('owner_id', '=', context['owner_id']))
if context.get('package_id'):
domain_quant.append(('package_id', '=', context['package_id']))
moves_in = []
moves_out = []
else:
domain_move_in += domain_move_in_loc
domain_move_out += domain_move_out_loc
moves_in = self.pool.get('stock.move').read_group(cr, uid, domain_move_in, ['product_id', 'product_qty'], ['product_id'], context=context)
moves_out = self.pool.get('stock.move').read_group(cr, uid, domain_move_out, ['product_id', 'product_qty'], ['product_id'], context=context)
if context.get('lot_id'):
domain_quant.append(('lot_id', '=', context['lot_id']))
if context.get('owner_id'):
domain_quant.append(('owner_id', '=', context['owner_id']))
owner_domain = ('restrict_partner_id', '=', context['owner_id'])
domain_move_in.append(owner_domain)
domain_move_out.append(owner_domain)
if context.get('package_id'):
domain_quant.append(('package_id', '=', context['package_id']))
domain_move_in += domain_move_in_loc
domain_move_out += domain_move_out_loc
moves_in = self.pool.get('stock.move').read_group(cr, uid, domain_move_in, ['product_id', 'product_qty'], ['product_id'], context=context)
moves_out = self.pool.get('stock.move').read_group(cr, uid, domain_move_out, ['product_id', 'product_qty'], ['product_id'], context=context)
domain_quant += domain_quant_loc
quants = self.pool.get('stock.quant').read_group(cr, uid, domain_quant, ['product_id', 'qty'], ['product_id'], context=context)
+4 -4
View File
@@ -1053,7 +1053,7 @@ class stock_picking(osv.osv):
# If we encounter an UoM that is smaller than the default UoM or the one already chosen, use the new one instead.
product_uom = {} # Determines UoM used in pack operations
for move in picking.move_lines:
for move in [x for x in picking.move_lines if x.state not in ('done', 'cancel')]:
if not product_uom.get(move.product_id.id):
product_uom[move.product_id.id] = move.product_id.uom_id
if move.product_uom.id != move.product_id.uom_id.id and move.product_uom.factor > product_uom[move.product_id.id].factor:
@@ -1136,7 +1136,7 @@ class stock_picking(osv.osv):
prevals[key[0]] = [val_dict]
# prevals var holds the operations in order to create them in the same order than the picking stock moves if possible
processed_products = set()
for move in picking.move_lines:
for move in [x for x in picking.move_lines if x.state not in ('done', 'cancel')]:
if move.product_id.id not in processed_products:
vals += prevals.get(move.product_id.id, [])
processed_products.add(move.product_id.id)
@@ -1247,7 +1247,7 @@ class stock_picking(osv.osv):
prod2move_ids = {}
still_to_do = []
#make a dictionary giving for each product, the moves and related quantity that can be used in operation links
for move in picking.move_lines:
for move in [x for x in picking.move_lines if x.state not in ('done', 'cancel')]:
if not prod2move_ids.get(move.product_id.id):
prod2move_ids[move.product_id.id] = [{'move': move, 'remaining_qty': move.product_qty}]
else:
@@ -2532,7 +2532,7 @@ class stock_move(models.Model):
}
if context.get('source_location_id'):
defaults['location_id'] = context['source_location_id']
new_move = self.copy(cr, uid, move.id, defaults)
new_move = self.copy(cr, uid, move.id, defaults, context=context)
ctx = context.copy()
ctx['do_not_propagate'] = True
+1
View File
@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from . import test_stock_flow
from . import test_owner_available
@@ -0,0 +1,75 @@
# Author: Leonardo Pistone
# Copyright 2015 Camptocamp SA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from openerp.addons.stock.tests.common import TestStockCommon
class TestVirtualAvailable(TestStockCommon):
def setUp(self):
super(TestVirtualAvailable, self).setUp()
self.env['stock.quant'].create({
'product_id': self.productA.id,
'location_id': self.stock_location,
'qty': 30.0,
})
self.env['stock.quant'].create({
'product_id': self.productA.id,
'location_id': self.stock_location,
'qty': 10.0,
'owner_id': self.ref('base.res_partner_4'),
})
self.picking_out = self.env['stock.picking'].create({
'picking_type_id': self.ref('stock.picking_type_out')})
self.env['stock.move'].create({
'name': 'a move',
'product_id': self.productA.id,
'product_uom_qty': 3.0,
'product_uom': self.productA.uom_id.id,
'picking_id': self.picking_out.id,
'location_id': self.stock_location,
'location_dest_id': self.customer_location})
self.picking_out_2 = self.env['stock.picking'].create({
'picking_type_id': self.ref('stock.picking_type_out')})
self.env['stock.move'].create({
'restrict_partner_id': self.ref('base.res_partner_4'),
'name': 'another move',
'product_id': self.productA.id,
'product_uom_qty': 5.0,
'product_uom': self.productA.uom_id.id,
'picking_id': self.picking_out_2.id,
'location_id': self.stock_location,
'location_dest_id': self.customer_location})
def test_without_owner(self):
self.assertAlmostEqual(40.0, self.productA.virtual_available)
self.picking_out.action_assign()
self.picking_out_2.action_assign()
self.assertAlmostEqual(32.0, self.productA.virtual_available)
def test_with_owner(self):
prod_context = self.productA.with_context(
owner_id=self.ref('base.res_partner_4')
)
self.assertAlmostEqual(10.0, prod_context.virtual_available)
self.picking_out.action_assign()
self.picking_out_2.action_assign()
self.assertAlmostEqual(5.0, prod_context.virtual_available)
@@ -48,8 +48,7 @@ class stock_transfer_details(models.TransientModel):
picking = self.pool.get('stock.picking').browse(cr, uid, picking_id, context=context)
items = []
packs = []
if not picking.pack_operation_ids:
picking.do_prepare_partial()
picking.do_prepare_partial()
for op in picking.pack_operation_ids:
item = {
'packop_id': op.id,
+2 -2
View File
@@ -245,11 +245,11 @@ class stock_quant(osv.osv):
move_obj = self.pool.get('account.move')
for cost, qty in quant_cost_qty.items():
move_lines = self._prepare_account_move_line(cr, uid, move, qty, cost, credit_account_id, debit_account_id, context=context)
period_id = context.get('force_period', self.pool.get('account.period').find(cr, uid, move.date, context=context)[0])
period_id = context.get('force_period', self.pool.get('account.period').find(cr, uid, context=context)[0])
move_obj.create(cr, uid, {'journal_id': journal_id,
'line_id': move_lines,
'period_id': period_id,
'date': move.date,
'date': fields.date.context_today(self, cr, uid, context=context),
'ref': move.picking_id.name}, context=context)
#def _reconcile_single_negative_quant(self, cr, uid, to_solve_quant, quant, quant_neg, qty, context=None):
+44 -31
View File
@@ -91,7 +91,7 @@ class stock_landed_cost(osv.osv):
),
'state': fields.selection([('draft', 'Draft'), ('done', 'Posted'), ('cancel', 'Cancelled')], 'State', readonly=True, track_visibility='onchange', copy=False),
'account_move_id': fields.many2one('account.move', 'Journal Entry', readonly=True, copy=False),
'account_journal_id': fields.many2one('account.journal', 'Account Journal', required=True),
'account_journal_id': fields.many2one('account.journal', 'Account Journal', required=True, states={'done': [('readonly', True)]}),
}
_defaults = {
@@ -121,41 +121,43 @@ class stock_landed_cost(osv.osv):
Afterwards, for the goods that are already out of stock, we should create the out moves
"""
aml_obj = self.pool.get('account.move.line')
aml_obj.create(cr, uid, {
base_line = {
'name': line.name,
'move_id': move_id,
'product_id': line.product_id.id,
'quantity': line.quantity,
'debit': line.additional_landed_cost,
'account_id': debit_account_id
}, context=context)
aml_obj.create(cr, uid, {
'name': line.name,
'move_id': move_id,
'product_id': line.product_id.id,
'quantity': line.quantity,
'credit': line.additional_landed_cost,
'account_id': credit_account_id
}, context=context)
}
debit_line = dict(base_line, account_id=debit_account_id)
credit_line = dict(base_line, account_id=credit_account_id)
diff = line.additional_landed_cost
if diff > 0:
debit_line['debit'] = diff
credit_line['credit'] = diff
else:
# negative cost, reverse the entry
debit_line['credit'] = -diff
credit_line['debit'] = -diff
aml_obj.create(cr, uid, debit_line, context=context)
aml_obj.create(cr, uid, credit_line, context=context)
#Create account move lines for quants already out of stock
if qty_out > 0:
aml_obj.create(cr, uid, {
'name': line.name + ": " + str(qty_out) + _(' already out'),
'move_id': move_id,
'product_id': line.product_id.id,
'quantity': qty_out,
'credit': line.additional_landed_cost * qty_out / line.quantity,
'account_id': debit_account_id
}, context=context)
aml_obj.create(cr, uid, {
'name': line.name + ": " + str(qty_out) + _(' already out'),
'move_id': move_id,
'product_id': line.product_id.id,
'quantity': qty_out,
'debit': line.additional_landed_cost * qty_out / line.quantity,
'account_id': already_out_account_id
}, context=context)
debit_line = dict(debit_line,
name=(line.name + ": " + str(qty_out) + _(' already out')),
quantity=qty_out)
credit_line = dict(credit_line,
name=(line.name + ": " + str(qty_out) + _(' already out')),
quantity=qty_out)
diff = diff * qty_out / line.quantity
if diff > 0:
debit_line['debit'] = diff
credit_line['credit'] = diff
else:
# negative cost, reverse the entry
debit_line['credit'] = -diff
credit_line['debit'] = -diff
aml_obj.create(cr, uid, debit_line, context=context)
aml_obj.create(cr, uid, credit_line, context=context)
return True
def _create_account_move(self, cr, uid, cost, context=None):
@@ -193,6 +195,8 @@ class stock_landed_cost(osv.osv):
quant_obj = self.pool.get('stock.quant')
for cost in self.browse(cr, uid, ids, context=context):
if cost.state != 'draft':
raise UserError(_('Only draft landed costs can be validated'))
if not cost.valuation_adjustment_lines or not self._check_sum(cr, uid, cost, context=context):
raise UserError(_('You cannot validate a landed cost which has no valid valuation lines.'))
move_id = self._create_account_move(cr, uid, cost, context=context)
@@ -209,6 +213,7 @@ class stock_landed_cost(osv.osv):
else:
quant_dict[quant.id] += diff
for key, value in quant_dict.items():
print value
quant_obj.write(cr, uid, key, {'cost': value}, context=context)
qty_out = 0
for quant in line.move_id.quant_ids:
@@ -219,8 +224,16 @@ class stock_landed_cost(osv.osv):
return True
def button_cancel(self, cr, uid, ids, context=None):
self.write(cr, uid, ids, {'state': 'cancel'}, context=context)
return True
cost = self.browse(cr, uid, ids, context=context)
if cost.state == 'done':
raise UserError(_('Validated landed costs cannot be cancelled, '
'but you could create negative landed costs to reverse them'))
return cost.write({'state': 'cancel'})
def unlink(self, cr, uid, ids, context=None):
# cancel or raise first
self.button_cancel(cr, uid, ids, context)
return super(stock_landed_cost, self).unlink(cr, uid, ids, context=context)
def compute_landed_cost(self, cr, uid, ids, context=None):
line_obj = self.pool.get('stock.valuation.adjustment.lines')
@@ -66,7 +66,7 @@
<group>
<separator string="Valuation Adjustments"/>
</group>
<group>
<group states='draft'>
<button name="compute_landed_cost" string="Compute" type="object" class="oe_right"/>
</group>
</group>
+3 -2
View File
@@ -534,14 +534,15 @@ class Home(http.Controller):
@http.route([
'/web/css/<xmlid>',
'/web/css/<xmlid>/<version>',
'/web/css.<int:page>/<xmlid>/<version>',
], type='http', auth='public')
def css_bundle(self, xmlid, version=None, **kw):
def css_bundle(self, xmlid, version=None, page=None, **kw):
try:
bundle = AssetsBundle(xmlid)
except QWebTemplateNotFound:
return request.not_found()
response = request.make_response(bundle.css(), [('Content-Type', 'text/css')])
response = request.make_response(bundle.css(page), [('Content-Type', 'text/css')])
return make_conditional(response, bundle.last_modified, max_age=BUNDLE_MAXAGE)
class WebClient(http.Controller):
+7 -3
View File
@@ -479,9 +479,13 @@ openerp.PropertiesMixin = _.extend({}, openerp.EventDispatcherMixin, {
if (tmp === val)
return;
if (key === 'value' && self.field && self.field.type === 'float' && tmp && val){
var precision = self.field.digits ? self.field.digits[1] : 2;
if (openerp.web.float_is_zero(tmp - val, precision))
return;
var digits = self.field.digits;
if (digits !== 0){
digits = digits ? digits[1] : 2;
if (openerp.web.float_is_zero(tmp - val, digits)){
return;
}
}
}
changed = true;
self.__getterSetterInternalMap[key] = val;
@@ -157,6 +157,9 @@
position: relative;
opacity: 0.75;
top: 26px; }
:root .openerp .oe_kanban_view .oe_kanban_group_title_vertical {
writing-mode: lr-tb\9;
}
.openerp .oe_kanban_view .oe_kanban_add, .openerp .oe_kanban_view .oe_kanban_header .oe_dropdown_toggle {
margin-left: 4px;
cursor: pointer;
@@ -859,7 +859,7 @@ function getColor(val, pct, col, noGradient, custSec) {
/** Fix Raphael display:none tspan dy attribute bug */
function setDy(elem, fontSize, txtYpos) {
if ((!ie || ie > 9) && elem.node.firstChild.attributes.dy) {
if ((!ie || ie > 8) && elem.node.firstChild.attributes.dy) {
elem.node.firstChild.attributes.dy.value = 0;
}
}
+10 -2
View File
@@ -61,6 +61,15 @@ class Website(openerp.addons.web.controllers.main.Home):
return http.redirect_with_hash(redirect)
return r
@http.route('/website/lang/<lang>', type='http', auth="public", website=True, multilang=False)
def change_lang(self, lang, r='/', **kwargs):
if lang == 'default':
lang = request.website.default_lang_code
r = '/%s%s' % (lang, r or '/')
redirect = werkzeug.utils.redirect(r or ('/%s' % lang), 303)
redirect.set_cookie('website_lang', lang)
return redirect
@http.route('/page/<page:page>', type='http', auth="public", website=True)
def page(self, page, **opt):
values = {
@@ -225,7 +234,6 @@ class Website(openerp.addons.web.controllers.main.Home):
return request.registry["ir.ui.view"].customize_template_get(
request.cr, request.uid, key, full=full, bundles=bundles,
context=request.context)
@http.route('/website/get_view_translations', type='json', auth='public', website=True)
def get_view_translations(self, xml_id, lang=None):
lang = lang or request.context.get('lang')
@@ -290,7 +298,7 @@ class Website(openerp.addons.web.controllers.main.Home):
uploads.append({'website_url': url})
name = url.split("/").pop() # recover filename
attachment_id = Attachments.create(request.cr, request.uid, {
'name':name,
'name': name,
'type': 'url',
'url': url,
'res_model': 'ir.ui.view',
+34 -9
View File
@@ -45,6 +45,16 @@ class ir_http(orm.AbstractModel):
else:
request.uid = request.session.uid
bots = "bot|crawl|slurp|spider|curl|wget".split("|")
def is_a_bot(self):
# We don't use regexp and ustr voluntarily
# timeit has been done to check the optimum method
ua = request.httprequest.environ.get('HTTP_USER_AGENT', '').lower()
try:
return any(bot in ua for bot in self.bots)
except UnicodeDecodeError:
return any(bot in ua.encode('ascii', 'ignore') for bot in self.bots)
def _dispatch(self):
first_pass = not hasattr(request, 'website')
request.website = None
@@ -57,7 +67,10 @@ class ir_http(orm.AbstractModel):
# in all cases, website processes them
request.website_enabled = True
request.website_multilang = request.website_enabled and func and func.routing.get('multilang', True)
request.website_multilang = (
request.website_enabled and
func and func.routing.get('multilang', func.routing['type'] == 'http')
)
if 'geoip' not in request.session:
record = {}
@@ -78,6 +91,7 @@ class ir_http(orm.AbstractModel):
record = self.geo_ip_resolver.record_by_addr(request.httprequest.remote_addr) or {}
request.session['geoip'] = record
cook_lang = request.httprequest.cookies.get('website_lang')
if request.website_enabled:
try:
if func:
@@ -92,10 +106,13 @@ class ir_http(orm.AbstractModel):
request.context['website_id'] = request.website.id
langs = [lg[0] for lg in request.website.get_languages()]
path = request.httprequest.path.split('/')
if first_pass:
if request.website_multilang:
is_a_bot = self.is_a_bot()
# If the url doesn't contains the lang and that it's the first connection, we to retreive the user preference if it exists.
if not path[1] in langs and not request.httprequest.cookies.get('session_id'):
if not path[1] in langs and not is_a_bot:
request.lang = cook_lang or request.lang
if request.lang not in langs:
# Try to find a similar lang. Eg: fr_BE and fr_FR
short = request.lang.split('_')[0]
@@ -104,14 +121,16 @@ class ir_http(orm.AbstractModel):
request.lang = langs_withshort[0]
else:
request.lang = request.website.default_lang_code
# We redirect with the right language in url
if request.lang != request.website.default_lang_code:
path.insert(1, request.lang)
path = '/'.join(path) or '/'
return request.redirect(path + '?' + request.httprequest.query_string)
else:
request.lang = request.website.default_lang_code
if request.lang != request.website.default_lang_code:
path.insert(1, request.lang)
path = '/'.join(path) or '/'
redirect = request.redirect(path + '?' + request.httprequest.query_string)
redirect.set_cookie('website_lang', request.lang)
return redirect
request.context['lang'] = request.lang
if not request.context.get('tz'):
request.context['tz'] = request.session['geoip'].get('time_zone')
@@ -122,11 +141,17 @@ class ir_http(orm.AbstractModel):
if request.lang == request.website.default_lang_code:
# If language is in the url and it is the default language, redirect
# to url without language so google doesn't see duplicate content
return request.redirect(path + '?' + request.httprequest.query_string, code=301)
resp = request.redirect(path + '?' + request.httprequest.query_string, code=301)
if cook_lang != request.lang: # If default lang setted in url directly
resp.set_cookie('website_lang', request.lang)
return resp
return self.reroute(path)
# bind modified context
request.website = request.website.with_context(request.context)
return super(ir_http, self)._dispatch()
resp = super(ir_http, self)._dispatch()
if not cook_lang:
resp.set_cookie('website_lang', request.lang)
return resp
def reroute(self, path):
if not hasattr(request, 'rerouting'):
+2 -1
View File
@@ -80,7 +80,8 @@ def is_multilang_url(local_url, langs=None):
router = request.httprequest.app.get_db_router(request.db).bind('')
# Force to check method to POST. Odoo uses methods : ['POST'] and ['GET', 'POST']
func = router.match(path, method='POST', query_args=query_string)[0]
return func.routing.get('website', False) and func.routing.get('multilang', True)
return (func.routing.get('website', False) and
func.routing.get('multilang', func.routing['type'] == 'http'))
except Exception:
return False
+24
View File
@@ -351,6 +351,30 @@
});
});
if (!$('.js_change_lang').length) {
// in case template is not up to date...
var links = $('ul.js_language_selector li a:not([data-oe-id])');
var m = $(_.min(links, function(l) { return $(l).attr('href').length; })).attr('href');
links.each(function() {
var t = $(this).attr('href');
var l = (t === m) ? "default" : t.split('/')[1];
$(this).data('lang', l).addClass('js_change_lang');
});
}
$(document).on('click', '.js_change_lang', function(e) {
e.preventDefault();
var self = $(this);
// retrieve the hash before the redirect
var redirect = {
lang: self.data('lang'),
url: self.attr('href'),
hash: location.hash
};
location.href = _.str.sprintf("/website/lang/%(lang)s?r=%(url)s%(hash)s", redirect);
});
/* ----- KANBAN WEBSITE ---- */
$('.js_kanban').each(function () {
website.init_kanban(this);
+40 -27
View File
@@ -29,15 +29,15 @@
<template id="website.submenu" name="Submenu">
<li t-if="not submenu.child_id" t-att-class="
((submenu.url != '/' and request.httprequest.path.startswith(submenu.url)) or
((submenu.url and submenu.url != '/' and request.httprequest.path.startswith(submenu.url)) or
request.httprequest.path == submenu.url) and 'active'
">
<a t-att-href="(website.menu_id.child_id[0] == submenu) and '/' or submenu.url" t-ignore="true" t-att-target="'blank' if submenu.new_window else None">
<a t-att-href="(website.menu_id.child_id[0] == submenu) and '/' or submenu.url" t-ignore="true" t-att-target="'_blank' if submenu.new_window else None">
<span t-field="submenu.name"/>
</a>
</li>
<li t-if="submenu.child_id" t-attf-class="dropdown #{
((submenu.url != '/' and [1 for submenu in submenu.child_id if request.httprequest.path.startswith(submenu.url)]) or
((submenu.url and submenu.url != '/' and [1 for submenu in submenu.child_id if request.httprequest.path.startswith(submenu.url)]) or
request.httprequest.path == submenu.url) and 'active'
}">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
@@ -394,10 +394,14 @@
<template id="language_selector">
<ul class="list-inline js_language_selector mt16" t-if="(request and request.website_multilang and len(languages) &gt; 1) or (website and editable)">
<li t-foreach="languages" t-as="lg">
<a t-att-href="url_for(request.httprequest.path + '?' + keep_query(), lang=lg[0])"
t-att-data-default-lang="editable and 'true' if website and lg[0] == website.default_lang_code else None">
<t t-esc="lg[1].split('/').pop()"/>
</a>
<a
t-att-href="url_for(request.httprequest.path + '?' + keep_query(), lang=lg[0])"
t-att-data-default-lang="editable and 'true' if website and lg[0] == website.default_lang_code else None"
t-att-data-lang="lg[0]"
class="js_change_lang"
>
<t t-esc="lg[1].split('/').pop()"/>
</a>
</li>
<li groups="base.group_website_publisher">
<t t-set="url_return" t-value="url_for('', '[lang]') + '?' + keep_query()"/>
@@ -513,6 +517,13 @@
<template id="info" name="Odoo Information">
<t t-call="website.layout">
<div id="wrap">
</div>
</t>
</template>
<template id="show_website_info" inherit_id="website.info" customize_show="True" name="Show Odoo Information">
<xpath expr="//div[@id='wrap']" position="inside">
<div class="oe_structure">
<section class="container">
@@ -539,35 +550,37 @@
</dt>
<dd>
<span t-raw="app.summary"/>
</dd><dd class="text-muted">
</dd><dd class="text-muted" groups='base.group_no_one'>
Technical name: <span t-field="app.name"/>, updated: <span t-field="app.write_date"/>, author: <span t-field="app.author"/>
</dd>
</dl>
<h2>Installed Modules</h2>
<dl class="dl-horizontal">
<t t-foreach="modules" t-as="app">
<dt>
<span t-raw="app.shortdesc"/>
</dt>
<dd t-if="app.summary">
<span t-raw="app.summary"/>
</dd><dd>
Technical name: <span t-field="app.name"/>,
updated: <span t-field="app.write_date"/>
<t t-if="app.author">
, author: <span t-field="app.author"/>
</t>
</dd>
</t>
</dl>
<div groups='base.group_no_one'>
<h2>Installed Modules</h2>
<dl class="dl-horizontal">
<t t-foreach="modules" t-as="app">
<dt>
<span t-raw="app.shortdesc"/>
</dt>
<dd t-if="app.summary">
<span t-raw="app.summary"/>
</dd><dd>
Technical name: <span t-field="app.name"/>,
updated: <span t-field="app.write_date"/>
<t t-if="app.author">
, author: <span t-field="app.author"/>
</t>
</dd>
</t>
</dl>
</div>
</section>
</div>
</div>
</t>
</xpath>
</template>
<template id="default_page">
+8
View File
@@ -365,6 +365,14 @@ class Post(models.Model):
self.env.user.sudo().add_karma(post.forum_id.karma_gen_question_new)
return post
@api.multi
def check_mail_message_access(self, operation, model_obj=None):
for post in self:
# Make sure only author or moderator can edit/delete messages
if operation in ('write', 'unlink') and not post.can_edit:
raise KarmaError('Not enough karma to edit a post.')
return super(Post, self).check_mail_message_access(operation, model_obj=model_obj)
@api.multi
@api.depends('name', 'post_type')
def name_get(self):
+9
View File
@@ -978,3 +978,12 @@ class website_sale(http.Controller):
}
ret['lines'] = self.order_lines_2_google_api(order.order_line)
return ret
@http.route(['/shop/get_unit_price'], type='json', auth="public", methods=['POST'], website=True)
def get_unit_price(self, product_ids, add_qty, **kw):
cr, uid, context, pool = request.cr, request.uid, request.context, request.registry
products = pool['product.product'].browse(cr, uid, product_ids, context=context)
partner = pool['res.users'].browse(cr, uid, uid, context=context).partner_id
pricelist_id = request.session.get('sale_order_code_pricelist_id') or partner.property_product_pricelist.id
prices = pool['product.pricelist'].price_rule_get_multi(cr, uid, [], [(product, add_qty, partner) for product in products], context=context)
return {product_id: prices[product_id][pricelist_id][0] for product_id in product_ids}
@@ -19,17 +19,56 @@ $('.oe_website_sale').each(function () {
});
});
$(oe_website_sale).on("change", 'input[name="add_qty"]', function (event) {
product_ids = [];
var product_dom = $("ul.js_add_cart_variants[data-attribute_value_ids]").first();
product_dom.data("attribute_value_ids").forEach(function(entry) {
product_ids.push(entry[0]);});
var qty = $(event.target).closest('form').find('input[name="add_qty"]').val();
openerp.jsonRpc("/shop/get_unit_price", 'call', {'product_ids': product_ids,'add_qty': parseInt(qty)})
.then(function (data) {
var current = product_dom.data("attribute_value_ids");
for(var j=0; j < current.length; j++){
current[j][2] = data[current[j][0]];
}
product_dom.attr("data-attribute_value_ids", JSON.stringify(current)).trigger("change");
});
});
// change for css
$(oe_website_sale).on('mouseup touchend', '.js_publish', function (ev) {
$(ev.currentTarget).parents(".thumbnail").toggleClass("disabled");
});
$(oe_website_sale).on("change", ".oe_cart input.js_quantity", function () {
$(oe_website_sale).on("change", ".oe_cart input.js_quantity", function (event) {
var $input = $(this);
var value = parseInt($input.val(), 10);
var $dom = $(event.target).closest('tr');
var default_price = parseFloat($dom.find('.text-danger > span.oe_currency_value').text());
var $dom_optional = $dom.nextUntil(':not(.optional_product.info)');
var line_id = parseInt($input.data('line-id'),10);
var product_id = parseInt($input.data('product-id'),10);
var product_ids = [product_id];
$dom_optional.each(function(){
product_ids.push($(this).find('span[data-oe-model="product.product"]').data('oe-id'));
});
if (isNaN(value)) value = 0;
openerp.jsonRpc("/shop/cart/update_json", 'call', {
openerp.jsonRpc("/shop/get_unit_price", 'call', {
'product_ids': product_ids,
'add_qty': value})
.then(function (res) {
//basic case
$dom.find('span.oe_currency_value').last().text(res[product_id].toFixed(2));
$dom.find('.text-danger').toggle(res[product_id]<default_price && (default_price-res[product_id] > default_price/100));
//optional case
$dom_optional.each(function(){
var id = $(this).find('span[data-oe-model="product.product"]').data('oe-id');
var price = parseFloat($(this).find(".text-danger > span.oe_currency_value").text());
$(this).find("span.oe_currency_value").last().text(res[id].toFixed(2));
$(this).find('.text-danger').toggle(res[id]<price && (price-res[id]>price/100));
});
openerp.jsonRpc("/shop/cart/update_json", 'call', {
'line_id': line_id,
'product_id': parseInt($input.data('product-id'),10),
'set_qty': value})
@@ -57,6 +96,7 @@ $('.oe_website_sale').each(function () {
$input.val(data.quantity);
}
});
});
});
// hack to add and rome from cart with json
@@ -112,12 +152,13 @@ $('.oe_website_sale').each(function () {
$img.attr("src", "/website/image/product.product/" + $(this).val() + "/image");
});
$(oe_website_sale).on('change', 'input.js_variant_change, select.js_variant_change', function (ev) {
var $ul = $(this).parents('ul.js_add_cart_variants:first');
$(oe_website_sale).on('change', 'input.js_variant_change, select.js_variant_change, ul[data-attribute_value_ids]', function (ev) {
var $ul = $(ev.target).closest('ul.js_add_cart_variants');
var $parent = $ul.closest('.js_product');
var $product_id = $parent.find('input.product_id').first();
var $price = $parent.find(".oe_price:first .oe_currency_value");
var $default_price = $parent.find(".oe_default_price:first .oe_currency_value");
var $optional_price = $parent.find(".oe_optional:first .oe_currency_value");
var variant_ids = $ul.data("attribute_value_ids");
var values = [];
$parent.find('input.js_variant_change:checked, select.js_variant_change').each(function () {
@@ -133,8 +174,10 @@ $('.oe_website_sale').each(function () {
$default_price.html(price_to_str(variant_ids[k][3]));
if (variant_ids[k][3]-variant_ids[k][2]>0.2) {
$default_price.closest('.oe_website_sale').addClass("discount");
$optional_price.closest('.oe_optional').show().css('text-decoration', 'line-through');
} else {
$default_price.closest('.oe_website_sale').removeClass("discount");
$optional_price.closest('.oe_optional').hide();
}
product_id = variant_ids[k][0];
break;
+8 -3
View File
@@ -619,16 +619,15 @@
<div class="text-muted" t-esc="'\n'.join(line.name.splitlines()[1:])"/>
</td>
<td class="text-center" name="price">
<t t-if="(compute_currency(line.product_id.lst_price) - line.price_unit) &gt; 1">
<del class="text-danger" style="white-space: nowrap;"
<del class="text-danger" t-att-style="'' if (compute_currency(line.product_id.lst_price) - line.price_unit) &gt; 1 else 'display: none;'" style="text-decoration: line-through; white-space: nowrap;"
t-field="line.product_id.lst_price" t-field-options='{
"widget": "monetary",
"from_currency": "line.company_id.currency_id",
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
}'/>&amp;nbsp;
</t>
<span t-field="line.price_unit" style="white-space: nowrap;" t-field-options='{
"widget": "monetary",
"from_currency": "website_sale_order.pricelist_id.currency_id",
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
}'/>
</td>
@@ -917,16 +916,19 @@
<div class="col-sm-6 text-right">Subtotal: </div>
<div class="col-sm-6"><span style="white-space: nowrap;" t-field="website_sale_order.amount_untaxed" t-field-options='{
"widget": "monetary",
"from_currency": "website_sale_order.pricelist_id.currency_id",
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
}'/></div>
<div class="col-sm-6 text-right">Taxes: </div>
<div class="col-sm-6"><span style="white-space: nowrap;" t-field="website_sale_order.amount_tax" t-field-options='{
"widget": "monetary",
"from_currency": "website_sale_order.pricelist_id.currency_id",
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
}'/></div>
<div class="col-sm-6 text-right"><h4>Total To Pay: </h4></div>
<div class="col-sm-6"><h4><span style="white-space: nowrap;" t-field="website_sale_order.amount_total" t-field-options='{
"widget": "monetary",
"from_currency": "website_sale_order.pricelist_id.currency_id",
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
}'/></h4></div>
</div>
@@ -986,6 +988,7 @@
<td class="text-center">
<span t-field="line.price_unit" style="white-space: nowrap;" t-field-options='{
"widget": "monetary",
"from_currency": "website_sale_order.pricelist_id.currency_id",
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
}'/>
</td>
@@ -1201,6 +1204,7 @@
<th class="text-right">
<h3><span t-field="website_sale_order.amount_total" style="white-space: nowrap;" t-field-options='{
"widget": "monetary",
"from_currency": "website_sale_order.pricelist_id.currency_id",
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
}'/></h3>
</th>
@@ -1210,6 +1214,7 @@
<td class="text-right">
<span t-field="website_sale_order.amount_tax" style="white-space: nowrap;" t-field-options='{
"widget": "monetary",
"from_currency": "website_sale_order.pricelist_id.currency_id",
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
}'/>
</td>
@@ -22,7 +22,7 @@ class website_sale_options(website_sale):
r.qcontext['optional_product_ids'] = optional_product_ids
return r
@http.route(['/shop/cart/update_option'], type='http', auth="public", methods=['POST'], website=True)
@http.route(['/shop/cart/update_option'], type='http', auth="public", methods=['POST'], website=True, multilang=False)
def cart_options_update_json(self, product_id, add_qty=1, set_qty=0, goto_shop=None, lang=None, **kw):
cr, uid, context, pool = request.cr, request.uid, request.context, request.registry
if lang:
@@ -48,13 +48,11 @@ class sale_order(osv.Model):
})
# select linked product
option_ids = [l.id for l in so.order_line if l.linked_line_id.id == line.id]
option_ids = [l for l in so.order_line if l.linked_line_id.id == line.id]
if option_ids:
# update line
sol.write(cr, SUPERUSER_ID, option_ids, {
'product_uom_qty': value.get('quantity')
}, context=context)
# update line
for l in option_ids:
super(sale_order, self)._cart_update(cr, uid, ids, l.product_id.id, l.id, add_qty, set_qty, context=context, **kwargs)
value['option_ids'] = option_ids
value['option_ids'] = [l.id for l in option_ids]
return value
@@ -10,7 +10,7 @@ $(document).ready(function () {
openerp.jsonRpc("/shop/modal", 'call', {
'product_id': product_id,
kwargs: {
context: openerp.website.get_context()
context: _.extend({'quantity': quantity}, openerp.website.get_context())
},
}).then(function (modal) {
var $modal = $(modal);
@@ -63,6 +63,34 @@ $(document).ready(function () {
$('ul.js_add_cart_variants').each(function () {
$('input.js_variant_change, select.js_variant_change', this).first().trigger('change');
});
$modal.on("change", 'input[name="add_qty"]', function (event) {
var product_id = $($modal.find('span.oe_price[data-oe-model="product.product"]').first()).data('oe-id');
var default_price = parseInt($('.text-danger.oe_default_price > span.oe_currency_value').text());
var $dom = $(event.target).closest('tr');
var qty = $dom.find('input[name="add_qty"]').val();
var product_ids = [product_id];
var $products_dom = [];
$modal.find("ul.js_add_cart_variants[data-attribute_value_ids]").each(function(){
var $el = $(this);
$products_dom.push($el);
_.each($el.data("attribute_value_ids"), function (values) {
product_ids.push(values[0]);
});
});
openerp.jsonRpc("/shop/get_unit_price", 'call', {'product_ids': product_ids, 'add_qty': parseInt(qty)})
.then(function (data) {
for(var i=0; i < $products_dom.length; i++) {
current = $products_dom[i].data("attribute_value_ids");
for(var j=0; j < current.length; j++){
current[j][2] = data[current[j][0]];
}
$products_dom[i].attr("data-attribute_value_ids", JSON.stringify(current)).trigger("change");
}
$dom.find(".oe_price .oe_currency_value").text(data[product_id].toFixed(2));
$dom.find('.text-danger.oe_default_price').toggle(data[product_id]<default_price && (default_price-data[product_id]>default_price/100)).css('text-decoration', 'line-through');
});
});
});
return false;
});
@@ -102,15 +102,13 @@
</div>
</td>
<td>
<t t-if="(compute_currency(product.lst_price) - product.price) &gt; 0.1">
<span class="text-danger" style="text-decoration: line-through; white-space: nowrap;"
<span class="text-danger oe_optional" t-att-style="'' if (compute_currency(product.lst_price) - product.price) &gt; 0.1 else 'display: none;'" style="text-decoration: line-through; white-space: nowrap;"
t-field="product.lst_price"
t-field-options='{
"widget": "monetary",
"from_currency": "website.currency_id",
"display_currency": "user_id.partner_id.property_product_pricelist.currency_id"
}'/><br/>
</t>
<span class="oe_price" style="white-space: nowrap;"
t-field="product.price"
t-field-options='{
+1 -1
View File
@@ -50,7 +50,7 @@ def navbarify(node, navbar=None):
# reference
# no bullet_list.list_item -> don't dropdownify
if not list_item.children[1].children:
return
continue
list_item['classes'].append('dropdown')
# list_item.compact_paragraph.reference
+1 -1
View File
@@ -795,7 +795,7 @@ set through the mapping argument, the default value will be used.
Update records
--------------
Reccords can be updated using :meth:`~openerp.models.Model.write`, it takes
Records can be updated using :meth:`~openerp.models.Model.write`, it takes
a list of records to update and a mapping of updated fields to values similar
to :meth:`~openerp.models.Model.create`.
+7
View File
@@ -0,0 +1,7 @@
==================
Business Mementoes
==================
.. toctree::
Accounting Memento <https://odoo.com/documentation/functional/accounting.html>
+15
View File
@@ -0,0 +1,15 @@
Lima, Peru, 2015-04-01
Cubic ERP S.A.C. agrees to the terms of the Odoo Corporate Contributor License Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this declaration.
Signed,
Cubic ERP S.A.C. info@cubicerp.com https://github.com/CubicERP
List of contributors:
Yury Oscar Tello Canchapoma ytello@cubicerp.com https://github.com/yurytello
Virginia De la Cruz Sinchez vdelacruz@cubicerp.com https://github.com/virginiamaura
Lisbeth Gutierrez Masa lgutierrez@cubicerp.com https://github.com/lisbethgutierrez
+11
View File
@@ -0,0 +1,11 @@
Spain, 2015-04-04
I hereby agree to the terms of the Odoo Individual Contributor License
Agreement v1.0.
I declare that I am authorized and able to make this agreement and sign this
declaration.
Signed,
Pedro M. Baeza pedro.baeza@gmail.com https://github.com/pedrobaeza
+1 -1
View File
@@ -194,7 +194,7 @@ def setup(app):
app.add_config_value('versions', '', 'env')
app.connect('html-page-context', analytics)
app.add_config_value('google_analytics_key', False, 'env')
app.add_config_value('google_analytics_key', '', 'env')
def canonicalize(app, pagename, templatename, context, doctree):
""" Adds a 'canonical' URL for the current document in the rendering
+1
View File
@@ -14,6 +14,7 @@ Odoo Technical Documentation
setup
reference
api_integration
business
.. ifconfig:: todo_include_todos
+4 -3
View File
@@ -335,10 +335,11 @@ class ir_attachment(osv.osv):
if ids:
if isinstance(ids, (int, long)):
ids = [ids]
cr.execute('SELECT DISTINCT res_model, res_id FROM ir_attachment WHERE id = ANY (%s)', (ids,))
for rmod, rid in cr.fetchall():
cr.execute('SELECT DISTINCT res_model, res_id, create_uid FROM ir_attachment WHERE id = ANY (%s)', (ids,))
for rmod, rid, create_uid in cr.fetchall():
if not (rmod and rid):
require_employee = True
if create_uid != uid:
require_employee = True
continue
res_ids.setdefault(rmod,set()).add(rid)
if values:
+10 -2
View File
@@ -102,10 +102,18 @@ class ir_http(osv.AbstractModel):
def _serve_attachment(self):
domain = [('type', '=', 'binary'), ('url', '=', request.httprequest.path)]
attach = self.pool['ir.attachment'].search_read(request.cr, openerp.SUPERUSER_ID, domain, ['__last_update', 'datas', 'mimetype', 'checksum'], context=request.context)
attach = self.pool['ir.attachment'].search_read(request.cr, openerp.SUPERUSER_ID, domain, ['__last_update', 'datas', 'name', 'mimetype', 'checksum'], context=request.context)
if attach:
wdate = attach[0]['__last_update']
datas = attach[0]['datas'] or '' # support empty ir_attachment
datas = attach[0]['datas'] or ''
name = attach[0]['name']
if not datas:
if name.startswith(('http://', 'https://', '/')):
return werkzeug.utils.redirect(name, 301)
else:
return werkzeug.wrappers.Response(status=204) # NO CONTENT
response = werkzeug.wrappers.Response()
server_format = openerp.tools.misc.DEFAULT_SERVER_DATETIME_FORMAT
try:
+2 -2
View File
@@ -377,10 +377,10 @@ class ir_mail_server(osv.osv):
'''
get_param = self.pool['ir.config_parameter'].get_param
postmaster = get_param(cr, uid, 'mail.bounce.alias',
postmaster = get_param(cr, SUPERUSER_ID, 'mail.bounce.alias',
default='postmaster-odoo',
context=context,)
domain = get_param(cr, uid, 'mail.catchall.domain', context=context)
domain = get_param(cr, SUPERUSER_ID, 'mail.catchall.domain', context=context)
if postmaster and domain:
return '%s@%s' % (postmaster, domain)
+46 -5
View File
@@ -35,6 +35,8 @@ from openerp.tools.translate import _
_logger = logging.getLogger(__name__)
MAX_CSS_RULES = 4095
#--------------------------------------------------------------------
# QWeb template engine
#--------------------------------------------------------------------
@@ -1168,7 +1170,12 @@ class AssetsBundle(object):
else:
url_for = self.context.get('url_for', lambda url: url)
if css and self.stylesheets:
href = '/web/css/%s/%s' % (self.xmlid, self.version)
suffix = ''
if request:
ua = request.httprequest.user_agent
if ua.browser == "msie" and int((ua.version or '0').split('.')[0]) < 10:
suffix = '.0'
href = '/web/css%s/%s/%s' % (suffix, self.xmlid, self.version)
response.append('<link href="%s" rel="stylesheet"/>' % url_for(href))
if js:
src = '/web/js/%s/%s' % (self.xmlid, self.version)
@@ -1204,8 +1211,11 @@ class AssetsBundle(object):
self.set_cache('js', content)
return content
def css(self):
def css(self, page_number=None):
"""Generate css content from given bundle"""
if page_number is not None:
return self.css_page(page_number)
content = self.get_cache('css')
if content is None:
content = self.preprocess_css()
@@ -1224,12 +1234,43 @@ class AssetsBundle(object):
matches.append(content)
content = u'\n'.join(matches)
if self.css_errors:
return content
self.set_cache('css', content)
if not self.css_errors:
self.set_cache('css', content)
content = content.encode('utf-8')
return content
def css_page(self, page_number):
content = self.get_cache('css.%d' % (page_number,))
if page_number:
return content
if content is None:
css = self.css().decode('utf-8')
re_rules = '([^{]+\{(?:[^{}]|\{[^{}]*\})*\})'
re_selectors = '()(?:\s*@media\s*[^{]*\{)?(?:\s*(?:[^,{]*(?:,|\{(?:[^}]*\}))))'
css_url = '@import url(\'/web/css.%%d/%s/%s\');' % (self.xmlid, self.version)
pages = [[]]
page = pages[0]
page_selectors = 0
for rule in re.findall(re_rules, css):
selectors = len(re.findall(re_selectors, rule))
if page_selectors + selectors < MAX_CSS_RULES:
page_selectors += selectors
page.append(rule)
else:
pages.append([rule])
page = pages[-1]
page_selectors = selectors
if len(pages) == 1:
pages = []
for idx, page in enumerate(pages):
self.set_cache("css.%d" % (idx+1), ''.join(page))
content = '\n'.join(css_url % i for i in range(1,len(pages)+1))
self.set_cache("css.0", content)
if not content:
return self.css()
return content
def get_cache(self, type):
content = None
domain = [('url', '=', '/web/%s/%s/%s' % (type, self.xmlid, self.version))]
+14 -11
View File
@@ -119,23 +119,26 @@ class ir_translation_import_cursor(object):
# Records w/o res_id must _not_ be inserted into our db, because they are
# referencing non-existent data.
cr.execute("DELETE FROM %s WHERE res_id IS NULL AND module IS NOT NULL" % \
self._table_name)
cr.execute("DELETE FROM %s WHERE res_id IS NULL AND module IS NOT NULL" % self._table_name)
find_expr = "irt.lang = ti.lang AND irt.type = ti.type " \
" AND irt.name = ti.name AND irt.src = ti.src " \
" AND irt.module = ti.module " \
" AND ( " \
" (ti.type NOT IN ('model', 'view')) " \
" OR (ti.type = 'model' AND ti.res_id = irt.res_id) " \
" OR (ti.type = 'view' AND irt.res_id IS NULL) " \
" OR (ti.type = 'view' AND irt.res_id IS NOT NULL AND ti.res_id = irt.res_id)) "
find_expr = """
irt.lang = ti.lang
AND irt.type = ti.type
AND irt.module = ti.module
AND irt.name = ti.name
AND (ti.type IN ('field', 'help') OR irt.src = ti.src)
AND ( ti.type NOT IN ('model', 'view')
OR (ti.type = 'model' AND ti.res_id = irt.res_id)
OR (ti.type = 'view' AND (irt.res_id IS NULL OR ti.res_id = irt.res_id))
)
"""
# Step 2: update existing (matching) translations
if self._overwrite:
cr.execute("""UPDATE ONLY %s AS irt
SET value = ti.value,
state = 'translated'
src = ti.src,
state = 'translated'
FROM %s AS ti
WHERE %s AND ti.value IS NOT NULL AND ti.value != ''
""" % (self._parent_table, self._table_name, find_expr))
+7 -2
View File
@@ -38,6 +38,9 @@ from openerp.exceptions import UserError
_logger = logging.getLogger(__name__)
# Only users who can modify the user (incl. the user herself) see the real contents of these fields
USER_PRIVATE_FIELDS = ['password']
#----------------------------------------------------------
# Basic res.groups and res.users
#----------------------------------------------------------
@@ -280,8 +283,10 @@ class res_users(osv.osv):
def read(self, cr, uid, ids, fields=None, context=None, load='_classic_read'):
def override_password(o):
if 'password' in o and ('id' not in o or o['id'] != uid):
o['password'] = '********'
if ('id' not in o or o['id'] != uid):
for f in USER_PRIVATE_FIELDS:
if f in o:
o[f] = '********'
return o
if fields and (ids == [uid] or ids == uid):
+7 -3
View File
@@ -1807,7 +1807,7 @@ class BaseModel(object):
``tools.ormcache`` or ``tools.ormcache_multi``.
"""
try:
self.pool.cache.clear_prefix((self.pool.db_name, self._name))
self.pool.cache.clear_prefix((self._name,))
self.pool._any_cache_cleared = True
except AttributeError:
pass
@@ -2421,8 +2421,12 @@ class BaseModel(object):
"""
self._foreign_keys = set()
raise_on_invalid_object_name(self._name)
if context is None:
context = {}
# This prevents anything called by this method (in particular default
# values) from prefetching a field for which the corresponding column
# has not been added in database yet!
context = dict(context or {}, prefetch_fields=False)
store_compute = False
stored_fields = [] # new-style stored fields with compute
todo_end = []
+1 -17
View File
@@ -73,6 +73,7 @@ class Registry(Mapping):
self.base_registry_signaling_sequence = None
self.base_cache_signaling_sequence = None
self.cache = LRU(8192)
# Flag indicating if at least one model cache has been cleared.
# Useful only in a multi-process context.
self._any_cache_cleared = False
@@ -104,10 +105,6 @@ class Registry(Mapping):
""" Same as ``self[model_name]``. """
return self.models[model_name]
@lazy_property
def cache(self):
return RegistryManager.cache
@lazy_property
def pure_function_fields(self):
""" Return the list of pure function fields (field objects) """
@@ -292,7 +289,6 @@ class RegistryManager(object):
"""
_registries = None
_cache = None
_lock = threading.RLock()
_saved_lock = None
@@ -314,18 +310,6 @@ class RegistryManager(object):
cls._registries = LRU(size)
return cls._registries
@classproperty
def cache(cls):
""" Return the global LRU ormcache. Its keys are tuples with the
following structure: (db_name, model_name, method, args...).
"""
with cls.lock():
if cls._cache is None:
# we allocate 8192 cache entries per registry
size = 8192 * cls.registries.count
cls._cache = LRU(size)
return cls._cache
@classmethod
def lock(cls):
""" Return the current registry lock. """
+9 -10
View File
@@ -155,8 +155,8 @@ def exp_dump(db_name, format):
def dump_db_manifest(cr):
pg_version = "%d.%d" % divmod(cr._obj.connection.server_version / 100, 100)
env = openerp.api.Environment(cr, SUPERUSER_ID, {})
modules = dict([(i.name,i.latest_version) for i in env['ir.module.module'].search([('state','=','installed')])])
cr.execute("SELECT name, latest_version FROM ir_module_module WHERE state = 'installed'")
modules = dict(cr.fetchall())
manifest = {
'odoo_dump': '1',
'db_name': cr.dbname,
@@ -185,14 +185,13 @@ def dump_db(db_name, stream, backup_format='zip'):
if backup_format == 'zip':
with openerp.tools.osutil.tempdir() as dump_dir:
registry = openerp.modules.registry.RegistryManager.get(db_name)
with registry.cursor() as cr:
filestore = registry['ir.attachment']._filestore(cr, SUPERUSER_ID)
if os.path.exists(filestore):
shutil.copytree(filestore, os.path.join(dump_dir, 'filestore'))
manifest = dump_db_manifest(cr)
with open(os.path.join(dump_dir, 'manifest.json'), 'w') as fh:
json.dump(manifest, fh, indent=4)
filestore = openerp.tools.config.filestore(db_name)
if os.path.exists(filestore):
shutil.copytree(filestore, os.path.join(dump_dir, 'filestore'))
with open(os.path.join(dump_dir, 'manifest.json'), 'w') as fh:
db = openerp.sql_db.db_connect(db_name)
with db.cursor() as cr:
json.dump(dump_db_manifest(cr), fh, indent=4)
cmd.insert(-1, '--file=' + os.path.join(dump_dir, 'dump.sql'))
openerp.tools.exec_pg_command(*cmd)
if stream:
+7 -4
View File
@@ -59,7 +59,7 @@ class ormcache(object):
return lookup
def lru(self, model):
return model.pool.cache, (model.pool.db_name, model._name, self.method)
return model.pool.cache, (model._name, self.method)
def lookup(self, method, *args, **kwargs):
d, key0 = self.lru(args[0])
@@ -181,16 +181,19 @@ def log_ormcache_stats(sig=None, frame=None):
import threading
me = threading.currentThread()
me_dbname = me.dbname
entries = defaultdict(int)
for key in RegistryManager.cache.iterkeys():
entries[key[:3]] += 1
for reg in RegistryManager.registries.itervalues():
for key in reg.cache.iterkeys():
entries[key[:3]] += 1
for key, count in sorted(entries.items()):
dbname, model_name, method = key
me.dbname = dbname
stat = STAT[key]
_logger.info("%6d entries, %6d hit, %6d miss, %6d err, %4.1f%% ratio, for %s.%s",
count, stat.hit, stat.miss, stat.err, stat.ratio, model_name, method.__name__)
count, stat.hit, stat.miss, stat.err, stat.ratio, model_name, method.__name__)
me.dbname = me_dbname
# For backward compatibility
cache = ormcache