</group>
<group>
<field domain="[('company_id', '=', parent.company_id), ('journal_id', '=', parent.journal_id), ('type', '<>', 'view')]" name="account_id" on_change="onchange_account_id(product_id, parent.partner_id, parent.type, parent.fiscal_position,account_id)" groups="account.group_account_user"/>
- <field name="invoice_line_tax_id" context="{'type':parent.type}" domain="[('parent_id','=',False),('company_id', '=', parent.company_id)]" widget="many2many_tags"/>
+ <field name="invoice_line_tax_id" context="{'type':parent.get('type')}" domain="[('parent_id','=',False),('company_id', '=', parent.company_id)]" widget="many2many_tags"/>
<field domain="[('type','<>','view'), ('company_id', '=', parent.company_id)]" name="account_analytic_id" groups="analytic.group_analytic_accounting"/>
<field name="company_id" groups="base.group_multi_company" readonly="1"/>
</group>
<field name="journal_id" invisible="1"/>
<field name="period_id" invisible="1" groups="account.group_account_user"/>
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
- <field name="user_id"/>
+ <field name="user_id" string="Responsible"/>
<field name="date_due"/>
<field name="origin"/>
<field name="currency_id" groups="base.group_multi_currency"/>
<group>
<group>
<field domain="[('partner_id', '=', partner_id)]" name="partner_bank_id" on_change="onchange_partner_bank(partner_bank_id)"/>
- <field name="user_id" context="{'default_groups_ref': ['base.group_user', 'base.group_partner_manager', 'account.group_account_invoice']}"/>
+ <field name="user_id" string="Responsible" context="{'default_groups_ref': ['base.group_user', 'base.group_partner_manager', 'account.group_account_invoice']}"/>
<field name="name" invisible="1"/>
<field name="payment_term" options="{'no_create': True}"/>
</group>
</group>
<group>
<field name="origin" groups="base.group_user"/>
- <field name="name" string="Customer Reference"/>
+ <field name="name"/>
<field name="move_id" groups="account.group_account_user"/>
</group>
</group>
##############################################################################
import time
+from openerp.osv import osv
from openerp.report import report_sxw
from common_report_header import common_report_header
+
class aged_trial_report(report_sxw.rml_parse, common_report_header):
def __init__(self, cr, uid, name, context):
dates_query += ' < %s)'
args_list += (form[str(i)]['stop'],)
args_list += (self.date_from,)
- self.cr.execute('''SELECT l.partner_id, SUM(l.debit-l.credit)
+ self.cr.execute('''SELECT l.partner_id, SUM(l.debit-l.credit), l.reconcile_partial_id
FROM account_move_line AS l, account_account, account_move am
WHERE (l.account_id = account_account.id) AND (l.move_id=am.id)
AND (am.state IN %s)
AND account_account.active
AND ''' + dates_query + '''
AND (l.date <= %s)
- GROUP BY l.partner_id''', args_list)
- t = self.cr.fetchall()
- d = {}
- for i in t:
- d[i[0]] = i[1]
- history.append(d)
+ GROUP BY l.partner_id, l.reconcile_partial_id''', args_list)
+ partners_partial = self.cr.fetchall()
+ partners_amount = dict((i[0],0) for i in partners_partial)
+ for partner_info in partners_partial:
+ if partner_info[2]:
+ # in case of partial reconciliation, we want to keep the left amount in the oldest period
+ self.cr.execute('''SELECT MIN(COALESCE(date_maturity,date)) FROM account_move_line WHERE reconcile_partial_id = %s''', (partner_info[2],))
+ date = self.cr.fetchall()
+ if date and args_list[-3] <= date[0][0] <= args_list[-2]:
+ # partial reconcilation
+ self.cr.execute('''SELECT SUM(l.debit-l.credit)
+ FROM account_move_line AS l
+ WHERE l.reconcile_partial_id = %s''', (partner_info[2],))
+ unreconciled_amount = self.cr.fetchall()
+ partners_amount[partner_info[0]] += unreconciled_amount[0][0]
+ else:
+ partners_amount[partner_info[0]] += partner_info[1]
+ history.append(partners_amount)
for partner in partners:
values = {}
return self._translate('Receivable and Payable Accounts')
return ''
-report_sxw.report_sxw('report.account.aged_trial_balance', 'res.partner',
- 'addons/account/report/account_aged_partner_balance.rml',parser=aged_trial_report, header="internal landscape")
+class report_agedpartnerbalance(osv.AbstractModel):
+ _name = 'report.account.report_agedpartnerbalance'
+ _inherit = 'report.abstract_report'
+ _template = 'account.report_agedpartnerbalance'
+ _wrapped_report_class = aged_trial_report
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
if not name:
name = self.pool.get('product.product').name_get(cr, uid, [res.id], context=local_context)[0][1]
if res.description_sale:
- result['name'] += '\n'+res.description_sale
+ name += '\n'+res.description_sale
result.update({'name': name or False,'uom_id': uom_id or res.uom_id.id or False, 'price_unit': price})
'nodestroy': True,
}
- def on_change_template(self, cr, uid, ids, template_id, context=None):
+ def on_change_template(self, cr, uid, ids, template_id, date_start=False, context=None):
if not template_id:
return {}
- obj_analytic_line = self.pool.get('account.analytic.invoice.line')
- res = super(account_analytic_account, self).on_change_template(cr, uid, ids, template_id, context=context)
+ res = super(account_analytic_account, self).on_change_template(cr, uid, ids, template_id, date_start=date_start, context=context)
template = self.browse(cr, uid, template_id, context=context)
- invoice_line_ids = []
- for x in template.recurring_invoice_line_ids:
- invoice_line_ids.append((0, 0, {
- 'product_id': x.product_id.id,
- 'uom_id': x.uom_id.id,
- 'name': x.name,
- 'quantity': x.quantity,
- 'price_unit': x.price_unit,
- 'analytic_account_id': x.analytic_account_id and x.analytic_account_id.id or False,
- }))
- res['value']['fix_price_invoices'] = template.fix_price_invoices
- res['value']['invoice_on_timesheets'] = template.invoice_on_timesheets
- res['value']['hours_qtt_est'] = template.hours_qtt_est
- res['value']['amount_max'] = template.amount_max
- res['value']['to_invoice'] = template.to_invoice.id
- res['value']['pricelist_id'] = template.pricelist_id.id
- res['value']['recurring_invoices'] = template.recurring_invoices
- res['value']['recurring_interval'] = template.recurring_interval
- res['value']['recurring_rule_type'] = template.recurring_rule_type
- res['value']['recurring_invoice_line_ids'] = invoice_line_ids
+
+ if not ids:
+ res['value']['fix_price_invoices'] = template.fix_price_invoices
+ res['value']['amount_max'] = template.amount_max
+ if not ids:
+ res['value']['invoice_on_timesheets'] = template.invoice_on_timesheets
+ res['value']['hours_qtt_est'] = template.hours_qtt_est
+
+ if template.to_invoice.id:
+ res['value']['to_invoice'] = template.to_invoice.id
+ if template.pricelist_id.id:
+ res['value']['pricelist_id'] = template.pricelist_id.id
+ if not ids:
+ invoice_line_ids = []
+ for x in template.recurring_invoice_line_ids:
+ invoice_line_ids.append((0, 0, {
+ 'product_id': x.product_id.id,
+ 'uom_id': x.uom_id.id,
+ 'name': x.name,
+ 'quantity': x.quantity,
+ 'price_unit': x.price_unit,
+ 'analytic_account_id': x.analytic_account_id and x.analytic_account_id.id or False,
+ }))
+ res['value']['recurring_invoices'] = template.recurring_invoices
+ res['value']['recurring_interval'] = template.recurring_interval
+ res['value']['recurring_rule_type'] = template.recurring_rule_type
+ res['value']['recurring_invoice_line_ids'] = invoice_line_ids
return res
def onchange_recurring_invoices(self, cr, uid, ids, recurring_invoices, date_start=False, context=None):
def onchange_invoice_on_timesheets(self, cr, uid, ids, invoice_on_timesheets, context=None):
if not invoice_on_timesheets:
- return {}
+ return {'value': {'to_invoice': False}}
result = {'value': {'use_timesheets': True}}
try:
to_invoice = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'hr_timesheet_invoice', 'timesheet_invoice_factor1')
new_date = next_date+relativedelta(days=+interval)
elif contract.recurring_rule_type == 'weekly':
new_date = next_date+relativedelta(weeks=+interval)
+ elif contract.recurring_rule_type == 'yearly':
+ new_date = next_date+relativedelta(years=+interval)
else:
new_date = next_date+relativedelta(months=+interval)
self.write(cr, uid, [contract.id], {'recurring_next_date': new_date.strftime('%Y-%m-%d')}, context=context)
#
##############################################################################
from datetime import datetime
from openerp.osv import fields, osv
from openerp.tools.translate import _
-from openerp.tools import html2plaintext
+
AVAILABLE_PRIORITIES = [
('', ''),
_description = "Applicant"
_order = "id desc"
_inherit = ['mail.thread', 'ir.needaction_mixin']
+
_track = {
'stage_id': {
# this is only an heuristics; depending on your particular stage configuration it may not match all 'new' stages
'hr_recruitment.mt_applicant_stage_changed': lambda self, cr, uid, obj, ctx=None: obj.stage_id and obj.stage_id.sequence > 1,
},
}
+ _mail_mass_mailing = _('Applicants')
def _get_default_department_id(self, cr, uid, context=None):
""" Gives default department by checking if present in the context """
return int(department_ids[0][0])
return None
+ def _get_default_company_id(self, cr, uid, department_id=None, context=None):
+ company_id = False
+ if department_id:
+ department = self.pool['hr.department'].browse(cr, uid, department_id, context=context)
+ company_id = department.company_id.id if department and department.company_id else False
+ if not company_id:
+ company_id = self.pool['res.company']._company_default_get(cr, uid, 'hr.applicant', context=context)
+ return company_id
+
def _read_group_stage_ids(self, cr, uid, ids, domain, read_group_order=None, access_rights_uid=None, context=None):
access_rights_uid = access_rights_uid or uid
stage_obj = self.pool.get('hr.recruitment.stage')
'partner_mobile': fields.char('Mobile', size=32),
'type_id': fields.many2one('hr.recruitment.degree', 'Degree'),
'department_id': fields.many2one('hr.department', 'Department'),
- 'survey': fields.related('job_id', 'survey_id', type='many2one', relation='survey', string='Survey'),
- 'response': fields.integer("Response"),
+ 'survey': fields.related('job_id', 'survey_id', type='many2one', relation='survey.survey', string='Survey'),
+ 'response_id': fields.many2one('survey.user_input', "Response", ondelete='set null', oldname="response"),
'reference': fields.char('Referred By', size=128),
'source_id': fields.many2one('hr.recruitment.source', 'Source'),
'day_open': fields.function(_compute_day, string='Days to Open', \
'user_id': lambda s, cr, uid, c: uid,
'stage_id': lambda s, cr, uid, c: s._get_default_stage_id(cr, uid, c),
'department_id': lambda s, cr, uid, c: s._get_default_department_id(cr, uid, c),
- 'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'hr.applicant', context=c),
+ 'company_id': lambda s, cr, uid, c: s._get_default_company_id(cr, uid, s._get_default_department_id(cr, uid, c), c),
'color': 0,
'date_last_stage_update': fields.datetime.now,
}
if job_id:
job_record = self.pool.get('hr.job').browse(cr, uid, job_id, context=context)
department_id = job_record and job_record.department_id and job_record.department_id.id or False
- return {'value': {'department_id': department_id}}
+ user_id = job_record and job_record.user_id and job_record.user_id.id or False
+ return {'value': {'department_id': department_id, 'user_id': user_id}}
def onchange_department_id(self, cr, uid, ids, department_id=False, stage_id=False, context=None):
if not stage_id:
}
return res
+ def action_start_survey(self, cr, uid, ids, context=None):
+ context = context if context else {}
+ applicant = self.browse(cr, uid, ids, context=context)[0]
+ survey_obj = self.pool.get('survey.survey')
+ response_obj = self.pool.get('survey.user_input')
+ # create a response and link it to this applicant
+ if not applicant.response_id:
+ response_id = response_obj.create(cr, uid, {'survey_id': applicant.survey.id, 'partner_id': applicant.partner_id.id}, context=context)
+ self.write(cr, uid, ids[0], {'response_id': response_id}, context=context)
+ else:
+ response_id = applicant.response_id.id
+ # grab the token of the response and start surveying
+ response = response_obj.browse(cr, uid, response_id, context=context)
+ context.update({'survey_token': response.token})
+ return survey_obj.action_start_survey(cr, uid, [applicant.survey.id], context=context)
+
def action_print_survey(self, cr, uid, ids, context=None):
- """
- If response is available then print this response otherwise print survey form(print template of the survey).
+ """ If response is available then print this response otherwise print survey form (print template of the survey) """
+ context = context if context else {}
+ applicant = self.browse(cr, uid, ids, context=context)[0]
+ survey_obj = self.pool.get('survey.survey')
+ response_obj = self.pool.get('survey.user_input')
+ if not applicant.response_id:
+ return survey_obj.action_print_survey(cr, uid, [applicant.survey.id], context=context)
+ else:
+ response = response_obj.browse(cr, uid, applicant.response_id.id, context=context)
+ context.update({'survey_token': response.token})
+ return survey_obj.action_print_survey(cr, uid, [applicant.survey.id], context=context)
- @param self: The object pointer
- @param cr: the current row, from the database cursor,
- @param uid: the current user’s ID for security checks,
- @param ids: List of Survey IDs
- @param context: A standard dictionary for contextual values
- @return: Dictionary value for print survey form.
- """
- if context is None:
- context = {}
- record = self.browse(cr, uid, ids, context=context)
- record = record and record[0]
- context.update({'survey_id': record.survey.id, 'response_id': [record.response], 'response_no': 0, })
- value = self.pool.get("survey").action_print_survey(cr, uid, ids, context=context)
- return value
-
- def action_get_attachment_tree_view(self, cr, uid, ids, context):
- domain = ['&', ('res_model', '=', 'hr.applicant'), ('res_id', 'in', ids)]
- return {
- 'name': _('Attachments'),
- 'domain': domain,
- 'res_model': 'ir.attachment',
- 'type': 'ir.actions.act_window',
- 'view_id': False,
- 'view_mode': 'tree,form',
- 'view_type': 'form',
- 'limit': 80,
- 'context': "{'default_res_model': '%s'}" % (self._name)
- }
+ def action_get_attachment_tree_view(self, cr, uid, ids, context=None):
+ model, action_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'action_attachment')
+ action = self.pool.get(model).read(cr, uid, action_id, context=context)
+ action['context'] = {'default_res_model': self._name, 'default_res_id': ids[0]}
+ action['domain'] = str(['&', ('res_model', '=', self._name), ('res_id', 'in', ids)])
+ return action
def message_get_suggested_recipients(self, cr, uid, ids, context=None):
recipients = super(hr_applicant, self).message_get_suggested_recipients(cr, uid, ids, context=context)
val = msg.get('from').split('<')[0]
defaults = {
'name': msg.get('subject') or _("No Subject"),
- 'partner_name':val,
+ 'partner_name': val,
'email_from': msg.get('from'),
'email_cc': msg.get('cc'),
'user_id': False,
def create(self, cr, uid, vals, context=None):
if context is None:
context = {}
+ context['mail_create_nolog'] = True
if vals.get('department_id') and not context.get('default_department_id'):
context['default_department_id'] = vals.get('department_id')
-
+ if vals.get('job_id') or context.get('default_job_id'):
+ job_id = vals.get('job_id') or context.get('default_job_id')
+ vals.update(self.onchange_job(cr, uid, [], job_id, context=context)['value'])
obj_id = super(hr_applicant, self).create(cr, uid, vals, context=context)
applicant = self.browse(cr, uid, obj_id, context=context)
if applicant.job_id:
- self.pool.get('hr.job').message_post(cr, uid, [applicant.job_id.id], body=_('Applicant <b>created</b>'), subtype="hr_recruitment.mt_job_new_applicant", context=context)
+ name = applicant.partner_name if applicant.partner_name else applicant.name
+ self.pool['hr.job'].message_post(
+ cr, uid, [applicant.job_id.id],
+ body=_('New application from %s') % name,
+ subtype="hr_recruitment.mt_job_applicant_new", context=context)
return obj_id
def write(self, cr, uid, ids, vals, context=None):
ids = [ids]
res = True
- # user_id change: update date_start
+ # user_id change: update date_open
if vals.get('user_id'):
- vals['date_start'] = fields.datetime.now()
+ vals['date_open'] = fields.datetime.now()
# stage_id: track last stage before update
if 'stage_id' in vals:
vals['date_last_stage_update'] = fields.datetime.now()
else:
res = super(hr_applicant, self).write(cr, uid, ids, vals, context=context)
+ # post processing: if job changed, post a message on the job
+ if vals.get('job_id'):
+ for applicant in self.browse(cr, uid, ids, context=None):
+ name = applicant.partner_name if applicant.partner_name else applicant.name
+ self.pool['hr.job'].message_post(
+ cr, uid, [vals['job_id']],
+ body=_('New application from %s') % name,
+ subtype="hr_recruitment.mt_job_applicant_new", context=context)
+
# post processing: if stage changed, post a message in the chatter
if vals.get('stage_id'):
stage = self.pool['hr.recruitment.stage'].browse(cr, uid, vals['stage_id'], context=context)
address_id = self.pool.get('res.partner').address_get(cr, uid, [applicant.partner_id.id], ['contact'])['contact']
contact_name = self.pool.get('res.partner').name_get(cr, uid, [applicant.partner_id.id])[0][1]
if applicant.job_id and (applicant.partner_name or contact_name):
- applicant.job_id.write({'no_of_recruitment': applicant.job_id.no_of_recruitment - 1})
+ applicant.job_id.write({'no_of_hired_employee': applicant.job_id.no_of_hired_employee + 1}, context=context)
+ create_ctx = dict(context, mail_broadcast=True)
emp_id = hr_employee.create(cr, uid, {'name': applicant.partner_name or contact_name,
'job_id': applicant.job_id.id,
'address_home_id': address_id,
'address_id': applicant.company_id and applicant.company_id.partner_id and applicant.company_id.partner_id.id or False,
'work_email': applicant.department_id and applicant.department_id.company_id and applicant.department_id.company_id.email or False,
'work_phone': applicant.department_id and applicant.department_id.company_id and applicant.department_id.company_id.phone or False,
- })
+ }, context=create_ctx)
self.write(cr, uid, [applicant.id], {'emp_id': emp_id}, context=context)
+ self.pool['hr.job'].message_post(
+ cr, uid, [applicant.job_id.id],
+ body=_('New Employee %s Hired') % applicant.partner_name if applicant.partner_name else applicant.name,
+ subtype="hr_recruitment.mt_job_applicant_hired", context=context)
else:
raise osv.except_osv(_('Warning!'), _('You must define an Applied Job and a Contact Name for this applicant.'))
_inherit = "hr.job"
_name = "hr.job"
_inherits = {'mail.alias': 'alias_id'}
+
+ def _get_attached_docs(self, cr, uid, ids, field_name, arg, context=None):
+ res = {}
+ attachment_obj = self.pool.get('ir.attachment')
+ for job_id in ids:
+ applicant_ids = self.pool.get('hr.applicant').search(cr, uid, [('job_id', '=', job_id)], context=context)
+ res[job_id] = attachment_obj.search(
+ cr, uid, [
+ '|',
+ '&', ('res_model', '=', 'hr.job'), ('res_id', '=', job_id),
+ '&', ('res_model', '=', 'hr.applicant'), ('res_id', 'in', applicant_ids)
+ ], context=context)
+ return res
+
_columns = {
- 'survey_id': fields.many2one('survey', 'Interview Form', help="Choose an interview form for this job position and you will be able to print/answer this interview from all applicants who apply for this job"),
+ 'survey_id': fields.many2one('survey.survey', 'Interview Form', help="Choose an interview form for this job position and you will be able to print/answer this interview from all applicants who apply for this job"),
'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="restrict", required=True,
help="Email alias for this job position. New emails will automatically "
"create new applicants for this job position."),
'address_id': fields.many2one('res.partner', 'Job Location', help="Address where employees are working"),
+ 'application_ids': fields.one2many('hr.applicant', 'job_id', 'Applications'),
+ 'manager_id': fields.related('department_id', 'manager_id', type='many2one', string='Department Manager', relation='hr.employee', readonly=True, store=True),
+ 'document_ids': fields.function(_get_attached_docs, type='one2many', relation='ir.attachment', string='Applications'),
+ 'user_id': fields.many2one('res.users', 'Recruitment Responsible', track_visibility='onchange'),
+ 'color': fields.integer('Color Index'),
}
+
def _address_get(self, cr, uid, context=None):
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
return user.company_id.partner_id.id
+
_defaults = {
'address_id': _address_get
}
return res
def action_print_survey(self, cr, uid, ids, context=None):
- if context is None:
- context = {}
- datas = {}
- record = self.browse(cr, uid, ids, context=context)[0]
- if record.survey_id:
- datas['ids'] = [record.survey_id.id]
- datas['model'] = 'survey.print'
- context.update({'response_id': [0], 'response_no': 0})
- return {
- 'type': 'ir.actions.report.xml',
- 'report_name': 'survey.form',
- 'datas': datas,
- 'context': context,
- 'nodestroy': True,
- }
+ job = self.browse(cr, uid, ids, context=context)[0]
+ survey_id = job.survey_id.id
+ return self.pool.get('survey.survey').action_print_survey(cr, uid, [survey_id], context=context)
+
+ def action_get_attachment_tree_view(self, cr, uid, ids, context=None):
+ #open attachments of job and related applicantions.
+ model, action_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'action_attachment')
+ action = self.pool.get(model).read(cr, uid, action_id, context=context)
+ applicant_ids = self.pool.get('hr.applicant').search(cr, uid, [('job_id', 'in', ids)], context=context)
+ action['context'] = {'default_res_model': self._name, 'default_res_id': ids[0]}
+ action['domain'] = str(['|', '&', ('res_model', '=', 'hr.job'), ('res_id', 'in', ids), '&', ('res_model', '=', 'hr.applicant'), ('res_id', 'in', applicant_ids)])
+ return action
+
+ def action_set_no_of_recruitment(self, cr, uid, id, value, context=None):
+ return self.write(cr, uid, [id], {'no_of_recruitment': value}, context=context)
class applicant_category(osv.osv):
}
$.when(recipient_done).done(function (partner_ids) {
var context = {
- 'default_composition_mode': default_composition_mode,
'default_parent_id': self.id,
'default_body': mail.ChatterUtils.get_text2html(self.$el ? (self.$el.find('textarea:not(.oe_compact)').val() || '') : ''),
'default_attachment_ids': _.map(self.attachment_ids, function (file) {return file.id;}),
'default_partner_ids': partner_ids,
+ 'default_is_log': self.is_log,
'mail_post_autofollow': true,
'mail_post_autofollow_partner_ids': partner_ids,
'is_private': self.is_private
};
- if (self.is_log) {
- _.extend(context, {'mail_compose_log': true});
- }
if (default_composition_mode != 'reply' && self.context.default_model && self.context.default_res_id) {
context.default_model = self.context.default_model;
context.default_res_id = self.context.default_res_id;
this.$('.oe_mail_expand').on('click', this.on_expand);
this.$('.oe_mail_reduce').on('click', this.on_expand);
this.$('.oe_mail_action_model').on('click', this.on_record_clicked);
+ this.$('.oe_mail_action_author').on('click', this.on_record_author_clicked);
},
on_record_clicked: function (event) {
+ event.preventDefault();
+ var self = this;
var state = {
'model': this.model,
'id': this.res_id,
'title': this.record_name
};
session.webclient.action_manager.do_push_state(state);
+ this.context.params = {
+ model: this.model,
+ res_id: this.res_id,
+ };
+ this.thread.ds_thread.call("message_redirect_action", {context: this.context}).then(function(action){
+ self.do_action(action);
+ });
+ },
+
+ on_record_author_clicked: function (event) {
+ event.preventDefault();
+ var partner_id = $(event.target).data('partner');
+ var state = {
+ 'model': 'res.partner',
+ 'id': partner_id,
+ 'title': this.record_name
+ };
+ session.webclient.action_manager.do_push_state(state);
+ var action = {
+ type:'ir.actions.act_window',
+ view_type: 'form',
+ view_mode: 'form',
+ res_model: 'res.partner',
+ views: [[false, 'form']],
+ res_id: partner_id,
+ }
+ this.do_action(action);
},
/* Call the on_compose_message on the thread of this message. */
'picking_ids': fields.one2many('stock.picking.in', 'purchase_id', 'Picking List', readonly=True, help="This is the list of incoming shipments that have been generated for this purchase order."),
'shipped':fields.boolean('Received', readonly=True, select=True, help="It indicates that a picking has been done"),
'shipped_rate': fields.function(_shipped_rate, string='Received Ratio', type='float'),
- 'invoiced': fields.function(_invoiced, string='Invoice Received', type='boolean', help="It indicates that an invoice has been paid"),
+ 'invoiced': fields.function(_invoiced, string='Invoice Received', type='boolean', help="It indicates that an invoice has been validated"),
'invoiced_rate': fields.function(_invoiced_rate, string='Invoiced', type='float'),
'invoice_method': fields.selection([('manual','Based on Purchase Order lines'),('order','Based on generated draft invoice'),('picking','Based on incoming shipments')], 'Invoicing Control', required=True,
readonly=True, states={'draft':[('readonly',False)], 'sent':[('readonly',False)]},
'''
assert len(ids) == 1, 'This option should only be used for a single id at a time'
self.signal_send_rfq(cr, uid, ids)
- datas = {
- 'model': 'purchase.order',
- 'ids': ids,
- 'form': self.read(cr, uid, ids[0], context=context),
- }
- return {'type': 'ir.actions.report.xml', 'report_name': 'purchase.quotation', 'datas': datas, 'nodestroy': True}
+ return self.pool['report'].get_action(cr, uid, ids, 'purchase.report_purchasequotation', context=context)
#TODO: implement messages system
def wkf_confirm_order(self, cr, uid, ids, context=None):
_name = 'mail.mail'
_inherit = 'mail.mail'
- def _postprocess_sent_message(self, cr, uid, mail, context=None):
- if mail.model == 'purchase.order':
+ def _postprocess_sent_message(self, cr, uid, mail, context=None, mail_sent=True):
+ if mail_sent and mail.model == 'purchase.order':
self.pool.get('purchase.order').signal_send_rfq(cr, uid, [mail.res_id])
- return super(mail_mail, self)._postprocess_sent_message(cr, uid, mail=mail, context=context)
+ return super(mail_mail, self)._postprocess_sent_message(cr, uid, mail=mail, context=context, mail_sent=mail_sent)
class product_template(osv.Model):
this._internal_set_values(result.value, processed);
}
if (!_.isEmpty(result.warning)) {
- instance.web.dialog($(QWeb.render("CrashManager.warning", result.warning)), {
+ new instance.web.Dialog(this, {
+ size: 'medium',
title:result.warning.title,
- modal: true,
buttons: [
- {text: _t("Ok"), click: function() { $(this).dialog("close"); }}
+ {text: _t("Ok"), click: function() { this.parents('.modal').modal('hide'); }}
]
- });
+ }, QWeb.render("CrashManager.warning", result.warning)).open();
}
return $.Deferred().resolve();
};
})
.value();
-
var d = new instance.web.Dialog(this, {
title: _t("Set Default"),
args: {
var defs = [];
_.each(this.to_replace, function(el) {
defs.push(el[0].replace(el[1]));
+ if (el[1].children().length) {
+ el[0].$el.append(el[1].children());
+ }
});
this.to_replace = [];
return $.when.apply($, defs);
var tagname = $tag[0].nodeName.toLowerCase();
if (this.tags_registry.contains(tagname)) {
this.tags_to_init.push($tag);
- return $tag;
+ return (tagname === 'button') ? this.process_button($tag) : $tag;
}
var fn = self['process_' + tagname];
if (fn) {
return $tag;
}
},
+ process_button: function ($button) {
+ var self = this;
+ $button.children().each(function() {
+ self.process($(this));
+ });
+ return $button;
+ },
process_widget: function($widget) {
this.widgets_to_init.push($widget);
return $widget;
template: 'WidgetButton',
init: function(field_manager, node) {
node.attrs.type = node.attrs['data-button-type'];
+ this.is_stat_button = /\boe_stat_button\b/.test(node.attrs['class']);
+ this.icon = node.attrs.icon && "<span class=\"fa " + node.attrs.icon + " fa-fw\"></span>";
this._super(field_manager, node);
this.force_disabled = false;
this.string = (this.node.attrs.string || '').replace(/_/g, '');
var exec_action = function() {
if (self.node.attrs.confirm) {
var def = $.Deferred();
- var dialog = instance.web.dialog($('<div/>').text(self.node.attrs.confirm), {
+ var dialog = new instance.web.Dialog(this, {
title: _t('Confirm'),
- modal: true,
buttons: [
{text: _t("Cancel"), click: function() {
- $(this).dialog("close");
+ this.parents('.modal').modal('hide');
}
},
{text: _t("Ok"), click: function() {
var self2 = this;
self.on_confirmed().always(function() {
- $(self2).dialog("close");
+ self2.parents('.modal').modal('hide');
});
}
}
],
- beforeClose: function() {
- def.resolve();
- },
- });
+ }, $('<div/>').text(self.node.attrs.confirm)).open();
+ dialog.on("closing", null, function() {def.resolve();});
return def.promise();
} else {
return self.on_confirmed();
var self = this;
var context = this.build_context();
-
return this.view.do_execute_action(
_.extend({}, this.node.attrs, {context: context}),
this.view.dataset, this.view.datarecord.id, function (reason) {
}
});
+instance.web.form.FieldCharDomain = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, {
+ init: function(field_manager, node) {
+ this._super.apply(this, arguments);
+ },
+ start: function() {
+ var self = this;
+ this._super.apply(this, arguments);
+ this.on("change:effective_readonly", this, function () {
+ this.display_field();
+ this.render_value();
+ });
+ this.display_field();
+ return this._super();
+ },
+ render_value: function() {
+ this.$('button.select_records').css('visibility', this.get('effective_readonly') ? 'hidden': '');
+ },
+ set_value: function(value_) {
+ var self = this;
+ this.set('value', value_ || false);
+ this.display_field();
+ },
+ display_field: function() {
+ var self = this;
+ this.$el.html(instance.web.qweb.render("FieldCharDomain", {widget: this}));
+ if (this.get('value')) {
+ var model = this.options.model || this.field_manager.get_field_value(this.options.model_field);
+ var domain = instance.web.pyeval.eval('domain', this.get('value'));
+ var ds = new instance.web.DataSetStatic(self, model, self.build_context());
+ ds.call('search_count', [domain]).then(function (results) {
+ $('.oe_domain_count', self.$el).text(results + ' records selected');
+ $('button span', self.$el).text(' Change selection');
+ });
+ } else {
+ $('.oe_domain_count', this.$el).text('0 record selected');
+ $('button span', this.$el).text(' Select records');
+ };
+ this.$('.select_records').on('click', self.on_click);
+ },
+ on_click: function(ev) {
+ event.preventDefault();
+ var self = this;
+ var model = this.options.model || this.field_manager.get_field_value(this.options.model_field);
+ this.pop = new instance.web.form.SelectCreatePopup(this);
+ this.pop.select_element(
+ model, {title: 'Select records...'},
+ [], this.build_context());
+ this.pop.on("elements_selected", self, function(element_ids) {
+ if (this.pop.$('input.oe_list_record_selector').prop('checked')) {
+ var search_data = this.pop.searchview.build_search_data();
+ var domain_done = instance.web.pyeval.eval_domains_and_contexts({
+ domains: search_data.domains,
+ contexts: search_data.contexts,
+ group_by_seq: search_data.groupbys || []
+ }).then(function (results) {
+ return results.domain;
+ });
+ }
+ else {
+ var domain = [["id", "in", element_ids]];
+ var domain_done = $.Deferred().resolve(domain);
+ }
+ $.when(domain_done).then(function (domain) {
+ var domain = self.pop.dataset.domain.concat(domain || []);
+ self.set_value(domain);
+ });
+ });
+ },
+});
+
instance.web.DateTimeWidget = instance.web.Widget.extend({
template: "web.datepicker",
jqueryui_object: 'datetimepicker',
}
});
+/**
+ The PercentPie field expect a float from 0 to 100.
+*/
+instance.web.form.FieldPercentPie = instance.web.form.AbstractField.extend({
+ template: 'FieldPercentPie',
+
+ render_value: function() {
+ var value = this.get('value'),
+ formatted_value = Math.round(value || 0) + '%',
+ svg = this.$('svg')[0];
+
+ svg.innerHTML = "";
+ nv.addGraph(function() {
+ var width = 42, height = 42;
+ var chart = nv.models.pieChart()
+ .width(width)
+ .height(height)
+ .margin({top: 0, right: 0, bottom: 0, left: 0})
+ .donut(true)
+ .showLegend(false)
+ .showLabels(false)
+ .tooltips(false)
+ .color(['#7C7BAD','#DDD'])
+ .donutRatio(0.62);
+
+ d3.select(svg)
+ .datum([{'x': 'value', 'y': value}, {'x': 'complement', 'y': 100 - value}])
+ .transition()
+ .call(chart)
+ .attr('style', 'width: ' + width + 'px; height:' + height + 'px;');
+
+ d3.select(svg)
+ .append("text")
+ .attr({x: width/2, y: height/2 + 3, 'text-anchor': 'middle'})
+ .style({"font-size": "10px", "font-weight": "bold"})
+ .text(formatted_value);
+
+ return chart;
+ });
+
+ }
+});
+
+/**
+ The FieldBarChart expectsa list of values (indeed)
+*/
+instance.web.form.FieldBarChart = instance.web.form.AbstractField.extend({
+ template: 'FieldBarChart',
+
+ render_value: function() {
+ var value = JSON.parse(this.get('value'));
+ var svg = this.$('svg')[0];
+ svg.innerHTML = "";
+ nv.addGraph(function() {
+ var width = 34, height = 34;
+ var chart = nv.models.discreteBarChart()
+ .x(function (d) { return d.tooltip })
+ .y(function (d) { return d.value })
+ .width(width)
+ .height(height)
+ .margin({top: 0, right: 0, bottom: 0, left: 0})
+ .tooltips(false)
+ .showValues(false)
+ .transitionDuration(350)
+ .showXAxis(false)
+ .showYAxis(false);
+
+ d3.select(svg)
+ .datum([{key: 'values', values: value}])
+ .transition()
+ .call(chart)
+ .attr('style', 'width: ' + (width + 4) + 'px; height: ' + (height + 8) + 'px;');
+
+ nv.utils.windowResize(chart.update);
+
+ return chart;
+ });
+
+ }
+});
+
instance.web.form.FieldSelection = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, {
template: 'FieldSelection',
init: function(parent) {
this._super(parent, {
title: _.str.sprintf(_t("Add %s"), parent.string),
- width: 312,
+ size: 'medium',
});
},
start: function() {
delete this.$drop_down;
}
if (this.$input) {
- this.$input.closest(".ui-dialog .ui-dialog-content").off('scroll');
+ this.$input.closest(".modal .modal-content").off('scroll');
this.$input.off('keyup blur autocompleteclose autocompleteopen ' +
'focus focusout change keydown');
delete this.$input;
return;
}
var pop = new instance.web.form.FormOpenPopup(self);
- pop.show_element(
- self.field.relation,
- self.get("value"),
- self.build_context(),
- {
- title: _t("Open: ") + self.string
- }
- );
- pop.on('write_completed', self, function(){
- self.display_value = {};
- self.display_value_backup = {};
- self.render_value();
- self.focus();
- self.trigger('changed_value');
+ var context = self.build_context().eval();
+ var model_obj = new instance.web.Model(self.field.relation);
+ model_obj.call('get_formview_id', [self.get("value"), context]).then(function(view_id){
+ pop.show_element(
+ self.field.relation,
+ self.get("value"),
+ self.build_context(),
+ {
+ title: _t("Open: ") + self.string,
+ view_id: view_id
+ }
+ );
+ pop.on('write_completed', self, function(){
+ self.display_value = {};
+ self.display_value_backup = {};
+ self.render_value();
+ self.focus();
+ self.trigger('changed_value');
+ });
});
});
self.$input.autocomplete("close");
}
}, 50);
- this.$input.closest(".ui-dialog .ui-dialog-content").on('scroll', this, close_autocomplete);
+ this.$input.closest(".modal .modal-content").on('scroll', this, close_autocomplete);
self.ed_def = $.Deferred();
self.uned_def = $.Deferred();
}
self.floating = false;
}
- if (used && self.get("value") === false && ! self.no_ed) {
+ if (used && self.get("value") === false && ! self.no_ed && (self.options.no_create === false || self.options.no_create === undefined)) {
self.ed_def.reject();
self.uned_def.reject();
self.ed_def = $.Deferred();
.html(link);
if (! this.options.no_open)
$link.click(function () {
- self.do_action({
- type: 'ir.actions.act_window',
- res_model: self.field.relation,
- res_id: self.get("value"),
- views: [[false, 'form']],
- target: 'current',
- context: self.build_context().eval(),
+ var context = self.build_context().eval();
+ var model_obj = new instance.web.Model(self.field.relation);
+ model_obj.call('get_formview_action', [self.get("value"), context]).then(function(action){
+ self.do_action(action);
});
return false;
});
else
return $.when();
}).done(function () {
- if (!self.o2m.options.reload_on_button) {
+ var ds = self.o2m.dataset;
+ var cached_records = _.any([ds.to_create, ds.to_delete, ds.to_write], function(value) {
+ return value.length;
+ });
+ if (!self.o2m.options.reload_on_button && !cached_records) {
self.handle_button(name, id, callback);
}else {
self.handle_button(name, id, function(){
var self = this;
this.renderElement();
var dialog = new instance.web.Dialog(this, {
- min_width: '800px',
dialogClass: 'oe_act_window',
- close: function() {
- self.check_exit(true);
- },
title: this.options.title || "",
}, this.$el).open();
+ dialog.on('closing', this, function (e){
+ self.check_exit(true);
+ });
this.$buttonpane = dialog.$buttons;
this.start();
},
},
destroy: function () {
this.trigger('closed');
- if (this.$el.is(":data(dialog)")) {
- this.$el.dialog('close');
+ if (this.$el.is(":data(bs.modal)")) {
+ this.$el.parents('.modal').modal('hide');
}
this._super();
},
* Options on attribute ; "blockui" {Boolean} block the UI or not
* during the file is uploading
*/
-instance.web.form.FieldMany2ManyBinaryMultiFiles = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, {
+instance.web.form.FieldMany2ManyBinaryMultiFiles = instance.web.form.AbstractField.extend({
template: "FieldBinaryFileUploader",
init: function(field_manager, node) {
this._super(field_manager, node);
start: function() {
this._super(this);
this.$el.on('change', 'input.oe_form_binary_file', this.on_file_change );
- this.on("change:effective_readonly", this, function () {
- this.render_value();
- });
},
set_value: function(value_) {
value_ = value_ || [];
},
render_value: function () {
var self = this;
- this.$('.oe_add').css('visibility', this.get('effective_readonly') ? 'hidden': '');
this.read_name_values().then(function (ids) {
var render = $(instance.web.qweb.render('FieldBinaryFileUploader.files', {'widget': self, 'values': ids}));
render.on('click', '.oe_delete', _.bind(self.on_file_delete, self));
});
/**
+ This widget is intended to be used on stat button numeric fields. It will display
+ the value many2many and one2many. It is a read-only field that will
+ display a simple string "<value of field> <label of the field>"
+*/
+instance.web.form.StatInfo = instance.web.form.AbstractField.extend({
+ is_field_number: true,
+ init: function() {
+ this._super.apply(this, arguments);
+ this.internal_set_value(0);
+ },
+ set_value: function(value_) {
+ if (value_ === false || value_ === undefined) {
+ value_ = 0;
+ }
+ this._super.apply(this, [value_]);
+ },
+ render_value: function() {
+ var options = {
+ value: this.get("value") || 0,
+ };
+ if (! this.node.attrs.nolabel) {
+ options.text = this.string
+ }
+ this.$el.html(QWeb.render("StatInfo", options));
+ },
+
+});
+
+
+/**
* Registry of form fields, called by :js:`instance.web.FormView`.
*
* All referenced classes must implement FieldInterface. Those represent the classes whose instances
'url' : 'instance.web.form.FieldUrl',
'text' : 'instance.web.form.FieldText',
'html' : 'instance.web.form.FieldTextHtml',
+ 'char_domain': 'instance.web.form.FieldCharDomain',
'date' : 'instance.web.form.FieldDate',
'datetime' : 'instance.web.form.FieldDatetime',
'selection' : 'instance.web.form.FieldSelection',
'reference' : 'instance.web.form.FieldReference',
'boolean' : 'instance.web.form.FieldBoolean',
'float' : 'instance.web.form.FieldFloat',
+ 'percentpie': 'instance.web.form.FieldPercentPie',
+ 'barchart': 'instance.web.form.FieldBarChart',
'integer': 'instance.web.form.FieldFloat',
'float_time': 'instance.web.form.FieldFloat',
'progressbar': 'instance.web.form.FieldProgressBar',
'monetary': 'instance.web.form.FieldMonetary',
'many2many_checkboxes': 'instance.web.form.FieldMany2ManyCheckBoxes',
'x2many_counter': 'instance.web.form.X2ManyCounter',
+ 'statinfo': 'instance.web.form.StatInfo',
});
/**
# -*- coding: utf-8 -*-
+import datetime
+import hashlib
import logging
import re
import traceback
-
import werkzeug
import werkzeug.routing
return self._dispatch()
- def _postprocess_args(self, arguments):
- if hasattr(request, 'rerouting'):
- url = request.rerouting[0]
- else:
- url = request.httprequest.url
- original_url = url
- for arg in arguments.itervalues():
- if isinstance(arg, orm.browse_record) and isinstance(arg._uid, RequestUID):
- placeholder = arg._uid
- arg._uid = request.uid
- try:
- good_slug = slug(arg)
- if str(arg.id) != placeholder.value and placeholder.value != good_slug:
- # TODO: properly recompose the url instead of using replace()
- url = url.replace(placeholder.value, good_slug)
- except KeyError:
- return self._handle_exception(werkzeug.exceptions.NotFound())
- if url != original_url:
- werkzeug.exceptions.abort(werkzeug.utils.redirect(url))
+ def _postprocess_args(self, arguments, rule):
+ if not getattr(request, 'website_enabled', False):
+ return super(ir_http, self)._postprocess_args(arguments, rule)
+
+ for arg, val in arguments.items():
+ # Replace uid placeholder by the current request.uid
+ if isinstance(val, orm.browse_record) and isinstance(val._uid, RequestUID):
+ val._uid = request.uid
+ try:
+ _, path = rule.build(arguments)
+ assert path is not None
+ except Exception:
+ return self._handle_exception(werkzeug.exceptions.NotFound())
+
+ if request.httprequest.method in ('GET', 'HEAD'):
+ generated_path = werkzeug.url_unquote_plus(path)
+ current_path = werkzeug.url_unquote_plus(request.httprequest.path)
+ if generated_path != current_path:
+ if request.lang != request.website.default_lang_code:
+ path = '/' + request.lang + path
+ return werkzeug.utils.redirect(path)
+
+ 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'], context=request.context)
+ if attach:
+ wdate = attach[0]['__last_update']
+ datas = attach[0]['datas']
+ response = werkzeug.wrappers.Response()
+ server_format = openerp.tools.misc.DEFAULT_SERVER_DATETIME_FORMAT
+ try:
+ response.last_modified = datetime.datetime.strptime(wdate, server_format + '.%f')
+ except ValueError:
+ # just in case we have a timestamp without microseconds
+ response.last_modified = datetime.datetime.strptime(wdate, server_format)
+
+ response.set_etag(hashlib.sha1(datas).hexdigest())
+ response.make_conditional(request.httprequest)
+
+ if response.status_code == 304:
+ return response
+
+ response.mimetype = attach[0]['mimetype']
+ response.data = datas.decode('base64')
+ return response
def _handle_exception(self, exception=None, code=500):
- res = super(ir_http, self)._handle_exception(exception)
- if isinstance(exception, werkzeug.exceptions.HTTPException) and hasattr(exception, 'response') and exception.response:
- return exception.response
+ try:
+ return super(ir_http, self)._handle_exception(exception)
+ except Exception:
+
+ attach = self._serve_attachment()
+ if attach:
+ return attach
+
- if getattr(request, 'website_enabled', False) and request.website:
- values = dict(
- exception=exception,
- traceback=traceback.format_exc(exception),
- )
- if exception:
- code = getattr(exception, 'code', code)
- if isinstance(exception, ir_qweb.QWebException):
- values.update(qweb_exception=exception)
- if isinstance(exception.qweb.get('cause'), openerp.exceptions.AccessError):
- code = 403
- if code == 500:
- logger.error("500 Internal Server Error:\n\n%s", values['traceback'])
- if 'qweb_exception' in values:
- view = request.registry.get("ir.ui.view")
- views = view._views_get(request.cr, request.uid, exception.qweb['template'], request.context)
- to_reset = [v for v in views if v.model_data_id.noupdate is True]
- values['views'] = to_reset
- elif code == 403:
- logger.warn("403 Forbidden:\n\n%s", values['traceback'])
-
- values.update(
- status_message=werkzeug.http.HTTP_STATUS_CODES[code],
- status_code=code,
- )
-
- if not request.uid:
- self._auth_method_public()
-
- try:
- html = request.website._render('website.%s' % code, values)
- except Exception:
- html = request.website._render('website.http_error', values)
- return werkzeug.wrappers.Response(html, status=code, content_type='text/html;charset=utf-8')
-
- return res
+ if getattr(request, 'website_enabled', False) and request.website:
+ values = dict(
+ exception=exception,
+ traceback=traceback.format_exc(exception),
+ )
+ if exception:
+ code = getattr(exception, 'code', code)
+ if isinstance(exception, ir_qweb.QWebException):
+ values.update(qweb_exception=exception)
+ if isinstance(exception.qweb.get('cause'), openerp.exceptions.AccessError):
+ code = 403
+ if code == 500:
+ logger.error("500 Internal Server Error:\n\n%s", values['traceback'])
+ if 'qweb_exception' in values:
+ view = request.registry.get("ir.ui.view")
+ views = view._views_get(request.cr, request.uid, exception.qweb['template'], request.context)
+ to_reset = [v for v in views if v.model_data_id.noupdate is True]
+ values['views'] = to_reset
+ elif code == 403:
+ logger.warn("403 Forbidden:\n\n%s", values['traceback'])
+
+ values.update(
+ status_message=werkzeug.http.HTTP_STATUS_CODES[code],
+ status_code=code,
+ )
+
+ if not request.uid:
+ self._auth_method_public()
+
+ try:
+ html = request.website._render('website.%s' % code, values)
+ except Exception:
+ html = request.website._render('website.http_error', values)
+ return werkzeug.wrappers.Response(html, status=code, content_type='text/html;charset=utf-8')
+
+ raise
class ModelConverter(ir.ir_http.ModelConverter):
- def __init__(self, url_map, model=False):
+ def __init__(self, url_map, model=False, domain='[]'):
super(ModelConverter, self).__init__(url_map, model)
+ self.domain = domain
self.regex = r'(?:[A-Za-z0-9-_]+?-)?(\d+)(?=$|/)'
def to_url(self, value):
return request.registry[self.model].browse(
request.cr, _uid, int(m.group(1)), context=request.context)
- def generate(self, cr, uid, query=None, context=None):
- return request.registry[self.model].name_search(
- cr, uid, name=query or '', context=context)
+ def generate(self, cr, uid, query=None, args=None, context=None):
+ obj = request.registry[self.model]
+ domain = eval( self.domain, (args or {}).copy())
+ if query:
+ domain.append((obj._rec_name, 'ilike', '%'+query+'%'))
+ for record in obj.search_read(cr, uid, domain=domain, fields=['write_date',obj._rec_name], context=context):
+ if record.get(obj._rec_name, False):
+ yield {'loc': (record['id'], record[obj._rec_name])}
class PageConverter(werkzeug.routing.PathConverter):
- """ Only point of this converter is to bundle pages enumeration logic
-
- Sads got: no way to get the view's human-readable name even if one exists
- """
- def generate(self, cr, uid, query=None, context=None):
+ """ Only point of this converter is to bundle pages enumeration logic """
+ def generate(self, cr, uid, query=None, args={}, context=None):
View = request.registry['ir.ui.view']
- views = View.search_read(
- cr, uid, [['page', '=', True]],
- fields=[], order='name', context=context)
- xids = View.get_external_id(
- cr, uid, [view['id'] for view in views], context=context)
-
+ views = View.search_read(cr, uid, [['page', '=', True]],
+ fields=['xml_id','priority','write_date'], order='name', context=context)
for view in views:
- xid = xids[view['id']]
- if xid and (not query or query.lower() in xid.lower()):
- yield xid
+ xid = view['xml_id'].startswith('website.') and view['xml_id'][8:] or view['xml_id']
+ # the 'page/homepage' url is indexed as '/', avoid aving the same page referenced twice
+ # when we will have an url mapping mechanism, replace this by a rule: page/homepage --> /
+ if xid=='homepage': continue
+ if query and query.lower() not in xid.lower():
+ continue
+ record = {'loc': xid}
+ if view['priority'] <> 16:
+ record['__priority'] = min(round(view['priority'] / 32.0,1), 1)
+ if view.get('write_date'):
+ record['__lastmod'] = view['write_date'][:10]
+ yield record
index = 0
maxy = 0
for p in products:
- x = p.website_size_x
- y = p.website_size_y
+ x = min(max(p.website_size_x, 1), PPR)
+ y = min(max(p.website_size_y, 1), PPR)
if index>PPG:
x = y = 1
return key_val
return False
- @http.route(['/shop/filters/'], type='http', auth="public", methods=['POST'], website=True, multilang=True)
+ @http.route(['/shop/filters'], type='http', auth="public", methods=['POST'], website=True, multilang=True)
def filters(self, category=None, **post):
index = []
filters = []
filters[index.index(cat_id)].append( cat[2] )
post.pop(key)
- url = "/shop/"
+ url = "/shop"
if category:
category_obj = request.registry.get('product.public.category')
- url = "%scategory/%s/" % (url, slug(category_obj.browse(request.cr, request.uid, int(category), context=request.context)))
+ url = "%s/category/%s" % (url, slug(category_obj.browse(request.cr, request.uid, int(category), context=request.context)))
if filters:
url = "%s?filters=%s" % (url, simplejson.dumps(filters))
if post.get("search"):
@http.route(['/shop/pricelist'], type='http', auth="public", website=True, multilang=True)
def shop_promo(self, promo=None, **post):
request.registry['website']._ecommerce_change_pricelist(request.cr, request.uid, code=promo, context=request.context)
- return request.redirect("/shop/mycart/")
+ return request.redirect("/shop/mycart")
@http.route([
- '/shop/',
- '/shop/page/<int:page>/',
- '/shop/category/<model("product.public.category"):category>/',
- '/shop/category/<model("product.public.category"):category>/page/<int:page>/'
+ '/shop',
+ '/shop/page/<int:page>',
+ '/shop/category/<model("product.public.category"):category>',
+ '/shop/category/<model("product.public.category"):category>/page/<int:page>'
], type='http', auth="public", website=True, multilang=True)
def shop(self, category=None, page=0, filters='', search='', **post):
cr, uid, context = request.cr, request.uid, request.context
ids = self.attributes_to_ids(cr, uid, filters)
domain.append(('id', 'in', ids or [0]))
- url = "/shop/"
+ url = "/shop"
product_count = product_obj.search_count(cr, uid, domain, context=context)
if search:
post["search"] = search
if filters:
post["filters"] = filters
if category:
- url = "/shop/category/%s/" % slug(category)
+ url = "/shop/category/%s" % slug(category)
pager = request.website.pager(url=url, total=product_count, page=page, step=PPG, scope=7, url_args=post)
request.context['pricelist'] = self.get_pricelist()
}
return request.website.render("website_sale.products", values)
- @http.route(['/shop/product/<model("product.template"):product>/'], type='http', auth="public", website=True, multilang=True)
+ @http.route(['/shop/product/<model("product.template"):product>'], type='http', auth="public", website=True, multilang=True)
def product(self, product, search='', category='', filters='', **kwargs):
if category:
category_obj = request.registry.get('product.public.category')
context=dict(context, mail_create_nosubcribe=True))
return werkzeug.utils.redirect(request.httprequest.referrer + "#comments")
- @http.route(['/shop/add_product/'], type='http', auth="user", methods=['POST'], website=True, multilang=True)
+ @http.route(['/shop/add_product'], type='http', auth="user", methods=['POST'], website=True, multilang=True)
def add_product(self, name=None, category=0, **post):
if not name:
name = _("New Product")
'name': name, 'public_categ_id': category
}, context=request.context)
product = Product.browse(request.cr, request.uid, product_id, context=request.context)
+
+ return request.redirect("/shop/product/%s?enable_editor=1" % slug(product.product_tmpl_id))
- return request.redirect("/shop/product/%s/?enable_editor=1" % product.product_tmpl_id.id)
-
- @http.route(['/shop/mycart/'], type='http', auth="public", website=True, multilang=True)
+ @http.route(['/shop/mycart'], type='http', auth="public", website=True, multilang=True)
def mycart(self, **post):
cr, uid, context = request.cr, request.uid, request.context
prod_obj = request.registry.get('product.product')
order = self.get_order()
if order and order.state != 'draft':
request.registry['website'].ecommerce_reset(cr, uid, context=context)
- return request.redirect('/shop/')
+ return request.redirect('/shop')
self.get_pricelist()
}
return request.website.render("website_sale.mycart", values)
- @http.route(['/shop/add_cart/'], type='http', auth="public", methods=['POST'], website=True, multilang=True)
+ @http.route(['/shop/add_cart'], type='http', auth="public", methods=['POST'], website=True, multilang=True)
def add_cart(self, product_id, remove=None, **kw):
request.registry['website']._ecommerce_add_product_to_cart(request.cr, request.uid,
product_id=int(product_id),
number=float(kw.get('number',1)),
set_number=float(kw.get('set_number',-1)),
context=request.context)
- return request.redirect("/shop/mycart/")
+ return request.redirect("/shop/mycart")
- @http.route(['/shop/change_cart/<int:order_line_id>/'], type='http', auth="public", website=True, multilang=True)
+ @http.route(['/shop/change_cart/<int:order_line_id>'], type='http', auth="public", website=True, multilang=True)
def add_cart_order_line(self, order_line_id=None, remove=None, **kw):
request.registry['website']._ecommerce_add_product_to_cart(request.cr, request.uid,
order_line_id=order_line_id, number=(remove and -1 or 1),
context=request.context)
- return request.redirect("/shop/mycart/")
+ return request.redirect("/shop/mycart")
- @http.route(['/shop/add_cart_json/'], type='json', auth="public", website=True, multilang=True)
+ @http.route(['/shop/add_cart_json'], type='json', auth="public", website=True, multilang=True)
def add_cart_json(self, product_id=None, order_line_id=None, remove=None):
quantity = request.registry['website']._ecommerce_add_product_to_cart(request.cr, request.uid,
product_id=product_id, order_line_id=order_line_id, number=(remove and -1 or 1),
order.amount_total,
request.website._render("website_sale.total", {'website_sale_order': order})]
- @http.route(['/shop/set_cart_json/'], type='json', auth="public", website=True, multilang=True)
+ @http.route(['/shop/set_cart_json'], type='json', auth="public", website=True, multilang=True)
def set_cart_json(self, path=None, product_id=None, order_line_id=None, set_number=0, json=None):
quantity = request.registry['website']._ecommerce_add_product_to_cart(request.cr, request.uid,
product_id=product_id, order_line_id=order_line_id, set_number=set_number,
order.amount_total,
request.website._render("website_sale.total", {'website_sale_order': order})]
- @http.route(['/shop/checkout/'], type='http', auth="public", website=True, multilang=True)
+ @http.route(['/shop/checkout'], type='http', auth="public", website=True, multilang=True)
def checkout(self, **post):
cr, uid, context, registry = request.cr, request.uid, request.context, request.registry
order = self.get_order()
if not order or order.state != 'draft' or not order.order_line:
request.registry['website'].ecommerce_reset(cr, uid, context=context)
- return request.redirect('/shop/')
+ return request.redirect('/shop')
# if transaction pending / done: redirect to confirmation
tx = context.get('website_sale_transaction')
if tx and tx.state != 'draft':
return request.website.render("website_sale.checkout", values)
- @http.route(['/shop/confirm_order/'], type='http', auth="public", website=True, multilang=True)
+ @http.route(['/shop/confirm_order'], type='http', auth="public", website=True, multilang=True)
def confirm_order(self, **post):
cr, uid, context, registry = request.cr, request.uid, request.context, request.registry
order_line_obj = request.registry.get('sale.order')
order = self.get_order()
if not order or order.state != 'draft' or not order.order_line:
request.registry['website'].ecommerce_reset(cr, uid, context=context)
- return request.redirect('/shop/')
+ return request.redirect('/shop')
# if transaction pending / done: redirect to confirmation
tx = context.get('website_sale_transaction')
if tx and tx.state != 'draft':
order_line_obj.write(cr, SUPERUSER_ID, [order.id], order_info, context=context)
- return request.redirect("/shop/payment/")
+ return request.redirect("/shop/payment")
- @http.route(['/shop/payment/'], type='http', auth="public", website=True, multilang=True)
+ @http.route(['/shop/payment'], type='http', auth="public", website=True, multilang=True)
def payment(self, **post):
""" Payment step. This page proposes several payment means based on available
payment.acquirer. State at this point :
order = self.get_order()
if not order or order.state != 'draft' or not order.order_line:
request.registry['website'].ecommerce_reset(cr, uid, context=context)
- return request.redirect("/shop/")
+ return request.redirect("/shop")
# alread a transaction: forward to confirmation
tx = context.get('website_sale_transaction')
if tx and tx.state != 'draft':
order = self.get_order()
if not order or not order.order_line or acquirer_id is None:
- return request.redirect("/shop/checkout/")
+ return request.redirect("/shop/checkout")
# find an already existing transaction
tx = context.get('website_sale_transaction')
'reference': order.name,
'sale_order_id': order.id,
}, context=context)
- request.httprequest.session['website_sale_transaction_id'] = tx_id
+ request.session['website_sale_transaction_id'] = tx_id
elif tx and tx.state == 'draft': # button cliked but no more info -> rewrite on tx or create a new one ?
tx.write({
'acquirer_id': acquirer_id,
cr, uid, context = request.cr, request.uid, request.context
order = request.registry['sale.order'].browse(cr, SUPERUSER_ID, sale_order_id, context=context)
- assert order.website_session_id == request.httprequest.session['website_session_id']
+ assert order.website_session_id == request.session['website_session_id']
if not order:
return {
message = '<p>The payment seems to have been canceled.</p>'
elif state == 'pending' and tx.acquirer_id.validation == 'manual':
message = '<p>Your transaction is waiting confirmation.</p>'
- message += tx.acquirer_id.post_msg
+ if tx.acquirer_id.post_msg:
+ message += tx.acquirer_id.post_msg
else:
message = '<p>Your transaction is waiting confirmation.</p>'
validation = tx.acquirer_id.validation
'validation': validation
}
- @http.route('/shop/payment/validate/', type='http', auth="public", website=True, multilang=True)
+ @http.route('/shop/payment/validate', type='http', auth="public", website=True, multilang=True)
def payment_validate(self, transaction_id=None, sale_order_id=None, **post):
""" Method that should be called by the server when receiving an update
for a transaction. State at this point :
order = self.get_order()
else:
order = request.registry['sale.order'].browse(cr, SUPERUSER_ID, sale_order_id, context=context)
- assert order.website_session_id == request.httprequest.session['website_session_id']
+ assert order.website_session_id == request.session['website_session_id']
if not order:
- return request.redirect('/shop/')
+ return request.redirect('/shop')
elif order.amount_total and not tx:
return request.redirect('/shop/mycart')
cr, uid, context = request.cr, request.uid, request.context
order = request.registry['sale.order'].browse(cr, SUPERUSER_ID, sale_order_id, context=context)
- assert order.website_session_id == request.httprequest.session['website_session_id']
+ assert order.website_session_id == request.session['website_session_id']
request.registry['website']._ecommerce_change_pricelist(cr, uid, None, context=context or {})
return request.website.render("website_sale.confirmation", {'order': order})
- @http.route(['/shop/change_sequence/'], type='json', auth="public")
+ @http.route(['/shop/change_sequence'], type='json', auth="public")
def change_sequence(self, id, sequence):
product_obj = request.registry.get('product.template')
if sequence == "top":
elif sequence == "down":
product_obj.set_sequence_down(request.cr, request.uid, [id], context=request.context)
- @http.route(['/shop/change_styles/'], type='json', auth="public")
+ @http.route(['/shop/change_styles'], type='json', auth="public")
def change_styles(self, id, style_id):
product_obj = request.registry.get('product.template')
product = product_obj.browse(request.cr, request.uid, id, context=request.context)
return not active
- @http.route(['/shop/change_size/'], type='json', auth="public")
+ @http.route(['/shop/change_size'], type='json', auth="public")
def change_size(self, id, x, y):
product_obj = request.registry.get('product.template')
product = product_obj.browse(request.cr, request.uid, id, context=request.context)
_inherit = ["product.template", "website.seo.metadata"]
_order = 'website_published desc, website_sequence desc, name'
_name = 'product.template'
+ _mail_post_access = 'read'
def _website_url(self, cr, uid, ids, field_name, arg, context=None):
res = dict.fromkeys(ids, '')
base_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
for product in self.browse(cr, uid, ids, context=context):
- res[product.id] = "%s/shop/product/%s/" % (base_url, product.id)
+ res[product.id] = "%s/shop/product/%s" % (base_url, product.id)
return res
_columns = {
res = {}
base_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
for product in self.browse(cr, uid, ids, context=context):
- res[product.id] = "%s/shop/product/%s/" % (base_url, product.product_tmpl_id.id)
+ res[product.id] = "%s/shop/product/%s" % (base_url, product.product_tmpl_id.id)
return res
_columns = {
import warnings
import babel.core
+import psutil
import psycopg2
import simplejson
import werkzeug.contrib.sessions
import openerp
from openerp.service import security, model as service_model
-import openerp.tools
+from openerp.tools.func import lazy_property
_logger = logging.getLogger(__name__)
A global proxy that always redirect to the current request object.
"""
+def replace_request_password(args):
+ # password is always 3rd argument in a request, we replace it in RPC logs
+ # so it's easier to forward logs for diagnostics/debugging purposes...
+ if len(args) > 2:
+ args = list(args)
+ args[2] = '*'
+ return tuple(args)
+
+def dispatch_rpc(service_name, method, params):
+ """ Handle a RPC call.
+
+ This is pure Python code, the actual marshalling (from/to XML-RPC) is done
+ in a upper layer.
+ """
+ try:
+ rpc_request = logging.getLogger(__name__ + '.rpc.request')
+ rpc_response = logging.getLogger(__name__ + '.rpc.response')
+ rpc_request_flag = rpc_request.isEnabledFor(logging.DEBUG)
+ rpc_response_flag = rpc_response.isEnabledFor(logging.DEBUG)
+ if rpc_request_flag or rpc_response_flag:
+ start_time = time.time()
+ start_rss, start_vms = 0, 0
+ start_rss, start_vms = psutil.Process(os.getpid()).get_memory_info()
+ if rpc_request and rpc_response_flag:
+ openerp.netsvc.log(rpc_request, logging.DEBUG, '%s.%s' % (service_name, method), replace_request_password(params))
+
+ threading.current_thread().uid = None
+ threading.current_thread().dbname = None
+ if service_name == 'common':
+ dispatch = openerp.service.common.dispatch
+ elif service_name == 'db':
+ dispatch = openerp.service.db.dispatch
+ elif service_name == 'object':
+ dispatch = openerp.service.model.dispatch
+ elif service_name == 'report':
+ dispatch = openerp.service.report.dispatch
+ else:
+ dispatch = openerp.service.wsgi_server.rpc_handlers.get(service_name)
+ result = dispatch(method, params)
+
+ if rpc_request_flag or rpc_response_flag:
+ end_time = time.time()
+ end_rss, end_vms = 0, 0
+ end_rss, end_vms = psutil.Process(os.getpid()).get_memory_info()
+ logline = '%s.%s time:%.3fs mem: %sk -> %sk (diff: %sk)' % (service_name, method, end_time - start_time, start_vms / 1024, end_vms / 1024, (end_vms - start_vms)/1024)
+ if rpc_response_flag:
+ openerp.netsvc.log(rpc_response, logging.DEBUG, logline, result)
+ else:
+ openerp.netsvc.log(rpc_request, logging.DEBUG, logline, replace_request_password(params), depth=1)
+
+ return result
+ except (openerp.osv.orm.except_orm, openerp.exceptions.AccessError, \
+ openerp.exceptions.AccessDenied, openerp.exceptions.Warning, \
+ openerp.exceptions.RedirectWarning):
+ raise
+ except openerp.exceptions.DeferredException, e:
+ _logger.exception(openerp.tools.exception_to_unicode(e))
+ openerp.tools.debugger.post_mortem(openerp.tools.config, e.traceback)
+ raise
+ except Exception, e:
+ _logger.exception(openerp.tools.exception_to_unicode(e))
+ openerp.tools.debugger.post_mortem(openerp.tools.config, sys.exc_info())
+ raise
+
def local_redirect(path, query=None, keep_hash=False, forward_debug=True, code=303):
url = path
if not query:
self.session_id = httprequest.session.sid
self.disable_db = False
self.uid = None
- self.func = None
- self.func_arguments = {}
+ self.endpoint = None
self.auth_method = None
self._cr_cm = None
self._cr = None
- self.func_request_type = None
# prevents transaction commit, use when you catch an exception during handling
self._failed = None
"""
# some magic to lazy create the cr
if not self._cr:
- self._cr = self.registry.db.cursor()
+ self._cr = self.registry.cursor()
return self._cr
def __enter__(self):
def __exit__(self, exc_type, exc_value, traceback):
_request_stack.pop()
+
if self._cr:
if exc_type is None and not self._failed:
self._cr.commit()
- else:
- # just to be explicit - happens at close() anyway
- self._cr.rollback()
self._cr.close()
# just to be sure no one tries to re-use the request
self.disable_db = True
self.uid = None
- def set_handler(self, func, arguments, auth):
+ def set_handler(self, endpoint, arguments, auth):
# is this needed ?
arguments = dict((k, v) for k, v in arguments.iteritems()
if not k.startswith("_ignored_"))
- self.func = func
- self.func_request_type = func.routing['type']
- self.func_arguments = arguments
+ endpoint.arguments = arguments
+ self.endpoint = endpoint
self.auth_method = auth
to abitrary responses. Anything returned (except None) will
be used as response."""
self._failed = exception # prevent tx commit
+ if isinstance(exception, werkzeug.exceptions.HTTPException):
+ return exception
+ raise
def _call_function(self, *args, **kwargs):
request = self
- if self.func_request_type != self._request_type:
+ if self.endpoint.routing['type'] != self._request_type:
raise Exception("%s, %s: Function declared as capable of handling request of type '%s' but called with a request of type '%s'" \
- % (self.func, self.httprequest.path, self.func_request_type, self._request_type))
+ % (self.endpoint.original, self.httprequest.path, self.endpoint.routing['type'], self._request_type))
- kwargs.update(self.func_arguments)
+ kwargs.update(self.endpoint.arguments)
# Backward for 7.0
- if getattr(self.func.method, '_first_arg_is_req', False):
+ if self.endpoint.first_arg_is_req:
args = (request,) + args
# Correct exception handling and concurency retry
# case, the request cursor is unusable. Rollback transaction to create a new one.
if self._cr:
self._cr.rollback()
- return self.func(*a, **kw)
+ return self.endpoint(*a, **kw)
if self.db:
return checked_call(self.db, *args, **kwargs)
- return self.func(*args, **kwargs)
+ return self.endpoint(*args, **kwargs)
@property
def debug(self):
else:
routes = [route]
routing['routes'] = routes
- f.routing = routing
- return f
+ @functools.wraps(f)
+ def response_wrap(*args, **kw):
+ response = f(*args, **kw)
+ if isinstance(response, Response) or f.routing_type == 'json':
+ return response
+ elif isinstance(response, werkzeug.wrappers.BaseResponse):
+ response = Response.force_type(response)
+ response.set_default()
+ return response
+ elif isinstance(response, basestring):
+ return Response(response)
+ else:
+ _logger.warn("<function %s.%s> returns an invalid response type for an http request" % (f.__module__, f.__name__))
+ return response
+ response_wrap.routing = routing
+ response_wrap.original_func = f
+ return response_wrap
return decorator
class JsonRequest(WebRequest):
mime = 'application/json'
body = simplejson.dumps(response)
- return werkzeug.wrappers.Response(
+ return Response(
body, headers=[('Content-Type', mime),
('Content-Length', len(body))])
def _handle_exception(self, exception):
"""Called within an except block to allow converting exceptions
to abitrary responses. Anything returned (except None) will
- be used as response."""
- super(JsonRequest, self)._handle_exception(exception)
- _logger.exception("Exception during JSON request handling.")
- error = {
- 'code': 200,
- 'message': "OpenERP Server Error",
- 'data': serialize_exception(exception)
- }
- if isinstance(exception, AuthenticationError):
- error['code'] = 100
- error['message'] = "OpenERP Session Invalid"
- return self._json_response(error=error)
+ be used as response."""
+ try:
+ return super(JsonRequest, self)._handle_exception(exception)
+ except Exception:
+ _logger.exception("Exception during JSON request handling.")
+ error = {
+ 'code': 200,
+ 'message': "OpenERP Server Error",
+ 'data': serialize_exception(exception)
+ }
+ if isinstance(exception, AuthenticationError):
+ error['code'] = 100
+ error['message'] = "OpenERP Session Invalid"
+ return self._json_response(error=error)
def dispatch(self):
""" Calls the method asked for by the JSON-RPC2 or JSONP request
self.params = params
def dispatch(self):
- # TODO: refactor this correctly. This is a quick fix for pos demo.
- if request.httprequest.method == 'OPTIONS' and request.func and request.func.routing.get('cors'):
- response = werkzeug.wrappers.Response(status=200)
- response.headers.set('Access-Control-Allow-Origin', request.func.routing['cors'])
- methods = 'GET, POST'
- if request.func_request_type == 'json':
- methods = 'POST'
- elif request.func.routing.get('methods'):
- methods = ', '.join(request.func.routing['methods'])
- response.headers.set('Access-Control-Allow-Methods', methods)
- response.headers.set('Access-Control-Max-Age',60*60*24)
- response.headers.set('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept')
- return response
+ if request.httprequest.method == 'OPTIONS' and request.endpoint and request.endpoint.routing.get('cors'):
+ headers = {
+ 'Access-Control-Max-Age': 60 * 60 * 24,
+ 'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept'
+ }
+ return Response(status=200, headers=headers)
r = self._call_function(**self.params)
if not r:
- r = werkzeug.wrappers.Response(status=204) # no content
+ r = Response(status=204) # no content
return r
def make_response(self, data, headers=None, cookies=None):
:type headers: ``[(name, value)]``
:param collections.Mapping cookies: cookies to set on the client
"""
- response = werkzeug.wrappers.Response(data, headers=headers)
+ response = Response(data, headers=headers)
if cookies:
for k, v in cookies.iteritems():
response.set_cookie(k, v)
return response
+ def render(self, template, qcontext=None, **kw):
+ """ Lazy render of QWeb template.
+
+ The actual rendering of the given template will occur at then end of
+ the dispatching. Meanwhile, the template and/or qcontext can be
+ altered or even replaced by a static response.
+
+ :param basestring template: template to render
+ :param dict qcontext: Rendering context to use
+ """
+ return Response(template=template, qcontext=qcontext, **kw)
+
def not_found(self, description=None):
""" Helper for 404 response, return its result from the method
"""
# flag old-style methods with req as first argument
for k, v in attrs.items():
- if inspect.isfunction(v):
- spec = inspect.getargspec(v)
+ if inspect.isfunction(v) and hasattr(v, 'original_func'):
+ spec = inspect.getargspec(v.original_func)
first_arg = spec.args[1] if len(spec.args) >= 2 else None
if first_arg in ["req", "request"]:
v._first_arg_is_req = True
class EndPoint(object):
def __init__(self, method, routing):
self.method = method
+ self.original = getattr(method, 'original_func', method)
self.routing = routing
+ self.arguments = {}
+
+ @property
+ def first_arg_is_req(self):
+ # Backward for 7.0
+ return getattr(self.method, '_first_arg_is_req', False)
+
def __call__(self, *args, **kw):
return self.method(*args, **kw)
if inspect.ismethod(mv) and hasattr(mv, 'routing'):
routing = dict(type='http', auth='user', methods=None, routes=None)
methods_done = list()
+ routing_type = None
for claz in reversed(mv.im_class.mro()):
fn = getattr(claz, mv.func_name, None)
if fn and hasattr(fn, 'routing') and fn not in methods_done:
+ fn_type = fn.routing.get('type')
+ if not routing_type:
+ routing_type = fn_type
+ else:
+ if fn_type and routing_type != fn_type:
+ _logger.warn("Subclass re-defines <function %s.%s> with different type than original."
+ " Will use original type: %r", fn.__module__, fn.__name__, routing_type)
+ fn.routing['type'] = routing_type
+ fn.original_func.routing_type = routing_type
methods_done.append(fn)
routing.update(fn.routing)
if not nodb_only or nodb_only == (routing['auth'] == "none"):
class Service(object):
"""
.. deprecated:: 8.0
- Use ``openerp.netsvc.dispatch_rpc()`` instead.
+ Use ``dispatch_rpc()`` instead.
"""
def __init__(self, session, service_name):
self.session = session
def __getattr__(self, method):
def proxy_method(*args):
- result = openerp.netsvc.dispatch_rpc(self.service_name, method, args)
+ result = dispatch_rpc(self.service_name, method, args)
return result
return proxy_method
class Model(object):
"""
.. deprecated:: 8.0
- Use the resistry and cursor in ``openerp.addons.web.http.request`` instead.
+ Use the resistry and cursor in ``openerp.http.request`` instead.
"""
def __init__(self, session, model):
self.session = session
HTTP_HOST=wsgienv['HTTP_HOST'],
REMOTE_ADDR=wsgienv['REMOTE_ADDR'],
)
- uid = openerp.netsvc.dispatch_rpc('common', 'authenticate', [db, login, password, env])
+ uid = dispatch_rpc('common', 'authenticate', [db, login, password, env])
else:
security.check(db, uid, password)
self.db = db
def send(self, service_name, method, *args):
"""
.. deprecated:: 8.0
- Use ``openerp.netsvc.dispatch_rpc()`` instead.
+ Use ``dispatch_rpc()`` instead.
"""
- return openerp.netsvc.dispatch_rpc(service_name, method, args)
+ return dispatch_rpc(service_name, method, args)
def proxy(self, service):
"""
.. deprecated:: 8.0
- Use ``openerp.netsvc.dispatch_rpc()`` instead.
+ Use ``dispatch_rpc()`` instead.
"""
return Service(self, service)
mimetypes.add_type('application/vnd.ms-fontobject', '.eot')
mimetypes.add_type('application/x-font-ttf', '.ttf')
-class LazyResponse(werkzeug.wrappers.Response):
- """ Lazy werkzeug response.
- API not yet frozen"""
+class Response(werkzeug.wrappers.Response):
+ """ Response object passed through controller route chain.
- def __init__(self, callback, status_code=None, **kwargs):
- super(LazyResponse, self).__init__(mimetype='text/html')
- if status_code:
- self.status_code = status_code
- self.callback = callback
- self.params = kwargs
- def process(self):
- response = self.callback(**self.params)
- self.response.append(response)
+ In addition to the werkzeug.wrappers.Response parameters, this
+ classe's constructor can take the following additional parameters
+ for QWeb Lazy Rendering.
+
+ :param basestring template: template to render
+ :param dict qcontext: Rendering context to use
+ :param int uid: User id to use for the ir.ui.view render call
+ """
+ default_mimetype = 'text/html'
+ def __init__(self, *args, **kw):
+ template = kw.pop('template', None)
+ qcontext = kw.pop('qcontext', None)
+ uid = kw.pop('uid', None)
+ super(Response, self).__init__(*args, **kw)
+ self.set_default(template, qcontext, uid)
+
+ def set_default(self, template=None, qcontext=None, uid=None):
+ self.template = template
+ self.qcontext = qcontext or dict()
+ self.uid = uid
+ # Support for Cross-Origin Resource Sharing
+ if request.endpoint and 'cors' in request.endpoint.routing:
+ self.headers.set('Access-Control-Allow-Origin', request.endpoint.routing['cors'])
+ methods = 'GET, POST'
+ if request.endpoint.routing['type'] == 'json':
+ methods = 'POST'
+ elif request.endpoint.routing.get('methods'):
+ methods = ', '.join(request.endpoint.routing['methods'])
+ self.headers.set('Access-Control-Allow-Methods', methods)
+
+ @property
+ def is_qweb(self):
+ return self.template is not None
+
+ def render(self):
+ view_obj = request.registry["ir.ui.view"]
+ uid = self.uid or request.uid or openerp.SUPERUSER_ID
+ return view_obj.render(request.cr, uid, self.template, self.qcontext, context=request.context)
+
+ def flatten(self):
+ self.response.append(self.render())
+ self.template = None
class DisableCacheMiddleware(object):
def __init__(self, app):
start_response(status, new_headers)
return self.app(environ, start_wrapped)
-def session_path():
- try:
- import pwd
- username = pwd.getpwuid(os.geteuid()).pw_name
- except ImportError:
- try:
- username = getpass.getuser()
- except Exception:
- username = "unknown"
- path = os.path.join(tempfile.gettempdir(), "oe-sessions-" + username)
- try:
- os.mkdir(path, 0700)
- except OSError as exc:
- if exc.errno == errno.EEXIST:
- # directory exists: ensure it has the correct permissions
- # this will fail if the directory is not owned by the current user
- os.chmod(path, 0700)
- else:
- raise
- return path
-
class Root(object):
"""Root WSGI application for the OpenERP Web Client.
"""
def __init__(self):
# Setup http sessions
- path = session_path()
+ path = openerp.tools.config.session_dir
_logger.debug('HTTP sessions stored in: %s', path)
self.session_store = werkzeug.contrib.sessions.FilesystemSessionStore(path, session_class=OpenERPSession)
+ self._loaded = False
- # TODO should we move this to ir.http so that only configured modules are served ?
- _logger.info("HTTP Configuring static files")
- self.load_addons()
-
+ @lazy_property
+ def nodb_routing_map(self):
_logger.info("Generating nondb routing")
- self.nodb_routing_map = routing_map([''] + openerp.conf.server_wide_modules, True)
+ return routing_map([''] + openerp.conf.server_wide_modules, True)
def __call__(self, environ, start_response):
""" Handle a WSGI request
"""
+ if not self._loaded:
+ self._loaded = True
+ self.load_addons()
return self.dispatch(environ, start_response)
def load_addons(self):
- """ Load all addons from addons patch containg static files and
+ """ Load all addons from addons path containing static files and
controllers and configure them. """
+ # TODO should we move this to ir.http so that only configured modules are served ?
statics = {}
for addons_path in openerp.modules.module.ad_paths:
_logger.debug("Loading %s", module)
if 'openerp.addons' in sys.modules:
m = __import__('openerp.addons.' + module)
+ else:
+ m = None
addons_module[module] = m
addons_manifest[module] = manifest
statics['/%s/static' % module] = path_static
- app = werkzeug.wsgi.SharedDataMiddleware(self.dispatch, statics)
- self.dispatch = DisableCacheMiddleware(app)
+ if statics:
+ _logger.info("HTTP Configuring static files")
+ app = werkzeug.wsgi.SharedDataMiddleware(self.dispatch, statics)
+ self.dispatch = DisableCacheMiddleware(app)
def setup_session(self, httprequest):
# recover or create session
return HttpRequest(httprequest)
def get_response(self, httprequest, result, explicit_session):
- if isinstance(result, LazyResponse):
+ if isinstance(result, Response) and result.is_qweb:
try:
- result.process()
+ result.flatten()
except(Exception), e:
if request.db:
result = request.registry['ir.http']._handle_exception(e)
raise
if isinstance(result, basestring):
- response = werkzeug.wrappers.Response(result, mimetype='text/html')
+ response = Response(result, mimetype='text/html')
else:
response = result
if not explicit_session and hasattr(response, 'set_cookie'):
response.set_cookie('session_id', httprequest.session.sid, max_age=90 * 24 * 60 * 60)
- # Support for Cross-Origin Resource Sharing
- if request.func and 'cors' in request.func.routing:
- response.headers.set('Access-Control-Allow-Origin', request.func.routing['cors'])
- methods = 'GET, POST'
- if request.func_request_type == 'json':
- methods = 'POST'
- elif request.func.routing.get('methods'):
- methods = ', '.join(request.func.routing['methods'])
- response.headers.set('Access-Control-Allow-Methods', methods)
-
return response
def dispatch(self, environ, start_response):
try:
with openerp.tools.mute_logger('openerp.sql_db'):
ir_http = request.registry['ir.http']
- except psycopg2.OperationalError:
- # psycopg2 error. At this point, that means the
- # database probably does not exists anymore. Log the
- # user out and fall back to nodb
+ except (AttributeError, psycopg2.OperationalError):
+ # psycopg2 error or attribute error while constructing
+ # the registry. That means the database probably does
+ # not exists anymore or the code doesnt match the db.
+ # Log the user out and fall back to nodb
request.session.logout()
result = _dispatch_nodb()
else:
return request.registry['ir.http'].routing_map()
def db_list(force=False, httprequest=None):
- dbs = openerp.netsvc.dispatch_rpc("db", "list", [force])
+ dbs = dispatch_rpc("db", "list", [force])
return db_filter(dbs, httprequest=httprequest)
def db_filter(dbs, httprequest=None):
httprequest = httprequest or request.httprequest
- h = httprequest.environ['HTTP_HOST'].split(':')[0]
+ h = httprequest.environ.get('HTTP_HOST', '').split(':')[0]
d = h.split('.')[0]
r = openerp.tools.config['dbfilter'].replace('%h', h).replace('%d', d)
dbs = [i for i in dbs if re.match(r, i)]
@route('/jsonrpc', type='json', auth="none")
def jsonrpc(self, service, method, args):
""" Method used by client APIs to contact OpenERP. """
- return openerp.netsvc.dispatch_rpc(service, method, args)
+ return dispatch_rpc(service, method, args)
@route('/gen_session_id', type='json', auth="none")
def gen_session_id(self):
nsession = root.session_store.new()
return nsession.sid
-root = None
-
-def wsgi_postload():
- global root
- root = Root()
- openerp.wsgi.register_wsgi_handler(root)
+# register main wsgi handler
+root = Root()
+openerp.service.wsgi_server.register_wsgi_handler(root)
# vim:et:ts=4:sw=4:
}
pageSize = A4
if self.localcontext.get('company'):
- pageSize = pagesize_map.get(self.localcontext.get('company').paper_format, A4)
+ pageSize = pagesize_map.get(self.localcontext.get('company').rml_paper_format, A4)
if node.get('pageSize'):
ps = map(lambda x:x.strip(), node.get('pageSize').replace(')', '').replace('(', '').split(','))
pageSize = ( utils.unit_get(ps[0]),utils.unit_get(ps[1]) )
if story_cnt > 0:
fis.append(platypus.PageBreak())
fis += r.render(node_story)
+ # Reset Page Number with new story tag
+ fis.append(PageReset())
story_cnt += 1
try:
if self.localcontext and self.localcontext.get('internal_header',False):