Adding date & datetime inputs in the website form builder couldn't work, because the posted values for these input types were not treated at all, and they were not in the format the date/datetime were stored in database. opw-672674
233 lines
9.9 KiB
Python
233 lines
9.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
import base64
|
|
|
|
import json
|
|
import pytz
|
|
from datetime import datetime
|
|
from psycopg2 import IntegrityError
|
|
from openerp import http, SUPERUSER_ID
|
|
from openerp.http import request
|
|
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
|
|
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 <model>
|
|
@http.route('/website_form/<string:model_name>', 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 date(self, field_label, field_input):
|
|
lang = request.env['ir.qweb.field'].user_lang()
|
|
return datetime.strptime(field_input, lang.date_format).strftime(DEFAULT_SERVER_DATE_FORMAT)
|
|
|
|
def datetime(self, field_label, field_input):
|
|
lang = request.env['ir.qweb.field'].user_lang()
|
|
strftime_format = (u"%s %s" % (lang.date_format, lang.time_format))
|
|
user_tz = pytz.timezone(request.context.get('tz') or request.env.user.tz or 'UTC')
|
|
dt = user_tz.localize(datetime.strptime(field_input, strftime_format)).astimezone(pytz.utc)
|
|
return dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
|
|
|
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,
|
|
'date': date,
|
|
'datetime': datetime,
|
|
'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': _('<p>Attached files : </p>'),
|
|
'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)]
|