Commit [1] removed the `email_from` field from the `project.task` model
but forgot to adapt/delete some field occurences.
One of those is related to the form in the website builder which allows
to create task when the form is sent.
Two errors were detected:
- Non critical:
The field is still passed to the form field whitelisting process.
Since the whitelisting is done in raw SQL, it didn't crash or log
anything even if the column did not exist anymore.
- Critical:
The field was still marked as model required in the form JS registry,
altering the form builder behaviors.
One of those is that when the form input related to this field was
re-created (eg when changing / hovering an option in the right panel),
it would lose it's "name" attribute.
Two possible issues from that point:
1. When a visitor submit the form, the "email_from" field value is not
set anymore in the task description and that information is just
lost. You then have no way to reach back to him.
2. (Minor) The auto-fill behavior of the form was not working anymore
Probably more issues were introduced but only those ones got detected as
of today.
Step to reproduce:
- Drop a website form, choose "Create a Task"
- Focus the default "Email" field
- Hover the mouse on the "eye" icon ("Invisible") next to the Position
attribute. If you inspect the DOM, the input has now lost the `name`
attribute.
- If you save, you will face the issues reported above.
- If you then reopen the editor and focus the email field again, the
tooltip will now say that the "null" field is required.
[1]: https://github.com/odoo/odoo/commit/a424cf481c676a230beeb6102fc67bedf472a882
opw-3626573
closes odoo/odoo#146170
X-original-commit: 6679fddbcaf7d9146ab242e0eed61ee0628f047f
Signed-off-by: Quentin Smetz (qsm) <qsm@odoo.com>
Signed-off-by: Romain Derie (rde) <rde@odoo.com>
142 lines
5.9 KiB
Python
142 lines
5.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import logging
|
|
|
|
from odoo import models, fields, api, SUPERUSER_ID
|
|
from odoo.http import request
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class website_form_config(models.Model):
|
|
_inherit = 'website'
|
|
|
|
def _website_form_last_record(self):
|
|
if request and request.session.form_builder_model_model:
|
|
return request.env[request.session.form_builder_model_model].browse(request.session.form_builder_id)
|
|
return False
|
|
|
|
|
|
class website_form_model(models.Model):
|
|
_name = 'ir.model'
|
|
_description = 'Models'
|
|
_inherit = 'ir.model'
|
|
|
|
website_form_access = fields.Boolean('Allowed to use in forms', help='Enable the form builder feature for this model.')
|
|
website_form_default_field_id = fields.Many2one('ir.model.fields', 'Field for custom form data', domain="[('model', '=', model), ('ttype', '=', 'text')]", help="Specify the field which will contain meta and custom form fields datas.")
|
|
website_form_label = fields.Char("Label for form action", help="Form action label. Ex: crm.lead could be 'Send an e-mail' and project.issue could be 'Create an Issue'.")
|
|
website_form_key = fields.Char(help='Used in FormBuilder Registry')
|
|
|
|
def _get_form_writable_fields(self):
|
|
"""
|
|
Restriction of "authorized fields" (fields which can be used in the
|
|
form builders) to fields which have actually been opted into form
|
|
builders and are writable. By default no field is writable by the
|
|
form builder.
|
|
"""
|
|
if self.model == "mail.mail":
|
|
included = {'email_from', 'email_to', 'email_cc', 'email_bcc', 'body', 'reply_to', 'subject'}
|
|
else:
|
|
included = {
|
|
field.name
|
|
for field in self.env['ir.model.fields'].sudo().search([
|
|
('model_id', '=', self.id),
|
|
('website_form_blacklisted', '=', False)
|
|
])
|
|
}
|
|
return {
|
|
k: v for k, v in self.get_authorized_fields(self.model).items()
|
|
if k in included
|
|
}
|
|
|
|
@api.model
|
|
def get_authorized_fields(self, model_name):
|
|
""" Return the fields of the given model name as a mapping like method `fields_get`. """
|
|
model = self.env[model_name]
|
|
fields_get = model.fields_get()
|
|
|
|
for key, val in model._inherits.items():
|
|
fields_get.pop(val, None)
|
|
|
|
# Unrequire fields with default values
|
|
default_values = model.with_user(SUPERUSER_ID).default_get(list(fields_get))
|
|
for field in [f for f in fields_get if f in default_values]:
|
|
fields_get[field]['required'] = False
|
|
|
|
# Remove readonly and magic fields
|
|
# Remove string domains which are supposed to be evaluated
|
|
# (e.g. "[('product_id', '=', product_id)]")
|
|
for field in list(fields_get):
|
|
if 'domain' in fields_get[field] and isinstance(fields_get[field]['domain'], str):
|
|
del fields_get[field]['domain']
|
|
if fields_get[field].get('readonly') or field in models.MAGIC_COLUMNS or \
|
|
fields_get[field]['type'] in ['many2one_reference', 'properties']:
|
|
del fields_get[field]
|
|
|
|
return fields_get
|
|
|
|
@api.model
|
|
def get_compatible_form_models(self):
|
|
if not self.env.user.has_group('website.group_website_restricted_editor'):
|
|
return []
|
|
return self.sudo().search_read(
|
|
[('website_form_access', '=', True)],
|
|
['id', 'model', 'name', 'website_form_label', 'website_form_key'],
|
|
)
|
|
|
|
|
|
class website_form_model_fields(models.Model):
|
|
""" fields configuration for form builder """
|
|
_name = 'ir.model.fields'
|
|
_description = 'Fields'
|
|
_inherit = 'ir.model.fields'
|
|
|
|
def init(self):
|
|
# set all existing unset website_form_blacklisted fields to ``true``
|
|
# (so that we can use it as a whitelist rather than a blacklist)
|
|
self._cr.execute('UPDATE ir_model_fields'
|
|
' SET website_form_blacklisted=true'
|
|
' WHERE website_form_blacklisted IS NULL')
|
|
# add an SQL-level default value on website_form_blacklisted to that
|
|
# pure-SQL ir.model.field creations (e.g. in _reflect) generate
|
|
# the right default value for a whitelist (aka fields should be
|
|
# blacklisted by default)
|
|
self._cr.execute('ALTER TABLE ir_model_fields '
|
|
' ALTER COLUMN website_form_blacklisted SET DEFAULT true')
|
|
|
|
@api.model
|
|
def formbuilder_whitelist(self, model, fields):
|
|
"""
|
|
:param str model: name of the model on which to whitelist fields
|
|
:param list(str) fields: list of fields to whitelist on the model
|
|
:return: nothing of import
|
|
"""
|
|
# postgres does *not* like ``in [EMPTY TUPLE]`` queries
|
|
if not fields:
|
|
return False
|
|
|
|
# only allow users who can change the website structure
|
|
if not self.env['res.users'].has_group('website.group_website_designer'):
|
|
return False
|
|
|
|
unexisting_fields = [field for field in fields if field not in self.env[model]._fields.keys()]
|
|
if unexisting_fields:
|
|
# TODO in master: Raise instead of log
|
|
_logger.error("Unable to whitelist field(s) %r for model %r.", unexisting_fields, model)
|
|
|
|
# the ORM only allows writing on custom fields and will trigger a
|
|
# registry reload once that's happened. We want to be able to
|
|
# whitelist non-custom fields and the registry reload absolutely
|
|
# isn't desirable, so go with a method and raw SQL
|
|
self.env.cr.execute(
|
|
"UPDATE ir_model_fields"
|
|
" SET website_form_blacklisted=false"
|
|
" WHERE model=%s AND name in %s", (model, tuple(fields)))
|
|
return True
|
|
|
|
website_form_blacklisted = fields.Boolean(
|
|
'Blacklisted in web forms', default=True, index=True,
|
|
help='Blacklist this field for web forms'
|
|
)
|