# -*- coding: utf-8 -*- import base64 import json from psycopg2 import IntegrityError from openerp import http, SUPERUSER_ID from openerp.http import request from openerp.tools.translate import _ from openerp.exceptions import ValidationError from openerp.addons.base.ir.ir_qweb import nl2br class WebsiteForm(http.Controller): # Check and insert values from the form on the model @http.route('/website_form/', type='http', auth="public", methods=['POST'], website=True) def website_form(self, model_name, **kwargs): model_record = request.env['ir.model'].search([('model', '=', model_name), ('website_form_access', '=', True)]) if not model_record: return json.dumps(False) try: data = self.extract_data(model_record, ** kwargs) # If we encounter an issue while extracting data except ValidationError, e: # I couldn't find a cleaner way to pass data to an exception return json.dumps({'error_fields' : e.args[0]}) try: id_record = self.insert_record(request, model_record, data['record'], data['custom'], data.get('meta')) if id_record: self.insert_attachment(model_record, id_record, data['attachments']) # Some fields have additionnal SQL constraints that we can't check generically # Ex: crm.lead.probability which is a float between 0 and 1 # TODO: How to get the name of the erroneous field ? except IntegrityError: return json.dumps(False) request.session['form_builder_model'] = model_record.name request.session['form_builder_id'] = id_record return json.dumps({'id': id_record}) # Constants string to make custom info and metadata readable on a text field _custom_label = "%s\n___________\n\n" % _("Custom infos") # Title for custom fields _meta_label = "%s\n________\n\n" % _("Metadata") # Title for meta data # Dict of dynamically called filters following type of field to be fault tolerent def identity(self, field_label, field_input): return field_input def integer(self, field_label, field_input): return int(field_input) def floating(self, field_label, field_input): return float(field_input) def boolean(self, field_label, field_input): return bool(field_input) def binary(self, field_label, field_input): return base64.b64encode(field_input.read()) def one2many(self, field_label, field_input): return [int(i) for i in field_input.split(',')] def many2many(self, field_label, field_input, *args): return [(args[0] if args else (6,0)) + (self.one2many(field_label, field_input),)] _input_filters = { 'char': identity, 'text': identity, 'html': identity, 'datetime': identity, 'many2one': integer, 'one2many': one2many, 'many2many':many2many, 'selection': identity, 'boolean': boolean, 'integer': integer, 'float': floating, 'binary': binary, } # Extract all data sent by the form and sort its on several properties def extract_data(self, model, **kwargs): data = { 'record': {}, # Values to create record 'attachments': [], # Attached files 'custom': '', # Custom fields values } authorized_fields = model.sudo()._get_form_writable_fields() error_fields = [] for field_name, field_value in kwargs.items(): # If the value of the field if a file if hasattr(field_value, 'filename'): # Undo file upload field name indexing field_name = field_name.rsplit('[', 1)[0] # If it's an actual binary field, convert the input file # If it's not, we'll use attachments instead if field_name in authorized_fields and authorized_fields[field_name]['type'] == 'binary': data['record'][field_name] = base64.b64encode(field_value.read()) else: field_value.field_name = field_name data['attachments'].append(field_value) # If it's a known field elif field_name in authorized_fields: try: input_filter = self._input_filters[authorized_fields[field_name]['type']] data['record'][field_name] = input_filter(self, field_name, field_value) except ValueError: error_fields.append(field_name) # If it's a custom field elif field_name != 'context': data['custom'] += "%s : %s\n" % (field_name.decode('utf-8'), field_value) # Add metadata if enabled environ = request.httprequest.headers.environ if(request.website.website_form_enable_metadata): data['meta'] += "%s : %s\n%s : %s\n%s : %s\n%s : %s\n" % ( "IP" , environ.get("REMOTE_ADDR"), "USER_AGENT" , environ.get("HTTP_USER_AGENT"), "ACCEPT_LANGUAGE" , environ.get("HTTP_ACCEPT_LANGUAGE"), "REFERER" , environ.get("HTTP_REFERER") ) # This function can be defined on any model to provide # a model-specific filtering of the record values # Example: # def website_form_input_filter(self, values): # values['name'] = '%s\'s Application' % values['partner_name'] # return values dest_model = request.env[model.model] if hasattr(dest_model, "website_form_input_filter"): data['record'] = dest_model.website_form_input_filter(request, data['record']) missing_required_fields = [label for label, field in authorized_fields.iteritems() if field['required'] and not label in data['record']] if any(error_fields): raise ValidationError(error_fields + missing_required_fields) return data def insert_record(self, request, model, values, custom, meta=None): record = request.env[model.model].sudo().create(values) if custom or meta: default_field = model.website_form_default_field_id default_field_data = values.get(default_field.name, '') custom_content = (default_field_data + "\n\n" if default_field_data else '') \ + (self._custom_label + custom + "\n\n" if custom else '') \ + (self._meta_label + meta if meta else '') # If there is a default field configured for this model, use it. # If there isn't, put the custom data in a message instead if default_field.name: if default_field.ttype == 'html' or model.model == 'mail.mail': custom_content = nl2br(custom_content) record.update({default_field.name: custom_content}) else: values = { 'body': nl2br(custom_content), 'model': model.model, 'message_type': 'comment', 'no_auto_thread': False, 'res_id': record.id, } mail_id = request.env['mail.message'].sudo().create(values) return record.id # Link all files attached on the form def insert_attachment(self, model, id_record, files): orphan_attachment_ids = [] record = model.env[model.model].browse(id_record) authorized_fields = model.sudo()._get_form_writable_fields() for file in files: custom_field = file.field_name not in authorized_fields attachment_value = { 'name': file.field_name if custom_field else file.filename, 'datas': base64.encodestring(file.read()), 'datas_fname': file.filename, 'res_model': model.model, 'res_id': record.id, } attachment_id = request.env['ir.attachment'].sudo().create(attachment_value) if attachment_id and not custom_field: record.sudo()[file.field_name] = [(4, attachment_id.id)] else: orphan_attachment_ids.append(attachment_id.id) # If some attachments didn't match a field on the model, # we create a mail.message to link them to the record if orphan_attachment_ids: if model.name != 'mail.mail': values = { 'body': _('

Attached files :

'), 'model': model.model, 'message_type': 'comment', 'no_auto_thread': False, 'res_id': id_record, 'attachment_ids': [(6, 0, orphan_attachment_ids)], } mail_id = request.env['mail.message'].sudo().create(values) else: # If the model is mail.mail then we have no other choice but to # attach the custom binary field files on the attachment_ids field. for attachment_id_id in orphan_attachment_ids: record.attachment_ids = [(4, attachment_id_id)]