239 lines
10 KiB
Python
239 lines
10 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
|
|
|
|
import base64
|
|
import json
|
|
import pytz
|
|
|
|
from datetime import datetime
|
|
from psycopg2 import IntegrityError
|
|
|
|
from flectra import http
|
|
from flectra.http import request
|
|
from flectra.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT
|
|
from flectra.tools.translate import _
|
|
from flectra.exceptions import ValidationError
|
|
from flectra.addons.base.ir.ir_qweb.fields 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'].sudo().search([('model', '=', model_name), ('website_form_access', '=', True)])
|
|
if not model_record:
|
|
return json.dumps(False)
|
|
|
|
try:
|
|
data = self.extract_data(model_record, request.params)
|
|
# If we encounter an issue while extracting data
|
|
except ValidationError as 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 additional 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'] = model_record.model
|
|
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, values):
|
|
|
|
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 values.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'] += u"%s : %s\n" % (field_name, 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.sudo().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.items() 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().with_context(mail_create_nosubscribe=True).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.model != '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)]
|