diff --git a/cms_form/README.rst b/cms_form/README.rst new file mode 100644 index 000000000..45c4137c7 --- /dev/null +++ b/cms_form/README.rst @@ -0,0 +1,85 @@ +.. image:: https://img.shields.io/badge/licence-lgpl--3-blue.svg + :target: http://www.gnu.org/licenses/LGPL-3.0-standalone.html + :alt: License: LGPL-3 + +CMS Form +======== + +Advanced contents form framework. Allows to define front-end forms for every models in a simple way. + +If you are tired of re-defining every time an edit form or a search form for your odoo portal or website, +this is the module you are looking for. + +Features +======== + +* automatic form generation (create, write, search, wizards) +* automatic route generation (create, write, search, wizards) +* automatic machinery based on fields' type: + * widget rendering + * field value load (from existing instance or from request) + * field value extraction (from request) + * field value write (to existing instance) +* automatic field grouping in fieldsets + +* highly customizable +* works with every odoo model +* works also without any model + +Usage +===== + +Check full documentation inside `doc` folder. + + +Known issues / Roadmap +====================== + +* add more tests, especially per each widget and type of field +* provide better widgets for image and file fields in general +* o2m fields: to be tested at all +* move widgets to abstract models too +* search form: generate default search domain in a clever way +* add easy way to switch from horizontal to vertical form +* provide more examples +* x2x fields: allow sub-items creation +* handle api onchanges +* support python expressions into master/slave rules +* the ajax UI blocking/spinner could be more beautiful + + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smashing it by providing a detailed and welcomed feedback. + +Credits +======= + +Sponsor +------- + +* `Fluxdock.io `_. + +Contributors +------------ + +* Simone Orsi + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit https://odoo-community.org. diff --git a/cms_form/__init__.py b/cms_form/__init__.py new file mode 100644 index 000000000..658653461 --- /dev/null +++ b/cms_form/__init__.py @@ -0,0 +1,4 @@ +# not sure why these lines are counted as not test covered +# since nothing would work in tests w/out them being loaded +from . import models # pragma: no cover +from . import controllers # pragma: no cover diff --git a/cms_form/__manifest__.py b/cms_form/__manifest__.py new file mode 100644 index 000000000..f43e65431 --- /dev/null +++ b/cms_form/__manifest__.py @@ -0,0 +1,41 @@ +# Copyright 2017 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +{ + "name": "CMS Form", + "summary": """ + Basic content type form""", + "version": "15.0.1.0.0", + "license": "LGPL-3", + "author": "Camptocamp, Odoo Community Association (OCA)", + "maintainers": ["simahawk"], + "website": "https://github.com/OCA/website-cms", + "depends": [ + "cms_info", + "cms_status_message", + # TODO: get rid of portal too + "portal", + "base_sparse_field", + "website", + "web", + ], + "data": [ + "security/cms_form.xml", + "templates/form.xml", + "templates/widgets.xml", + "templates/portal.xml", + ], + "installable": True, + "assets": { + "web.assets_frontend": [ + "cms_form/static/src/scss/cms_form.scss", + "cms_form/static/src/scss/progressbar.scss", + # TODO: review them all w/ modern JS + "cms_form/static/src/js/select2widgets.js", + "cms_form/static/src/js/textarea_widget.js", + "cms_form/static/src/js/master_slave.js", + "cms_form/static/src/js/lock_copy_paste.js", + "cms_form/static/src/js/ajax.js", + ], + }, +} diff --git a/cms_form/controllers/__init__.py b/cms_form/controllers/__init__.py new file mode 100644 index 000000000..12a7e529b --- /dev/null +++ b/cms_form/controllers/__init__.py @@ -0,0 +1 @@ +from . import main diff --git a/cms_form/controllers/main.py b/cms_form/controllers/main.py new file mode 100644 index 000000000..69f957e07 --- /dev/null +++ b/cms_form/controllers/main.py @@ -0,0 +1,259 @@ +# Copyright 2017 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +import json + +import werkzeug + +from odoo import _, http +from odoo.http import request + +from ..exceptions import FormRedirect + + +class FormControllerMixin(object): + + # default template + template = "cms_form.portal_form_wrapper" + + def get_template(self, form, **kw): + """Retrieve rendering template. + + Defaults to `template` attribute. + Can be overridden straight in the form + using the attribute `form_wrapper_template`. + """ + template = form.form_wrapper_template or self.template + + if not template: + raise NotImplementedError("You must provide a template!") + return template + + def get_render_values(self, form, **kw): + """Retrieve rendering values. + + You can override this to inject more values. + """ + main_object = form.main_object + # Cleanup render values and remove form fields' values. + # When you submit a form and there's an error odoo will give you back + # all submitted values into `kw` but: + # 1. we don't need them since all values are encapsulated + # into form.form_render_values + # and are already accessible on each widget + # 2. this can break website rendering because you might have fields + # w/ a name that overrides a rendering value not related to a form. + # Most common example: field named `website` will override + # odoo record for current website. + vals = {k: v for k, v in kw.items() if k not in form.form_fields_get()} + vals.update({"form": form, "main_object": main_object, "controller": self}) + return vals + + def form_model_key(self, model, **kw): + """Return a valid form model.""" + return "cms.form." + model + + def get_form(self, model, model_id=None, **kw): + """Retrieve form for given model and initialize it.""" + form_model_key = kw.pop("form_model_key", None) + if not form_model_key: + form_model_key = self.form_model_key(model, **kw) + if form_model_key in request.env: + if model: + main_object = request.env[model] + if model_id: + main_object = request.env[model].browse(model_id) + else: + # HACK: odoo requires a stupid `main_object` to stay there + # See https://github.com/odoo/odoo/pull/22384 + # So here we mock main_object to the form model recordset + main_object = request.env[form_model_key] + form = request.env[form_model_key].form_init( + request, main_object=main_object, **kw + ) + else: + # TODO: enable form by default? + # How? with a flag on ir.model.model? + # And which fields to include automatically? + raise NotImplementedError(_("%s model has no CMS form registered.") % model) + return form + + def make_response(self, model, model_id=None, **kw): + """Prepare and return form response. + + :param model: an odoo model's name + :param model_id: an odoo record's id + :param page: current page if any (mostly for search forms) + :param kw: extra parameters + + How it works: + * retrieve current main object if any + * check permission on model and/or main object + * retrieve the form + * make the form process current request + * if the form is successful and has `form_redirect` attribute + it redirects to it. + * otherwise it just renders the form + """ + form = self.get_form(model, model_id=model_id, **kw) + form.form_check_permission() + # pass only specific extra args, to not pollute form render values + try: + form.form_process(extra_args={"page": kw.get("page")}) + except FormRedirect as exc: + return werkzeug.utils.redirect(exc.next_url, code=303) + # search forms do not need these attrs + if getattr(form, "form_success", None) and getattr(form, "form_redirect", None): + # anything went fine, redirect to next url + # https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/303 + return werkzeug.utils.redirect(form.form_next_url(), code=303) + + if form.form_render_values.get("errors"): + # transient model fields on cms.form are reset after savepoint rollback + # so in case of errors, the form needs to be re-initialized + # otherwise, the template won't be able to render + form = self.get_form(model, model_id=model_id, form_data=None) + + # render form wrapper + values = self.get_render_values(form, **kw) + return request.render( + self.get_template(form, **kw), + values, + headers={"Cache-Control": "no-cache"}, + ) + + def make_response_ajax(self, model, model_id=None, **kw): + """Return only results to replace the current result with""" + response = self.make_response(model, model_id=model_id, **kw) + data = { + "content": self._make_response_ajax_content(response), + } + return json.dumps(data) + + def _make_response_ajax_content(self, response): + """Return a HTML string to be passed to the client""" + raise NotImplementedError() + + +class CMSFormController(http.Controller, FormControllerMixin): + """CMS form controller.""" + + @http.route( + ["/cms/create/", "/cms/edit//"], + type="http", + auth="user", + website=True, + ) + def cms_form(self, model, model_id=None, **kw): + """Handle a `form` route.""" + return self.make_response(model, model_id=model_id, **kw) + + @http.route( + [ + "/cms/render/form/", + "/cms/render/form//", + ], + type="json", + auth="user", + website=True, + # FIXME + csfr=False, + ) + def cms_form_render(self, form_model, model_id=None, **kw): + kw["form_model_key"] = form_model + data = request.get_json_data() + process_form = data.get("process", kw.pop("process", False)) + widget_params = data.get("widget_params", kw.pop("widget_params", {})) + if widget_params: + kw["form_model_fields"] = list(widget_params) + form = self.get_form(None, model_id=model_id, **kw) + form.form_check_permission() + if process_form: + form.form_process() + by_widget = {} + for fname, params in widget_params.items(): + widget = form.form_get_current_widget(fname) + widget.update(params) + by_widget[fname] = widget.render() + if by_widget: + return {"by_widget": by_widget} + return {"form": form.form_render()} + + +class WizardFormControllerMixin(FormControllerMixin): + + template = "cms_form.portal_wizard_form_wrapper" + + def make_response(self, wiz_model, model_id=None, page=1, **kw): + """Custom response. + + The main difference w/ the base form controller is that + we retrieve the form model via wizard step. + """ + # init wizard 1st + wiz = request.env[wiz_model].form_init(request, page=page, **kw) + step_info = wiz.wiz_get_step_info(page) + # retrieve form model for current step + form_model = step_info["form_model"] + model = request.env[form_model].form_model_name + kw["form_model_key"] = form_model + return super().make_response(model, model_id=model_id, page=page, **kw) + + +class CMSWizardFormController(http.Controller, WizardFormControllerMixin): + """CMS wizard controller.""" + + @http.route( + ["/cms/wiz//page/"], + type="http", + auth="user", + website=True, + ) + def cms_wiz(self, wiz_model, model_id=None, **kw): + """Handle a wizard route.""" + return self.make_response(wiz_model, model_id=model_id, **kw) + + +class SearchFormControllerMixin(FormControllerMixin): + + template = "cms_form.portal_search_form_wrapper" + + def form_model_key(self, model, **kw): + return "cms.form.search." + model + + def get_render_values(self, form, **kw): + values = super().get_render_values(form, **kw) + values.update({"pager": form.form_search_results["pager"]}) + return values + + +class CMSSearchFormController(http.Controller, SearchFormControllerMixin): + """CMS form controller.""" + + @http.route( + ["/cms/search/", "/cms/search//page/"], + type="http", + auth="public", + website=True, + ) + def cms_form(self, model, **kw): + """Handle a search `form` route.""" + response = self.make_response(model, **kw) + return response + + @http.route( + [ + "/cms/ajax/search/", + "/cms/ajax/search//", + ], + type="http", + auth="public", + website=True, + ) + def ajax(self, model, model_id=None, **kw): + """handle an ajax request""" + return self.make_response_ajax(model, **kw) + + def _make_response_ajax_content(self, response): + template = response.qcontext["form"].form_search_results_template + return request.env["ir.qweb"]._render(template, response.qcontext) diff --git a/cms_form/doc/Makefile b/cms_form/doc/Makefile new file mode 100644 index 000000000..2b8c89dfe --- /dev/null +++ b/cms_form/doc/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SPHINXPROJ = CMSForm +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/cms_form/doc/source/_static/images/cms_form_example_create_partner.png b/cms_form/doc/source/_static/images/cms_form_example_create_partner.png new file mode 100644 index 000000000..6c5f33c01 Binary files /dev/null and b/cms_form/doc/source/_static/images/cms_form_example_create_partner.png differ diff --git a/cms_form/doc/source/_static/images/cms_form_example_edit_partner.png b/cms_form/doc/source/_static/images/cms_form_example_edit_partner.png new file mode 100644 index 000000000..73be6518d Binary files /dev/null and b/cms_form/doc/source/_static/images/cms_form_example_edit_partner.png differ diff --git a/cms_form/doc/source/_static/images/cms_form_example_fieldsets.png b/cms_form/doc/source/_static/images/cms_form_example_fieldsets.png new file mode 100644 index 000000000..e2162aaee Binary files /dev/null and b/cms_form/doc/source/_static/images/cms_form_example_fieldsets.png differ diff --git a/cms_form/doc/source/_static/images/cms_form_example_search.png b/cms_form/doc/source/_static/images/cms_form_example_search.png new file mode 100644 index 000000000..45b4164ac Binary files /dev/null and b/cms_form/doc/source/_static/images/cms_form_example_search.png differ diff --git a/cms_form/doc/source/_static/images/cms_form_example_tabbed.png b/cms_form/doc/source/_static/images/cms_form_example_tabbed.png new file mode 100644 index 000000000..ccc9141a9 Binary files /dev/null and b/cms_form/doc/source/_static/images/cms_form_example_tabbed.png differ diff --git a/cms_form/doc/source/advanced.rst b/cms_form/doc/source/advanced.rst new file mode 100644 index 000000000..91a7ea67b --- /dev/null +++ b/cms_form/doc/source/advanced.rst @@ -0,0 +1,71 @@ +Form advanced +============= + +Master / slave fields +--------------------- + +A typical use case nowadays: you want to show/hide fields based on other fields' values. +For the simplest cases you don't have to write a single line of JS. You can do it like this: + +.. code-block:: python + + class PartnerForm(models.AbstractModel): + + _name = 'cms.form.res.partner' + _inherit = 'cms.form' + _form_model = 'res.partner' + _form_model_fields = ('name', 'type', 'foo') + + def _form_master_slave_info(self): + info = self._super._form_master_slave_info() + info.update({ + # master field + 'type':{ + # actions + 'hide': { + # slave field: action values + 'foo': ('contact', ), + }, + 'show': { + 'foo': ('address', 'invoice', ), + } + }, + }) + return info + +Here we declared that: + +* when `type` field is equal to `contact` -> hide `foo` field +* when `type` field is equal to `address` or `invoice` -> show `foo` field + + +Hidden fields +------------- + +You might want to use `` for certain fields. +To achieve this just use `_form_fields_hidden`: + +.. code-block:: python + + class PartnerForm(models.AbstractModel): + + _name = 'cms.form.res.partner' + _inherit = 'cms.form' + _form_model = 'res.partner' + _form_model_fields = ('name', 'type', 'foo') + _form_fields_hidden = ('foo', ) + +Field "foo" will be rendered at the top of the form markup with an hidden input. +For create forms, you might want to pass a default value to it, like: + + +.. code-block:: python + + class PartnerForm(models.AbstractModel): + [...] + def form_load_defaults(self, main_object=None, request_values=None): + defaults = super().form_load_defaults( + main_object=main_object, request_values=request_values + ) + defaults['foo'] = 'I can pass a default value here' + return defaults diff --git a/cms_form/doc/source/basics.rst b/cms_form/doc/source/basics.rst new file mode 100644 index 000000000..229ca58e7 --- /dev/null +++ b/cms_form/doc/source/basics.rst @@ -0,0 +1,150 @@ +Form basics +=========== + +Create / Edit form +------------------ + +Just inherit from ``cms.form`` to add a form for your model. Quick example for partner: + +.. code-block:: python + + class PartnerForm(models.AbstractModel): + + _name = 'cms.form.res.partner' + _inherit = 'cms.form' + form_model = 'res.partner' + form_model_fields = ('name', 'country_id') + form_required_fields = ('name', 'country_id') + + +In this case you'll have form with the following characteristics: + +* works with ``res.partner`` model +* have only ``name`` and ``country_id`` fields +* both fields are required (is not possible to submit the form w/out one of those values) + +Here's the result: + +.. image:: ./_static/images/cms_form_example_create_partner.png +.. image:: ./_static/images/cms_form_example_edit_partner.png + +The form will be automatically available on these routes: + +* ``/cms/create/res.partner`` to create new partners +* ``/cms/edit/res.partner/1`` edit existing partners (partner id=1 in this case) + +NOTE: default generic routes work if the form's name is ``cms.form.`` + model name, like ``cms.form.res.partner``. +If you want you can easily define your own controller and give your form a different name, +and have more elegant routes like ```/partner/edit/partner-slug-1``. +Take a look at `cms_form_example` module. + +By default, the form is rendered as an horizontal twitter bootstrap form, but you can provide your own templates of course. +By default, fields are ordered by their order in the model's schema. You can tweak it using ``_form_fields_order``. + + +Form with extra control fields +------------------------------ + +Imagine you want to notify the partner after its creation but only if you really need it. + +The form above can be extended with extra fields that are not part of the ``_form_model`` schema: + +.. code-block:: python + + class PartnerForm(models.AbstractModel): + + _name = 'cms.form.res.partner' + _inherit = 'cms.form' + form_model = 'res.partner' + form_model_fields = ('name', 'country_id', 'email') + form_required_fields = ('name', 'country_id', 'email') + + notify_partner = fields.Boolean() + + def form_after_create_or_update(self, values, extra_values): + if extra_values.get('notify_partner'): + self.something_to_do_in_this_case() + +``notify_partner`` will be included into the form but it will be discarded on create and write. +Nevertheless you can use it as a control flag before and after the record has been created or updated +using the hook ``form_after_create_or_update``, as you see in this example. + + +Form with fieldsets and tabs +---------------------------- + +You want to group fields into meaningful groups. You can use fieldsets: + +.. code-block:: python + + class PartnerForm(models.AbstractModel): + + _name = 'cms.form.res.partner' + _inherit = 'cms.form' + form_model = 'res.partner' + form_model_fields = ('name', 'country_id', 'email') + form_required_fields = ('name', 'country_id', 'email') + form_fieldsets = [ + { + 'id': 'main', + 'title': 'Main', + 'fields': [ + 'name', + 'email', + ], + }, + { + 'id': 'secondary', + 'title': 'Secondary', + 'fields': [ + 'country_id', + 'notify_partner', + ], + }, + ] + + notify_partner = fields.Boolean() + +.. image:: ./_static/images/cms_form_example_fieldsets.png + + +If you want fieldsets to be displayed as tabs, just override this option: + +.. code-block:: python + + class PartnerForm(models.AbstractModel): + + _name = 'cms.form.res.partner' + _inherit = 'cms.form' + form_fieldsets = [...] + form_fieldsets_display = 'tabs' + + +.. image:: ./_static/images/cms_form_example_tabbed.png + + +Search form +----------- + +Just inherit from ``cms.form.search`` to add a form for your model. Quick example for partner: + +.. code-block:: python + + class PartnerSearchForm(models.AbstractModel): + """Partner model search form.""" + + _name = 'cms.form.search.res.partner' + _inherit = 'cms.form.search' + form_model = 'res.partner' + form_model_fields = ('name', 'country_id', ) + form_fields_order = ('country_id', 'name', ) + + +.. image:: ./_static/images/cms_form_example_search.png + +The form will be automatically available at: ``/cms/search/res.partner``. + +NOTE: default generic routes work if the form's name is ```cms.form.search`` + model name, like ``cms.form.search.res.partner``. +If you want you can easily define your own controller and give your form a different name, +and have more elegant routes like ``/partners``. +Take a look at `cms_form_example`. diff --git a/cms_form/doc/source/conf.py b/cms_form/doc/source/conf.py new file mode 100644 index 000000000..8fdc6dd8c --- /dev/null +++ b/cms_form/doc/source/conf.py @@ -0,0 +1,172 @@ +# +# Configuration file for the Sphinx documentation builder. +# +# This file does only contain a selection of the most common options. For a +# full list see the documentation: +# http://www.sphinx-doc.org/en/master/config + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + + +# -- Project information ----------------------------------------------------- + +project = "Odoo CMS Form" +# FIXME: builtin word. In any case move to GH pages! +# copyright = "2018, Simone Orsi" +author = "Simone Orsi" + +# The short X.Y version +version = "" +# The full version, including alpha/beta/rc tags +release = "11.0.1.4.1" + + +# -- General configuration --------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.todo", + "sphinx.ext.coverage", + "sphinx.ext.githubpages", +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = ".rst" + +# The master toctree document. +master_doc = "index" + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path . +exclude_patterns = [] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = "sphinx" + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = "alabaster" + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ["_static"] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# The default sidebars (for documents that don't match any pattern) are +# defined by theme itself. Builtin themes are using these templates by +# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', +# 'searchbox.html']``. +# +# html_sidebars = {} + + +# -- Options for HTMLHelp output --------------------------------------------- + +# Output file base name for HTML help builder. +htmlhelp_basename = "CMSFormdoc" + + +# -- Options for LaTeX output ------------------------------------------------ + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + ( + master_doc, + "CMSForm.tex", + "Odoo CMS Form Documentation", + "Simone Orsi", + "manual", + ), +] + + +# -- Options for manual page output ------------------------------------------ + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [(master_doc, "cmsform", "Odoo CMS Form Documentation", [author], 1)] + + +# -- Options for Texinfo output ---------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ( + master_doc, + "CMSForm", + "Odoo CMS Form Documentation", + author, + "CMSForm", + "One line description of project.", + "Miscellaneous", + ), +] + + +# -- Extension configuration ------------------------------------------------- + +# -- Options for todo extension ---------------------------------------------- + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True diff --git a/cms_form/doc/source/index.rst b/cms_form/doc/source/index.rst new file mode 100644 index 000000000..7320899b3 --- /dev/null +++ b/cms_form/doc/source/index.rst @@ -0,0 +1,23 @@ +.. CMS Form documentation master file, created by + sphinx-quickstart on Sun Apr 29 15:56:53 2018. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to CMS Form's documentation! +==================================== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + basics.rst + advanced.rst + wizard.rst + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/cms_form/doc/source/wizard.rst b/cms_form/doc/source/wizard.rst new file mode 100644 index 000000000..e5e484a7c --- /dev/null +++ b/cms_form/doc/source/wizard.rst @@ -0,0 +1,59 @@ +Wizards +======= + +Basics +------ + +Just inherit from ``cms.form.wizard`` and describe your steps. Quick example: + +.. code-block:: python + + class FakeWiz(models.AbstractModel): + """A wizard form.""" + + _name = 'fake.wiz' + _inherit = 'cms.form.wizard' + _wiz_name = _name + + def wiz_configure_steps(self): + return { + 1: {'form_model': 'fake.wiz.step1.country'}, + 2: {'form_model': 'fake.wiz.step2.partner'}, + 3: {'form_model': 'fake.wiz.step3.partner'}, + } + + + class FakeWizStep1Country(models.AbstractModel): + + _name = 'fake.wiz.step1.country' + _inherit = 'fake.wiz' + _form_model = 'res.country' + _form_model_fields = ('name', ) + + + class FakeWizStep2Partner(models.AbstractModel): + + _name = 'fake.wiz.step2.partner' + _inherit = 'fake.wiz' + _form_model = 'res.partner' + _form_model_fields = ('name', ) + + + class FakeWizStep3Partner(models.AbstractModel): + + _name = 'fake.wiz.step3.partner' + _inherit = 'fake.wiz' + _form_model = 'res.partner' + _form_model_fields = ('category_id', ) + + + +The form will be automatically available at: ``/cms/wiz/fake.wiz/page/1``. + +As you see each step can use its own form. +In this case on the 1st page the user will deal with countries, +then on the 2nd step with the name of the partner +and on the last step only with partner category. + +The wizard machinery will handle session storage and navigation +between steps automatically. diff --git a/cms_form/exceptions.py b/cms_form/exceptions.py new file mode 100644 index 000000000..a9930800e --- /dev/null +++ b/cms_form/exceptions.py @@ -0,0 +1,10 @@ +# Copyright 2024 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + + +class FormRedirect(Exception): + """Triggers a redirect to given URL.""" + + def __init__(self, message, next_url): + super().__init__(message) + self.next_url = next_url diff --git a/cms_form/i18n/ca.po b/cms_form/i18n/ca.po new file mode 100644 index 000000000..8ed175018 --- /dev/null +++ b/cms_form/i18n/ca.po @@ -0,0 +1,1141 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * cms_form +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 13.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2024-12-07 11:06+0000\n" +"Last-Translator: QuiJoQuim \n" +"Language-Team: none\n" +"Language: ca\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.6.2\n" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/controllers/main.py:0 +#, python-format +msgid "%s model has no CMS form registered." +msgstr "%s el model no té cap formulari CMS registrat." + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form_mixin.py:0 +#, python-format +msgid "Are you sure want to submit the form as it is?" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form +msgid "CMS Form" +msgstr "Formulari CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_many2many +msgid "CMS Form M2M widget" +msgstr "widget del formulari CMS M2M" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_many2one_multi +msgid "CMS Form M2O multi widget" +msgstr "Multi widget del formulari CMS M20" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_many2one +msgid "CMS Form M2O widget" +msgstr "Widget del formulari CMS M2O" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_one2many +msgid "CMS Form O2M widget" +msgstr "Widget del formulari CMS O2M" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_x2m_mixin +msgid "CMS Form X2M widget" +msgstr "Widget del formulatri CMS X2M" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_binary_mixin +msgid "CMS Form binary widget" +msgstr "Widget binari del formulari CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_boolean +msgid "CMS Form boolean widget" +msgstr "Widget booleà del formulari CMs" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_char +#: model:ir.model,name:cms_form.model_cms_form_widget_char_mixin +msgid "CMS Form char widget" +msgstr "Widget de caracters del formulari CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_date +msgid "CMS Form date widget" +msgstr "Widget de data del formulari CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_binary +msgid "CMS Form file widget" +msgstr "Giny (widget) del formulari CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_float +msgid "CMS Form float widget" +msgstr "Widget flotant del formulario CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_hidden +msgid "CMS Form hidden widget" +msgstr "Widget ocult del formulari CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_image +msgid "CMS Form image widget" +msgstr "Widget d'imatge del formulari CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_integer +msgid "CMS Form integer widget" +msgstr "Widget enter del formulari CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_mixin +msgid "CMS Form mixin" +msgstr "CMS Form mixin" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_numeric_mixin +msgid "CMS Form numeric mixin widget" +msgstr "Giny (widget) numèric mixt en formulari CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_radio +msgid "CMS Form radio widget" +msgstr "Widget de ràdio del formulari CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_rel_mixin +msgid "CMS Form relation widget mixin" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_search +msgid "CMS Form search" +msgstr "Cerca de formularis CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_selection +msgid "CMS Form selection widget" +msgstr "Widget de selecció de formularis CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_text +msgid "CMS Form text widget" +msgstr "Widget de text del formulari CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_mixin +msgid "CMS Form widget mixin" +msgstr "Combinació de widgets de formulari CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_wizard +msgid "CMS Form wizard" +msgstr "Assistent de formularis CMS" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.base_form_buttons +msgid "Cancel" +msgstr "Cancel·lar" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.wizard_form_confirm_modal +msgid "Close" +msgstr "" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.wizard_form_confirm_modal +msgid "Confirm submit" +msgstr "" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form.py:0 +#, python-format +msgid "Create %s" +msgstr "Crear %s" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form.py:0 +#, python-format +msgid "Edit \"%s\"" +msgstr "Editar \"%s\"" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_action +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_action +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_action +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_action +msgid "Form Action" +msgstr "Acció Formulari" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_ajax +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_ajax +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_ajax +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_ajax +msgid "Form Ajax" +msgstr "Formulari Ajax" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_ajax_onchange +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_ajax_onchange +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_ajax_onchange +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_ajax_onchange +msgid "Form Ajax Onchange" +msgstr "Formulari Ajax Onchange" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_buttons_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_buttons_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_buttons_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_buttons_template +msgid "Form Buttons Template" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_confirm_submit +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_confirm_submit +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_confirm_submit +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_confirm_submit +msgid "Form Confirm Submit" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_content_selector +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_content_selector +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_content_selector +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_content_selector +msgid "Form Content Selector" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_data +msgid "Form Data" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_description +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_description +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_description +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_description +msgid "Form Description" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_display_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_display_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_display_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_display_mode +msgid "Form Display Mode" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_extra_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_extra_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_extra_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_extra_css_klass +msgid "Form Extra Css Klass" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_extract_value_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_extract_value_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_extract_value_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_extract_value_mode +msgid "Form Extract Value Mode" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_attributes +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_attributes +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_attributes +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_attributes +msgid "Form Fields Attributes" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_blacklist +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_blacklist +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_blacklist +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_blacklist +msgid "Form Fields Blacklist" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_hidden +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_hidden +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_hidden +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_hidden +msgid "Form Fields Hidden" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_ignore +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_ignore +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_ignore +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_ignore +msgid "Form Fields Ignore" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_order +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_order +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_order +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_order +msgid "Form Fields Order" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_template +msgid "Form Fields Template" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_whitelist +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_whitelist +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_whitelist +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_whitelist +msgid "Form Fields Whitelist" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_wrapper_template +msgid "Form Fields Wrapper Template" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fieldsets +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fieldsets +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fieldsets +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fieldsets +msgid "Form Fieldsets" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fieldsets_display +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fieldsets_display +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fieldsets_display +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fieldsets_display +msgid "Form Fieldsets Display" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fname_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fname_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fname_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fname_pattern +msgid "Form Fname Pattern" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_method +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_method +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_method +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_method +msgid "Form Method" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_mode +msgid "Form Mode" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_model_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_model_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_model_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_model_fields +msgid "Form Model Fields" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_model_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_model_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_model_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_model_name +msgid "Form Model Name" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_no_result_msg +msgid "Form No Result Msg" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_no_search_results_template +msgid "Form No Search Results Template" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_redirect +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_redirect +msgid "Form Redirect" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_render_values +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_render_values +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_render_values +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_render_values +msgid "Form Render Values" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_required_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_required_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_required_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_required_fields +msgid "Form Required Fields" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_reset +msgid "Form Reset" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_results_orderby +msgid "Form Results Orderby" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_results_per_page +msgid "Form Results Per Page" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_search_domain_rules +msgid "Form Search Domain Rules" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_search_fields_multi +msgid "Form Search Fields Multi" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_search_results +msgid "Form Search Results" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_search_results_template +msgid "Form Search Results Template" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_show_progress_bar +msgid "Form Show Progress Bar" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_show_results_no_submit +msgid "Form Show Results No Submit" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_show_search_form +msgid "Form Show Search Form" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_step_store_all_fields +msgid "Form Step Store All Fields" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_step_stored_fields +msgid "Form Step Stored Fields" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_sub_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_sub_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_sub_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_sub_fields +msgid "Form Sub Fields" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_success +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_success +msgid "Form Success" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_template +msgid "Form Template" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_title +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_title +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_title +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_title +msgid "Form Title" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_validators +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_validators +msgid "Form Validators" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_wrapper_extra_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_wrapper_extra_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_wrapper_extra_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_wrapper_extra_css_klass +msgid "Form Wrapper Extra Css Klass" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_wrapper_template +msgid "Form Wrapper Template" +msgstr "" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.fieldsets_as_tabs +msgid "Home" +msgstr "Inici" + +#. module: cms_form +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form__form_display_mode__horizontal +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_mixin__form_display_mode__horizontal +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_search__form_display_mode__horizontal +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_wizard__form_display_mode__horizontal +msgid "Horizontal" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__id +msgid "ID" +msgstr "ID" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form.py:0 +#, python-format +msgid "Item created." +msgstr "Element creat." + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form.py:0 +#, python-format +msgid "Item updated." +msgstr "Element actualitzat." + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.field_widget_image +msgid "Keep current image" +msgstr "Mantenir la imatge actual" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__main_object +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__main_object +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__main_object +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__main_object +msgid "Main Object" +msgstr "" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.form_field_label_after_wrapper +#: model_terms:ir.ui.view,arch_db:cms_form.form_horizontal_field_wrapper +#: model_terms:ir.ui.view,arch_db:cms_form.form_vertical_field_wrapper +msgid "Name" +msgstr "Nom" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.wizard_form_buttons +msgid "Next" +msgstr "Següent" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_search_form.py:0 +#, python-format +msgid "No items" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__o_request +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__o_request +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__o_request +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__o_request +msgid "O Request" +msgstr "" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.wizard_form_buttons +msgid "Prev" +msgstr "Anterior" + +#. module: cms_form +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form__form_extract_value_mode__read +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_mixin__form_extract_value_mode__read +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_wizard__form_extract_value_mode__read +msgid "Read" +msgstr "" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.field_widget_image +msgid "Replace current image" +msgstr "Substitueix la imatge actual" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__request +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__request +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__request +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__request +msgid "Request" +msgstr "" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.base_form +msgid "Required fields" +msgstr "Els camps obligatoris" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_search_form.py:0 +#: model_terms:ir.ui.view,arch_db:cms_form.search_form_buttons +#, python-format +msgid "Search" +msgstr "Cerca" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_search_form.py:0 +#, python-format +msgid "Search %s" +msgstr "Cerca %s" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form.py:0 +#, python-format +msgid "Some required fields are empty." +msgstr "Alguns camps obligatoris estan buits." + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.base_form_buttons +#: model_terms:ir.ui.view,arch_db:cms_form.wizard_form_buttons +#: model_terms:ir.ui.view,arch_db:cms_form.wizard_form_confirm_modal +msgid "Submit" +msgstr "Enviar" + +#. module: cms_form +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form__form_fieldsets_display__tabs +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_mixin__form_fieldsets_display__tabs +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_search__form_fieldsets_display__tabs +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_wizard__form_fieldsets_display__tabs +msgid "Tabs" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,help:cms_form.field_cms_form_widget_char__w_valid_pattern +#: model:ir.model.fields,help:cms_form.field_cms_form_widget_char_mixin__w_valid_pattern +#: model:ir.model.fields,help:cms_form.field_cms_form_widget_float__w_valid_pattern +#: model:ir.model.fields,help:cms_form.field_cms_form_widget_integer__w_valid_pattern +#: model:ir.model.fields,help:cms_form.field_cms_form_widget_numeric_mixin__w_valid_pattern +#: model:ir.model.fields,help:cms_form.field_cms_form_widget_text__w_valid_pattern +msgid "Used to validate inputs with `pattern` attr" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form__form_display_mode__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form__form_fieldsets_display__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_mixin__form_display_mode__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_mixin__form_fieldsets_display__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_search__form_display_mode__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_search__form_fieldsets_display__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_wizard__form_display_mode__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_wizard__form_fieldsets_display__vertical +msgid "Vertical" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_comodel_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_comodel_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_comodel_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_comodel_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_comodel_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_comodel_name +msgid "W Comodel Name" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_css_klass +msgid "W Css Klass" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_data +msgid "W Data" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_date_format +msgid "W Date Format" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_default_today +msgid "W Default Today" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_display_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_display_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_display_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_display_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_display_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_display_field +msgid "W Display Field" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_domain +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_domain +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_domain +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_domain +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_domain +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_domain +msgid "W Domain" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_field +msgid "W Field" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_field_value +msgid "W Field Value" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_fname +msgid "W Fname" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_form +msgid "W Form" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_input_max +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_input_max +msgid "W Input Max" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_input_min +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_input_min +msgid "W Input Min" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_input_type +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_input_type +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_input_type +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_input_type +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_input_type +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_input_type +msgid "W Input Type" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_maxlength +msgid "W Maxlength" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_options_help +msgid "W Options Help" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_placeholder +msgid "W Placeholder" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_readonly +msgid "W Readonly" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_record +msgid "W Record" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_subfields +msgid "W Subfields" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_template +msgid "W Template" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_true_values +msgid "W True Values" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_valid_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_valid_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_valid_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_valid_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_valid_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_valid_pattern +msgid "W Valid Pattern" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_wrapper_css_klass +msgid "W Wrapper Css Klass" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_wrapper_template +msgid "W Wrapper Template" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form__form_extract_value_mode__write +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_mixin__form_extract_value_mode__write +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_wizard__form_extract_value_mode__write +msgid "Write" +msgstr "" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form_mixin.py:0 +#, python-format +msgid "You are not allowed to create any record for the model `%s`." +msgstr "No teniu permís per crear cap registre per al model `%s`." + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form_mixin.py:0 +#, python-format +msgid "You cannot edit this record. Model: %(model)s, ID: %(obj_id)s." +msgstr "" + +#~ msgid "Display Name" +#~ msgstr "Nom de visualització" + +#~ msgid "Last Modified on" +#~ msgstr "Última modificació el" + +#, python-format +#~ msgid "You cannot edit this record. Model: %s, ID: %s." +#~ msgstr "No podeu editar aquest registre. Model: %s, ID: %s." diff --git a/cms_form/i18n/cms_form.pot b/cms_form/i18n/cms_form.pot new file mode 100644 index 000000000..f2e3ff7b9 --- /dev/null +++ b/cms_form/i18n/cms_form.pot @@ -0,0 +1,1129 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * cms_form +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/controllers/main.py:0 +#, python-format +msgid "%s model has no CMS form registered." +msgstr "" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form_mixin.py:0 +#, python-format +msgid "Are you sure want to submit the form as it is?" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form +msgid "CMS Form" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_many2many +msgid "CMS Form M2M widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_many2one_multi +msgid "CMS Form M2O multi widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_many2one +msgid "CMS Form M2O widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_one2many +msgid "CMS Form O2M widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_x2m_mixin +msgid "CMS Form X2M widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_binary_mixin +msgid "CMS Form binary widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_boolean +msgid "CMS Form boolean widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_char +#: model:ir.model,name:cms_form.model_cms_form_widget_char_mixin +msgid "CMS Form char widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_date +msgid "CMS Form date widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_binary +msgid "CMS Form file widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_float +msgid "CMS Form float widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_hidden +msgid "CMS Form hidden widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_image +msgid "CMS Form image widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_integer +msgid "CMS Form integer widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_mixin +msgid "CMS Form mixin" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_numeric_mixin +msgid "CMS Form numeric mixin widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_radio +msgid "CMS Form radio widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_rel_mixin +msgid "CMS Form relation widget mixin" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_search +msgid "CMS Form search" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_selection +msgid "CMS Form selection widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_text +msgid "CMS Form text widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_mixin +msgid "CMS Form widget mixin" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_wizard +msgid "CMS Form wizard" +msgstr "" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.base_form_buttons +msgid "Cancel" +msgstr "" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.wizard_form_confirm_modal +msgid "Close" +msgstr "" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.wizard_form_confirm_modal +msgid "Confirm submit" +msgstr "" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form.py:0 +#: code:addons/cms_form/models/cms_form.py:0 +#, python-format +msgid "Create %s" +msgstr "" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form.py:0 +#, python-format +msgid "Edit \"%s\"" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_action +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_action +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_action +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_action +msgid "Form Action" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_ajax +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_ajax +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_ajax +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_ajax +msgid "Form Ajax" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_ajax_onchange +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_ajax_onchange +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_ajax_onchange +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_ajax_onchange +msgid "Form Ajax Onchange" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_buttons_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_buttons_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_buttons_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_buttons_template +msgid "Form Buttons Template" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_confirm_submit +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_confirm_submit +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_confirm_submit +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_confirm_submit +msgid "Form Confirm Submit" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_content_selector +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_content_selector +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_content_selector +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_content_selector +msgid "Form Content Selector" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_data +msgid "Form Data" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_description +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_description +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_description +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_description +msgid "Form Description" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_display_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_display_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_display_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_display_mode +msgid "Form Display Mode" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_extra_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_extra_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_extra_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_extra_css_klass +msgid "Form Extra Css Klass" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_extract_value_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_extract_value_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_extract_value_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_extract_value_mode +msgid "Form Extract Value Mode" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_attributes +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_attributes +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_attributes +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_attributes +msgid "Form Fields Attributes" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_blacklist +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_blacklist +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_blacklist +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_blacklist +msgid "Form Fields Blacklist" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_hidden +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_hidden +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_hidden +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_hidden +msgid "Form Fields Hidden" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_ignore +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_ignore +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_ignore +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_ignore +msgid "Form Fields Ignore" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_order +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_order +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_order +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_order +msgid "Form Fields Order" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_template +msgid "Form Fields Template" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_whitelist +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_whitelist +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_whitelist +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_whitelist +msgid "Form Fields Whitelist" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_wrapper_template +msgid "Form Fields Wrapper Template" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fieldsets +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fieldsets +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fieldsets +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fieldsets +msgid "Form Fieldsets" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fieldsets_display +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fieldsets_display +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fieldsets_display +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fieldsets_display +msgid "Form Fieldsets Display" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fname_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fname_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fname_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fname_pattern +msgid "Form Fname Pattern" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_method +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_method +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_method +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_method +msgid "Form Method" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_mode +msgid "Form Mode" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_model_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_model_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_model_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_model_fields +msgid "Form Model Fields" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_model_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_model_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_model_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_model_name +msgid "Form Model Name" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_no_result_msg +msgid "Form No Result Msg" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_no_search_results_template +msgid "Form No Search Results Template" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_redirect +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_redirect +msgid "Form Redirect" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_render_values +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_render_values +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_render_values +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_render_values +msgid "Form Render Values" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_required_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_required_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_required_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_required_fields +msgid "Form Required Fields" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_reset +msgid "Form Reset" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_results_orderby +msgid "Form Results Orderby" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_results_per_page +msgid "Form Results Per Page" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_search_domain_rules +msgid "Form Search Domain Rules" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_search_fields_multi +msgid "Form Search Fields Multi" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_search_results +msgid "Form Search Results" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_search_results_template +msgid "Form Search Results Template" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_show_progress_bar +msgid "Form Show Progress Bar" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_show_results_no_submit +msgid "Form Show Results No Submit" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_show_search_form +msgid "Form Show Search Form" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_step_store_all_fields +msgid "Form Step Store All Fields" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_step_stored_fields +msgid "Form Step Stored Fields" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_sub_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_sub_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_sub_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_sub_fields +msgid "Form Sub Fields" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_success +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_success +msgid "Form Success" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_template +msgid "Form Template" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_title +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_title +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_title +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_title +msgid "Form Title" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_validators +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_validators +msgid "Form Validators" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_wrapper_extra_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_wrapper_extra_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_wrapper_extra_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_wrapper_extra_css_klass +msgid "Form Wrapper Extra Css Klass" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_wrapper_template +msgid "Form Wrapper Template" +msgstr "" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.fieldsets_as_tabs +msgid "Home" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form__form_display_mode__horizontal +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_mixin__form_display_mode__horizontal +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_search__form_display_mode__horizontal +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_wizard__form_display_mode__horizontal +msgid "Horizontal" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__id +msgid "ID" +msgstr "" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form.py:0 +#, python-format +msgid "Item created." +msgstr "" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form.py:0 +#, python-format +msgid "Item updated." +msgstr "" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.field_widget_image +msgid "Keep current image" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__main_object +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__main_object +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__main_object +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__main_object +msgid "Main Object" +msgstr "" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.form_field_label_after_wrapper +#: model_terms:ir.ui.view,arch_db:cms_form.form_horizontal_field_wrapper +#: model_terms:ir.ui.view,arch_db:cms_form.form_vertical_field_wrapper +msgid "Name" +msgstr "" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.wizard_form_buttons +msgid "Next" +msgstr "" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_search_form.py:0 +#, python-format +msgid "No items" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__o_request +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__o_request +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__o_request +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__o_request +msgid "O Request" +msgstr "" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.wizard_form_buttons +msgid "Prev" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form__form_extract_value_mode__read +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_mixin__form_extract_value_mode__read +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_wizard__form_extract_value_mode__read +msgid "Read" +msgstr "" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.field_widget_image +msgid "Replace current image" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__request +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__request +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__request +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__request +msgid "Request" +msgstr "" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.base_form +msgid "Required fields" +msgstr "" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_search_form.py:0 +#: model_terms:ir.ui.view,arch_db:cms_form.search_form_buttons +#, python-format +msgid "Search" +msgstr "" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_search_form.py:0 +#, python-format +msgid "Search %s" +msgstr "" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form.py:0 +#, python-format +msgid "Some required fields are empty." +msgstr "" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.base_form_buttons +#: model_terms:ir.ui.view,arch_db:cms_form.wizard_form_buttons +#: model_terms:ir.ui.view,arch_db:cms_form.wizard_form_confirm_modal +msgid "Submit" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form__form_fieldsets_display__tabs +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_mixin__form_fieldsets_display__tabs +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_search__form_fieldsets_display__tabs +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_wizard__form_fieldsets_display__tabs +msgid "Tabs" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,help:cms_form.field_cms_form_widget_char__w_valid_pattern +#: model:ir.model.fields,help:cms_form.field_cms_form_widget_char_mixin__w_valid_pattern +#: model:ir.model.fields,help:cms_form.field_cms_form_widget_float__w_valid_pattern +#: model:ir.model.fields,help:cms_form.field_cms_form_widget_integer__w_valid_pattern +#: model:ir.model.fields,help:cms_form.field_cms_form_widget_numeric_mixin__w_valid_pattern +#: model:ir.model.fields,help:cms_form.field_cms_form_widget_text__w_valid_pattern +msgid "Used to validate inputs with `pattern` attr" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form__form_display_mode__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form__form_fieldsets_display__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_mixin__form_display_mode__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_mixin__form_fieldsets_display__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_search__form_display_mode__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_search__form_fieldsets_display__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_wizard__form_display_mode__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_wizard__form_fieldsets_display__vertical +msgid "Vertical" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_comodel_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_comodel_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_comodel_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_comodel_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_comodel_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_comodel_name +msgid "W Comodel Name" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_css_klass +msgid "W Css Klass" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_data +msgid "W Data" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_date_format +msgid "W Date Format" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_default_today +msgid "W Default Today" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_display_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_display_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_display_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_display_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_display_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_display_field +msgid "W Display Field" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_domain +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_domain +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_domain +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_domain +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_domain +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_domain +msgid "W Domain" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_field +msgid "W Field" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_field_value +msgid "W Field Value" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_fname +msgid "W Fname" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_form +msgid "W Form" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_input_max +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_input_max +msgid "W Input Max" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_input_min +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_input_min +msgid "W Input Min" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_input_type +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_input_type +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_input_type +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_input_type +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_input_type +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_input_type +msgid "W Input Type" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_maxlength +msgid "W Maxlength" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_options_help +msgid "W Options Help" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_placeholder +msgid "W Placeholder" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_readonly +msgid "W Readonly" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_record +msgid "W Record" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_subfields +msgid "W Subfields" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_template +msgid "W Template" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_true_values +msgid "W True Values" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_valid_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_valid_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_valid_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_valid_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_valid_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_valid_pattern +msgid "W Valid Pattern" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_wrapper_css_klass +msgid "W Wrapper Css Klass" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_wrapper_template +msgid "W Wrapper Template" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form__form_extract_value_mode__write +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_mixin__form_extract_value_mode__write +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_wizard__form_extract_value_mode__write +msgid "Write" +msgstr "" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form_mixin.py:0 +#, python-format +msgid "You are not allowed to create any record for the model `%s`." +msgstr "" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form_mixin.py:0 +#, python-format +msgid "You cannot edit this record. Model: %(model)s, ID: %(obj_id)s." +msgstr "" diff --git a/cms_form/i18n/de.po b/cms_form/i18n/de.po new file mode 100644 index 000000000..29ed7b931 --- /dev/null +++ b/cms_form/i18n/de.po @@ -0,0 +1,1147 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * cms_form +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 9.0c\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-03-30 10:43+0000\n" +"PO-Revision-Date: 2017-03-30 12:45+0200\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"Language: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: \n" +"X-Generator: Poedit 1.8.9\n" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/controllers/main.py:0 +#, python-format +msgid "%s model has no CMS form registered." +msgstr "" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form_mixin.py:0 +#, python-format +msgid "Are you sure want to submit the form as it is?" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form +msgid "CMS Form" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_many2many +msgid "CMS Form M2M widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_many2one_multi +msgid "CMS Form M2O multi widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_many2one +msgid "CMS Form M2O widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_one2many +msgid "CMS Form O2M widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_x2m_mixin +msgid "CMS Form X2M widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_binary_mixin +msgid "CMS Form binary widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_boolean +msgid "CMS Form boolean widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_char +#: model:ir.model,name:cms_form.model_cms_form_widget_char_mixin +msgid "CMS Form char widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_date +msgid "CMS Form date widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_binary +msgid "CMS Form file widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_float +msgid "CMS Form float widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_hidden +msgid "CMS Form hidden widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_image +msgid "CMS Form image widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_integer +msgid "CMS Form integer widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_mixin +msgid "CMS Form mixin" +msgstr "CMS Form mixin" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_numeric_mixin +msgid "CMS Form numeric mixin widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_radio +msgid "CMS Form radio widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_rel_mixin +msgid "CMS Form relation widget mixin" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_search +msgid "CMS Form search" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_selection +msgid "CMS Form selection widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_text +msgid "CMS Form text widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_mixin +msgid "CMS Form widget mixin" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_wizard +msgid "CMS Form wizard" +msgstr "" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.base_form_buttons +msgid "Cancel" +msgstr "Abbrechen" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.wizard_form_confirm_modal +msgid "Close" +msgstr "" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.wizard_form_confirm_modal +msgid "Confirm submit" +msgstr "" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form.py:0 +#, python-format +msgid "Create %s" +msgstr "%s anlegen" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form.py:0 +#, python-format +msgid "Edit \"%s\"" +msgstr "\"%s\" Bearbeiten" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_action +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_action +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_action +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_action +msgid "Form Action" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_ajax +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_ajax +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_ajax +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_ajax +msgid "Form Ajax" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_ajax_onchange +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_ajax_onchange +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_ajax_onchange +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_ajax_onchange +msgid "Form Ajax Onchange" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_buttons_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_buttons_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_buttons_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_buttons_template +msgid "Form Buttons Template" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_confirm_submit +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_confirm_submit +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_confirm_submit +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_confirm_submit +msgid "Form Confirm Submit" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_content_selector +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_content_selector +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_content_selector +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_content_selector +msgid "Form Content Selector" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_data +msgid "Form Data" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_description +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_description +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_description +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_description +msgid "Form Description" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_display_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_display_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_display_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_display_mode +msgid "Form Display Mode" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_extra_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_extra_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_extra_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_extra_css_klass +msgid "Form Extra Css Klass" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_extract_value_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_extract_value_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_extract_value_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_extract_value_mode +msgid "Form Extract Value Mode" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_attributes +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_attributes +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_attributes +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_attributes +msgid "Form Fields Attributes" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_blacklist +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_blacklist +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_blacklist +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_blacklist +msgid "Form Fields Blacklist" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_hidden +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_hidden +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_hidden +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_hidden +msgid "Form Fields Hidden" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_ignore +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_ignore +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_ignore +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_ignore +msgid "Form Fields Ignore" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_order +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_order +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_order +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_order +msgid "Form Fields Order" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_template +msgid "Form Fields Template" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_whitelist +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_whitelist +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_whitelist +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_whitelist +msgid "Form Fields Whitelist" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_wrapper_template +msgid "Form Fields Wrapper Template" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fieldsets +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fieldsets +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fieldsets +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fieldsets +msgid "Form Fieldsets" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fieldsets_display +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fieldsets_display +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fieldsets_display +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fieldsets_display +msgid "Form Fieldsets Display" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fname_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fname_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fname_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fname_pattern +msgid "Form Fname Pattern" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_method +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_method +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_method +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_method +msgid "Form Method" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_mode +msgid "Form Mode" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_model_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_model_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_model_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_model_fields +msgid "Form Model Fields" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_model_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_model_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_model_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_model_name +msgid "Form Model Name" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_no_result_msg +msgid "Form No Result Msg" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_no_search_results_template +msgid "Form No Search Results Template" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_redirect +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_redirect +msgid "Form Redirect" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_render_values +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_render_values +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_render_values +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_render_values +msgid "Form Render Values" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_required_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_required_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_required_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_required_fields +msgid "Form Required Fields" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_reset +msgid "Form Reset" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_results_orderby +msgid "Form Results Orderby" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_results_per_page +msgid "Form Results Per Page" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_search_domain_rules +msgid "Form Search Domain Rules" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_search_fields_multi +msgid "Form Search Fields Multi" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_search_results +msgid "Form Search Results" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_search_results_template +msgid "Form Search Results Template" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_show_progress_bar +msgid "Form Show Progress Bar" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_show_results_no_submit +msgid "Form Show Results No Submit" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_show_search_form +msgid "Form Show Search Form" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_step_store_all_fields +msgid "Form Step Store All Fields" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_step_stored_fields +msgid "Form Step Stored Fields" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_sub_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_sub_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_sub_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_sub_fields +msgid "Form Sub Fields" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_success +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_success +msgid "Form Success" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_template +msgid "Form Template" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_title +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_title +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_title +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_title +msgid "Form Title" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_validators +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_validators +msgid "Form Validators" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_wrapper_extra_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_wrapper_extra_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_wrapper_extra_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_wrapper_extra_css_klass +msgid "Form Wrapper Extra Css Klass" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_wrapper_template +msgid "Form Wrapper Template" +msgstr "" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.fieldsets_as_tabs +msgid "Home" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form__form_display_mode__horizontal +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_mixin__form_display_mode__horizontal +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_search__form_display_mode__horizontal +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_wizard__form_display_mode__horizontal +msgid "Horizontal" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__id +msgid "ID" +msgstr "ID" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form.py:0 +#, python-format +msgid "Item created." +msgstr "Objekt wurde erstellt." + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form.py:0 +#, python-format +msgid "Item updated." +msgstr "Objekt wurde aktualisiert." + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.field_widget_image +msgid "Keep current image" +msgstr "Aktuelles Bild beibehalten" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__main_object +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__main_object +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__main_object +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__main_object +msgid "Main Object" +msgstr "" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.form_field_label_after_wrapper +#: model_terms:ir.ui.view,arch_db:cms_form.form_horizontal_field_wrapper +#: model_terms:ir.ui.view,arch_db:cms_form.form_vertical_field_wrapper +msgid "Name" +msgstr "Bezeichnung" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.wizard_form_buttons +msgid "Next" +msgstr "" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_search_form.py:0 +#, python-format +msgid "No items" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__o_request +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__o_request +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__o_request +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__o_request +msgid "O Request" +msgstr "" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.wizard_form_buttons +msgid "Prev" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form__form_extract_value_mode__read +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_mixin__form_extract_value_mode__read +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_wizard__form_extract_value_mode__read +msgid "Read" +msgstr "" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.field_widget_image +msgid "Replace current image" +msgstr "Aktuelles Bild ersetzen" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__request +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__request +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__request +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__request +msgid "Request" +msgstr "" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.base_form +msgid "Required fields" +msgstr "Pflichtfelder" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_search_form.py:0 +#: model_terms:ir.ui.view,arch_db:cms_form.search_form_buttons +#, python-format +msgid "Search" +msgstr "Suche" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_search_form.py:0 +#, python-format +msgid "Search %s" +msgstr "%s suchen" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form.py:0 +#, python-format +msgid "Some required fields are empty." +msgstr "Bitte füllen Sie alle Pflichtfelder aus." + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.base_form_buttons +#: model_terms:ir.ui.view,arch_db:cms_form.wizard_form_buttons +#: model_terms:ir.ui.view,arch_db:cms_form.wizard_form_confirm_modal +msgid "Submit" +msgstr "Absenden" + +#. module: cms_form +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form__form_fieldsets_display__tabs +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_mixin__form_fieldsets_display__tabs +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_search__form_fieldsets_display__tabs +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_wizard__form_fieldsets_display__tabs +msgid "Tabs" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,help:cms_form.field_cms_form_widget_char__w_valid_pattern +#: model:ir.model.fields,help:cms_form.field_cms_form_widget_char_mixin__w_valid_pattern +#: model:ir.model.fields,help:cms_form.field_cms_form_widget_float__w_valid_pattern +#: model:ir.model.fields,help:cms_form.field_cms_form_widget_integer__w_valid_pattern +#: model:ir.model.fields,help:cms_form.field_cms_form_widget_numeric_mixin__w_valid_pattern +#: model:ir.model.fields,help:cms_form.field_cms_form_widget_text__w_valid_pattern +msgid "Used to validate inputs with `pattern` attr" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form__form_display_mode__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form__form_fieldsets_display__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_mixin__form_display_mode__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_mixin__form_fieldsets_display__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_search__form_display_mode__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_search__form_fieldsets_display__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_wizard__form_display_mode__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_wizard__form_fieldsets_display__vertical +msgid "Vertical" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_comodel_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_comodel_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_comodel_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_comodel_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_comodel_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_comodel_name +msgid "W Comodel Name" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_css_klass +msgid "W Css Klass" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_data +msgid "W Data" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_date_format +msgid "W Date Format" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_default_today +msgid "W Default Today" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_display_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_display_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_display_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_display_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_display_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_display_field +msgid "W Display Field" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_domain +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_domain +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_domain +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_domain +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_domain +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_domain +msgid "W Domain" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_field +msgid "W Field" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_field_value +msgid "W Field Value" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_fname +msgid "W Fname" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_form +msgid "W Form" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_input_max +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_input_max +msgid "W Input Max" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_input_min +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_input_min +msgid "W Input Min" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_input_type +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_input_type +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_input_type +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_input_type +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_input_type +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_input_type +msgid "W Input Type" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_maxlength +msgid "W Maxlength" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_options_help +msgid "W Options Help" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_placeholder +msgid "W Placeholder" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_readonly +msgid "W Readonly" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_record +msgid "W Record" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_subfields +msgid "W Subfields" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_template +msgid "W Template" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_true_values +msgid "W True Values" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_valid_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_valid_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_valid_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_valid_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_valid_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_valid_pattern +msgid "W Valid Pattern" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_wrapper_css_klass +msgid "W Wrapper Css Klass" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_wrapper_template +msgid "W Wrapper Template" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form__form_extract_value_mode__write +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_mixin__form_extract_value_mode__write +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_wizard__form_extract_value_mode__write +msgid "Write" +msgstr "" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form_mixin.py:0 +#, python-format +msgid "You are not allowed to create any record for the model `%s`." +msgstr "" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form_mixin.py:0 +#, python-format +msgid "You cannot edit this record. Model: %(model)s, ID: %(obj_id)s." +msgstr "" + +#~ msgid "Display Name" +#~ msgstr "Angezeigter Name" + +#~ msgid "Last Modified on" +#~ msgstr "Zuletzt geändert am" + +#~ msgid "YYYY-MM-DD" +#~ msgstr "JJJJ-MM-TT" + +#~ msgid "CMS edit URL" +#~ msgstr "CMS edit URL" + +#~ msgid "website.published.mixin" +#~ msgstr "website.published.mixin" diff --git a/cms_form/i18n/es.po b/cms_form/i18n/es.po new file mode 100644 index 000000000..df51766e8 --- /dev/null +++ b/cms_form/i18n/es.po @@ -0,0 +1,1131 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * cms_form +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2023-12-13 14:36+0000\n" +"Last-Translator: Ivorra78 \n" +"Language-Team: none\n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.17\n" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/controllers/main.py:0 +#, python-format +msgid "%s model has no CMS form registered." +msgstr "%s El modelo no tiene ningún formulario CMS registrado." + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form_mixin.py:0 +#, python-format +msgid "Are you sure want to submit the form as it is?" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form +msgid "CMS Form" +msgstr "Form CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_many2many +msgid "CMS Form M2M widget" +msgstr "Formulario CMS Widget M2M" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_many2one_multi +msgid "CMS Form M2O multi widget" +msgstr "Formulario CMS M2O multi widget" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_many2one +msgid "CMS Form M2O widget" +msgstr "Formulario CMS Widget M2O" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_one2many +msgid "CMS Form O2M widget" +msgstr "Formulario CMS O2M widget" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_x2m_mixin +msgid "CMS Form X2M widget" +msgstr "Formulario CMS Widget X2M" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_binary_mixin +msgid "CMS Form binary widget" +msgstr "Widget binario de formulario CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_boolean +msgid "CMS Form boolean widget" +msgstr "Widget booleano de formulario CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_char +#: model:ir.model,name:cms_form.model_cms_form_widget_char_mixin +msgid "CMS Form char widget" +msgstr "Widget de caracteres de formulario CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_date +msgid "CMS Form date widget" +msgstr "Widget de fecha del formulario CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_binary +msgid "CMS Form file widget" +msgstr "Widget del formulario CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_float +msgid "CMS Form float widget" +msgstr "Widget flotante de formulario CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_hidden +msgid "CMS Form hidden widget" +msgstr "Widget oculto de formulario CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_image +msgid "CMS Form image widget" +msgstr "Widget de imagen de formulario CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_integer +msgid "CMS Form integer widget" +msgstr "Widget de número entero de formulario CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_mixin +msgid "CMS Form mixin" +msgstr "Formulario CMS mezcla" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_numeric_mixin +msgid "CMS Form numeric mixin widget" +msgstr "Widget mixto numérico CMS Form" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_radio +msgid "CMS Form radio widget" +msgstr "Widget de radio Formulario CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_rel_mixin +msgid "CMS Form relation widget mixin" +msgstr "Mezcla de widgets de relación de formulario CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_search +msgid "CMS Form search" +msgstr "Búsqueda de formularios CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_selection +msgid "CMS Form selection widget" +msgstr "Widget de selección de formulario CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_text +msgid "CMS Form text widget" +msgstr "Widget de texto de formulario CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_mixin +msgid "CMS Form widget mixin" +msgstr "Mezcla de widgets de formulario CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_wizard +msgid "CMS Form wizard" +msgstr "Asistente de formularios CMS" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.base_form_buttons +msgid "Cancel" +msgstr "Cancelar" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.wizard_form_confirm_modal +msgid "Close" +msgstr "" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.wizard_form_confirm_modal +msgid "Confirm submit" +msgstr "" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form.py:0 +#, python-format +msgid "Create %s" +msgstr "Crear %s" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form.py:0 +#, python-format +msgid "Edit \"%s\"" +msgstr "Editar \"%s\"" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_action +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_action +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_action +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_action +msgid "Form Action" +msgstr "Acción Formulario" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_ajax +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_ajax +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_ajax +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_ajax +msgid "Form Ajax" +msgstr "Formulario Ajax" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_ajax_onchange +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_ajax_onchange +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_ajax_onchange +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_ajax_onchange +msgid "Form Ajax Onchange" +msgstr "Formulario Ajax Onchange" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_buttons_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_buttons_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_buttons_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_buttons_template +msgid "Form Buttons Template" +msgstr "Plantilla de Botones de Formulario" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_confirm_submit +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_confirm_submit +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_confirm_submit +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_confirm_submit +msgid "Form Confirm Submit" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_content_selector +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_content_selector +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_content_selector +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_content_selector +msgid "Form Content Selector" +msgstr "Selector de Contenido de Formulario" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_data +msgid "Form Data" +msgstr "Datos del Formulario" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_description +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_description +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_description +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_description +msgid "Form Description" +msgstr "Descripción del Formulario" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_display_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_display_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_display_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_display_mode +msgid "Form Display Mode" +msgstr "Modo de Visualización de Formulario" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_extra_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_extra_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_extra_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_extra_css_klass +msgid "Form Extra Css Klass" +msgstr "Formulario Clase Css Extra" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_extract_value_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_extract_value_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_extract_value_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_extract_value_mode +msgid "Form Extract Value Mode" +msgstr "Modo de Valor de Extracción de Formulario" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_attributes +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_attributes +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_attributes +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_attributes +msgid "Form Fields Attributes" +msgstr "Atributos de los Campos del Formulario" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_blacklist +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_blacklist +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_blacklist +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_blacklist +msgid "Form Fields Blacklist" +msgstr "Lista Negra de Campos de Formulario" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_hidden +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_hidden +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_hidden +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_hidden +msgid "Form Fields Hidden" +msgstr "Campos de Formulario Ocultos" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_ignore +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_ignore +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_ignore +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_ignore +msgid "Form Fields Ignore" +msgstr "Ignorar Campos de Formulario" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_order +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_order +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_order +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_order +msgid "Form Fields Order" +msgstr "Orden de los Campos del Formulario" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_template +msgid "Form Fields Template" +msgstr "Plantilla de Campos de Formulario" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_whitelist +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_whitelist +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_whitelist +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_whitelist +msgid "Form Fields Whitelist" +msgstr "Lista Blanca de Campos de Formulario" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_wrapper_template +msgid "Form Fields Wrapper Template" +msgstr "Plantilla Envolvente de Campos de Formulario" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fieldsets +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fieldsets +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fieldsets +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fieldsets +msgid "Form Fieldsets" +msgstr "Conjuntos de campos de formulario" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fieldsets_display +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fieldsets_display +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fieldsets_display +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fieldsets_display +msgid "Form Fieldsets Display" +msgstr "Visualización de Conjuntos de Campos de Formulario" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fname_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fname_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fname_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fname_pattern +msgid "Form Fname Pattern" +msgstr "Patrón del Formulario Fname" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_method +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_method +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_method +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_method +msgid "Form Method" +msgstr "Método de Formulario" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_mode +msgid "Form Mode" +msgstr "Modo Formulario" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_model_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_model_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_model_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_model_fields +msgid "Form Model Fields" +msgstr "Campos del Modelo de Formulario" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_model_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_model_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_model_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_model_name +msgid "Form Model Name" +msgstr "Nombre del Modelo de Formulario" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_no_result_msg +msgid "Form No Result Msg" +msgstr "No de Form Resultado Msg" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_no_search_results_template +msgid "Form No Search Results Template" +msgstr "Plantilla de Formulario sin Resultados de Búsqueda" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_redirect +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_redirect +msgid "Form Redirect" +msgstr "Redirección de Formularios" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_render_values +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_render_values +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_render_values +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_render_values +msgid "Form Render Values" +msgstr "Valores de Renderizado de Formularios" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_required_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_required_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_required_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_required_fields +msgid "Form Required Fields" +msgstr "Campos Obligatorios del Formulario" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_reset +msgid "Form Reset" +msgstr "Restablecer Formulario" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_results_orderby +msgid "Form Results Orderby" +msgstr "Resultados del formulario Ordenar por" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_results_per_page +msgid "Form Results Per Page" +msgstr "Resultados de Formularios por Página" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_search_domain_rules +msgid "Form Search Domain Rules" +msgstr "Reglas del Dominio de Búsqueda de Formularios" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_search_fields_multi +msgid "Form Search Fields Multi" +msgstr "Campos de Búsqueda en el Formulario Multi" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_search_results +msgid "Form Search Results" +msgstr "Resultados de la Búsqueda de Formularios" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_search_results_template +msgid "Form Search Results Template" +msgstr "Plantilla de Resultados de Búsqueda de Formularios" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_show_progress_bar +msgid "Form Show Progress Bar" +msgstr "Formulario Mostrar barra de Progreso" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_show_results_no_submit +msgid "Form Show Results No Submit" +msgstr "Formulario Mostrar resultados No enviar" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_show_search_form +msgid "Form Show Search Form" +msgstr "Formulario Mostrar Formulario de búsqueda" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_step_store_all_fields +msgid "Form Step Store All Fields" +msgstr "Paso de formulario Almacenar todos los Campos" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_step_stored_fields +msgid "Form Step Stored Fields" +msgstr "Campos Almacenados en el Paso del Formulario" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_sub_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_sub_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_sub_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_sub_fields +msgid "Form Sub Fields" +msgstr "Campos Secundarios del Formulario" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_success +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_success +msgid "Form Success" +msgstr "Formulario Éxito" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_template +msgid "Form Template" +msgstr "Plantilla de Formulario" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_title +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_title +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_title +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_title +msgid "Form Title" +msgstr "Título del Formulario" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_validators +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_validators +msgid "Form Validators" +msgstr "Validadores de Formularios" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_wrapper_extra_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_wrapper_extra_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_wrapper_extra_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_wrapper_extra_css_klass +msgid "Form Wrapper Extra Css Klass" +msgstr "Envoltorio de formulario Extra Css Klass" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_wrapper_template +msgid "Form Wrapper Template" +msgstr "Plantilla de Contenedor de Formulario" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.fieldsets_as_tabs +msgid "Home" +msgstr "Casa" + +#. module: cms_form +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form__form_display_mode__horizontal +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_mixin__form_display_mode__horizontal +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_search__form_display_mode__horizontal +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_wizard__form_display_mode__horizontal +msgid "Horizontal" +msgstr "Horizontal" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__id +msgid "ID" +msgstr "ID" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form.py:0 +#, python-format +msgid "Item created." +msgstr "Ítem creado." + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form.py:0 +#, python-format +msgid "Item updated." +msgstr "Ítem actualizado." + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.field_widget_image +msgid "Keep current image" +msgstr "Dejar imagen actual" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__main_object +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__main_object +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__main_object +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__main_object +msgid "Main Object" +msgstr "Objeto Principal" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.form_field_label_after_wrapper +#: model_terms:ir.ui.view,arch_db:cms_form.form_horizontal_field_wrapper +#: model_terms:ir.ui.view,arch_db:cms_form.form_vertical_field_wrapper +msgid "Name" +msgstr "Nombre" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.wizard_form_buttons +msgid "Next" +msgstr "Siguiente" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_search_form.py:0 +#, python-format +msgid "No items" +msgstr "No hay artículos" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__o_request +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__o_request +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__o_request +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__o_request +msgid "O Request" +msgstr "Solicitud O" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.wizard_form_buttons +msgid "Prev" +msgstr "Previo" + +#. module: cms_form +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form__form_extract_value_mode__read +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_mixin__form_extract_value_mode__read +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_wizard__form_extract_value_mode__read +msgid "Read" +msgstr "Leer" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.field_widget_image +msgid "Replace current image" +msgstr "Reemplaza imagen actual" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__request +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__request +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__request +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__request +msgid "Request" +msgstr "Petición" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.base_form +msgid "Required fields" +msgstr "Campos requeridos" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_search_form.py:0 +#: model_terms:ir.ui.view,arch_db:cms_form.search_form_buttons +#, python-format +msgid "Search" +msgstr "Búsqueda" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_search_form.py:0 +#, python-format +msgid "Search %s" +msgstr "Búsqueda %s" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form.py:0 +#, python-format +msgid "Some required fields are empty." +msgstr "Algunos campos requeridos están vacíos." + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.base_form_buttons +#: model_terms:ir.ui.view,arch_db:cms_form.wizard_form_buttons +#: model_terms:ir.ui.view,arch_db:cms_form.wizard_form_confirm_modal +msgid "Submit" +msgstr "Confirmar" + +#. module: cms_form +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form__form_fieldsets_display__tabs +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_mixin__form_fieldsets_display__tabs +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_search__form_fieldsets_display__tabs +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_wizard__form_fieldsets_display__tabs +msgid "Tabs" +msgstr "Pestañas" + +#. module: cms_form +#: model:ir.model.fields,help:cms_form.field_cms_form_widget_char__w_valid_pattern +#: model:ir.model.fields,help:cms_form.field_cms_form_widget_char_mixin__w_valid_pattern +#: model:ir.model.fields,help:cms_form.field_cms_form_widget_float__w_valid_pattern +#: model:ir.model.fields,help:cms_form.field_cms_form_widget_integer__w_valid_pattern +#: model:ir.model.fields,help:cms_form.field_cms_form_widget_numeric_mixin__w_valid_pattern +#: model:ir.model.fields,help:cms_form.field_cms_form_widget_text__w_valid_pattern +msgid "Used to validate inputs with `pattern` attr" +msgstr "Se utiliza para validar entradas con `pattern` attr" + +#. module: cms_form +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form__form_display_mode__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form__form_fieldsets_display__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_mixin__form_display_mode__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_mixin__form_fieldsets_display__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_search__form_display_mode__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_search__form_fieldsets_display__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_wizard__form_display_mode__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_wizard__form_fieldsets_display__vertical +msgid "Vertical" +msgstr "Vertical" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_comodel_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_comodel_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_comodel_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_comodel_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_comodel_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_comodel_name +msgid "W Comodel Name" +msgstr "W Nombre del Comodelo" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_css_klass +msgid "W Css Klass" +msgstr "W Css Klass" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_data +msgid "W Data" +msgstr "W Datos" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_date_format +msgid "W Date Format" +msgstr "W Formato de Fecha" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_default_today +msgid "W Default Today" +msgstr "W Por Defecto Hoy" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_display_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_display_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_display_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_display_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_display_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_display_field +msgid "W Display Field" +msgstr "W Campo de Visualización" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_domain +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_domain +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_domain +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_domain +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_domain +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_domain +msgid "W Domain" +msgstr "W Dominio" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_field +msgid "W Field" +msgstr "Campo W" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_field_value +msgid "W Field Value" +msgstr "W Valor del Campo" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_fname +msgid "W Fname" +msgstr "W Fname" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_form +msgid "W Form" +msgstr "Formulario W" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_input_max +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_input_max +msgid "W Input Max" +msgstr "W Entrada Máx" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_input_min +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_input_min +msgid "W Input Min" +msgstr "W Entrada Mín" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_input_type +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_input_type +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_input_type +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_input_type +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_input_type +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_input_type +msgid "W Input Type" +msgstr "W Tipo de Entrada" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_maxlength +msgid "W Maxlength" +msgstr "W Longitud máx" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_options_help +msgid "W Options Help" +msgstr "W Opciones Ayuda" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_placeholder +msgid "W Placeholder" +msgstr "W Marcador de posición" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_readonly +msgid "W Readonly" +msgstr "Sólo lectura W" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_record +msgid "W Record" +msgstr "Registro W" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_subfields +msgid "W Subfields" +msgstr "W Subcampos" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_template +msgid "W Template" +msgstr "Plantilla W" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_true_values +msgid "W True Values" +msgstr "W Valores Verdaderos" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_valid_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_valid_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_valid_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_valid_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_valid_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_valid_pattern +msgid "W Valid Pattern" +msgstr "W Patrón Válido" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_wrapper_css_klass +msgid "W Wrapper Css Klass" +msgstr "Clase W Wrapper Css" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_wrapper_template +msgid "W Wrapper Template" +msgstr "Plantilla de Envoltura W" + +#. module: cms_form +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form__form_extract_value_mode__write +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_mixin__form_extract_value_mode__write +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_wizard__form_extract_value_mode__write +msgid "Write" +msgstr "Escribir" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form_mixin.py:0 +#, python-format +msgid "You are not allowed to create any record for the model `%s`." +msgstr "No se le permite crear ningún registro para el modelo `%s`." + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form_mixin.py:0 +#, python-format +msgid "You cannot edit this record. Model: %(model)s, ID: %(obj_id)s." +msgstr "No puede editar este registro. Modelo: %(model)s, ID: %(obj_id)s." diff --git a/cms_form/i18n/fr.po b/cms_form/i18n/fr.po new file mode 100644 index 000000000..2fd337fc7 --- /dev/null +++ b/cms_form/i18n/fr.po @@ -0,0 +1,1144 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * cms_form +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 11.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2018-10-12 15:08+0000\n" +"Last-Translator: Akim Juillerat \n" +"Language-Team: none\n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" +"X-Generator: Weblate 3.1.1\n" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/controllers/main.py:0 +#, python-format +msgid "%s model has no CMS form registered." +msgstr "Le modèle %s n'a pas de CMS form enregistré." + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form_mixin.py:0 +#, python-format +msgid "Are you sure want to submit the form as it is?" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form +msgid "CMS Form" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_many2many +msgid "CMS Form M2M widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_many2one_multi +msgid "CMS Form M2O multi widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_many2one +msgid "CMS Form M2O widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_one2many +msgid "CMS Form O2M widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_x2m_mixin +msgid "CMS Form X2M widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_binary_mixin +msgid "CMS Form binary widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_boolean +msgid "CMS Form boolean widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_char +#: model:ir.model,name:cms_form.model_cms_form_widget_char_mixin +msgid "CMS Form char widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_date +msgid "CMS Form date widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_binary +msgid "CMS Form file widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_float +msgid "CMS Form float widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_hidden +msgid "CMS Form hidden widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_image +msgid "CMS Form image widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_integer +msgid "CMS Form integer widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_mixin +msgid "CMS Form mixin" +msgstr "Mixin CMS Form" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_numeric_mixin +msgid "CMS Form numeric mixin widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_radio +msgid "CMS Form radio widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_rel_mixin +msgid "CMS Form relation widget mixin" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_search +msgid "CMS Form search" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_selection +msgid "CMS Form selection widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_text +msgid "CMS Form text widget" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_mixin +msgid "CMS Form widget mixin" +msgstr "" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_wizard +msgid "CMS Form wizard" +msgstr "" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.base_form_buttons +msgid "Cancel" +msgstr "Annuler" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.wizard_form_confirm_modal +msgid "Close" +msgstr "" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.wizard_form_confirm_modal +msgid "Confirm submit" +msgstr "" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form.py:0 +#, python-format +msgid "Create %s" +msgstr "Créer %s" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form.py:0 +#, python-format +msgid "Edit \"%s\"" +msgstr "Editer \"%s\"" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_action +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_action +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_action +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_action +msgid "Form Action" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_ajax +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_ajax +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_ajax +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_ajax +msgid "Form Ajax" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_ajax_onchange +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_ajax_onchange +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_ajax_onchange +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_ajax_onchange +msgid "Form Ajax Onchange" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_buttons_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_buttons_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_buttons_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_buttons_template +msgid "Form Buttons Template" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_confirm_submit +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_confirm_submit +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_confirm_submit +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_confirm_submit +msgid "Form Confirm Submit" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_content_selector +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_content_selector +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_content_selector +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_content_selector +msgid "Form Content Selector" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_data +msgid "Form Data" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_description +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_description +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_description +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_description +msgid "Form Description" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_display_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_display_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_display_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_display_mode +msgid "Form Display Mode" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_extra_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_extra_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_extra_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_extra_css_klass +msgid "Form Extra Css Klass" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_extract_value_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_extract_value_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_extract_value_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_extract_value_mode +msgid "Form Extract Value Mode" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_attributes +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_attributes +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_attributes +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_attributes +msgid "Form Fields Attributes" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_blacklist +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_blacklist +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_blacklist +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_blacklist +msgid "Form Fields Blacklist" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_hidden +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_hidden +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_hidden +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_hidden +msgid "Form Fields Hidden" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_ignore +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_ignore +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_ignore +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_ignore +msgid "Form Fields Ignore" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_order +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_order +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_order +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_order +msgid "Form Fields Order" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_template +msgid "Form Fields Template" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_whitelist +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_whitelist +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_whitelist +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_whitelist +msgid "Form Fields Whitelist" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_wrapper_template +msgid "Form Fields Wrapper Template" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fieldsets +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fieldsets +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fieldsets +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fieldsets +msgid "Form Fieldsets" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fieldsets_display +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fieldsets_display +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fieldsets_display +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fieldsets_display +msgid "Form Fieldsets Display" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fname_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fname_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fname_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fname_pattern +msgid "Form Fname Pattern" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_method +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_method +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_method +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_method +msgid "Form Method" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_mode +msgid "Form Mode" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_model_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_model_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_model_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_model_fields +msgid "Form Model Fields" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_model_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_model_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_model_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_model_name +msgid "Form Model Name" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_no_result_msg +msgid "Form No Result Msg" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_no_search_results_template +msgid "Form No Search Results Template" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_redirect +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_redirect +msgid "Form Redirect" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_render_values +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_render_values +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_render_values +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_render_values +msgid "Form Render Values" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_required_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_required_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_required_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_required_fields +msgid "Form Required Fields" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_reset +msgid "Form Reset" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_results_orderby +msgid "Form Results Orderby" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_results_per_page +msgid "Form Results Per Page" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_search_domain_rules +msgid "Form Search Domain Rules" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_search_fields_multi +msgid "Form Search Fields Multi" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_search_results +msgid "Form Search Results" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_search_results_template +msgid "Form Search Results Template" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_show_progress_bar +msgid "Form Show Progress Bar" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_show_results_no_submit +msgid "Form Show Results No Submit" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_show_search_form +msgid "Form Show Search Form" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_step_store_all_fields +msgid "Form Step Store All Fields" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_step_stored_fields +msgid "Form Step Stored Fields" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_sub_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_sub_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_sub_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_sub_fields +msgid "Form Sub Fields" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_success +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_success +msgid "Form Success" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_template +msgid "Form Template" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_title +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_title +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_title +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_title +msgid "Form Title" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_validators +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_validators +msgid "Form Validators" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_wrapper_extra_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_wrapper_extra_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_wrapper_extra_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_wrapper_extra_css_klass +msgid "Form Wrapper Extra Css Klass" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_wrapper_template +msgid "Form Wrapper Template" +msgstr "" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.fieldsets_as_tabs +msgid "Home" +msgstr "Accueil" + +#. module: cms_form +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form__form_display_mode__horizontal +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_mixin__form_display_mode__horizontal +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_search__form_display_mode__horizontal +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_wizard__form_display_mode__horizontal +msgid "Horizontal" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__id +msgid "ID" +msgstr "ID" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form.py:0 +#, python-format +msgid "Item created." +msgstr "Élément créé." + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form.py:0 +#, python-format +msgid "Item updated." +msgstr "Élément mis à jour." + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.field_widget_image +msgid "Keep current image" +msgstr "Garder l'image actuelle" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__main_object +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__main_object +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__main_object +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__main_object +msgid "Main Object" +msgstr "" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.form_field_label_after_wrapper +#: model_terms:ir.ui.view,arch_db:cms_form.form_horizontal_field_wrapper +#: model_terms:ir.ui.view,arch_db:cms_form.form_vertical_field_wrapper +msgid "Name" +msgstr "Nom" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.wizard_form_buttons +msgid "Next" +msgstr "Suivant" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_search_form.py:0 +#, python-format +msgid "No items" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__o_request +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__o_request +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__o_request +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__o_request +msgid "O Request" +msgstr "" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.wizard_form_buttons +msgid "Prev" +msgstr "Précédent" + +#. module: cms_form +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form__form_extract_value_mode__read +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_mixin__form_extract_value_mode__read +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_wizard__form_extract_value_mode__read +msgid "Read" +msgstr "" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.field_widget_image +msgid "Replace current image" +msgstr "Remplacer l'image actuelle" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__request +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__request +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__request +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__request +msgid "Request" +msgstr "" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.base_form +msgid "Required fields" +msgstr "Champs obligatoires" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_search_form.py:0 +#: model_terms:ir.ui.view,arch_db:cms_form.search_form_buttons +#, python-format +msgid "Search" +msgstr "Rechercher" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_search_form.py:0 +#, python-format +msgid "Search %s" +msgstr "Rechercher %s" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form.py:0 +#, python-format +msgid "Some required fields are empty." +msgstr "Des champs obligatoires sont vides." + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.base_form_buttons +#: model_terms:ir.ui.view,arch_db:cms_form.wizard_form_buttons +#: model_terms:ir.ui.view,arch_db:cms_form.wizard_form_confirm_modal +msgid "Submit" +msgstr "Envoyer" + +#. module: cms_form +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form__form_fieldsets_display__tabs +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_mixin__form_fieldsets_display__tabs +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_search__form_fieldsets_display__tabs +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_wizard__form_fieldsets_display__tabs +msgid "Tabs" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,help:cms_form.field_cms_form_widget_char__w_valid_pattern +#: model:ir.model.fields,help:cms_form.field_cms_form_widget_char_mixin__w_valid_pattern +#: model:ir.model.fields,help:cms_form.field_cms_form_widget_float__w_valid_pattern +#: model:ir.model.fields,help:cms_form.field_cms_form_widget_integer__w_valid_pattern +#: model:ir.model.fields,help:cms_form.field_cms_form_widget_numeric_mixin__w_valid_pattern +#: model:ir.model.fields,help:cms_form.field_cms_form_widget_text__w_valid_pattern +msgid "Used to validate inputs with `pattern` attr" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form__form_display_mode__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form__form_fieldsets_display__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_mixin__form_display_mode__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_mixin__form_fieldsets_display__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_search__form_display_mode__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_search__form_fieldsets_display__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_wizard__form_display_mode__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_wizard__form_fieldsets_display__vertical +msgid "Vertical" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_comodel_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_comodel_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_comodel_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_comodel_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_comodel_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_comodel_name +msgid "W Comodel Name" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_css_klass +msgid "W Css Klass" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_data +msgid "W Data" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_date_format +msgid "W Date Format" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_default_today +msgid "W Default Today" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_display_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_display_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_display_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_display_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_display_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_display_field +msgid "W Display Field" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_domain +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_domain +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_domain +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_domain +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_domain +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_domain +msgid "W Domain" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_field +msgid "W Field" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_field_value +msgid "W Field Value" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_fname +msgid "W Fname" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_form +msgid "W Form" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_input_max +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_input_max +msgid "W Input Max" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_input_min +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_input_min +msgid "W Input Min" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_input_type +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_input_type +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_input_type +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_input_type +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_input_type +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_input_type +msgid "W Input Type" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_maxlength +msgid "W Maxlength" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_options_help +msgid "W Options Help" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_placeholder +msgid "W Placeholder" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_readonly +msgid "W Readonly" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_record +msgid "W Record" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_subfields +msgid "W Subfields" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_template +msgid "W Template" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_true_values +msgid "W True Values" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_valid_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_valid_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_valid_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_valid_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_valid_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_valid_pattern +msgid "W Valid Pattern" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_wrapper_css_klass +msgid "W Wrapper Css Klass" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_wrapper_template +msgid "W Wrapper Template" +msgstr "" + +#. module: cms_form +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form__form_extract_value_mode__write +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_mixin__form_extract_value_mode__write +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_wizard__form_extract_value_mode__write +msgid "Write" +msgstr "" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form_mixin.py:0 +#, python-format +msgid "You are not allowed to create any record for the model `%s`." +msgstr "" +"Vous n'êtes pas autorisé à créer des enregistrements pour le modèle `%s`." + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form_mixin.py:0 +#, python-format +msgid "You cannot edit this record. Model: %(model)s, ID: %(obj_id)s." +msgstr "" + +#~ msgid "Display Name" +#~ msgstr "Nom affiché" + +#~ msgid "Last Modified on" +#~ msgstr "Dernière modification le" + +#, python-format +#~ msgid "You cannot edit this record. Model: %s, ID: %s." +#~ msgstr "Vous ne pouvez pas éditer cet enregistrement. Modèle : %s, ID: %s." + +#~ msgid "YYYY-MM-DD" +#~ msgstr "YYYY-MM-DD" diff --git a/cms_form/i18n/it.po b/cms_form/i18n/it.po new file mode 100644 index 000000000..cf13f395a --- /dev/null +++ b/cms_form/i18n/it.po @@ -0,0 +1,1132 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * cms_form +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2025-02-06 23:06+0000\n" +"Last-Translator: mymage \n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.6.2\n" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/controllers/main.py:0 +#, python-format +msgid "%s model has no CMS form registered." +msgstr "Il modello %s non ha il modulo CMS registrato." + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form_mixin.py:0 +#, python-format +msgid "Are you sure want to submit the form as it is?" +msgstr "Si è sicuri di voler inviare il modulo così com'è?" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form +msgid "CMS Form" +msgstr "Form CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_many2many +msgid "CMS Form M2M widget" +msgstr "Widget m2m form CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_many2one_multi +msgid "CMS Form M2O multi widget" +msgstr "Widget multi m2o form CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_many2one +msgid "CMS Form M2O widget" +msgstr "Widget m2o form CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_one2many +msgid "CMS Form O2M widget" +msgstr "Widget o2m form CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_x2m_mixin +msgid "CMS Form X2M widget" +msgstr "Widget x2m form CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_binary_mixin +msgid "CMS Form binary widget" +msgstr "Widget binario form CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_boolean +msgid "CMS Form boolean widget" +msgstr "Widget booleano form CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_char +#: model:ir.model,name:cms_form.model_cms_form_widget_char_mixin +msgid "CMS Form char widget" +msgstr "Widget carattere form CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_date +msgid "CMS Form date widget" +msgstr "Widget data form CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_binary +msgid "CMS Form file widget" +msgstr "Widget file form CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_float +msgid "CMS Form float widget" +msgstr "Widget numero reale form CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_hidden +msgid "CMS Form hidden widget" +msgstr "Widget attributo hidden form CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_image +msgid "CMS Form image widget" +msgstr "Widget immagine form CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_integer +msgid "CMS Form integer widget" +msgstr "Widget numero intero form CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_mixin +msgid "CMS Form mixin" +msgstr "Mixin form CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_numeric_mixin +msgid "CMS Form numeric mixin widget" +msgstr "Widget mixin numerico form CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_radio +msgid "CMS Form radio widget" +msgstr "Widget camo radio form CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_rel_mixin +msgid "CMS Form relation widget mixin" +msgstr "MIxin widget relazione form CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_search +msgid "CMS Form search" +msgstr "Ricerca form CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_selection +msgid "CMS Form selection widget" +msgstr "Widget selezione form CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_text +msgid "CMS Form text widget" +msgstr "Widget testo form CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_widget_mixin +msgid "CMS Form widget mixin" +msgstr "Mixin widget form CMS" + +#. module: cms_form +#: model:ir.model,name:cms_form.model_cms_form_wizard +msgid "CMS Form wizard" +msgstr "Procedura guidata form CMS" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.base_form_buttons +msgid "Cancel" +msgstr "Annulla" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.wizard_form_confirm_modal +msgid "Close" +msgstr "Chiudi" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.wizard_form_confirm_modal +msgid "Confirm submit" +msgstr "Invio conferma" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form.py:0 +#, python-format +msgid "Create %s" +msgstr "Crea %s" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form.py:0 +#, python-format +msgid "Edit \"%s\"" +msgstr "Modifica \"%s\"" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_action +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_action +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_action +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_action +msgid "Form Action" +msgstr "Azione form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_ajax +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_ajax +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_ajax +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_ajax +msgid "Form Ajax" +msgstr "AJAX form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_ajax_onchange +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_ajax_onchange +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_ajax_onchange +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_ajax_onchange +msgid "Form Ajax Onchange" +msgstr "Attributo onchange AJAX form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_buttons_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_buttons_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_buttons_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_buttons_template +msgid "Form Buttons Template" +msgstr "Modello pulsante form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_confirm_submit +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_confirm_submit +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_confirm_submit +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_confirm_submit +msgid "Form Confirm Submit" +msgstr "Invio conferma modulo" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_content_selector +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_content_selector +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_content_selector +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_content_selector +msgid "Form Content Selector" +msgstr "Selettore contento form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_data +msgid "Form Data" +msgstr "Dati form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_description +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_description +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_description +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_description +msgid "Form Description" +msgstr "Descrizione form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_display_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_display_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_display_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_display_mode +msgid "Form Display Mode" +msgstr "Modo visualizzazione form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_extra_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_extra_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_extra_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_extra_css_klass +msgid "Form Extra Css Klass" +msgstr "Classe CSS extra form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_extract_value_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_extract_value_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_extract_value_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_extract_value_mode +msgid "Form Extract Value Mode" +msgstr "Modo estrazione valore form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_attributes +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_attributes +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_attributes +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_attributes +msgid "Form Fields Attributes" +msgstr "Attributi campi form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_blacklist +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_blacklist +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_blacklist +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_blacklist +msgid "Form Fields Blacklist" +msgstr "Blacklist campi form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_hidden +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_hidden +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_hidden +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_hidden +msgid "Form Fields Hidden" +msgstr "Attributo hidden campi form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_ignore +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_ignore +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_ignore +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_ignore +msgid "Form Fields Ignore" +msgstr "Attributo ignore campi form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_order +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_order +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_order +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_order +msgid "Form Fields Order" +msgstr "Atributo order campi form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_template +msgid "Form Fields Template" +msgstr "Modello campi form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_whitelist +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_whitelist +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_whitelist +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_whitelist +msgid "Form Fields Whitelist" +msgstr "Whitelist campi form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fields_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fields_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fields_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fields_wrapper_template +msgid "Form Fields Wrapper Template" +msgstr "Modello wrapper campi form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fieldsets +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fieldsets +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fieldsets +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fieldsets +msgid "Form Fieldsets" +msgstr "Impostazioni campo form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fieldsets_display +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fieldsets_display +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fieldsets_display +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fieldsets_display +msgid "Form Fieldsets Display" +msgstr "Attributo display impostazioni campi form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_fname_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_fname_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_fname_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_fname_pattern +msgid "Form Fname Pattern" +msgstr "Schema Fname form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_method +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_method +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_method +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_method +msgid "Form Method" +msgstr "Metodo form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_mode +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_mode +msgid "Form Mode" +msgstr "Modo form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_model_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_model_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_model_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_model_fields +msgid "Form Model Fields" +msgstr "Campi modo form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_model_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_model_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_model_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_model_name +msgid "Form Model Name" +msgstr "Nme modello form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_no_result_msg +msgid "Form No Result Msg" +msgstr "Messaggio form nessun risultato" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_no_search_results_template +msgid "Form No Search Results Template" +msgstr "Modello form nessun risultato ricerca" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_redirect +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_redirect +msgid "Form Redirect" +msgstr "Redirezione form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_render_values +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_render_values +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_render_values +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_render_values +msgid "Form Render Values" +msgstr "Render valori form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_required_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_required_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_required_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_required_fields +msgid "Form Required Fields" +msgstr "Campi form richiesti" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_reset +msgid "Form Reset" +msgstr "Reset form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_results_orderby +msgid "Form Results Orderby" +msgstr "Ordna per risultati form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_results_per_page +msgid "Form Results Per Page" +msgstr "Risultati form per pagina" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_search_domain_rules +msgid "Form Search Domain Rules" +msgstr "Regole form dominio ricerca" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_search_fields_multi +msgid "Form Search Fields Multi" +msgstr "Campi form ricerca multipa" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_search_results +msgid "Form Search Results" +msgstr "Risultati ricerca form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_search_results_template +msgid "Form Search Results Template" +msgstr "Modello risultati ricerca form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_show_progress_bar +msgid "Form Show Progress Bar" +msgstr "Visualizza barra progresso form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_show_results_no_submit +msgid "Form Show Results No Submit" +msgstr "Visualizza risultati non invio form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_show_search_form +msgid "Form Show Search Form" +msgstr "Visualizza ricerca form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_step_store_all_fields +msgid "Form Step Store All Fields" +msgstr "Passo calva tutti i campi form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_step_stored_fields +msgid "Form Step Stored Fields" +msgstr "Passo campi salvati form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_sub_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_sub_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_sub_fields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_sub_fields +msgid "Form Sub Fields" +msgstr "Sotto campi form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_success +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_success +msgid "Form Success" +msgstr "Successo form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_template +msgid "Form Template" +msgstr "Modello form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_title +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_title +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_title +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_title +msgid "Form Title" +msgstr "Titolo form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_validators +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_validators +msgid "Form Validators" +msgstr "Validatori form" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_wrapper_extra_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_wrapper_extra_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_wrapper_extra_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_wrapper_extra_css_klass +msgid "Form Wrapper Extra Css Klass" +msgstr "Wrapper classe extra CSS" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__form_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__form_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__form_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__form_wrapper_template +msgid "Form Wrapper Template" +msgstr "Modello wrapper form" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.fieldsets_as_tabs +msgid "Home" +msgstr "Home" + +#. module: cms_form +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form__form_display_mode__horizontal +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_mixin__form_display_mode__horizontal +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_search__form_display_mode__horizontal +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_wizard__form_display_mode__horizontal +msgid "Horizontal" +msgstr "Orizzontale" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__id +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__id +msgid "ID" +msgstr "ID" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form.py:0 +#, python-format +msgid "Item created." +msgstr "Elemento creato." + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form.py:0 +#, python-format +msgid "Item updated." +msgstr "Elemento aggIornato." + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.field_widget_image +msgid "Keep current image" +msgstr "Mantieni immagine attuale" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__main_object +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__main_object +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__main_object +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__main_object +msgid "Main Object" +msgstr "Oggetto principale" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.form_field_label_after_wrapper +#: model_terms:ir.ui.view,arch_db:cms_form.form_horizontal_field_wrapper +#: model_terms:ir.ui.view,arch_db:cms_form.form_vertical_field_wrapper +msgid "Name" +msgstr "Nome" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.wizard_form_buttons +msgid "Next" +msgstr "Successivo" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_search_form.py:0 +#, python-format +msgid "No items" +msgstr "Nessun elemento" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__o_request +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__o_request +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__o_request +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__o_request +msgid "O Request" +msgstr "o_request" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.wizard_form_buttons +msgid "Prev" +msgstr "Prec" + +#. module: cms_form +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form__form_extract_value_mode__read +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_mixin__form_extract_value_mode__read +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_wizard__form_extract_value_mode__read +msgid "Read" +msgstr "Leggi" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.field_widget_image +msgid "Replace current image" +msgstr "Sostituisci immagine attuale" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form__request +#: model:ir.model.fields,field_description:cms_form.field_cms_form_mixin__request +#: model:ir.model.fields,field_description:cms_form.field_cms_form_search__request +#: model:ir.model.fields,field_description:cms_form.field_cms_form_wizard__request +msgid "Request" +msgstr "Richiesta" + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.base_form +msgid "Required fields" +msgstr "Campo obbligatorio" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_search_form.py:0 +#: model_terms:ir.ui.view,arch_db:cms_form.search_form_buttons +#, python-format +msgid "Search" +msgstr "Cerca" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_search_form.py:0 +#, python-format +msgid "Search %s" +msgstr "Cerca %s" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form.py:0 +#, python-format +msgid "Some required fields are empty." +msgstr "Alcuni campi richiesti sono vuoti." + +#. module: cms_form +#: model_terms:ir.ui.view,arch_db:cms_form.base_form_buttons +#: model_terms:ir.ui.view,arch_db:cms_form.wizard_form_buttons +#: model_terms:ir.ui.view,arch_db:cms_form.wizard_form_confirm_modal +msgid "Submit" +msgstr "Invia" + +#. module: cms_form +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form__form_fieldsets_display__tabs +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_mixin__form_fieldsets_display__tabs +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_search__form_fieldsets_display__tabs +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_wizard__form_fieldsets_display__tabs +msgid "Tabs" +msgstr "Linguette" + +#. module: cms_form +#: model:ir.model.fields,help:cms_form.field_cms_form_widget_char__w_valid_pattern +#: model:ir.model.fields,help:cms_form.field_cms_form_widget_char_mixin__w_valid_pattern +#: model:ir.model.fields,help:cms_form.field_cms_form_widget_float__w_valid_pattern +#: model:ir.model.fields,help:cms_form.field_cms_form_widget_integer__w_valid_pattern +#: model:ir.model.fields,help:cms_form.field_cms_form_widget_numeric_mixin__w_valid_pattern +#: model:ir.model.fields,help:cms_form.field_cms_form_widget_text__w_valid_pattern +msgid "Used to validate inputs with `pattern` attr" +msgstr "Utilizzato per validare input con attibuto `pattern`" + +#. module: cms_form +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form__form_display_mode__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form__form_fieldsets_display__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_mixin__form_display_mode__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_mixin__form_fieldsets_display__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_search__form_display_mode__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_search__form_fieldsets_display__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_wizard__form_display_mode__vertical +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_wizard__form_fieldsets_display__vertical +msgid "Vertical" +msgstr "Verticale" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_comodel_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_comodel_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_comodel_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_comodel_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_comodel_name +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_comodel_name +msgid "W Comodel Name" +msgstr "Nome comodel widget" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_css_klass +msgid "W Css Klass" +msgstr "Classe CSS widget" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_data +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_data +msgid "W Data" +msgstr "Dati widget" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_date_format +msgid "W Date Format" +msgstr "Formato data widget" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_default_today +msgid "W Default Today" +msgstr "Oggi predefinito widget" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_display_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_display_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_display_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_display_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_display_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_display_field +msgid "W Display Field" +msgstr "Visualizza campo widget" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_domain +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_domain +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_domain +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_domain +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_domain +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_domain +msgid "W Domain" +msgstr "Domain widget" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_field +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_field +msgid "W Field" +msgstr "Campo widget" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_field_value +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_field_value +msgid "W Field Value" +msgstr "Valore campo widget" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_fname +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_fname +msgid "W Fname" +msgstr "Fname widget" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_form +msgid "W Form" +msgstr "Form widget" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_input_max +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_input_max +msgid "W Input Max" +msgstr "Max input widget" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_input_min +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_input_min +msgid "W Input Min" +msgstr "Min input widget" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_input_type +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_input_type +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_input_type +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_input_type +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_input_type +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_input_type +msgid "W Input Type" +msgstr "Tipo input widget" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_maxlength +msgid "W Maxlength" +msgstr "Maxlength widget" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_options_help +msgid "W Options Help" +msgstr "Opzioni aiuto widget" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_placeholder +msgid "W Placeholder" +msgstr "Segnaposto widget" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_readonly +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_readonly +msgid "W Readonly" +msgstr "Sola lettura widget" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_record +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_record +msgid "W Record" +msgstr "Record widget" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_subfields +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_subfields +msgid "W Subfields" +msgstr "Sotto campi widget" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_template +msgid "W Template" +msgstr "Modello widget" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_true_values +msgid "W True Values" +msgstr "Valori true widget" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_valid_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_valid_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_valid_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_valid_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_valid_pattern +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_valid_pattern +msgid "W Valid Pattern" +msgstr "Schema valido widget" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_wrapper_css_klass +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_wrapper_css_klass +msgid "W Wrapper Css Klass" +msgstr "Wrapper classe CSS widget" + +#. module: cms_form +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_binary_mixin__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_boolean__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_char_mixin__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_date__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_float__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_hidden__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_image__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_integer__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2many__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_many2one_multi__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_mixin__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_numeric_mixin__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_one2many__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_radio__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_rel_mixin__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_selection__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_text__w_wrapper_template +#: model:ir.model.fields,field_description:cms_form.field_cms_form_widget_x2m_mixin__w_wrapper_template +msgid "W Wrapper Template" +msgstr "Modello wrapper widget" + +#. module: cms_form +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form__form_extract_value_mode__write +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_mixin__form_extract_value_mode__write +#: model:ir.model.fields.selection,name:cms_form.selection__cms_form_wizard__form_extract_value_mode__write +msgid "Write" +msgstr "Scrivi" + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form_mixin.py:0 +#, python-format +msgid "You are not allowed to create any record for the model `%s`." +msgstr "Non si è autorizzati a creare nuovi record per il modello `%s`." + +#. module: cms_form +#. odoo-python +#: code:addons/cms_form/models/cms_form_mixin.py:0 +#, python-format +msgid "You cannot edit this record. Model: %(model)s, ID: %(obj_id)s." +msgstr "" +"Non si può modificare questo record. Modell: %(model)s, ID: %(obj_id)s." diff --git a/cms_form/marshallers.py b/cms_form/marshallers.py new file mode 100644 index 000000000..d78af8513 --- /dev/null +++ b/cms_form/marshallers.py @@ -0,0 +1,235 @@ +# Copyright 2018 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +import base64 +import html +from collections.abc import Callable +from dataclasses import dataclass +from typing import Any + +import werkzeug + +from odoo.tools import pycompat +from odoo.tools.mimetypes import guess_mimetype + +from . import utils + + +def marshal_request_values(values): + """Transform given request values using marshallers. + + Available marshallers: see Marshaller class. + """ + return Marshaller(values).marshall() + + +@dataclass +class Todo: + okey: str + oval: Any + handlers: list[Callable] + + +class Marshaller: + def __init__(self, req_values): + self.req_values = req_values + self.todos = [] + self.skip_keys = {"csrf_token"} + self._collect_todo() + + def _add_todo(self, orig_key, orig_value, *handlers): + self.todos.append(Todo(okey=orig_key, oval=orig_value, handlers=handlers)) + + def _collect_todo(self): + done = set() + for k, v in self.req_values.items(): + if k in self.skip_keys: + continue + for operator, handler in self._marshallers(): + if k.endswith(operator): + self._add_todo(k, v, handler) + done.add(k) + continue + # plain + if k not in done: + self._add_todo(k, v, self.marshal_plain) + done.add(k) + + def _marshallers(self): + # TODO: add docs + # TODO: support combinations like `:list:int` or `:dict:int` + return ( + (":esc", self.marshal_esc), + (":dict:list", self.marshal_dict_list), + (":list", self.marshal_list), + (":dict", self.marshal_dict), + (":int", self.marshal_int), + (":float", self.marshal_float), + (":file", self.marshal_file), + ) + + def marshall(self): + res = {} + for todo in self.todos: + k, v = todo.okey, todo.oval + for handler in todo.handlers: + k, v = handler(k, v) + res[k] = v + return res + + def marshal_plain(self, orig_key, orig_value): + """No transform.""" + return orig_key, orig_value + + def marshal_esc(self, orig_key, orig_value): + """Transform `foo:esc` inputs to escaped value.""" + k = orig_key[: -len(":esc")] + v = html.escape(orig_value) + return k, v + + def marshal_list(self, orig_key, orig_value): + """Transform `foo:list` inputs to list of values.""" + k = orig_key[: -len(":list")] + v = self.req_values.getlist(orig_key) + return k, v + + def marshal_int(self, orig_key, orig_value): + """Transform `foo:int` inputs to integer values.""" + k = orig_key[: -len(":int")] + return k, utils.safe_to_integer(orig_value) + + def marshal_float(self, orig_key, orig_value): + """Transform `foo:float` inputs to float values.""" + k = orig_key[: -len(":float")] + return k, utils.safe_to_float(orig_value) + + def marshal_dict(self, orig_key, orig_value): + """Transform `foo:dict` inputs to dictionary values. + + `orig_key` must be formatted like: + + `$fname.$dict_key:dict` + + Every request key matching `$fname` prefix + will be merged into a dict whereas keys will match all `$dict_key`. + + Example: + + values = [ + ('foo.a:dict', '1'), + ('foo.b:dict', '2'), + ('foo.c:dict', '3'), + ] + + will be translated to: + + values['foo'] = { + 'a': '1', + 'b': '2', + 'c': '3', + } + + """ + res = {} + key = orig_key.split(".")[0] + for _k, _v in self.req_values.items(): + # get all the keys matching fname + if not _k.startswith(key): + continue + # TODO: `__` will be to support extra marshallers, like: + # foo.1:dict:int -> get a dictionary w/ integer values + full_key, _, __ = _k.partition(":dict") + res[full_key.split(".")[-1]] = _v + return key, res + + def marshal_dict_list(self, orig_key, orig_value): + """Transform `foo:dict:list` inputs to list of dict values. + + `orig_key` must be formatted like: + + `$fname.$index.$dict_key:dict:list` + + Every request key matching `$fname` prefix + will be merged into a list of dicts whereas keys will match all `$dict_key` + and the position in the list will match $index. + + Example: + + values = [ + ("b.1.x:dict:list", "b1x"), + ("b.1.y:dict:list", "b1y"), + ("b.2.x:dict:list", "b2x"), + ("b.2.y:dict:list", "b2y"), + ] + + will be translated to: + + values['b'] = [ + { + 'x': 'b1x', + 'y': 'b1y', + }, + { + 'x': 'b2x', + 'y': 'b2y', + }, + } + + """ + res = [] + + def parse_key(key): + main_key, index, inner_key = key[: -len(":dict:list")].split(".") + if not index.isdigit(): + raise ValueError(":dict:list requires an integer index") + return main_key, int(index), inner_key + + main_key, index, inner_key = parse_key(orig_key) + by_index = {} + for _k, _v in self.req_values.items(): + # get all the keys matching fname + if not _k.startswith(f"{main_key}."): + continue + self.skip_keys.add(_k) + __, index, inner_key = parse_key(_k) + by_index.setdefault(index, []).append((inner_key, _v)) + # by_index = {0: [(x, xv), (y, yv)]} + for __, values in sorted(by_index.items()): + item = {} + for inner_key, value in values: + item[inner_key] = value + res.append(item) + return main_key, res + + def marshal_file(self, orig_key, orig_value): + k = orig_key[: -len(":file")] + value = orig_value + if isinstance(value, werkzeug.datastructures.FileStorage): + _value = self._filedata_from_filestorage(value) + else: + mimetype = guess_mimetype(value) + _value = { + "value": value, + "raw_value": value, + "mimetype": mimetype, + "content_type": mimetype, + } + _value["_from_request"] = True + return k, _value + + @staticmethod + def _filedata_from_filestorage(fs): + raw_value = fs.read() + value = base64.b64encode(raw_value) + value = pycompat.to_text(value) + data = dict(raw_value=value, value=value) + for attr in ( + "content_length", + "content_type", + "filename", + "headers", + "mimetype", + "mimetype_params", + ): + data[attr] = getattr(fs, attr) + return data diff --git a/cms_form/models/__init__.py b/cms_form/models/__init__.py new file mode 100644 index 000000000..86c3e5ab7 --- /dev/null +++ b/cms_form/models/__init__.py @@ -0,0 +1,5 @@ +from . import cms_form_mixin +from . import cms_form +from . import cms_form_wizard +from . import cms_search_form +from . import widgets diff --git a/cms_form/models/cms_form.py b/cms_form/models/cms_form.py new file mode 100644 index 000000000..a9bafdafc --- /dev/null +++ b/cms_form/models/cms_form.py @@ -0,0 +1,234 @@ +# Copyright 2017 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + + +import psycopg2 as pg + +from odoo import _, exceptions, fields, models + +from .fields import Serialized + + +class CMSForm(models.AbstractModel): + _name = "cms.form" + _description = "CMS Form" + _inherit = "cms.form.mixin" + + # internal flag for successful form + form_success = fields.Boolean(form_tech=True, default=False) + # internal flag for turning on redirection + form_redirect = fields.Boolean(form_tech=True, default=False) + # default validators by field type + # 'many2one': 'my_validation_method' + form_validators = Serialized(form_tech=True, default={}) + # make it computed + form_title = fields.Char(form_tech=True, compute="_compute_form_title") + + def _compute_form_title(self): + for rec in self: + rec.form_title = rec._get_form_title() + + def _get_form_title(self): + if not self.form_model_name: + return "" + if self.main_object: + rec_field = self.main_object[self.form_model._rec_name] + if hasattr(rec_field, "id"): + rec_field = rec_field.name + title = _('Edit "%s"') % rec_field + else: + title = _("Create %s") + if self.form_model_name: + model = ( + self.env["ir.model"] + .sudo() + .search([("model", "=", self.form_model_name)]) + ) + name = model and model.name or "" + title = _("Create %s") % name + return title + + @property + def form_msg_success_created(self): + # TODO: include form model name if any + msg = _("Item created.") + return msg + + @property + def form_msg_success_updated(self): + return _("Item updated.") + + @property + def form_msg_error_missing(self): + return _("Some required fields are empty.") + + def form_next_url(self, main_object=None): + """URL to redirect to after successful form submission.""" + if self.request.args.get("redirect"): + # redirect overridden + return self.request.args.get("redirect") + main_object = main_object or self.main_object + if main_object and "url" in main_object._fields: + return main_object.url + return "/" + + def form_cancel_url(self, main_object=None): + """URL to redirect to after click on "cancel" button.""" + if self.request.args.get("redirect"): + # redirect overridden + return self.request.args.get("redirect") + main_object = main_object or self.main_object + if main_object and "url" in main_object._fields: + return main_object.url + return self.request.referrer or "/" + + def form_check_empty_value(self, fname, field, value, **req_values): + """Return True if passed field value is really empty.""" + # delegate to each specific widget + return field["widget"].w_check_empty_value(value, **req_values) + + def form_validate(self, request_values=None): + """Validate submitted values.""" + errors = {} + errors_message = {} + request_values = request_values or self.form_get_request_values() + + missing = False + for fname, field in self.form_fields_get().items(): + value = request_values.get(fname) + error = False + if field["required"] and self.form_check_empty_value( + fname, field, value, **request_values + ): + errors[fname] = "missing" + missing = True + validator = self.form_get_validator(fname, field) + if validator: + error, error_msg = validator(value, **request_values) + if error: + errors[fname] = error + errors_message[fname] = error_msg + + # error message for empty required fields + if missing: + msg = self.form_msg_error_missing + self.add_status_message(msg, kind="danger") + return errors, errors_message + + def form_get_validator(self, fname, field): + """Retrieve field validator.""" + # 1nd lookup for a default type validator + validator = self.form_validators.get(field["type"], None) + # 2nd lookup for a specific type validator + validator = getattr(self, "_form_validate_" + field["type"], validator) + # 3rd lookup and override by named validator if any + validator = getattr(self, "_form_validate_" + fname, validator) + return validator + + def form_before_create_or_update(self, values, extra_values): + """Pre create/update hook.""" + + def form_after_create_or_update(self, values, extra_values): + """Post create/update hook.""" + + def _form_purge_non_model_fields(self, values): + """Purge fields that are not in `form_model` schema and return them.""" + extra_values = {} + if not self.form_model_name: + return extra_values + _model_fields = list( + self.form_model.fields_get( + self.form_model_fields, + attributes=self.form_fields_attributes, + ).keys() + ) + submitted_keys = list(values.keys()) + for fname in submitted_keys: + if fname not in _model_fields: + extra_values[fname] = values.pop(fname) + return extra_values + + def _form_write(self, values): + """Just write on the main object.""" + # pass a copy to avoid pollution of initial values by odoo + self.main_object.write(values.copy()) + + def _form_create(self, values): + """Just create the main object.""" + # pass a copy to avoid pollution of initial values by odoo + self.main_object = self.env[self.form_model].create(values.copy()) + + def form_create_or_update(self): + """Prepare values and create or update main_object.""" + write_values = self.form_extract_values() + extra_values = self._form_purge_non_model_fields(write_values) + # pre hook + self.form_before_create_or_update(write_values, extra_values) + if self.main_object: + self._form_write(write_values) + msg = self.form_msg_success_updated + else: + self._form_create(write_values) + msg = self.form_msg_success_created + # post hook + self.form_after_create_or_update(write_values, extra_values) + if msg: + self.add_status_message(msg) + return self.main_object + + def form_process_POST(self, render_values): + """Process POST requests.""" + errors, errors_message = self.form_validate() + # Do not flush to keep the caches of current in memory objects + if not errors: + try: + with self.env.cr.savepoint(): + self.form_create_or_update() + self.form_success = True + self.form_redirect = True + return render_values + except exceptions.ValidationError as err: + # sounds like there's no way to validate fields + # before calling write or create, + # hence we are forced to do it here. + errors["_validation"] = True + # err message can be something like + # u'Error while validating constraint\n + # \nEnd Date cannot be set before Start Date.\nNone' + errors_message["_validation"] = "\n".join( + [ + x + for x in err.args[0].replace("None", "").split("\n") + if x.strip() + ] + ) + except (pg.IntegrityError, pg.OperationalError) as err: + errors["_integrity"] = True + errors_message["_integrity"] = "
".join( + [x for x in str(err).split("\n") if x.strip()] + ) + # TODO: how to handle validation error on create? + # If you use @api.constrains to validate fields' value + # the check happens only AFTER the record has been created. + # So, on the 1st submit the record is created + # and the user is not aware of it since it gets redirected to the form + # with errors highlighted. He corrects the error and submit again. + # If no errors, he gets redirected to the new record, ANOTHER record. + # In the end: we get 2 duplicated objects :( + # To avoid this beforehand we need validation on JS side + + # pre-validation (where possible) before create or write. + + self.form_success = False + # handle ORM validation error + orm_error = errors.get("_validation") or errors.get("_integrity") + if orm_error: + msg = errors_message.get("_validation") or errors_message.get("_integrity") + if msg: + self.add_status_message( + msg, kind="danger", title=None, dismissible=False + ) + render_values.update({"errors": errors, "errors_message": errors_message}) + return render_values + + def add_status_message(self, msg, **kw): + self.env["ir.http"].add_status_message(msg, request=self.o_request, **kw) diff --git a/cms_form/models/cms_form_mixin.py b/cms_form/models/cms_form_mixin.py new file mode 100644 index 000000000..1595cb0c5 --- /dev/null +++ b/cms_form/models/cms_form_mixin.py @@ -0,0 +1,746 @@ +# Copyright 2017 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +import json +from collections import OrderedDict + +from odoo import _, api, exceptions, fields, models, tools + +from .. import marshallers, utils +from .fields import Serialized + +IGNORED_FORM_FIELDS = [ + "display_name", + "__last_update", + # TODO: retrieve from inherited schema + "message_ids", + "message_follower_ids", + "message_follower", + "message_last_post", + "message_unread", + "message_unread_counter", + "message_needaction_counter", + # Loose dep + "website_message_ids", + "website_published", +] + models.MAGIC_COLUMNS + + +class CMSFormMixin(models.AbstractModel): + """Base abstract CMS form.""" + + _name = "cms.form.mixin" + _description = "CMS Form mixin" + + id = fields.Id(automatic=True) + + # Special fields / + # TODO: would be better to have python obj fields + request = fields.Binary(form_tech=True, store=False) + o_request = fields.Binary(form_tech=True, store=False) + main_object = fields.Binary(form_tech=True, store=False, default=None) + # Values used to render the form + form_render_values = fields.Binary( + form_tech=True, store=False, compute="_compute_form_render_values" + ) + form_data = Serialized(form_tech=True, default={}) + # / special fields + form_wrapper_template = fields.Char(form_tech=True, default="") + form_template = fields.Char(form_tech=True, default="cms_form.base_form") + form_fields_template = fields.Char( + form_tech=True, default="cms_form.base_form_fields" + ) + form_fields_wrapper_template = fields.Char( + form_tech=True, compute="_compute_form_fields_wrapper_template", readonly=False + ) + form_buttons_template = fields.Char( + form_tech=True, default="cms_form.base_form_buttons" + ) + form_display_mode = fields.Selection( + form_tech=True, + selection=[("horizontal", "Horizontal"), ("vertical", "Vertical")], + default="horizontal", + ) + form_action = fields.Char(form_tech=True, default="") + form_method = fields.Char(form_tech=True, default="POST") + form_mode = fields.Char( + form_tech=True, default="", compute="_compute_form_mode", readonly=False + ) + form_title = fields.Char(form_tech=True, default="") + form_description = fields.Char(form_tech=True, default="") + # extra css klasses for the whole form wrapper + form_wrapper_extra_css_klass = fields.Char(form_tech=True, default="") + # extra css klasses for just the form element + form_extra_css_klass = fields.Char(form_tech=True, default="") + # model tied to this form + form_model_name = fields.Char(form_tech=True, default="") + # model's fields to load + form_model_fields = Serialized(form_tech=True, default=[]) + # force fields order + form_fields_order = Serialized(form_tech=True, default=[]) + # quickly force required fields + form_required_fields = Serialized(form_tech=True, default=[]) + # mark some fields as "sub". + # For usability reasons you might want to move some fields + # inside the widget of another field. + # If you mark a field as "sub" this field + # won't be included into fields' list as usual + # but you can still find it in `form_fields` value. + # 'mainfield': { + # # loaded for a specific value + # 'mainfield_value': ('subfield1', 'subfield2'), + # # loaded for all values + # '_all': ('subfield3', ), + # } + form_sub_fields = Serialized(form_tech=True, default={}) + # fields' attributes to load + form_fields_attributes = Serialized( + form_tech=True, + default=[ + "type", + "string", + "domain", + "required", + "readonly", + "relation", + "store", + "help", + "selection", + ], + ) + # include only these fields + form_fields_whitelist = Serialized(form_tech=True, default=[]) + # exclude these fields + form_fields_blacklist = Serialized(form_tech=True, default=[]) + # include fields but make them input[type]=hidden + form_fields_hidden = Serialized(form_tech=True, default=[]) + # group form fields together + # [ + # { + # 'id': 'main', + # 'title': 'My group of fields', + # 'description': 'Bla bla bla', + # 'fields': ['name', 'age', 'foo'], + # 'css_extra_klass': 'best_fieldset', + # }, + # { + # 'id': 'extras', + # 'title': 'My group of fields 2', + # 'description': 'Bla bla bla', + # 'fields': ['some', 'other', 'field'], + # 'css_extra_klass': '', + # }, + # ] + form_fieldsets = Serialized(form_tech=True, default=[]) + # control fieldset display + # options: + # * `tabs` -> rendered as tabs + # * `vertical` -> one after each other, vertically + form_fieldsets_display = fields.Selection( + form_tech=True, + selection=[("tabs", "Tabs"), ("vertical", "Vertical")], + default="vertical", + ) + # extract values mode + # This param can be used to alter value format + # when extracting values from request. + # eg: in write mode you can get on a m2m [(6, 0, [1,2,3])] + # while in read mode you can get just the ids [1,2,3] + form_extract_value_mode = fields.Selection( + form_tech=True, + selection=[("write", "Write"), ("read", "Read")], + default="write", + ) + # ignore this fields default + form_fields_ignore = Serialized(form_tech=True, default=IGNORED_FORM_FIELDS) + # default is to post the form and have a full reload. Set to true to keep + # the search form as it is and only replace the result pane + form_ajax = fields.Boolean(form_tech=True, default=False) + form_ajax_onchange = fields.Boolean(form_tech=True, default=False) + # jQuery selector to find container of search results + form_content_selector = fields.Char(form_tech=True, default=".form_content") + # used to interpolate widgets' html field name + form_fname_pattern = fields.Char(form_tech=True, default="") + # used enable submit confirmation at last step + form_confirm_submit = fields.Boolean(form_tech=True, default="") + + def _valid_field_parameter(self, field, name): + res = super()._valid_field_parameter(field, name) + # allow form tech fields + return name.startswith("form_") or res + + def _compute_form_render_values(self): + for rec in self: + rec.form_render_values = rec._get_render_values() + + def _get_render_values(self): + return { + # TODO: default for BInary field is "False" but we need "None" + "main_object": self.main_object or None, + "form": self, + "errors": {}, + "errors_messages": {}, + } + + def _compute_form_mode(self): + for rec in self: + rec.form_mode = rec._get_form_mode() + + def _get_form_mode(self): + if self.form_mode: + # forced mode + return self.form_mode + if self.main_object: + return "edit" + return "create" + + _form_field_wrapper_template_pattern = ( + "cms_form.form_{form.form_display_mode}_field_wrapper" + ) + + def _compute_form_fields_wrapper_template(self): + pattern = self._form_field_wrapper_template_pattern + for rec in self: + rec.form_fields_wrapper_template = pattern.format(form=rec) + + def form_init(self, request, main_object=None, **kw): + """Initalize a form instance. + + @param request: an odoo-wrapped werkeug request + @param main_object: current model instance if any + @param kw: pass any override for `_form_` attributes + ie: `fields_attributes` -> `form_fields_attributes` + """ + vals = { + "o_request": request, + "request": request.httprequest, + "main_object": main_object, + } + form_kw = {k: v for k, v in kw.items() if k in self._fields} + vals.update(form_kw) + form = self.new(vals) + if "form_data" not in vals: + form.form_data = form.form_load_defaults() + return form + + def form_check_permission(self, raise_exception=True): + """Check permission on current model and main object if any.""" + res = True + msg = "" + if self.main_object: + if hasattr(self.main_object, "cms_can_edit"): + res = self.main_object.cms_can_edit() + else: + # `cms.info.mixin` not provided by model. + res = self._can_edit(raise_exception=False) + msg = _( + "You cannot edit this record. Model: %(model)s, ID: %(obj_id)s.", + model=self.main_object._name, + obj_id=self.main_object.id, + ) + else: + if self.form_model_name: + if hasattr(self.form_model, "cms_can_create"): + res = self.form_model.cms_can_create() + else: + res = self._can_create(raise_exception=False) + msg = ( + _("You are not allowed to create any record for the model `%s`.") + % self.form_model_name + ) + if raise_exception and not res: + raise exceptions.AccessError(msg) + return res + + def _can_create(self, raise_exception=True): + """Check that current user can create instances of given model.""" + if self.form_model_name: + return self.form_model.check_access_rights( + "create", raise_exception=raise_exception + ) + # just a safe fallback if you call this method directly + return True + + def _can_edit(self, raise_exception=True): + """Check that current user can edit main object if any.""" + if not self.main_object: + # just a safe fallback if you call this method directly + return True + try: + self.main_object.check_access_rights("write") + self.main_object.check_access_rule("write") + can = True + except exceptions.AccessError: + if raise_exception: + raise + can = False + return can + + @property + def form_model(self): + # queue_job tries to read properties. Be defensive. + return self.env.get(self.form_model_name) + + def form_fields_get(self, hidden=None): + """Retrieve form fields. + + :param hidden: whether to include or not hidden inputs. + Options: + * None, default: include all fields, hidden or not + * True: include only hidden fields + * False: include all fields but those hidden. + """ + _fields = self._form_fields_get() + # update fields attributes + self.form_update_fields_attributes(_fields) + if hidden is not None: + # make sure ordering is preserved + filtered = OrderedDict() + for k, v in _fields.items(): + if v.get("hidden", False) == hidden: + filtered[k] = v + return filtered + return _fields + + def _form_fields_attributes_get(self): + return self.form_fields_attributes or [] + + @tools.cache("self") + def _form_fields_get(self): + """Retrieve form fields ready to be used. + + Fields lookup: + * model's fields + * form's fields + + Blacklisted fields are skipped. + Whitelisted fields are loaded only. + """ + attributes = self._form_fields_attributes_get() + _all_fields = OrderedDict() + # load model fields + _model_fields = {} + if self.form_model_name: + _model_fields = self.form_model.fields_get( + self.form_model_fields, + attributes=attributes, + ) + # inject defaults + defaults = self.form_model.default_get(self.form_model_fields) + for k, v in defaults.items(): + _model_fields[k]["_default"] = v + # load form fields + _form_fields = self.fields_get(attributes=attributes) + # inject defaults + for k, v in self.default_get(list(_form_fields.keys())).items(): + _form_fields[k]["_default"] = v + _all_fields.update(_model_fields) + # form fields override model fields + # TODO: add tests + _all_fields = utils.data_merge(_all_fields, _form_fields) + # exclude blacklisted + for fname in self.form_fields_blacklist: + # make it fail if passing wrong field name + _all_fields.pop(fname) + # include whitelisted + _all_whitelisted = {} + for fname in self.form_fields_whitelist: + _all_whitelisted[fname] = _all_fields[fname] + _all_fields = _all_whitelisted or _all_fields + # remove unwanted fields + self._form_remove_uwanted(_all_fields) + # remove non-stored fields to exclude computed + # NOTE: make sure to use `v.get` because sometimes (like for res.users) + # you can get auto-generated fields here (like `in_group_XX`) + # whereas some core fields attributes are missing. + _all_fields = {k: v for k, v in _all_fields.items() if v.get("store")} + # update fields order + if self.form_fields_order: + _sorted_all_fields = OrderedDict() + for fname in self.form_fields_order: + # this check is required since you can have `groups` attribute + # on a field, making the field unavailable if not satisfied. + if fname in _all_fields: + _sorted_all_fields[fname] = _all_fields[fname] + _all_fields = _sorted_all_fields + # compute subfields and remove them from all fields if any + self._form_prepare_subfields(_all_fields) + return _all_fields + + @api.model + def fields_get(self, allfields=None, attributes=None): + res = super().fields_get(allfields, attributes) + # Wipe tech fields + return { + k: v + for k, v in res.items() + if not getattr(self._fields[k], "form_tech", False) + } + + def _form_prepare_subfields(self, _all_fields): + """Add subfields to related main fields.""" + # TODO: document this + for mainfield, subfields in self.form_sub_fields.items(): + if mainfield not in _all_fields: + continue + _subfields = {} + for val, subs in subfields.items(): + _subfields[val] = {} + for sub in subs: + if sub in _all_fields: + _subfields[val][sub] = _all_fields[sub] + _all_fields[sub]["is_subfield"] = True + _all_fields[mainfield]["subfields"] = _subfields + + def _form_remove_uwanted(self, _all_fields): + """Remove fields from form fields.""" + for fname in self.form_fields_ignore: + _all_fields.pop(fname, None) + + def form_fieldsets_get(self): + # exclude empty ones + form_fields = self._form_fields_get() + res = [] + for fset in self.form_fieldsets: + if any([form_fields.get(fname) for fname in fset["fields"]]): + # at least one field is here + res.append(fset) + return res + + @property + def form_fieldsets_wrapper_klass(self): + klass = [] + if self.form_fieldsets: + klass = ["has_fieldsets", self.form_fieldsets_display] + return " ".join(klass) + + def form_update_fields_attributes(self, _fields): + """Manipulate fields attributes.""" + for fname, field in _fields.items(): + if fname in self.form_required_fields: + _fields[fname]["required"] = True + if self._form_is_field_hidden(fname, field): + _fields[fname]["hidden"] = True + _fields[fname]["widget"] = self.form_get_widget(fname, field) + + def _form_is_field_hidden(self, fname, field): + return ( + fname in self.form_fields_hidden + or fname in self._fields + and getattr(self._fields[fname], "form_hidden", False) + ) + + def form_get_field_wrapper_template(self, fname, field): + return field["widget"].w_wrapper_template or self.form_fields_wrapper_template + + def _form_get_default_widget_model(self, fname, field): + """Retrieve widget model name.""" + if field.get("hidden"): + # special case + return "cms.form.widget.hidden" + widget_model = "cms.form.widget.char" + for key in (field["type"], fname): + model_key = "cms.form.widget." + key + if model_key in self.env: + widget_model = model_key + return widget_model + + def form_get_widget(self, fname, field, **kw): + """Retrieve and initialize widget.""" + specific_widget = self._form_get_specific_widget(fname, field, **kw) + if specific_widget: + return specific_widget + model = self._form_get_default_widget_model(fname, field) + return self.env[model].widget_init(self, fname, field, **kw) + + def form_get_current_widget(self, fname): + return self.form_fields_get()[fname]["widget"] + + def _form_get_specific_widget(self, fname, field, **kw): + """Retrieve and initialize fields' specific widgets. + + Form fields' can declare custom widgets using `form_widget` attribute. + Properties: + + `resolver`: callable that returns a widget already initialized (optional) + `model`: widget model if no `resolver` is passed (mandatory) + `options`: dictionary or callable to resolve widget's options + """ + widget_conf = {} + if fname in self._fields: + # Note: a custom widget for a field on the related model + # can come only from an override of the field in the form. + widget_conf = getattr(self._fields[fname], "form_widget", {}) + if not widget_conf: + return None + if widget_conf.get("resolver"): + return widget_conf["resolver"](self, fname, field, **kw) + try: + model = widget_conf["model"] + except KeyError: + model = self._form_get_default_widget_model(fname, field) + options = widget_conf.get("options", {}) + if options and callable(options): + options = options(self, fname, field, **kw) + return self.env[model].widget_init(self, fname, field, **options) + + @property + def form_file_fields(self): + """File fields.""" + return { + k: v for k, v in self.form_fields_get().items() if v["type"] == "binary" + } + + def form_get_request_values(self): + """Retrieve fields values from current request.""" + # on POST requests values are in `form` attr + # on GET requests values are in `args` attr + _values = self.request.form + if not _values: + # make sure to give precedence to form attribute + # since you might get some extra params (like ?redirect) + # and this will make the form machinery miss all the fields + _values = self.request.args + # normal fields + res = marshallers.marshal_request_values(_values) + # file fields + files = self.request.files + # Convert files always. Main reasons: + # * file descriptors will be consumed on 1st read. + # If you access them again you won't find any info. + # * homegenous handling of files + # * no need to parse metadata down the stack as is done by the marshaller + parsed_files = getattr(self.request, "_cms_form_files_processed", None) + if files and not parsed_files: + _file_values = {} + _file_fields = self.form_file_fields + for fname, fobj in files.items(): + if fname in _file_fields: + # fake field name enforcing marshaller + if not fname.endswith(":file"): + fname = f"{fname}:file" + _file_values[fname] = fobj + file_values = marshallers.marshal_request_values(_file_values) + self.request._cms_form_files_processed = file_values + elif parsed_files: + res.update(parsed_files) + return res + + # TODO: rename to form_load + # TODO: adapt signature to form_extract (eg: kw args) + def form_load_defaults(self, main_object=None, request_values=None): + """Load default values. + + Values lookup order: + + 1. `main_object` fields' values (if an existing main_object is passed) + 2. request parameters (only parameters matching form fields names) + """ + if self.form_data: + return self.form_data + main_object = main_object or self.main_object + request_values = request_values or self.form_get_request_values() + defaults = request_values.copy() + form_fields = self.form_fields_get() + for fname, field in form_fields.items(): + value = field["widget"].w_load(**request_values) + # override via specific form loader when needed + loader = self.form_get_loader( + fname, field, main_object=main_object, value=value, **request_values + ) + if loader: + value = loader(fname, field, value, **request_values) + defaults[fname] = value + return defaults + + def form_get_loader(self, fname, field, main_object=None, value=None, **req_values): + """Retrieve form value loader. + + :param fname: field name + :param field: field description as `fields_get` + :param main_object: current main object if any + :param value: current field value if any + :param req_values: custom request valuess + """ + # lookup for a specific type loader method + loader = getattr(self, "_form_load_" + field["type"], None) + # 3rd lookup and override by named loader if any + loader = getattr(self, "_form_load_" + fname, loader) + return loader + + # TODO: rename to form_extract + def form_extract_values(self, **request_values): + """Extract values from request form.""" + request_values = request_values or self.form_get_request_values() + values = {} + for fname, field in self.form_fields_get().items(): + value = field["widget"].w_extract(**request_values) + # override via specific form extractor when needed + extractor = self.form_get_extractor( + fname, field, value=value, **request_values + ) + if extractor: + value = extractor(self, fname, value, **request_values) + if value is None: + # we assume we do not want to override the field value. + # a typical example is an image field. + # If you have an existing image + # you cannot set the default value on the file input + # for standard HTML security restrictions. + # If you want to flush a value on a field just return "False". + continue + values[fname] = value + return values + + def form_get_extractor(self, fname, field, value=None, **req_values): + """Retrieve form value extractor. + + :param fname: field name + :param field: field description as `fields_get` + :param value: current field value if any + :param req_values: custom request valuess + """ + # lookup for a specific type handler + extractor = getattr(self, "_form_extract_" + field["type"], None) + # 3rd lookup and override by named handler if any + extractor = getattr(self, "_form_extract_" + fname, extractor) + return extractor + + def form_render(self, **kw): + """Renders form template declared in `form_template`. + + To render the form simply do: + + + """ + values = self.form_render_values.copy() + values.update(kw) + return self.env["ir.qweb"]._render(self.form_template, values) + + def form_process(self, **kw): + """Process current request. + + :param kw: inject custom / extra rendering values. + + Lookup correct request handler by request method + and call it with rendering values. + The handler can perform any action (like creating objects) + and then return final rendering form values + and store them into `form_render_values`. + """ + render_values = self.form_render_values + render_values.update(kw) + handler = getattr(self, "form_process_" + self.request.method.upper()) + self.form_render_values = dict(render_values, **handler(render_values)) + + def form_process_GET(self, render_values): + """Process GET requests.""" + return render_values # pragma: no cover + + def form_process_POST(self, render_values): + """Process POST requests.""" + raise NotImplementedError() # pragma: no cover + + @property + def form_wrapper_css_klass(self): + """Return form wrapper css klass. + + By default the form markup is wrapped + into a `cms_form_wrapper` element. + You can use this set of klasses to customize form styles. + + Included by default: + * `cms_form_wrapper` marker + * form model name normalized (res.partner -> res_partner) + * `form_wrapper_extra_css_klass` extra klasses from form attribute + * `mode_` + form mode (ie: 'mode_write') + """ + parts = [ + "cms_form_wrapper", + self._name.replace(".", "_").lower(), + self.form_model_name.replace(".", "_").lower(), + self.form_wrapper_extra_css_klass, + "mode_" + self.form_mode, + ] + return " ".join([x.strip() for x in parts if x.strip()]) + + @property + def form_css_klass(self): + """Return `
` element css klasses. + + By default you can provide extra klasses via `form_extra_css_klass`. + """ + klass = "" + if self.form_display_mode == "horizontal": + klass = "form-horizontal" + elif self.form_display_mode == "vertical": + # actually not a real BS3 css klass but helps styling + klass = "form-vertical" + if self.form_extra_css_klass: + klass += " " + self.form_extra_css_klass + return klass + + def form_make_field_wrapper_klass(self, fname, field, **kw): + """Return specific CSS klass for the field wrapper.""" + klass = [ + "form-group", + "form-field", + "field-{type}", + "field-{fname}", + ] + if field["required"]: + klass.append("field-required") + if kw.get("errors", {}).get(fname): + klass.append("has-error") + if field["widget"].w_wrapper_css_klass: + klass.append(field["widget"].w_wrapper_css_klass) + return " ".join(klass).format(fname=fname, **field) + + def _form_json_info(self): + info = {} + info.update( + { + "master_slave": self._form_master_slave_info(), + "model": self.form_model_name, + "form_content_selector": self.form_content_selector, + } + ) + return info + + def form_json_info(self): + return json.dumps(self._form_json_info()) + + def _form_master_slave_info(self): + """Return info about master/slave fields JSON compatible. + + # TODO: support pyeval expressions in JS + + Eg: { + 'field_master1': { + 'hide': { + # field to hide: values + # TODO: support pyeval expressions + 'field_slave1': (master_value1, ), + }, + 'show': { + # field to show: pyeval expr + 'field_slave1': (master_value2, ), + }, + } + } + """ + return {} + + def _form_info_merge(self, info, tomerge): + """Merge info dictionaries. + + Practical example: + when inheriting forms you can add extra rules for the same master field + so if you don't want to override info completely + you can use this method to merge them properly. + """ + return utils.data_merge(info, tomerge) + + @property + def form_msg_confirm_submit(self): + return _("Are you sure want to submit the form as it is?") diff --git a/cms_form/models/cms_form_wizard.py b/cms_form/models/cms_form_wizard.py new file mode 100644 index 000000000..3d1d74d7a --- /dev/null +++ b/cms_form/models/cms_form_wizard.py @@ -0,0 +1,246 @@ +# Copyright 2017 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from copy import deepcopy + +from odoo import fields, models + +from .fields import Serialized + + +class CMSFormWizard(models.AbstractModel): + """Base class for wizards. + + Every wizard is composed by steps. + Each step can be handled by a different form (see `wiz_configure_steps`). + Each form must inherit from the main wizard class. + See also `tests.fake_models.FakeWiz`. + """ + + _name = "cms.form.wizard" + _description = "CMS Form wizard" + _inherit = "cms.form" + _wiz_name = _name + form_buttons_template = fields.Char( + form_tech=True, default="cms_form.wizard_form_buttons" + ) + # display wizard progress bar? + form_show_progress_bar = fields.Boolean(form_tech=True, default=True) + # Fields declared here will be automatically stored + # into wizard storage + # Use `form_step_store_all_fields = True` to store them all. + # You can pass a list of fields if you don't want to store them all. + form_step_stored_fields = Serialized(form_tech=True, default=[]) + form_step_store_all_fields = fields.Boolean(form_tech=True, default=True) + form_reset = fields.Boolean(form_tech=True, default=False) + + def _is_wiz_main_model(self): + return self._name == self._wiz_name + + def _get_form_mode(self): + return "wizard" + + @property + def form_wrapper_css_klass(self): + klass = super().form_wrapper_css_klass + return klass + " " + self._wiz_name.replace(".", "_").lower() + + @property + def _wiz_storage_key(self): + """Main storage key.""" + return self._wiz_name + + @property + def _wiz_storage(self): + return self.o_request.session + + def wiz_storage_get(self): + self._wiz_storage_prepare() + storage = self._wiz_storage[self._wiz_storage_key] + if "steps" in storage: + # Depending of the type of session storage data might be serialized. + # When this happens steps keys might be converted to strings. + # Ensure we always get integers. + storage["steps"] = {int(k): v for k, v in storage["steps"].items()} + return storage + + def _wiz_storage_prepare(self, reset=False): + if not self._wiz_storage.get(self._wiz_storage_key) or reset: + # use `deepcopy` to not reference steps' dict + self._wiz_storage[self._wiz_storage_key] = deepcopy( + self.DEFAULT_STORAGE_KEYS + ) + + def wiz_storage_set(self, storage): + self.o_request.session.update({self._wiz_storage_key: storage}) + # Important: ensure the session is stored (will be flagged as dirty) + # Mandatory since v16 when storing nested objs. + self.o_request.session.touch() + + DEFAULT_STORAGE_KEYS = { + "steps": {}, + "current": 1, + "next": None, + "prev": None, + } + + def form_init(self, request, main_object=None, page=1, wizard=None, **kw): + form = super().form_init(request, main_object=main_object, **kw) + if form.form_reset: + form._wiz_storage_prepare(reset=True) + form.form_reset = False + form.wiz_init(page=page, **kw) + return form + + def wiz_init(self, page=1, **kw): + steps = self.wiz_configure_steps() + storage = self.wiz_storage_get() + for k in steps.keys(): + if k not in storage["steps"]: + # init missing step + storage["steps"][k] = {} + current = page + storage["current"] = current + _next = None + if (current + 1) in steps: + _next = current + 1 + _prev = None + if (current - 1) in steps: + _prev = current - 1 + storage["next"] = _next + storage["prev"] = _prev + + @property + def wiz_steps(self): + return list(self.wiz_configure_steps().keys()) + + def wiz_configure_steps(self): + """Configure wizard steps. + + Each step can use a different form, for instance: + + return { + 1: { + 'form_model': 'form.a', + 'title': 'Step 1', + 'description': 'Preliminary info', + }, + 2: { + 'form_model': 'form.b', + 'title': 'Step 2', + }, + 3: { + 'form_model': 'form.c', + 'title': 'Step 3', + 'description': 'Foo', + }, + } + """ + return {} + + def wiz_get_step_info(self, step): + step = int(step) + try: + return self.wiz_configure_steps()[step] + except KeyError as e: + raise ValueError("Step `%s` does not exists." % str(step)) from e + + def wiz_current_step(self): + return self.wiz_storage_get().get("current") or 1 + + def wiz_next_step(self): + return self.wiz_storage_get().get("next") + + def wiz_prev_step(self): + return self.wiz_storage_get().get("prev") + + def form_next_url(self, main_object=None): + direction = self.request.form.get("wiz_submit", "next") + if direction == "next": + step = self.wiz_next_step() + else: + step = self.wiz_prev_step() + + main_object = main_object or self.main_object + if ( + main_object + and "url" in main_object._fields + and self.is_final_step_process() + ): + return main_object.url + if not step: + # fallback to page 1 + step = 1 + return self._wiz_url_for_step(step, main_object=main_object) + + def _wiz_url_for_step(self, step, main_object=None): + return "{}/page/{}".format(self._wiz_base_url(), step) + + def _wiz_base_url(self): + return "/cms/wiz/{}".format(self._wiz_name) + + def wiz_save_step(self, values, step=None): + step = step or self.wiz_current_step() + storage = self.wiz_storage_get() + if step not in storage["steps"]: + # safely re-init step + storage["steps"][step] = {} + + storage["steps"][step].update(values) + self.wiz_storage_set(storage) + + def wiz_load_step(self, step=None): + step = step or self.wiz_current_step() + return self.wiz_storage_get()["steps"].get(step) or {} + + def wiz_load_steps(self, steps=None): + """Load all steps data merged together.""" + data = self.wiz_storage_get()["steps"] + steps = steps or data.keys() + res = {} + for step in steps: + res.update(data.get(step, {})) + return res + + def form_after_create_or_update(self, values, extra_values): + step_values = self._prepare_step_values_to_store(values, extra_values) + self.wiz_save_step(step_values) + if self.is_final_step_process(): + # Wipe session data when done + self._wiz_storage_prepare(reset=True) + + def is_final_step_process(self): + # Helper method to determine if the submot action is the last one + return self.request.form.get("wiz_submit") == "process" + + def _prepare_step_values_to_store(self, values, extra_values): + values = values.copy() + values.update(extra_values) + step_values = {} + stored_fields = self.form_step_stored_fields + if not stored_fields and self.form_step_store_all_fields: + stored_fields = values.keys() + for fname in stored_fields: + if fname in values: + step_values[fname] = values[fname] + return step_values + + # TODO: tests + def form_load_defaults(self, main_object=None, request_values=None): + # Override to load values from the storage + if self._is_wiz_main_model(): + # Do not load anything if we are initializing the main wiz model + return {} + defaults = super().form_load_defaults( + main_object=main_object, request_values=request_values + ) + request_values = request_values or {} + step_values = self.wiz_load_step() + if step_values: + for fname in self.form_fields_get().keys(): + if fname in request_values: + # req value has precedence + continue + if fname in step_values: + defaults[fname] = step_values[fname] + return defaults diff --git a/cms_form/models/cms_search_form.py b/cms_form/models/cms_search_form.py new file mode 100644 index 000000000..445eb7824 --- /dev/null +++ b/cms_form/models/cms_search_form.py @@ -0,0 +1,192 @@ +# Copyright 2017 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import _, fields, models +from odoo.tools import pycompat + +from odoo.addons.portal.controllers.portal import pager as portal_pager + +from .fields import Serialized + + +class CMSFormSearch(models.AbstractModel): + _name = "cms.form.search" + _description = "CMS Form search" + _inherit = "cms.form.mixin" + + # change defaults + form_buttons_template = fields.Char( + form_tech=True, default="cms_form.search_form_buttons" + ) + form_search_results_template = fields.Char( + form_tech=True, default="cms_form.search_results" + ) + form_no_search_results_template = fields.Char( + form_tech=True, default="cms_form.no_search_results" + ) + form_method = fields.Char(form_tech=True, default="GET") + # you might want to just list items based on a predefined query + # if this flag is false the search form won't be rendered + form_show_search_form = fields.Boolean(form_tech=True, default=True) + form_mode = fields.Char(form_tech=True, default="search") + form_extract_value_mode = fields.Char(form_tech=True, default="read") + # show results if no query has been submitted? + form_show_search_form = fields.Boolean(form_tech=True, default=True) + form_show_results_no_submit = fields.Boolean(form_tech=True, default=True) + form_results_per_page = fields.Integer(form_tech=True, default=10) + # sort by this param, defaults to model's `_order` + form_results_orderby = fields.Char(form_tech=True, default="") + # declare fields that must be searched w/ multiple values + form_search_fields_multi = Serialized(form_tech=True, default=[]) + # declare custom domain computation rules + # opt 1: `field name: (leaf field name, operator, format value)` + # `format_value` is a formatting compatible string + # 'product_id': ('product_id.name', 'ilike', '{}') + # opt 2: function that give back `(fname, operator, value)`` + # 'foo': lambda field, value, search_values: ('foo', 'not like', value) + form_search_domain_rules = fields.Binary(form_tech=True, default={}, store=False) + form_search_results = fields.Binary(form_tech=True, default={}, store=False) + + # make it computed + form_title = fields.Char(form_tech=True, compute="_compute_form_title") + form_no_result_msg = fields.Char( + form_tech=True, compute="_compute_form_no_result_msg" + ) + + def _get_form_mode(self): + return "search" + + def _compute_form_title(self): + for rec in self: + rec.form_title = rec._get_form_title() + + def _get_form_title(self): + title = _("Search") + if self.form_model_name: + model = self.env["ir.model"]._get(self.form_model_name) + name = model and model.name or "" + title = _("Search %s") % name + return title + + def _compute_form_no_result_msg(self): + for rec in self: + rec.form_no_result_msg = rec._get_form_no_result_msg() + + def _get_form_no_result_msg(self): + return _("No items") + + def form_check_permission(self): + """Just searching, nothing to check here.""" + return True + + def form_update_fields_attributes(self, _fields): + """No field should be mandatory.""" + res = super().form_update_fields_attributes(_fields) + for _fname, field in _fields.items(): + field["required"] = False + return res + + def _form_get_default_widget_model(self, fname, field): + """Search via related field needs a simple char widget.""" + res = super()._form_get_default_widget_model(fname, field) + if fname in self.form_search_domain_rules: + res = "cms.form.widget.char" + return res + + def form_process_GET(self, render_values): + self.form_search(render_values) + return render_values + + def form_search(self, render_values): + """Produce search results.""" + search_values = self.form_extract_values() + if not search_values and not self.form_show_results_no_submit: + return self.form_search_results + domain = self.form_search_domain(search_values) + count = self.form_model.search_count(domain) + page = render_values.get("extra_args", {}).get("page", 0) + url = self._form_get_url_for_pager(render_values) + pager = self._form_results_pager(count=count, page=page, url=url) + order = self.form_results_orderby or None + results = self.form_model.search( + domain, + limit=self.form_results_per_page, + offset=pager["offset"], + order=order, + ) + self.form_search_results = { + "results": results, + "count": count, + "pager": pager, + } + return self.form_search_results + + def _form_get_url_for_pager(self, render_values): + # default to current path w/out paging + path = pycompat.to_text(self.request.path) + url = path.split("/page")[0] + if self.form_model_name: + # rely on model's cms search url + url = getattr(self.form_model, "cms_search_url", None) or url + # override via controller/request specific value + url = render_values.get("extra_args", {}).get("pager_url", url) + return url + + def pager(self, **kw): + return portal_pager(**kw) + + def _form_results_pager(self, count=None, page=0, url="", url_args=None): + """Prepare pager for current search.""" + url_args = url_args or self.request.args.to_dict() + count = count + return self.pager( + url=url, + total=count, + page=page, + step=self.form_results_per_page, + scope=self.form_results_per_page, + url_args=url_args, + ) + + def form_search_domain(self, search_values): + """Build search domain.""" + domain = [] + for fname, field in self.form_fields_get().items(): + value = search_values.get(fname) + if value is None: + continue + if fname in self.form_search_fields_multi: + leaf = (fname, "in", value) + domain.append(leaf) + continue + # TODO: find the way to properly handle this. + # It would be nice to guess leafs in a clever way. + operator = "=" + if field["type"] in ("char", "text"): + # Do not use empty strings + if not value: + continue + operator = "ilike" + value = "%{}%".format(value) + elif field["type"] in ("integer", "float", "many2one"): + operator = "=" + elif field["type"] in ("one2many", "many2many"): + if not value: + continue + operator = "in" + elif field["type"] in ("boolean",): + value = value == "on" and True + elif field["type"] in ("date", "datetime"): + if not value: + # searching for an empty string breaks search + continue + if fname in self.form_search_domain_rules: + rule = self.form_search_domain_rules[fname] + if callable(rule): + fname, operator, value = rule(field, value, search_values) + else: + fname, operator, fmt_value = rule + value = fmt_value.format(value) if fmt_value else value + leaf = (fname, operator, value) + domain.append(leaf) + return domain diff --git a/cms_form/models/fields.py b/cms_form/models/fields.py new file mode 100644 index 000000000..477231f7a --- /dev/null +++ b/cms_form/models/fields.py @@ -0,0 +1,35 @@ +# Copyright 2023 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). +import json +import logging + +from odoo.addons.base_sparse_field.models.fields import Serialized as BaseSerialized + +_logger = logging.getLogger(__name__) + + +class Serialized(BaseSerialized): + """Better implementation of Serialized field. + + 1. load proper default (core always load {}) + 2. do not fail if value is already a py obj + TODO: propose to odoo core + """ + + def convert_to_record(self, value, record): + default = ( + self.default(self.model_name) if callable(self.default) else self.default + ) + # Important: if you want to set an empty value and bypass the default + # you must use a string (eg: "[]" or "{}") + value = value if value is not None else default + if isinstance(value, str): + try: + return json.loads(value) + except ValueError: + _logger.error("%s got bad json string: %s", self.name, value) + # Likely a string that is not convert-able. + # Consider using a special encoder/decoder. + return value + + return value diff --git a/cms_form/models/widgets/__init__.py b/cms_form/models/widgets/__init__.py new file mode 100644 index 000000000..d9bac0196 --- /dev/null +++ b/cms_form/models/widgets/__init__.py @@ -0,0 +1,11 @@ +from . import widget_mixin +from . import widget_rel_mixin +from . import widget_text +from . import widget_hidden +from . import widget_numeric +from . import widget_boolean +from . import widget_date +from . import widget_selection +from . import widget_many2one +from . import widget_x2many +from . import widget_binary diff --git a/cms_form/models/widgets/widget_binary.py b/cms_form/models/widgets/widget_binary.py new file mode 100644 index 000000000..360362451 --- /dev/null +++ b/cms_form/models/widgets/widget_binary.py @@ -0,0 +1,102 @@ +# Copyright 2017 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + + +import werkzeug + +from odoo import fields, models +from odoo.tools.image import image_data_uri + + +class BinaryWidget(models.AbstractModel): + _name = "cms.form.widget.binary.mixin" + _inherit = "cms.form.widget.mixin" + _description = "CMS Form binary widget" + + def w_extract(self, **req_values): + value = super().w_extract(**req_values) + return self.form_to_binary(value, **req_values) + + def form_to_binary(self, value, **req_values): + if self.html_fname not in req_values: + return None + _value = False + keepcheck_flag_key = self.html_fname + "_keepcheck" + keepcheck_flag = req_values.get(keepcheck_flag_key) + # If no keepcheck flag is given the file or img is always replaced + if keepcheck_flag_key in req_values and keepcheck_flag == "yes": + # no flag or flag marked as "keep current value" + # prevent discarding image + req_values.pop(self.html_fname, None) + req_values.pop(keepcheck_flag_key, None) + return None + if value: + _value = value + # TODO: move this to image widget + if isinstance(value, str): + if value.startswith("data:"): + # like 'data:image/jpeg;base64,jRyRuUm2VP... + _value = value.split(",")[-1] + elif isinstance(value, dict): + _value = value.get("value") + return _value + + def w_check_empty_value(self, value, **req_values): + if isinstance(value, werkzeug.datastructures.FileStorage): + has_value = bool(value.filename) + keep_flag = req_values.get(self.w_fname + "_keepcheck") + if not has_value and keep_flag == "yes": + # no value, but we want to preserve existing one + return False + # file field w/ no content + # TODO: this is not working sometime... + # return not bool(value.content_length) + return not has_value + return super().w_check_empty_value(value, **req_values) + + +class ImageWidget(models.AbstractModel): + _name = "cms.form.widget.image" + _inherit = "cms.form.widget.binary.mixin" + _description = "CMS Form image widget" + + w_template = fields.Char(default="cms_form.field_widget_image") + + def w_load(self, **req_values): + value = super().w_load(**req_values) + # TODO: can we get a dict here? Likely only when loading from request + if isinstance(value, dict) and value.get("mimetype", "").startswith("image/"): + val = ( + value["value"].encode() + if isinstance(value["value"], str) + else value["value"] + ) + value["value"] = image_data_uri(val) + elif isinstance(value, (str, bytes)): + bvalue = value + if isinstance(value, str): + bvalue = value.encode() + else: + value = value.decode() + if value.startswith("data:"): + raw_value = value.split(",")[-1] + else: + raw_value = value + value = image_data_uri(bvalue) + mimetype = value.split(";")[0].replace("data:", "") + value = { + "value": value, + "raw_value": raw_value, + "mimetype": mimetype, + "content_type": mimetype, + "content_lenght": len(raw_value), + } + return value + + +class FileWidget(models.AbstractModel): + _name = "cms.form.widget.binary" + _inherit = "cms.form.widget.binary.mixin" + _description = "CMS Form file widget" + + w_template = fields.Char(default="cms_form.field_widget_file") diff --git a/cms_form/models/widgets/widget_boolean.py b/cms_form/models/widgets/widget_boolean.py new file mode 100644 index 000000000..2a9c28ce6 --- /dev/null +++ b/cms_form/models/widgets/widget_boolean.py @@ -0,0 +1,29 @@ +# Copyright 2017 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import fields, models + +from ... import utils +from ..fields import Serialized + + +class BooleanWidget(models.AbstractModel): + _name = "cms.form.widget.boolean" + _inherit = "cms.form.widget.mixin" + _description = "CMS Form boolean widget" + + w_template = fields.Char(default="cms_form.field_widget_boolean") + w_wrapper_template = fields.Char(default="cms_form.form_field_label_after_wrapper") + w_wrapper_css_klass = fields.Char(default="form-check") + w_true_values = Serialized(default=utils.TRUE_VALUES) + w_field_value = fields.Boolean() + + def widget_init(self, form, fname, field, **kw): + widget = super().widget_init(form, fname, field, **kw) + widget.w_true_values = kw.get("true_values", self.w_true_values) + widget.w_field_value = widget.w_field_value in self.w_true_values + return widget + + def w_extract(self, **req_values): + value = super().w_extract(**req_values) + return utils.string_to_bool(value, true_values=self.w_true_values) diff --git a/cms_form/models/widgets/widget_date.py b/cms_form/models/widgets/widget_date.py new file mode 100644 index 000000000..dd819a5ac --- /dev/null +++ b/cms_form/models/widgets/widget_date.py @@ -0,0 +1,40 @@ +# Copyright 2017 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import fields, models + +from ... import utils + + +# TODO: add datetime widget +class DateWidget(models.AbstractModel): + _name = "cms.form.widget.date" + _inherit = "cms.form.widget.mixin" + _description = "CMS Form date widget" + + w_template = fields.Char(default="cms_form.field_widget_date") + # Both default to current lang format. + w_placeholder = fields.Char(default="") + w_date_format = fields.Char(default="") + # change type of field + w_field_value = fields.Date(default=None) + w_default_today = fields.Boolean(default=True) + + def widget_init(self, form, fname, field, **kw): + widget = super().widget_init(form, fname, field, **kw) + w_data = widget.w_data + if "defaultToday" not in w_data: + # set today's date by default + w_data["defaultToday"] = widget.w_default_today + if kw.get("format", widget.w_date_format): + w_data["dp"] = {"format": kw.get("format", widget.w_date_format)} + widget.w_data = w_data + widget.w_placeholder = kw.get("placeholder", widget.w_placeholder) + return widget + + def w_extract(self, **req_values): + value = super().w_extract(**req_values) + return self.form_to_date(value, **req_values) + + def form_to_date(self, value, **req_values): + return utils.safe_to_date(value) diff --git a/cms_form/models/widgets/widget_hidden.py b/cms_form/models/widgets/widget_hidden.py new file mode 100644 index 000000000..a8c9e182e --- /dev/null +++ b/cms_form/models/widgets/widget_hidden.py @@ -0,0 +1,31 @@ +# Copyright 2017 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import fields, models + + +class HiddenWidget(models.AbstractModel): + _name = "cms.form.widget.hidden" + _inherit = "cms.form.widget.mixin" + _description = "CMS Form hidden widget" + + w_template = fields.Char(default="cms_form.field_widget_hidden") + + @property + def html_fname(self): + """Field name for final HTML markup.""" + # TODO: use this for all fields and get rid of custom w_extract + # where possible + marshaller = "" + if self.w_field["type"] in ("many2one", "integer"): + marshaller = ":int" + elif self.w_field["type"] in ("float",): + marshaller = ":float" + elif self.w_field["type"] == "selection" and self.w_field["selection"]: + first_value = self.w_field["selection"][0][0] + # fields.Selection does the same check to determine PG col type + if isinstance(first_value, int): + marshaller = ":int" + elif isinstance(first_value, float): + marshaller = ":float" + return super().html_fname + marshaller diff --git a/cms_form/models/widgets/widget_many2one.py b/cms_form/models/widgets/widget_many2one.py new file mode 100644 index 000000000..62161ed1e --- /dev/null +++ b/cms_form/models/widgets/widget_many2one.py @@ -0,0 +1,73 @@ +# Copyright 2017 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +import json + +from odoo import fields, models + +from ... import utils +from ..fields import Serialized + + +class M2OWidget(models.AbstractModel): + _name = "cms.form.widget.many2one" + _inherit = "cms.form.widget.rel.mixin" + _description = "CMS Form M2O widget" + + w_template = fields.Char(default="cms_form.field_widget_m2o") + w_field_value = fields.Integer() + + @property + def w_option_items(self): + return self.w_comodel.search(self.w_domain) + + def w_load(self, **req_values): + value = super().w_load(**req_values) + return self.m2o_to_form(value, **req_values) + + def m2o_to_form(self, value, **req_values): + # important: return False if no value + # otherwise you will compare an empty recordset with an id + # in a select input in form widget template. + if isinstance(value, str) and value.isdigit(): + # number as string + return int(value) if int(value) > 0 else None + elif isinstance(value, models.BaseModel): + return value and value.id or None + elif isinstance(value, int): + return value + return None + + def w_extract(self, **req_values): + value = super().w_extract(**req_values) + return self.form_to_m2o(value, **req_values) + + def form_to_m2o(self, value, **req_values): + val = utils.safe_to_integer(value) or 0 + # we don't want m2o value do be < 1 + return val if val > 0 else None + + +class M2OMultiWidget(models.AbstractModel): + _name = "cms.form.widget.many2one.multi" + _inherit = "cms.form.widget.many2one" + _description = "CMS Form M2O multi widget" + + w_template = fields.Char(default="cms_form.field_widget_m2o_multi") + # TODO: not used ATM + w_display_field = fields.Char(default="display_name") + w_field_value = Serialized(default=[]) + + def m2o_to_form(self, value, **req_values): + if not value: + return json.dumps([]) + if isinstance(value, str) and value == req_values.get(self.w_fname): + value = self.w_comodel.browse( + # TODO: we should allow customizations of fields to read + self.w_ids_from_input(value) + ).read(["name"]) + value = json.dumps(value) + return value + + def form_to_m2o(self, value, **req_values): + return self.w_ids_from_input(value) if value else None diff --git a/cms_form/models/widgets/widget_mixin.py b/cms_form/models/widgets/widget_mixin.py new file mode 100644 index 000000000..09f34040b --- /dev/null +++ b/cms_form/models/widgets/widget_mixin.py @@ -0,0 +1,128 @@ +# Copyright 2017 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +import json + +from odoo import fields, models + +from ..fields import Serialized + + +class Widget(models.AbstractModel): + _name = "cms.form.widget.mixin" + _description = "CMS Form widget mixin" + + # use `w_` prefix as a namespace for all widget properties + + id = fields.Id(automatic=True) + # Special fields + # TODO: would be better to have python obj fields + w_form = fields.Binary(store=False) + w_record = fields.Binary(store=False) + w_field = fields.Binary(store=False) + w_subfields = fields.Binary(default={}, store=False) + + w_template = fields.Char(default="") + w_wrapper_template = fields.Char(default="") + w_css_klass = fields.Char(default="") + w_wrapper_css_klass = fields.Char(default="") + + w_fname = fields.Char(default="") + w_field_value = fields.Char() + w_readonly = fields.Boolean() + w_data = Serialized(default={}) + + @property + def html_fname(self): + if self.w_form.form_fname_pattern: + return self.w_form.form_fname_pattern.format(widget=self) + return self.w_fname + + @property + def html_readonly(self): + return self.w_form.form_mode == "readonly" or self.w_readonly + + @property + def html_value(self): + return self.w_field_value + + def widget_init(self, form, fname, field, data=None, subfields=None, **kw): + vals = { + "w_form": form, + "w_record": form.main_object, + "w_field": field, + "w_fname": fname, + "w_data": data or {}, + "w_subfields": subfields or field.get("subfields", {}), + } + for k, v in kw.items(): + if k in self._fields: + vals[k] = v + for k in ("template", "css_klass"): + if kw.get(k): + # TODO: deprecate + vals[f"w_{k}"] = kw[k] + field_value = form.form_data.get(fname, kw.get("field_value")) + if field_value: + vals["w_field_value"] = field_value + widget = self.new(vals) + return widget + + def render(self): + return self.env["ir.qweb"]._render(self.w_template, {"widget": self}) + + @property + def w_form_model(self): + return self.w_form.form_model + + @property + def w_form_values(self): + return self.w_form.form_data + + def w_load(self, **req_values): + """Load value for current field in current request.""" + value = self.w_field.get("_default") + # we could have form-only fields + if self.w_record and self.w_fname in self.w_record: + value = self.w_record[self.w_fname] or value + # maybe a POST request with new values: override item value + value = req_values.get(self.w_fname, value) + if isinstance(value, str) and value == "None": + # Corner case for when field values are set as None in the request. + # Odoo request will convert the value to a string. + # Here we might have data stored in session (eg: wizards) + # or other kind of serialized value. + value = None + return value + + def w_extract(self, **req_values): + """Extract value from form submit.""" + value = req_values.get(self.w_fname) + if isinstance(value, str) and value == "None": + # Corner case for when field values are set as None in the request. + # Odoo request will convert the value to a string. + value = None + return value + + def w_check_empty_value(self, value, **req_values): + # `None` values are meant to be ignored as not changed + return value in (False, "") + + @staticmethod + def w_ids_from_input(value): + """Convert list of ids from form input.""" + return [ + int(rec_id.strip()) + for rec_id in value.split(",") + if rec_id.strip().isdigit() + ] + + def w_subfields_by_value(self, value="_all"): + return self.w_subfields.get(value, {}) + + def w_data_json(self): + data = dict(self.w_data, name=self.html_fname) + return self._data_to_json(data) + + def _data_to_json(self, data): + return json.dumps(data, sort_keys=True) diff --git a/cms_form/models/widgets/widget_numeric.py b/cms_form/models/widgets/widget_numeric.py new file mode 100644 index 000000000..f6342bca9 --- /dev/null +++ b/cms_form/models/widgets/widget_numeric.py @@ -0,0 +1,53 @@ +# Copyright 2017 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import fields, models + +from ... import utils + + +class NumericWidgetMixin(models.AbstractModel): + _name = "cms.form.widget.numeric.mixin" + _inherit = "cms.form.widget.char.mixin" + _description = "CMS Form numeric mixin widget" + + w_template = fields.Char(default="cms_form.field_widget_numeric") + w_input_type = fields.Char(default="number") + w_input_min = fields.Char(default="") + w_input_max = fields.Char(default="") + + def _num_value_to_attr(self, value): + """Safely convert numeric value for html attr rendering.""" + if value is False or value is None: + return None + if isinstance(value, str) and not value.isdigit(): + return None + return str(value) + + @property + def html_input_min(self): + return self._num_value_to_attr(self.w_input_min) + + @property + def html_input_max(self): + return self._num_value_to_attr(self.w_input_max) + + +class IntegerWidget(models.AbstractModel): + _name = "cms.form.widget.integer" + _inherit = "cms.form.widget.numeric.mixin" + _description = "CMS Form integer widget" + + def w_extract(self, **req_values): + value = super().w_extract(**req_values) + return utils.safe_to_integer(value) + + +class FloatWidget(models.AbstractModel): + _name = "cms.form.widget.float" + _inherit = "cms.form.widget.char" + _description = "CMS Form float widget" + + def w_extract(self, **req_values): + value = super().w_extract(**req_values) + return utils.safe_to_float(value) diff --git a/cms_form/models/widgets/widget_rel_mixin.py b/cms_form/models/widgets/widget_rel_mixin.py new file mode 100644 index 000000000..f5d5b03e8 --- /dev/null +++ b/cms_form/models/widgets/widget_rel_mixin.py @@ -0,0 +1,29 @@ +# Copyright 2017 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + + +from odoo import fields, models + +from ..fields import Serialized + + +class RelWidgetMixin(models.AbstractModel): + _name = "cms.form.widget.rel.mixin" + _inherit = "cms.form.widget.mixin" + _description = "CMS Form relation widget mixin" + + w_comodel_name = fields.Char(default="") + w_domain = Serialized(default=[]) + w_display_field = fields.Char(default="display_name") + + def widget_init(self, form, fname, field, **kw): + widget = super().widget_init(form, fname, field, **kw) + widget.w_comodel_name = widget.w_field["relation"] + for k in ("domain", "display_field"): + if widget.w_field.get(k): + setattr(widget, f"w_{k}", widget.w_field.get(k)) + return widget + + @property + def w_comodel(self): + return self.env[self.w_comodel_name] diff --git a/cms_form/models/widgets/widget_selection.py b/cms_form/models/widgets/widget_selection.py new file mode 100644 index 000000000..0af7e4376 --- /dev/null +++ b/cms_form/models/widgets/widget_selection.py @@ -0,0 +1,66 @@ +# Copyright 2017 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import fields, models + +from ..fields import Serialized + + +class SelectionWidget(models.AbstractModel): + _name = "cms.form.widget.selection" + _inherit = "cms.form.widget.mixin" + _description = "CMS Form selection widget" + + w_template = fields.Char(default="cms_form.field_widget_selection") + + def w_extract(self, **req_values): + # Handle case where sel options are integers. + # TODO: unify this using marshallers? See 'hidden' widget + # Maybe we can have an internal field name + # and a widget field name. In any case we should be careful + # and not brake existing forms/widgets. + value = super().w_extract(**req_values) + return self.cast_field_value(value) + + def cast_field_value(self, value): + first_value = None + # use `get` as you might want to use the selection widget + # for non-Selection fields and just pass options via `w_option_items`. + if self.w_field.get("selection"): + # `fields.Selection` does this under the hood + # to state the PG column type to be used. + first_value = self.w_field["selection"][0][0] + # fields.Selection does the same check to determine PG col type + if first_value and value: + # convert to same type + value = type(first_value)(value) + return value + + @property + def w_option_items(self): + return [ + {"value": x[0], "label": x[1]} for x in self.w_field.get("selection", []) + ] + + def is_option_selected(self, opt_item): + return ( + "selected" + if opt_item["value"] == self.cast_field_value(self.w_field_value) + else None + ) + + +class RadioSelectionWidget(models.AbstractModel): + _name = "cms.form.widget.radio" + _inherit = "cms.form.widget.selection" + _description = "CMS Form radio widget" + + w_template = fields.Char(default="cms_form.field_widget_radio_selection") + # you can define help message per each options + # opt value: help msg (can be html too) + w_options_help = Serialized(default={}) + + def widget_init(self, form, fname, field, **kw): + widget = super(RadioSelectionWidget, self).widget_init(form, fname, field, **kw) + widget.w_options_help = kw.get("options_help") or {} + return widget diff --git a/cms_form/models/widgets/widget_text.py b/cms_form/models/widgets/widget_text.py new file mode 100644 index 000000000..2c29f8ad1 --- /dev/null +++ b/cms_form/models/widgets/widget_text.py @@ -0,0 +1,42 @@ +# Copyright 2017 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import fields, models + + +class CharWidgetMixin(models.AbstractModel): + _name = "cms.form.widget.char.mixin" + _inherit = "cms.form.widget.mixin" + _description = "CMS Form char widget" + + w_template = fields.Char(default="cms_form.field_widget_char") + w_input_type = fields.Char(default="text") + w_valid_pattern = fields.Char(help="Used to validate inputs with `pattern` attr") + + +class CharWidget(models.AbstractModel): + _name = "cms.form.widget.char" + _inherit = "cms.form.widget.char.mixin" + _description = "CMS Form char widget" + + @property + def html_value(self): + return self.w_field_value.strip() if self.w_field_value else "" + + +class TextWidget(models.AbstractModel): + _name = "cms.form.widget.text" + _inherit = "cms.form.widget.char.mixin" + _description = "CMS Form text widget" + + w_template = fields.Char(default="cms_form.field_widget_text") + w_maxlength = fields.Integer() + + @property + def html_value(self): + return self.w_field_value.strip() if self.w_field_value else "" + + def widget_init(self, form, fname, field, **kw): + widget = super().widget_init(form, fname, field, **kw) + widget.w_maxlength = field.get("maxlength") or kw.get("maxlength") + return widget diff --git a/cms_form/models/widgets/widget_x2many.py b/cms_form/models/widgets/widget_x2many.py new file mode 100644 index 000000000..79d4087cc --- /dev/null +++ b/cms_form/models/widgets/widget_x2many.py @@ -0,0 +1,83 @@ +# Copyright 2017 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +import json + +from odoo import fields, models + +from ..fields import Serialized + + +class X2MWidget(models.AbstractModel): + _name = "cms.form.widget.x2m.mixin" + _inherit = "cms.form.widget.rel.mixin" + _description = "CMS Form X2M widget" + + w_template = fields.Char(default="cms_form.field_widget_x2m") + w_field_value = Serialized(default=[]) + + # TODO: rename all widget-specific methods like: + # `x2many_to_form` -> `_w_orm_to_form` + # `form_to_x2many` -> `_w_form_to_orm` + # and make mixin's `w_load` and `w_extract` methods use them. + # In this way we remove all the overrides on `w_load` and `w_extract`. + def w_load(self, **req_values): + value = super().w_load(**req_values) + return self.x2many_to_form(value, **req_values) + + def _is_not_valued(self, value): + if not value: + return True + if isinstance(value, (list, tuple)): + # if value comes from `default_get` we have [(6, 0, [])] + if not all([x[-1] for x in value]): + return True + return False + + def x2many_to_form(self, value, **req_values): + if self._is_not_valued(value): + return json.dumps([]) + ids = [] + if isinstance(value, type(self.w_comodel)): + ids = value.ids + elif isinstance(value, str): + ids = self.w_ids_from_input(value) + req_val = self.w_ids_from_input(req_values.get(self.w_fname, "")) + if req_val: + # request value take precedence + ids = req_val[:] + read_fields = [ + self.w_display_field, + ] + if "name" in self.w_comodel: + read_fields.append("name") + return json.dumps(self.w_comodel.browse(ids).read(read_fields)) + + def w_extract(self, **req_values): + value = super().w_extract(**req_values) + return self.form_to_x2many(value, **req_values) + + def form_to_x2many(self, value, **req_values): + _value = False + if self.w_form.form_extract_value_mode == "write": + if value: + _value = [(6, False, self.w_ids_from_input(value))] + else: + # wipe them + _value = [(5,)] + else: + _value = value and self.w_ids_from_input(value) or [] + return _value + + +# TODO: handle advanced editing via table view and subform for such fields +class O2ManyWidget(models.AbstractModel): + _name = "cms.form.widget.one2many" + _inherit = "cms.form.widget.x2m.mixin" + _description = "CMS Form O2M widget" + + +class M2MWidget(models.AbstractModel): + _name = "cms.form.widget.many2many" + _inherit = "cms.form.widget.x2m.mixin" + _description = "CMS Form M2M widget" diff --git a/cms_form/security/cms_form.xml b/cms_form/security/cms_form.xml new file mode 100644 index 000000000..3442155bd --- /dev/null +++ b/cms_form/security/cms_form.xml @@ -0,0 +1,16 @@ + + + + + cms.form access name + + + + + + + + diff --git a/cms_form/static/description/icon.png b/cms_form/static/description/icon.png new file mode 100644 index 000000000..3a0328b51 Binary files /dev/null and b/cms_form/static/description/icon.png differ diff --git a/cms_form/static/src/js/ajax.js b/cms_form/static/src/js/ajax.js new file mode 100644 index 000000000..a429e91f5 --- /dev/null +++ b/cms_form/static/src/js/ajax.js @@ -0,0 +1,69 @@ +odoo.define("cms_form.ajax", function (require) { + "use strict"; + + // FIXME: website dep + var core = require("web.core"), + animation = require("website.content.snippets.animation"); + + animation.registry.CMSFormAjax = animation.Class.extend({ + selector: ".cms_form_wrapper form[data-ajax]", + events: { + submit: "submit_form", + }, + start: function () { + this.data = this.$el.data("form"); + if (this.$el.data("ajax-onchange")) { + this.$el.on("change", this.proxy("submit_form")); + } + this.$el + .parents(".cms_form_wrapper") + .find(this.data.form_content_selector) + .find(".pagination a[href]") + .on("click", this.proxy("pager")); + }, + ajax_submit: function (additional_data) { + return jQuery.ajax(_.str.sprintf("/cms/ajax/search/%s", this.data.model), { + data: + this.$el.serialize() + + "&csrf_token=" + + core.csrf_token + + (additional_data || ""), + dataType: "json", + success: this.proxy("success"), + error: this.proxy("error"), + }); + }, + submit_form: function (ev) { + var $container = this.$container(); + + jQuery.blockUI(); + ev.preventDefault(); + $container.empty(); + + return this.ajax_submit(); + }, + success: function (data) { + jQuery.unblockUI(); + var $container = this.$container(); + + $container.html(data.content); + $container.find(".pagination a[href]").on("click", this.proxy("pager")); + }, + error: function () { + jQuery.unblockUI(); + }, + pager: function (ev) { + var $a = jQuery(ev.currentTarget); + + jQuery.blockUI(); + ev.preventDefault(); + + return this.ajax_submit("&page=" + $a.data("page")); + }, + $container: function () { + return this.$el + .parents(".cms_form_wrapper") + .find(this.data.form_content_selector); + }, + }); +}); diff --git a/cms_form/static/src/js/lock_copy_paste.js b/cms_form/static/src/js/lock_copy_paste.js new file mode 100644 index 000000000..e5661371a --- /dev/null +++ b/cms_form/static/src/js/lock_copy_paste.js @@ -0,0 +1,21 @@ +odoo.define("cms_form.lock_copy_paste", function (require) { + "use strict"; + + // FIXME: website dep + var sAnimation = require("website.content.snippets.animation"); + + sAnimation.registry.CMSFormLockCopyPaste = sAnimation.Class.extend({ + selector: ".cms_form_wrapper form .lock_copy_paste", + start: function () { + this.setup_handlers(); + }, + setup_handlers: function () { + this.$el.bind("cut copy paste", function (e) { + e.preventDefault(); + }); + this.$el.on("contextmenu", function () { + return false; + }); + }, + }); +}); diff --git a/cms_form/static/src/js/master_slave.js b/cms_form/static/src/js/master_slave.js new file mode 100644 index 000000000..3cb7f2b61 --- /dev/null +++ b/cms_form/static/src/js/master_slave.js @@ -0,0 +1,91 @@ +odoo.define("cms_form.master_slave", function (require) { + "use strict"; + /* + Handle master / slave fields automatically. + TODO: explain behavior. + */ + + // TODO: this does not work ATM :( + // var pyeval = require('web.pyeval'); + // FIXME: website dep + var sAnimation = require("website.content.snippets.animation"); + + sAnimation.registry.CMSFormMasterSlave = sAnimation.Class.extend({ + selector: ".cms_form_wrapper form", + start: function () { + this.data = this.$el.data("form"); + this.setup_handlers(); + this.load_master_slave(); + }, + setup_handlers: function () { + this.handlers = { + hide: $.proxy(this.handle_hide, this), + show: $.proxy(this.handle_show, this), + readonly: $.proxy(this.handle_readonly, this), + no_readonly: $.proxy(this.handle_no_readonly, this), + required: $.proxy(this.handle_required, this), + no_required: $.proxy(this.handle_no_required, this), + }; + }, + load_master_slave: function () { + var self = this; + $.each(this.data.master_slave, function (master, slaves) { + var $master_input = $('[name="' + master + '"]'); + $.each(slaves, function (action, mapping) { + var handler = self.handlers[action]; + if (handler) { + $master_input + .on("change", function () { + var $input = $(this), + val = $input.val(); + if ($input.is(":checkbox")) { + // Value == 'on' => true/false + val = $input.is(":checked"); + } + $.each(mapping, function (slave_fname, values) { + if (_.contains(values, val)) { + handler(slave_fname); + } + }); + }) + .filter( + "select,[type=checkbox],[type=radio]:checked,[type=text]" + ) + .trigger("change"); + // Trigger change to apply maste/slave rules at load + } + }); + }); + }, + handle_hide: function (slave_fname) { + $(".field-" + slave_fname).hide(); + }, + handle_show: function (slave_fname) { + $(".field-" + slave_fname).show(); + }, + handle_readonly: function (slave_fname) { + $('[name="' + slave_fname + '"]') + .attr("disabled", "disabled") + .closest(".form-group") + .addClass("disabled"); + }, + handle_no_readonly: function (slave_fname) { + $('[name="' + slave_fname + '"]') + .attr("disabled", null) + .closest(".form-group") + .removeClass("disabled"); + }, + handle_required: function (slave_fname) { + $('[name="' + slave_fname + '"]') + .attr("required", "required") + .closest(".form-group") + .addClass("field-required"); + }, + handle_no_required: function (slave_fname) { + $('[name="' + slave_fname + '"]') + .attr("required", null) + .closest(".form-group") + .removeClass("field-required"); + }, + }); +}); diff --git a/cms_form/static/src/js/select2widgets.js b/cms_form/static/src/js/select2widgets.js new file mode 100644 index 000000000..caf30bbf1 --- /dev/null +++ b/cms_form/static/src/js/select2widgets.js @@ -0,0 +1,83 @@ +odoo.define("cms_form.select2widgets", function (require) { + "use strict"; + + var ajax = require("web.ajax"); + var weContext = require("web_editor.context"); + + require("web.dom_ready"); + + if (!$(".js_select2_m2m_widget").length) { + return $.Deferred().reject("DOM doesn't contain '.js_select2_m2m_widget'"); + } + + $(document).ready(function () { + $("input.js_select2_m2m_widget").each(function () { + var $input = $(this); + $input.select2({ + multiple: true, + tags: true, + tokenSeparators: [",", " ", "_"], + formatResult: function (term) { + var formatted = _.escape(term.text); + if (term.isNew) { + formatted = + 'New ' + formatted; + } + return formatted; + }, + query: function (options) { + var domain = []; + if (options.term) { + domain.push([ + $input.data("search_field") || "name", + "ilike", + "%" + options.term + "%", + ]); + } + // TODO: use data.CompundDomain to build domain + // ATM it's just in backend assets + // and requires both data.js and pyeval.js + domain = _.union(domain, $input.data("domain")); + ajax.jsonRpc("/web/dataset/call_kw", "call", { + model: $input.data("model"), + method: "search_read", + args: [domain], + kwargs: { + fields: $input.data("fields"), + context: weContext.get(), + }, + }).then(function (data) { + var display_name = $input.data("display_name"); + data.sort(function (a, b) { + return a[display_name].localeCompare(b[display_name]); + }); + var res = { + results: [], + }; + _.each(data, function (x) { + res.results.push({ + id: x.id, + text: x[display_name], + isNew: false, + }); + }); + options.callback(res); + }); + }, + // Default tags from the input value + initSelection: function (element, callback) { + var data = []; + _.each(element.data("init-value"), function (x) { + data.push({ + id: x.id, + text: x.name, + isNew: false, + }); + }); + element.val(""); + callback(data); + }, + }); + }); + }); +}); diff --git a/cms_form/static/src/js/textarea_widget.js b/cms_form/static/src/js/textarea_widget.js new file mode 100644 index 000000000..c3b6e0755 --- /dev/null +++ b/cms_form/static/src/js/textarea_widget.js @@ -0,0 +1,24 @@ +odoo.define("cms_form.textarea_widget", function (require) { + "use strict"; + + require("web.dom_ready"); + + $(document).ready(function () { + $("textarea[maxlength]") + .bind("input propertychange", function () { + var $self = $(this), + maxlength = parseInt($self.attr("maxlength"), 10), + length = $self.val().length, + left = maxlength - length, + $counter = $self.siblings(".text-counter"); + if ($self.data("counter")) { + $counter = $($self.data("counter")); + } + if (left < 0) { + left = 0; + } + $counter.val(left); + }) + .trigger("input"); + }); +}); diff --git a/cms_form/static/src/scss/cms_form.scss b/cms_form/static/src/scss/cms_form.scss new file mode 100644 index 000000000..1f3129873 --- /dev/null +++ b/cms_form/static/src/scss/cms_form.scss @@ -0,0 +1,24 @@ +.cms_form_wrapper { + form { + .field-required { + .control-label::after { + content: "*"; + font-weight: bold; + } + } + .text-counter { + width: auto; + float: right; + margin-top: 0.2em; + } + fieldset { + padding: 1em; + } + .has_fieldsets.tabs .tab-pane { + padding: 0.5em; + } + .has-error .error-msg { + margin-top: 0.5em; + } + } +} diff --git a/cms_form/static/src/scss/progressbar.scss b/cms_form/static/src/scss/progressbar.scss new file mode 100644 index 000000000..e353ce390 --- /dev/null +++ b/cms_form/static/src/scss/progressbar.scss @@ -0,0 +1,95 @@ +$cms-prog-bar-bg: $primary; +$cms-prog-bar-bg-active: darken($cms-prog-bar-bg, 10%); +$cms-prog-bar-bg-hover: lighten($cms-prog-bar-bg, 10%); + +// not using `progress-bar` as it's already styled +// for percentage bar by bootstrap +.cms_form_wrapper .status-bar { + float: right; + overflow: hidden; + list-style: none; + display: inline-block; + margin: 1em 0 !important; + + .icon { + font-size: 14px; + } + + li { + float: left; + a, + span { + color: #fff; + display: block; + background-color: $cms-prog-bar-bg; + text-decoration: none; + position: relative; + height: 40px; + line-height: 40px; + padding: 0 20px 0 10px; + text-align: center; + margin-right: 23px; + } + &:first-child { + a, + span { + padding-left: 15px; + &:before { + border: none; + } + } + } + &:last-child { + a, + span { + padding-right: 15px; + &:after { + border: none; + } + } + } + + a, + span { + &:before, + &:after { + content: ""; + position: absolute; + top: 0; + border: 0 solid $cms-prog-bar-bg; + border-width: 20px 10px; + width: 0; + height: 0; + } + &:before { + left: -20px; + border-left-color: transparent; + } + &:after { + left: 100%; + border-color: transparent; + border-left-color: $cms-prog-bar-bg; + } + &:hover { + background-color: $cms-prog-bar-bg-hover; + &:before { + border-color: $cms-prog-bar-bg-hover; + border-left-color: transparent; + } + &:after { + border-left-color: $cms-prog-bar-bg-hover; + } + } + &.active { + background-color: $cms-prog-bar-bg-active; + &:before { + border-color: $cms-prog-bar-bg-active; + border-left-color: transparent; + } + &:after { + border-left-color: $cms-prog-bar-bg-active; + } + } + } + } +} diff --git a/cms_form/templates/form.xml b/cms_form/templates/form.xml new file mode 100644 index 000000000..181c43ac4 --- /dev/null +++ b/cms_form/templates/form.xml @@ -0,0 +1,441 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cms_form/templates/portal.xml b/cms_form/templates/portal.xml new file mode 100644 index 000000000..91d25d1d9 --- /dev/null +++ b/cms_form/templates/portal.xml @@ -0,0 +1,24 @@ + + + + + + + + + diff --git a/cms_form/templates/widgets.xml b/cms_form/templates/widgets.xml new file mode 100644 index 000000000..3a12a64d0 --- /dev/null +++ b/cms_form/templates/widgets.xml @@ -0,0 +1,272 @@ + + + + + + + + + + + + + + + + + diff --git a/cms_form/tests/__init__.py b/cms_form/tests/__init__.py new file mode 100644 index 000000000..3170d13c9 --- /dev/null +++ b/cms_form/tests/__init__.py @@ -0,0 +1,22 @@ +from . import test_controllers +from . import test_form_base +from . import test_form_cms +from . import test_form_permission +from . import test_form_render +from . import test_form_search +from . import test_form_wizard +from . import test_loaders +from . import test_marshallers +from . import test_utils +from .widgets import test_widget_base +from .widgets import test_widget_text +from .widgets import test_widget_hidden +from .widgets import test_widget_integer +from .widgets import test_widget_float +from .widgets import test_widget_selection +from .widgets import test_widget_radio +from .widgets import test_widget_boolean +from .widgets import test_widget_date +from .widgets import test_widget_many2one +from .widgets import test_widget_x2many +from .widgets import test_widget_binary diff --git a/cms_form/tests/common.py b/cms_form/tests/common.py new file mode 100644 index 000000000..08e613ec9 --- /dev/null +++ b/cms_form/tests/common.py @@ -0,0 +1,143 @@ +# Copyright 2017 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from lxml import html +from odoo_test_helper import FakeModelLoader + +from odoo.tests.common import HttpCase, TransactionCase + +from .utils import fake_request, fake_session, session_store + + +def get_form(env, form_model, req=None, session=None, ctx=None, sudo_uid=None, **kw): + """Retrieve and initialize a form. + + :param form_model: model dotted name + :param req: a fake request. Default to base fake request + :param session: a fake session. Default to base fake session + :param ctx: extra context keys + :param sudo_uid: init form w/ another user uid + :param kw: extra arguments to init the form + """ + model = env[form_model] + if sudo_uid: + model = model.with_user(sudo_uid) + if ctx: + model = model.with_context(**ctx) + + session = session if session is not None else fake_session(env) + request = req or fake_request(session=session) + return model.form_init(request, **kw) + + +class FakeModelMixin(object): + """Mixin to setup fake models just for testing.""" + + @staticmethod + def _get_test_models(): + return () + + @staticmethod + def _setup_models(class_or_instance): + """Setup new fake models for testing.""" + if hasattr(class_or_instance, "loader"): + # Especially for HttpCase: try to setup models only once + return + class_or_instance.loader = FakeModelLoader( + class_or_instance.env, class_or_instance.__module__ + ) + class_or_instance.loader.backup_registry() + test_models = class_or_instance._get_test_models() + if test_models: + class_or_instance.loader.update_registry(test_models) + for klass in test_models: + # Make them available on the test class + setattr(class_or_instance, klass.__name__, klass) + + @staticmethod + def _teardown_models(class_or_instance): + """Wipe fake models once tests have finished.""" + if hasattr(class_or_instance, "loader"): + class_or_instance.loader.restore_registry() + + +class HTMLRenderMixin(object): + """Mixin with helpers to test HTML rendering.""" + + def to_xml_node(self, html_): + return html.fragments_fromstring(html_) + + def find_input_name(self, node, name): + return node.xpath('(//input|//select|//textarea)[@name="{}"]'.format(name)) + + def assert_match_attrs(self, value, expected): + for k, v in expected.items(): + self.assertEqual(value[k].strip(), v.strip()) + + def assert_match_inputs(self, node, expected): + for name in expected: + self.assertEqual(len(self.find_input_name(node, name)), 1) + + +class FormTestCase(TransactionCase, FakeModelMixin): + """Form test cases.""" + + at_install = False + post_install = True + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls._setup_models(cls) + + @classmethod + def tearDownClass(cls): + cls._teardown_models(cls) + super().tearDownClass() + + def get_form(self, form_model, **kw): + return get_form(self.env, form_model, **kw) + + +class FormSessionTestCase(FormTestCase): + """Form test cases where you deal w/ a session.""" + + def setUp(self): + super().setUp() + self.session = fake_session(self.env) + + def tearDown(self): + session_store.delete(self.session) + super().tearDown() + + +class FormRenderTestCase(FormTestCase, HTMLRenderMixin): + """Form test cases where you test HTML rendering.""" + + +class FormHttpTestCase(HttpCase, FakeModelMixin, HTMLRenderMixin): + """Form test cases where you test HTML rendering and HTTP requests.""" + + # FIXME: use setupClass + def setUp(self): + # HttpCase has no ENV on setUpClass we have to setup fake models here + super().setUp() + self._setup_models(self) + + def tearDown(self): + # HttpCase has no ENV on setUpClass + self._teardown_models(self) + super().tearDown() + + def html_get(self, url): + resp = self.url_open(url, timeout=30) + return self.parse_html(resp.content) + + def parse_html(self, content, fragment=False): + parser = html.document_fromstring + if fragment: + parser = html.fragment_fromstring + return parser(content) + + def get_form(self, form_model, **kw): + return get_form(self.env, form_model, **kw) diff --git a/cms_form/tests/fake_models/__init__.py b/cms_form/tests/fake_models/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/cms_form/tests/fake_models/fake_fields_form.py b/cms_form/tests/fake_models/fake_fields_form.py new file mode 100644 index 000000000..4f90be4ab --- /dev/null +++ b/cms_form/tests/fake_models/fake_fields_form.py @@ -0,0 +1,57 @@ +# Copyright 2017 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import fields, models + + +class FakeFloatWidget(models.AbstractModel): + + _name = "cms.form.test_fields.widget.float" + _inherit = "cms.form.widget.float" + + +class FakeFieldsForm(models.AbstractModel): + """A test model form.""" + + _name = "cms.form.test_fields" + _inherit = "cms.form" + _description = "CMS Form test fields form" + + a_char = fields.Char() + a_number = fields.Integer() + a_float = fields.Float() + # fake relation fields + a_many2one = fields.Char() + a_one2many = fields.Char() + a_many2many = fields.Char() + + def _form_fields_get(self): + _fields = super()._form_fields_get() + # fake fields' types + _fields["a_many2one"]["type"] = "many2one" + _fields["a_many2one"]["relation"] = "res.partner" + _fields["a_many2many"]["type"] = "many2many" + _fields["a_many2many"]["relation"] = "res.partner" + _fields["a_one2many"]["type"] = "one2many" + _fields["a_one2many"]["relation"] = "res.partner" + return _fields + + def _form_validate_a_float(self, value, **request_values): + """Specific validator for `a_float` field.""" + value = float(value or "0") + return not value > 5, "Must be greater than 5!" + + def _form_validate_char(self, value, **request_values): + """Specific validator for all `char` fields.""" + return not len(value) > 8, "Text length must be greater than 8!" + + +class FakeFieldsForm2(models.AbstractModel): + """A test model form.""" + + _name = "cms.form.test_fields2" + _inherit = "cms.form.test_fields" + + a_float_with_another_widget = fields.Float( + form_widget={"model": FakeFloatWidget._name} + ) diff --git a/cms_form/tests/fake_models/fake_fields_form_fieldsets.py b/cms_form/tests/fake_models/fake_fields_form_fieldsets.py new file mode 100644 index 000000000..f039205a1 --- /dev/null +++ b/cms_form/tests/fake_models/fake_fields_form_fieldsets.py @@ -0,0 +1,33 @@ +# Copyright 2017 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import fields, models + +from odoo.addons.cms_form.models.fields import Serialized # pylint: disable=W8150 + + +class FakeFieldsFormWithFieldsets(models.AbstractModel): + """A test model form.""" + + _name = "cms.form.test_fieldsets" + _inherit = "cms.form.test_fields" + _description = "CMS Form test fieldsets form" + form_fieldsets = Serialized( + default=[ + {"id": "main", "fields": ["a_char"]}, + { + "id": "numbers", + "title": "Number fields", + "description": "Only number fields here", + "fields": ["a_number", "a_float"], + "css_extra_klass": "best_fieldset", + }, + { + "id": "relations", + "title": "Only relations here", + "fields": ["a_many2one", "a_many2many", "a_one2many"], + }, + {"id": "protected", "fields": ["ihaveagroup"]}, + ] + ) + ihaveagroup = fields.Char(groups="base.group_system") diff --git a/cms_form/tests/fake_models/fake_nonpub_model_form.py b/cms_form/tests/fake_models/fake_nonpub_model_form.py new file mode 100644 index 000000000..6a606f52b --- /dev/null +++ b/cms_form/tests/fake_models/fake_nonpub_model_form.py @@ -0,0 +1,21 @@ +# Copyright 2017 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import fields, models + +from odoo.addons.cms_form.models.fields import Serialized # pylint: disable=W8150 + + +class FakeNonPubModel(models.Model): + _name = "fake.non.publishable" + _description = "CMS Form fake non publishable model" + name = fields.Char() + + +class FakeNonPubModelForm(models.AbstractModel): + _name = "cms.form.fake.non.publishable" + _inherit = "cms.form" + _description = "CMS Form fake non publishable model form" + + form_model_name = fields.Char(default="fake.non.publishable") + form_model_fields = Serialized(default=("name",)) diff --git a/cms_form/tests/fake_models/fake_partner_channel_form.py b/cms_form/tests/fake_models/fake_partner_channel_form.py new file mode 100644 index 000000000..4bacd47fa --- /dev/null +++ b/cms_form/tests/fake_models/fake_partner_channel_form.py @@ -0,0 +1,24 @@ +# Copyright 2017 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import fields, models + + +class FakePartnerRelModel(models.Model): + _name = "fake.partner.related" + _description = _name + _rec_name = "partner_id" + + foo = fields.Char() + partner_id = fields.Many2one("res.partner") + + +class FakePartnerRelatedForm(models.AbstractModel): + """A test model form.""" + + _name = "cms.form.rel.partner" + _inherit = "cms.form" + _description = "CMS Form test partner form" + # This model has `_rec_name = 'partner_id'` and allows us + # to test a specific case for form_title computation + form_model_name = fields.Char(default=FakePartnerRelModel._name) diff --git a/cms_form/tests/fake_models/fake_partner_form.py b/cms_form/tests/fake_models/fake_partner_form.py new file mode 100644 index 000000000..4943763a1 --- /dev/null +++ b/cms_form/tests/fake_models/fake_partner_form.py @@ -0,0 +1,23 @@ +# Copyright 2017 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import fields, models + +from odoo.addons.cms_form.models.fields import Serialized # pylint: disable=W8150 + + +class FakePartnerForm(models.AbstractModel): + """A test model form.""" + + _name = "cms.form.res.partner" + _inherit = "cms.form" + _description = "CMS Form test partner form" + + form_model_name = fields.Char(default="res.partner") + form_model_fields = Serialized(default=("name", "country_id")) + form_required_fields = Serialized(default=("name", "country_id")) + + custom = fields.Char(default=lambda self: "I am your default") + + def _form_load_custom(self, fname, field, value, **req_values): + return req_values.get("custom", "oh yeah!") diff --git a/cms_form/tests/fake_models/fake_partner_form_protected_fields.py b/cms_form/tests/fake_models/fake_partner_form_protected_fields.py new file mode 100644 index 000000000..b63b96ab3 --- /dev/null +++ b/cms_form/tests/fake_models/fake_partner_form_protected_fields.py @@ -0,0 +1,17 @@ +# Copyright 2017 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import fields, models + + +class FakePartnerFormProtectedFields(models.AbstractModel): + """A test model form w/ `groups` protected fields.""" + + _name = "cms.form.protected.fields" + _inherit = "cms.form" + _description = "CMS Form test partner protected fields form" + # we'll test specifically that ordering won't break + _form_fields_order = ["ihaveagroup", "nogroup"] + + nogroup = fields.Char() + ihaveagroup = fields.Char(groups="base.group_system") diff --git a/cms_form/tests/fake_models/fake_pub_model_form.py b/cms_form/tests/fake_models/fake_pub_model_form.py new file mode 100644 index 000000000..15c3c7d84 --- /dev/null +++ b/cms_form/tests/fake_models/fake_pub_model_form.py @@ -0,0 +1,29 @@ +# Copyright 2017 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import fields, models + +from odoo.addons.cms_form.models.fields import Serialized # pylint: disable=W8150 + + +class FakePubModel(models.Model): + _name = "fake.publishable" + _inherit = [ + "cms.info.mixin", + ] + _description = "CMS Form fake publishable model form" + + name = fields.Char() + + def _compute_cms_view_url(self): + for item in self: + item.url = "/publishable/%d" % item.id + + +class FakePubModelForm(models.AbstractModel): + _name = "cms.form.fake.publishable" + _inherit = "cms.form" + _description = "CMS Form fake publishable form" + + form_model_name = fields.Char(default="fake.publishable") + form_model_fields = Serialized(default=("name",)) diff --git a/cms_form/tests/fake_models/fake_search_partner_form.py b/cms_form/tests/fake_models/fake_search_partner_form.py new file mode 100644 index 000000000..97f0b3217 --- /dev/null +++ b/cms_form/tests/fake_models/fake_search_partner_form.py @@ -0,0 +1,53 @@ +# Copyright 2017 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import fields, models + +from odoo.addons.cms_form.models.fields import Serialized # pylint: disable=W8150 + +TEST_RECORD_IDS = [] + + +class FakeSearchPartnerForm(models.AbstractModel): + """A test model search form.""" + + _name = "cms.form.search.res.partner" + _inherit = "cms.form.search" + _description = "CMS Form test partner search form" + + form_model_name = fields.Char(default="res.partner") + form_model_fields = Serialized(default=("name", "country_id")) + + def form_search_domain(self, search_values): + """Force domain to include only test-created records.""" + domain = super().form_search_domain(search_values) + # we use this attr in tests to limit search results + # to test records' scope + include_only_ids = self._get_test_record_ids() + if include_only_ids: + domain.append(("id", "in", include_only_ids)) + return domain + + @classmethod + def _get_test_record_ids(cls): + global TEST_RECORD_IDS + return TEST_RECORD_IDS + + @classmethod + def _set_test_record_ids(cls, ids): + global TEST_RECORD_IDS + TEST_RECORD_IDS = ids + + +class FakeSearchPartnerFormMulti(models.AbstractModel): + """A test model search form w/ multiple values for country.""" + + _name = "cms.form.search.res.partner.multicountry" + _inherit = "cms.form.search.res.partner" + _description = "CMS Form test partner search multi form" + + form_search_fields_multi = Serialized(default=("country_id",)) + country_id = fields.Many2one( + comodel_name="res.country", + form_widget={"model": "cms.form.widget.many2one.multi"}, + ) diff --git a/cms_form/tests/fake_models/fake_wizard_form.py b/cms_form/tests/fake_models/fake_wizard_form.py new file mode 100644 index 000000000..3ac6f0214 --- /dev/null +++ b/cms_form/tests/fake_models/fake_wizard_form.py @@ -0,0 +1,74 @@ +# Copyright 2017 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo import fields, models + +from odoo.addons.cms_form.models.fields import Serialized # pylint: disable=W8150 + + +class FakeWiz(models.AbstractModel): + """A wizard form.""" + + FAKE_STORAGE = {} + + _name = "fake.wiz" + _inherit = "cms.form.wizard" + _description = "CMS Form test wizard form" + _wiz_name = _name + + def form_check_permission(self, raise_exception=True): + # no need for this + pass + + @property + def _wiz_storage(self): + return self.FAKE_STORAGE + + def wiz_configure_steps(self): + return { + 1: {"form_model": "fake.wiz.step1.country"}, + 2: {"form_model": "fake.wiz.step2.partner"}, + 3: {"form_model": "fake.wiz.step3.partner"}, + } + + +class FakeWizStep1Country(models.AbstractModel): + + _name = "fake.wiz.step1.country" + _inherit = "fake.wiz" + _description = "CMS Form test wizard form step 1" + form_model_name = fields.Char(default="res.country") + form_model_fields = Serialized(default=("name",)) + + +class FakeWizStep2Partner(models.AbstractModel): + + _name = "fake.wiz.step2.partner" + _inherit = "fake.wiz" + _description = "CMS Form test wizard form step 2" + form_model_name = fields.Char(default="res.partner") + form_model_fields = Serialized(default=("name", "to_be_stored")) + form_step_stored_fields = Serialized( + default=[ + "to_be_stored", + ] + ) + + to_be_stored = fields.Char() + + +class FakeWizStep3Partner(models.AbstractModel): + + _name = "fake.wiz.step3.partner" + _inherit = "fake.wiz" + _description = "CMS Form test wizard form step 3" + form_model_name = fields.Char(default="res.partner") + form_model_fields = Serialized(default=("name",)) + + +ALL_WIZ_KLASSES = [ + FakeWiz, + FakeWizStep1Country, + FakeWizStep2Partner, + FakeWizStep3Partner, +] diff --git a/cms_form/tests/test_controllers.py b/cms_form/tests/test_controllers.py new file mode 100644 index 000000000..7e7acfe23 --- /dev/null +++ b/cms_form/tests/test_controllers.py @@ -0,0 +1,258 @@ +# Copyright 2017 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +import json +import os +import unittest + +from ..controllers import main +from .common import FormHttpTestCase, FormTestCase +from .utils import fake_request, mock_request + +IMPORT = "odoo.addons.cms_form.controllers.main" + + +class TestControllersAPI(FormTestCase): + @staticmethod + def _get_test_models(): + from .fake_models.fake_partner_form import FakePartnerForm + from .fake_models.fake_search_partner_form import FakeSearchPartnerForm + from .fake_models.fake_wizard_form import ALL_WIZ_KLASSES + + return ALL_WIZ_KLASSES + [FakePartnerForm, FakeSearchPartnerForm] + + def setUp(self): + super().setUp() + self.form_controller = main.CMSFormController() + self.form_search_controller = main.CMSSearchFormController() + self.form_wiz_controller = main.CMSWizardFormController() + + def test_get_template(self): + with mock_request(self.env): + form = self.form_controller.get_form("res.partner") + # default + self.assertEqual( + self.form_controller.get_template(form), + "cms_form.portal_form_wrapper", + ) + # custom on form + form.form_wrapper_template = "foo.baz" + self.assertEqual(self.form_controller.get_template(form), "foo.baz") + self.form_controller.template = None + form.form_wrapper_template = None + with self.assertRaises(NotImplementedError): + self.form_controller.get_template(form) + + def test_get_render_values(self): + with mock_request(self.env): + form = self.form_controller.get_form("res.partner") + # default, no main object + self.assertEqual( + self.form_controller.get_render_values(form), + { + "form": form, + "main_object": self.env["res.partner"], + "controller": self.form_controller, + }, + ) + # get a main obj + partner = self.env.ref("base.res_partner_12") + form = self.form_controller.get_form("res.partner", model_id=partner.id) + self.assertEqual( + self.form_controller.get_render_values(form), + { + "form": form, + "main_object": partner, + "controller": self.form_controller, + }, + ) + # strip out form fields values (they are held by the form itself) + self.assertEqual( + self.form_controller.get_render_values( + form, name="John", custom="foo", not_a_form_field=1 + ), + { + "form": form, + "main_object": partner, + "controller": self.form_controller, + "not_a_form_field": 1, + }, + ) + + def test_get_no_form(self): + with mock_request(self.env): + # we do not have a specific form for res.groups + # and cms form is not enabled on partner model + with self.assertRaises(NotImplementedError): + self.form_controller.get_form("res.groups") + + def test_get_form_no_model_no_main_object(self): + with mock_request(self.env): + form = self.form_controller.get_form( + None, form_model_key=self.FakePartnerForm._name + ) + self.assertEqual(form.main_object, self.env[self.FakePartnerForm._name]) + + def test_get_default_form(self): + with mock_request(self.env): + # we have one for res.partner + form = self.form_controller.get_form("res.partner") + self.assertTrue( + isinstance(form, self.env["cms.form.res.partner"].__class__) + ) + self.assertEqual(form.form_model_name, "res.partner") + self.assertEqual(form.form_mode, "create") + + def test_get_specific_form(self): + with mock_request(self.env): + # we have a specific form here + form = self.form_search_controller.get_form("res.partner") + self.assertTrue( + isinstance(form, self.env["cms.form.search.res.partner"].__class__) + ) + self.assertEqual(form.form_model_name, "res.partner") + self.assertEqual(form.form_mode, "search") + + def test_get_wizard_form(self): + with mock_request(self.env): + # we have a specific form here + form = self.form_wiz_controller.get_form("res.partner") + self.assertTrue( + isinstance(form, self.env["cms.form.res.partner"].__class__) + ) + self.assertEqual(form.form_model_name, "res.partner") + self.assertEqual(form.form_mode, "create") + + def test_redirect_after_success(self): + req = fake_request( + form_data={"name": "John"}, + method="POST", + ) + with mock_request(self.env, httprequest=req.httprequest): + partner = self.env.ref("base.res_partner_12") + response = self.form_controller.make_response( + "res.partner", model_id=partner.id + ) + self.assertEqual(response.status_code, 303) + if "url" in partner: + # website_partner installed + self.assertEqual(response.location, partner.url) + else: + self.assertEqual(response.location, "/") + + +@unittest.skipIf(os.getenv("SKIP_HTTP_CASE"), "HTTP case disabled.") +class TestControllersRender(FormHttpTestCase): + def setUp(self): + super().setUp() + self.authenticate("admin", "admin") + + @staticmethod + def _get_test_models(): + from .fake_models.fake_partner_form import FakePartnerForm + from .fake_models.fake_search_partner_form import FakeSearchPartnerForm + from .fake_models.fake_wizard_form import ALL_WIZ_KLASSES + + return ALL_WIZ_KLASSES + [FakePartnerForm, FakeSearchPartnerForm] + + def _check_rendering(self, dom, form_model, model, mode, extra_klass=""): + """Check default markup for form and form wrapper.""" + # test wrapper klass + wrapper_node = dom.find_class("cms_form_wrapper")[0] + expected_attrs = { + "class": "cms_form_wrapper {form_model} {model} mode_{mode}".format( + form_model=form_model.replace(".", "_"), + model=model.replace(".", "_"), + mode=mode, + ) + + (" " + extra_klass if extra_klass else "") + } + self.assert_match_attrs(wrapper_node.attrib, expected_attrs) + # test form is there and has correct klass + form_node = dom.xpath("//form")[0] + expected_attrs = { + "enctype": "multipart/form-data", + "method": "POST", + "class": "form-horizontal", + } + self.assert_match_attrs(form_node.attrib, expected_attrs) + + def _check_route(self, url): + resp = self.url_open(url, timeout=30) + self.assertTrue(resp.ok) + self.assertEqual(resp.status_code, 200) + + def test_default_routes(self): + self._check_route("/cms/create/res.partner") + self._check_route("/cms/edit/res.partner/1") + self._check_route("/cms/search/res.partner") + self._check_route("/cms/ajax/search/res.partner") + + def test_default_create_rendering(self): + dom = self.html_get("/cms/create/res.partner") + self._check_rendering(dom, "cms.form.res.partner", "res.partner", "create") + + def test_default_edit_rendering(self): + partner = self.env.ref("base.res_partner_1") + dom = self.html_get("/cms/edit/res.partner/{}".format(partner.id)) + self._check_rendering(dom, "cms.form.res.partner", "res.partner", "edit") + + def _check_wiz_rendering(self, dom, form_model, model, mode, extra_klass=""): + self._check_rendering(dom, form_model, model, mode, extra_klass=extra_klass) + # TODO: check more (paging etc) + + def test_default_wiz_rendering(self): + dom = self.html_get("/cms/wiz/fake.wiz/page/1") + self._check_wiz_rendering( + dom, + "fake.wiz.step1.country", + "res.country", + "wizard", + extra_klass="fake_wiz", + ) + dom = self.html_get("/cms/wiz/fake.wiz/page/2") + self._check_wiz_rendering( + dom, + "fake.wiz.step2.partner", + "res.partner", + "wizard", + extra_klass="fake_wiz", + ) + dom = self.html_get("/cms/wiz/fake.wiz/page/3") + self._check_wiz_rendering( + dom, + "fake.wiz.step3.partner", + "res.partner", + "wizard", + extra_klass="fake_wiz", + ) + # TODO: check more (paging etc) + + def test_render_only_form(self): + url = self.base_url() + "/cms/render/form/cms.form.res.partner" + resp = self.url_open( + url, + data=json.dumps({"widget_params": {}}), + headers={"Content-Type": "application/json"}, + timeout=30, + ) + form = resp.json()["result"]["form"] + dom = self.parse_html(form, fragment=True) + self.assertTrue(dom.tag == "form") + data_form = json.loads(dom.attrib["data-form"]) + self.assertEqual(data_form["model"], "res.partner") + + def test_render_only_form_widget(self): + url = self.base_url() + "/cms/render/form/cms.form.res.partner" + resp = self.url_open( + url, + data=json.dumps({"widget_params": {"custom": {}}}), + headers={"Content-Type": "application/json"}, + timeout=30, + ) + by_widget = resp.json()["result"]["by_widget"] + dom = self.parse_html(by_widget["custom"], fragment=True) + self.assertTrue(dom.tag == "input") + self.assertEqual(dom.attrib["id"], "custom") + self.assertEqual(dom.attrib["name"], "custom") + self.assertEqual(dom.attrib["value"], "oh yeah!") diff --git a/cms_form/tests/test_extractors.py b/cms_form/tests/test_extractors.py new file mode 100644 index 000000000..8b9cc3afe --- /dev/null +++ b/cms_form/tests/test_extractors.py @@ -0,0 +1,4 @@ +# Copyright 2017 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +# TODO diff --git a/cms_form/tests/test_form_base.py b/cms_form/tests/test_form_base.py new file mode 100644 index 000000000..de320761f --- /dev/null +++ b/cms_form/tests/test_form_base.py @@ -0,0 +1,577 @@ +# Copyright 2017 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from unittest import mock + +from werkzeug.wrappers import Request + +from odoo import http + +from .common import FormTestCase +from .utils import fake_request + + +class TestFormBase(FormTestCase): + @staticmethod + def _get_test_models(): + from .fake_models.fake_fields_form import ( + FakeFieldsForm, + FakeFieldsForm2, + FakeFloatWidget, + ) + from .fake_models.fake_partner_form import FakePartnerForm + from .fake_models.fake_partner_form_protected_fields import ( + FakePartnerFormProtectedFields, + ) + + return ( + FakeFieldsForm, + FakeFieldsForm2, + FakeFloatWidget, + FakePartnerForm, + FakePartnerFormProtectedFields, + ) + + def test_form_init(self): + form = self.get_form("cms.form.mixin") + self.assertTrue(isinstance(form.request, Request)) + self.assertTrue(isinstance(form.o_request, http.Request)) + + def test_form_init_overrides(self): + overrides = dict( + form_model_name="res.partner", + form_mode="foo", + form_fields_whitelist=("name",), + form_fields_blacklist=("country_id",), + form_fields_attributes=( + "string", + "type", + ), + form_wrapper_extra_css_klass="foo", + form_extra_css_klass="baz", + ) + form = self.get_form("cms.form.mixin", **overrides) + for k, v in overrides.items(): + self.assertEqual(form[k], v) + + def test_form_mode(self): + form = self.get_form("cms.form.mixin") + self.assertEqual(form.form_mode, "create") + form = self.get_form("cms.form.mixin", main_object=object()) + self.assertEqual(form.form_mode, "edit") + form = self.get_form("cms.form.mixin", form_mode="custom") + self.assertEqual(form.form_mode, "custom") + + def test_fields_load(self): + form = self.get_form("cms.form.res.partner") + fields = form.form_fields_get() + self.assertEqual(len(fields), 3) + self.assertTrue("name" in list(fields.keys())) + self.assertTrue("country_id" in list(fields.keys())) + self.assertTrue("custom" in list(fields.keys())) + + # whitelist + form = self.get_form("cms.form.res.partner", form_fields_whitelist=("name",)) + fields = form.form_fields_get() + self.assertEqual(len(fields), 1) + self.assertTrue("name" in list(fields.keys())) + self.assertTrue("country_id" not in list(fields.keys())) + self.assertTrue("custom" not in list(fields.keys())) + + # blacklist + form = self.get_form( + "cms.form.res.partner", form_fields_blacklist=("country_id",) + ) + fields = form.form_fields_get() + self.assertEqual(len(fields), 2) + self.assertTrue("name" in list(fields.keys())) + self.assertTrue("country_id" not in list(fields.keys())) + self.assertTrue("custom" in list(fields.keys())) + + def test_fields_order(self): + form = self.get_form( + "cms.form.res.partner", + form_fields_order=["name", "custom", "country_id"], + ) + fields = form.form_fields_get() + self.assertEqual(list(fields.keys())[0], "name") + self.assertEqual(list(fields.keys())[1], "custom") + self.assertEqual(list(fields.keys())[2], "country_id") + + # change order + form = self.get_form( + "cms.form.res.partner", + form_fields_order=["country_id", "name", "custom"], + ) + fields = form.form_fields_get() + self.assertEqual(list(fields.keys())[0], "country_id") + self.assertEqual(list(fields.keys())[1], "name") + self.assertEqual(list(fields.keys())[2], "custom") + + def test_form_fields_get(self): + form = self.get_form("cms.form.res.partner") + fields = form.form_fields_get() + # must include ONLY non tech fields and model fields + expected = ["name", "country_id", "custom"] + self.assertEqual(sorted(fields.keys()), sorted(expected)) + + def test_fields_attributes(self): + form = self.get_form("cms.form.res.partner") + fields = form.form_fields_get() + # this one is required in partner model + self.assertTrue(fields["name"]["required"]) + # this one is forced to required in our custom form + self.assertTrue(fields["country_id"]["required"]) + + def test_fields_defaults(self): + form = self.get_form("cms.form.res.partner") + self.env["ir.default"].set("res.partner", "name", "DEFAULT NAME") + fields = form.form_fields_get() + self.assertEqual(fields["name"]["_default"], "DEFAULT NAME") + self.assertEqual(fields["custom"]["_default"], "I am your default") + + def test_fields_hidden(self): + form = self.get_form("cms.form.res.partner", form_fields_hidden=("country_id",)) + # get all fields (default) + fields = form.form_fields_get() + self.assertListEqual(sorted(fields.keys()), ["country_id", "custom", "name"]) + # country is flagged as hidden + self.assertTrue(fields["country_id"]["hidden"]) + # get only visible + fields = form.form_fields_get(hidden=False) + self.assertListEqual(sorted(fields.keys()), ["custom", "name"]) + # get only hidden + fields = form.form_fields_get(hidden=True) + self.assertListEqual(sorted(fields.keys()), ["country_id"]) + self.assertTrue(fields["country_id"]["hidden"]) + + def test_fields_hidden_keep_order(self): + form = self.get_form( + "cms.form.res.partner", + form_fields_hidden=("country_id",), + form_fields_order=["country_id", "name", "custom"], + ) + fields = form.form_fields_get(hidden=False) + self.assertListEqual(list(fields.keys()), ["name", "custom"]) + form = self.get_form( + "cms.form.res.partner", + form_fields_hidden=("country_id",), + form_fields_order=["country_id", "custom", "name"], + ) + fields = form.form_fields_get(hidden=False) + self.assertListEqual(list(fields.keys()), ["custom", "name"]) + + def test_subfields(self): + form = self.get_form( + "cms.form.res.partner", + form_sub_fields={ + "name": {"_all": ("custom",)}, + "do_not_exists": {"_all": ("foo",)}, # skipped + }, + ) + fields = form.form_fields_get() + self.assertEqual( + fields["name"]["subfields"], {"_all": {"custom": fields["custom"]}} + ) + self.assertTrue(fields["custom"]["is_subfield"]) + + def test_fields_binary(self): + form = self.get_form( + "cms.form.res.partner", form_model_fields=["name", "image_1024"] + ) + self.assertEqual(list(form.form_file_fields.keys()), ["image_1024"]) + + def test_fields_protected(self): + group = self.env.ref("base.group_system") + user = self.env.ref("base.user_demo") + # user does not have the group + self.assertNotIn(group, user.groups_id) + form = self.get_form("cms.form.protected.fields", sudo_uid=user.id) + fields = form.form_fields_get() + # field is skipped + self.assertEqual(list(fields.keys()), ["nogroup"]) + # now add the group + user.write({"groups_id": [(4, group.id)]}) + fields = form.form_fields_get() + # now we get protected field too + self.assertEqual(sorted(fields.keys()), sorted(["ihaveagroup", "nogroup"])) + + def test_get_loader(self): + form = self.get_form("cms.form.test_fields") + expected = {}.fromkeys( + ( + "a_char", + "a_number", + "a_float", + "a_many2one", + "a_one2many", + "a_many2many", + ), + None, + ) + fields = form.form_fields_get() + for fname, loader in expected.items(): + self.assertEqual(loader, form.form_get_loader(fname, fields[fname])) + + def custom_loader(self, *pa, **ka): + return pa, ka + + with ( + mock.patch.object( + type(form), "_form_load_char", custom_loader, create=True + ), + mock.patch.object( + type(form), "_form_load_integer", custom_loader, create=True + ), + mock.patch.object( + type(form), "_form_load_float", custom_loader, create=True + ), + mock.patch.object( + type(form), "_form_load_a_many2many", custom_loader, create=True + ), + ): + for fname in ("a_char", "a_number", "a_float", "a_many2many"): + self.assertEqual( + custom_loader.__name__, + form.form_get_loader(fname, fields[fname]).__name__, + ) + + def test_get_extractor(self): + form = self.get_form("cms.form.test_fields") + expected = {}.fromkeys( + ( + "a_char", + "a_number", + "a_float", + "a_many2one", + "a_one2many", + "a_many2many", + ), + None, + ) + fields = form.form_fields_get() + for fname, loader in expected.items(): + self.assertEqual(loader, form.form_get_extractor(fname, fields[fname])) + + def custom_extractor(self, *pa, **ka): + return pa, ka + + with ( + mock.patch.object( + type(form), "_form_extract_char", custom_extractor, create=True + ), + mock.patch.object( + type(form), "_form_extract_integer", custom_extractor, create=True + ), + mock.patch.object( + type(form), "_form_extract_integer", custom_extractor, create=True + ), + mock.patch.object( + type(form), "_form_extract_float", custom_extractor, create=True + ), + mock.patch.object( + type(form), "_form_extract_a_many2many", custom_extractor, create=True + ), + ): + for fname in ("a_char", "a_number", "a_float", "a_many2many"): + self.assertEqual( + custom_extractor.__name__, + form.form_get_extractor(fname, fields[fname]).__name__, + ) + + def test_load_defaults(self): + # create mode, no main_object + main_object = None + form = self.get_form("cms.form.res.partner", main_object=main_object) + defaults = form.form_load_defaults() + expected = {"name": None, "country_id": None, "custom": "oh yeah!"} + for k, v in expected.items(): + self.assertEqual(defaults[k], v) + + # write mode, have main_object + main_object = self.env["res.partner"].new({}) + main_object.name = "John Wayne" + main_object.country_id = 5 + form = self.get_form("cms.form.res.partner", main_object=main_object) + defaults = form.form_load_defaults() + expected = { + "name": "John Wayne", + "country_id": 5, + "custom": "oh yeah!", + } + for k, v in expected.items(): + self.assertEqual(defaults[k], v) + + # values from request + data = {"name": "Paul Newman", "country_id": 7, "custom": "yay!"} + request = fake_request(form_data=data) + form = self.get_form( + "cms.form.res.partner", req=request, main_object=main_object + ) + defaults = form.form_load_defaults() + for k, v in data.items(): + self.assertEqual(defaults[k], v) + + # TODO: test marshallers integration + def test_extract_from_request(self): + form = self.get_form("cms.form.test_fields") + # values from request + data = { + "a_char": "Jack White", + "a_number": "10", + "a_float": "5", + "a_many2one": "123", + "a_many2many": "1,2,3", + "a_one2many": "4,5,6", + } + request = fake_request(form_data=data) + # write mode + form = self.get_form("cms.form.test_fields", req=request) + values = form.form_extract_values() + expected = { + "a_char": "Jack White", + "a_number": 10, + "a_float": 5.0, + "a_many2one": 123, + "a_many2many": [(6, False, [1, 2, 3])], + "a_one2many": [(6, False, [4, 5, 6])], + } + for k, v in values.items(): + self.assertEqual(expected[k], v) + # read mode + form = self.get_form( + "cms.form.test_fields", req=request, form_extract_value_mode="read" + ) + values = form.form_extract_values() + expected.update({"a_many2many": [1, 2, 3], "a_one2many": [4, 5, 6]}) + for k, v in values.items(): + self.assertEqual(expected[k], v) + + def test_extract_from_request_no_value(self): + """If you submit no value for a field it gets ignored.""" + form = self.get_form("cms.form.test_fields") + # values from request + data = { + # not convertable value -> we'll get None + "a_float": "5/0", + "a_number": "10A", + } + request = fake_request(form_data=data) + form = self.get_form("cms.form.test_fields", req=request) + values = form.form_extract_values() + # these are converted to `None` and ignored + for fname in [ + "a_char", + "a_number", + "a_float", + "a_many2one", + ]: + self.assertNotIn(fname, values) + # special case: when you don't submit a value form x2m we wipe it + for fname in [ + "a_many2many", + "a_one2many", + ]: + self.assertEqual(values[fname], [(5,)]) + + def test_extract_from_request_custom_extractor(self): + # test custom extractor integration w/ form_extract_values + # values from request + data = { + "a_char": "Jack White", + "a_number": "10", + "a_float": "5", + "a_many2one": "123", + "a_many2many": "1,2,3", + "a_one2many": "4,5,6", + } + request = fake_request(form_data=data) + # write mode + form = self.get_form("cms.form.test_fields", req=request) + + def custom_extractor(self, form, fname, value, **request_values): + return "custom for: " + fname + + # by type + with ( + mock.patch.object( + type(form), "_form_extract_a_char", custom_extractor, create=True + ), + mock.patch.object( + type(form), "_form_extract_a_number", custom_extractor, create=True + ), + ): + values = form.form_extract_values() + expected = { + "a_char": "custom for: a_char", + "a_number": "custom for: a_number", + "a_float": 5.0, + "a_many2one": 123, + "a_many2many": [(6, False, [1, 2, 3])], + "a_one2many": [(6, False, [4, 5, 6])], + } + for k, v in values.items(): + self.assertEqual(expected[k], v) + + def test_form_process_GET(self): + form = self.get_form("cms.form.test_fields") + self.assertEqual( + form.form_render_values, + { + "main_object": None, + "form": form, + "errors": {}, + "errors_messages": {}, + }, + ) + with mock.patch.object(type(form), "form_process_GET") as handler: + handler.return_value = { + "extra_key1": "foo", + "extra_key2": "baz", + } + form.form_process() + # the right process method has been called + handler.assert_called() + # and got called w/ the right render values + default_form_data = { + "a_char": None, + "a_float": None, + "a_many2many": "[]", + "a_many2one": None, + "a_number": None, + "a_one2many": "[]", + } + self.assertEqual(form.form_data, default_form_data) + expected = { + "main_object": None, + "form": form, + "errors": {}, + "errors_messages": {}, + } + handler.assert_called_with(expected) + # and the result of the handler is injected into render values + self.assertEqual( + form.form_render_values, + { + # extra args have been injected + "extra_key1": "foo", + "extra_key2": "baz", + "main_object": None, + "form": form, + "errors": {}, + "errors_messages": {}, + }, + ) + + def test_form_process_POST(self): + request = fake_request(method="POST") + form = self.get_form("cms.form.test_fields", req=request) + with mock.patch.object(type(form), "form_process_POST") as handler: + handler.return_value = { + "extra_key3": "foo", + "extra_key4": "baz", + } + form.form_process() + # the right process method has been called + handler.assert_called() + # and got called w/ the right render values + default_form_data = { + "a_char": None, + "a_float": None, + "a_many2many": "[]", + "a_many2one": None, + "a_number": None, + "a_one2many": "[]", + } + self.assertEqual(form.form_data, default_form_data) + expected = { + "main_object": None, + "form": form, + "errors": {}, + "errors_messages": {}, + } + handler.assert_called_with(expected) + # self.assert_nested_dict_equal(call_args, expected) + # and the result of the handler is injected into render values + self.assertEqual( + form.form_render_values, + { + # extra args have been injected + "extra_key3": "foo", + "extra_key4": "baz", + "main_object": None, + "form": form, + "errors": {}, + "errors_messages": {}, + }, + ) + + def test_get_widget(self): + form = self.get_form("cms.form.test_fields2") + expected = { + "a_char": "cms.form.widget.char", + "a_number": "cms.form.widget.integer", + "a_float": "cms.form.widget.float", + "a_many2one": "cms.form.widget.many2one", + "a_many2many": "cms.form.widget.many2many", + "a_one2many": "cms.form.widget.one2many", + "a_float_with_another_widget": self.FakeFloatWidget._name, + } + fields = form.form_fields_get() + for fname, widget_model in expected.items(): + self.assertEqual( + form.form_get_widget(fname, fields[fname]).__class__, + self.env[widget_model].__class__, + ) + + def test_wrapper_css_klass(self): + form = self.get_form("cms.form.res.partner") + expected = "cms_form_wrapper cms_form_res_partner " "res_partner mode_create" + self.assertEqual(form.form_wrapper_css_klass, expected) + form.form_wrapper_extra_css_klass = "foo" + expected = ( + "cms_form_wrapper cms_form_res_partner " "res_partner foo mode_create" + ) + self.assertEqual(form.form_wrapper_css_klass, expected) + + def test_css_klass(self): + form = self.get_form("cms.form.res.partner") + self.assertEqual(form.form_css_klass, "form-horizontal") + form.form_extra_css_klass = "cool" + self.assertEqual(form.form_css_klass, "form-horizontal cool") + form.form_display_mode = "vertical" + self.assertEqual(form.form_css_klass, "form-vertical cool") + + def test_field_wrapper_css_klass(self): + form = self.get_form("cms.form.res.partner") + field = form.form_fields_get()["custom"] + self.assertEqual( + form.form_make_field_wrapper_klass("custom", field), + "form-group form-field field-char field-custom", + ) + field = form.form_fields_get()["country_id"] + self.assertEqual( + form.form_make_field_wrapper_klass("country_id", field), + ("form-group form-field field-many2one " "field-country_id field-required"), + ) + field = form.form_fields_get()["custom"] + field["required"] = True + self.assertEqual( + form.form_make_field_wrapper_klass( + "custom", + field, + errors={"custom": "bad_value"}, + ), + ( + "form-group form-field field-char " + "field-custom field-required has-error" + ), + ) + + def test_info_merge_call(self): + form = self.get_form("cms.form.res.partner") + with mock.patch( + "odoo.addons.cms_form" ".models.cms_form_mixin.utils.data_merge" + ) as mocked: + form._form_info_merge({"a": "1"}, {"b": "2"}) + mocked.assert_called_with({"a": "1"}, {"b": "2"}) diff --git a/cms_form/tests/test_form_cms.py b/cms_form/tests/test_form_cms.py new file mode 100644 index 000000000..8e09a9902 --- /dev/null +++ b/cms_form/tests/test_form_cms.py @@ -0,0 +1,233 @@ +# Copyright 2017 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from unittest import mock + +from odoo import exceptions +from odoo.tools import mute_logger + +from .common import FormTestCase +from .utils import fake_request, mock_request + + +class TestCMSForm(FormTestCase): + @staticmethod + def _get_test_models(): + from .fake_models.fake_fields_form import FakeFieldsForm + from .fake_models.fake_partner_channel_form import ( + FakePartnerRelatedForm, + FakePartnerRelModel, + ) + from .fake_models.fake_partner_form import FakePartnerForm + from .fake_models.fake_pub_model_form import FakePubModel, FakePubModelForm + + return ( + FakePartnerForm, + FakeFieldsForm, + FakePartnerRelatedForm, + FakePartnerRelModel, + FakePubModel, + FakePubModelForm, + ) + + def test_form_base_attrs(self): + form = self.get_form("cms.form.test_fields") + # no form model, no default title + self.assertEqual(form.form_title, "") + form = self.get_form("cms.form.res.partner") + # form model present, title now depends on mode and model descr + # no record to edit, create mode on + self.assertEqual(form.form_mode, "create") + self.assertEqual(form.form_title, "Create Contact") + # now edit a record + partner = self.env.ref("base.main_partner") + form = self.get_form("cms.form.res.partner", main_object=partner) + self.assertEqual(form.form_mode, "edit") + self.assertEqual(form.form_title, 'Edit "%s"' % partner.name) + # now edit a record that has a m2o as rec name + partner.name = "Johnny" + partner_rel = self.env[self.FakePartnerRelModel._name].create( + {"partner_id": partner.id} + ) + form = self.get_form(self.FakePartnerRelatedForm._name, main_object=partner_rel) + self.assertEqual(form.form_title, 'Edit "Johnny"') + + def test_form_special_attrs_getter_setter(self): + form = self.get_form("cms.form.test_fields") + # submit success flag + self.assertFalse(form.form_success) + form.form_success = True + self.assertTrue(form.form_success) + # submit redirect flag + self.assertFalse(form.form_redirect) + form.form_redirect = True + self.assertTrue(form.form_redirect) + + def test_next_url(self): + # no record, no redirrect param, default to root + form = self.get_form("cms.form.res.partner") + self.assertEqual(form.form_next_url(), "/") + # redirect param in request + request = fake_request(query_string="redirect=/foo") + form = self.get_form("cms.form.res.partner", req=request) + self.assertEqual(form.form_next_url(), "/foo") + # edit a record: get to its ws URL + record = self.env["fake.publishable"].create({"name": "Baz"}) + form = self.get_form("cms.form.fake.publishable", main_object=record) + self.assertEqual( + form.form_next_url(), "/cms/view/fake.publishable/%d" % record.id + ) + # edit a record that has an URL but got redirect in request + request = fake_request(query_string="redirect=/sorry/go/here") + form = self.get_form( + "cms.form.fake.publishable", + req=request, + main_object=record, + ) + self.assertEqual(form.form_next_url(), "/sorry/go/here") + + def test_cancel_url(self): + # no record, no redirrect param, default to root + form = self.get_form("cms.form.res.partner") + self.assertEqual(form.form_cancel_url(), "/") + # redirect param in request + request = fake_request(query_string="redirect=/foo") + form = self.get_form("cms.form.res.partner", req=request) + self.assertEqual(form.form_cancel_url(), "/foo") + # edit a record: get to its ws URL + record = self.env["fake.publishable"].create({"name": "Baz"}) + form = self.get_form("cms.form.fake.publishable", main_object=record) + self.assertEqual( + form.form_cancel_url(), "/cms/view/fake.publishable/%d" % record.id + ) + # edit a record that has an URL but got redirect in request + request = fake_request(query_string="redirect=/sorry/go/here") + form = self.get_form( + "cms.form.fake.publishable", + req=request, + main_object=record, + ) + self.assertEqual(form.form_cancel_url(), "/sorry/go/here") + + def test_validate(self): + form = self.get_form("cms.form.test_fields") + # values from request + data = { + "a_char": "Foo", + "a_number": "10", + "a_float": "5", + "a_many2one": "", + "a_many2many": "", + } + request = fake_request(form_data=data) + required = ("a_many2one", "a_many2many") + form = self.get_form( + "cms.form.test_fields", req=request, form_required_fields=required + ) + errors, errors_message = form.form_validate() + self.assertEqual( + errors, + { + "a_char": True, + "a_float": True, + "a_many2many": "missing", + "a_many2one": "missing", + }, + ) + self.assertEqual( + errors_message, + { + "a_char": "Text length must be greater than 8!", + "a_float": "Must be greater than 5!", + }, + ) + + def test_create_or_update(self): + # create + data = { + "name": "Edward Norton", + } + request = fake_request(form_data=data, method="POST") + form = self.get_form( + "cms.form.res.partner", req=request, form_required_fields=("name",) + ) + form.form_process() + main_object = form.main_object + self.assertEqual(main_object._name, "res.partner") + self.assertEqual(main_object.name, data["name"]) + # update + data = { + "name": "Edward Flip", + "country_id": 1, + "custom": "foo", + } + request = fake_request(form_data=data, method="POST") + form = self.get_form( + "cms.form.res.partner", + req=request, + main_object=main_object, + form_required_fields=("name",), + ) + form.form_process() + self.assertEqual(main_object.name, data["name"]) + self.assertEqual(main_object.country_id.id, data["country_id"]) + + def test_create_or_update_with_errors(self): + request = fake_request(form_data={}, method="POST") + form = self.get_form("cms.form.res.partner", req=request) + with mute_logger("odoo.sql_db"), mock_request( + self.env, httprequest=request.httprequest + ): + values = form.form_process_POST({}) + self.assertFalse(form.form_success) + self.assertTrue( + # custom modules can provide different errors for constraints + "_integrity" in values["errors"] + or "_validation" in values["errors"] + ) + with mock.patch.object( + type(form), "form_create_or_update" + ) as mocked, mock_request(self.env, httprequest=request.httprequest): + random_msg = ( + "Error while validating constraint\n" + "\nEnd Date cannot be set before Start Date.\nNone" + ) + mocked.side_effect = exceptions.ValidationError(random_msg) + values = form.form_process_POST({}) + # validation error flag + self.assertEqual(values["errors"], {"_validation": True}) + # formatted error message + self.assertEqual( + values["errors_message"], + { + "_validation": "Error while validating constraint" + "
" + "End Date cannot be set before Start Date." + }, + ) + + def test_purge_non_model_fields_no_model(self): + form = self.get_form("cms.form.test_fields") + self.assertEqual(form._form_purge_non_model_fields({}), {}) + + def test_purge_non_model_fields(self): + data = { + "name": "Johnny Glamour", + "custom": "Remove me from write and create, tnx!", + } + request = fake_request(form_data=data, method="POST") + form = self.get_form("cms.form.res.partner", req=request) + to_patch = "odoo.addons.cms_form.models.cms_form.CMSForm._form_create" + with mock.patch(to_patch) as patched: + form.form_create_or_update() + patched.assert_called_with({"name": "Johnny Glamour"}) + + main_object = self.env["res.partner"].create({"name": "Update Me"}) + request = fake_request(form_data=data, method="POST") + form = self.get_form( + "cms.form.res.partner", main_object=main_object, req=request + ) + to_patch = "odoo.addons.cms_form.models.cms_form.CMSForm._form_write" + with mock.patch(to_patch) as patched: + form.form_create_or_update() + patched.assert_called_with({"name": "Johnny Glamour"}) diff --git a/cms_form/tests/test_form_permission.py b/cms_form/tests/test_form_permission.py new file mode 100644 index 000000000..a16457b4e --- /dev/null +++ b/cms_form/tests/test_form_permission.py @@ -0,0 +1,109 @@ +# Copyright 2017 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from unittest import mock + +from odoo import exceptions + +from .common import FormTestCase + + +class TestFormPermCheck(FormTestCase): + @staticmethod + def _get_test_models(): + from .fake_models.fake_nonpub_model_form import ( + FakeNonPubModel, + FakeNonPubModelForm, + ) + from .fake_models.fake_partner_form import FakePartnerForm + from .fake_models.fake_pub_model_form import FakePubModel, FakePubModelForm + + return ( + FakeNonPubModel, + FakeNonPubModelForm, + FakePartnerForm, + FakePubModel, + FakePubModelForm, + ) + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.record = cls.env[cls.FakePubModel._name].create({"name": "Foo"}) + + @classmethod + def tearDownClass(cls): + cls.record.unlink() + super().tearDownClass() + + mixin_path = "odoo.addons.cms_info.models.cms_mixin.CMSInfoMixin" + + def test_form_check_permission_can_create(self): + form = self.get_form(self.FakePubModelForm._name, main_object=None) + with mock.patch(self.mixin_path + ".cms_can_create") as patched: + patched.return_value = True + self.assertTrue(form.form_check_permission()) + patched.assert_called() + + def test_form_check_permission_cannot_create(self): + form = self.get_form(self.FakePubModelForm._name, main_object=None) + with mock.patch(self.mixin_path + ".cms_can_create") as patched: + patched.return_value = False + try: + form.form_check_permission() + except exceptions.AccessError as err: + patched.assert_called() + msg = ( + "You are not allowed to create any record " "for the model `%s`." + ) % self.FakePubModel._name + self.assertEqual(err.args[0], msg) + + def test_form_check_permission_can_edit(self): + form = self.get_form(self.FakePubModelForm._name, main_object=self.record) + with mock.patch(self.mixin_path + ".cms_can_edit") as patched: + patched.return_value = True + self.assertTrue(form.form_check_permission()) + patched.assert_called() + + def test_form_check_permission_cannot_edit(self): + form = self.get_form(self.FakePubModelForm._name, main_object=self.record) + with mock.patch(self.mixin_path + ".cms_can_edit") as patched: + patched.return_value = False + try: + form.form_check_permission() + except exceptions.AccessError as err: + patched.assert_called() + msg = ("You cannot edit this record. Model: %s, ID: %d.") % ( + self.record._name, + self.record.id, + ) + self.assertEqual(err.args[0], msg) + + def test_form_check_permission_no_ws_mixin_can_create(self): + form = self.get_form(self.FakeNonPubModelForm._name, main_object=None) + self.assertTrue(form.form_check_permission()) + + def test_form_check_permission_no_ws_mixin_can_edit(self): + rec = self.env[self.FakeNonPubModel._name].create({"name": "Foo"}) + form = self.get_form(self.FakeNonPubModelForm._name, main_object=rec) + self.assertTrue(form.form_check_permission()) + + def test_form_check_permission_no_record_no_model_can_edit_create(self): + form = self.get_form(self.FakePartnerForm._name, main_object=None) + form.form_model_name = None + self.assertTrue(form._can_edit()) + self.assertTrue(form._can_create()) + + def test_form_check_permission_form_cannot_edit(self): + form = self.get_form(self.FakePartnerForm._name) + with mock.patch.object( + type(self.record), "check_access_rights", spec=True + ) as patched: + patched.side_effect = exceptions.AccessError("boom") + with self.assertRaises(exceptions.AccessError): + # FIXME: for some reason entering this ctx manager and exiting + # wipes the main_object thus I have to set it twice + form.main_object = self.record + form._can_edit() + form.main_object = self.record + self.assertFalse(form._can_edit(raise_exception=False)) diff --git a/cms_form/tests/test_form_render.py b/cms_form/tests/test_form_render.py new file mode 100644 index 000000000..45705b8e8 --- /dev/null +++ b/cms_form/tests/test_form_render.py @@ -0,0 +1,124 @@ +# Copyright 2017 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from .common import FormRenderTestCase + + +class TestRender(FormRenderTestCase): + @staticmethod + def _get_test_models(): + from .fake_models.fake_fields_form import FakeFieldsForm + from .fake_models.fake_fields_form_fieldsets import FakeFieldsFormWithFieldsets + + return ( + FakeFieldsForm, + FakeFieldsFormWithFieldsets, + ) + + def test_render_form_attrs(self): + form = self.get_form("cms.form.test_fields") + html = form.form_render() + node = self.to_xml_node(html)[0] + self.assertEqual(node.tag, "form") + expected_attrs = { + "enctype": "multipart/form-data", + "method": "POST", + "class": "form-horizontal", + } + self.assert_match_attrs(node.attrib, expected_attrs) + + def test_render_form_fields_get(self): + form = self.get_form("cms.form.test_fields") + html = form.form_render() + node = self.to_xml_node(html)[0] + expected_fields = ( + "csrf_token", + "a_char", + "a_float", + "a_number", + "a_many2one", + "a_many2many", + "a_one2many", + ) + # all fields are rendered + self.assertEqual(len(node[0].xpath("//input|//select")), len(expected_fields)) + self.assert_match_inputs(node, expected_fields) + + def test_field_wrapper_attrs(self): + form = self.get_form("cms.form.test_fields") + form_fields = form.form_fields_get() + html = form.form_render() + node = self.to_xml_node(html)[0] + expected_fields = ( + "a_char", + "a_float", + "a_number", + "a_many2one", + "a_many2many", + "a_one2many", + ) + for fname in expected_fields: + fnode = self.find_input_name(node, fname)[0] + # catch 2nd one since the 1st is the main `form-fields` wrapper + fwrapper = fnode.xpath("ancestor::div[contains(@class, 'form-field')]")[1] + self.assertEqual( + fwrapper.attrib["class"], + form.form_make_field_wrapper_klass(fname, form_fields[fname]), + ) + + def test_render_form_fieldsets_get(self): + form = self.get_form("cms.form.test_fieldsets") + html = form.form_render() + node = self.to_xml_node(html)[0] + # we still get all the fields + expected_fields = ( + "csrf_token", + "a_char", + "ihaveagroup", + "a_float", + "a_number", + "a_many2one", + "a_many2many", + "a_one2many", + ) + # all fields are rendered + self.assertEqual(len(node[0].xpath("//input|//select")), len(expected_fields)) + self.assert_match_inputs(node, expected_fields) + # and they are organized by fieldset + for fset in form.form_fieldsets_get(): + fset_node = node.xpath('//fieldset[@id="%s"]' % fset["id"])[0] + if fset.get("title"): + legend_node = fset_node.find("legend") + self.assertEqual(legend_node.text, fset["title"]) + if fset.get("description"): + desc_node = fset_node.find('p[@class="fieldset-description"]') + self.assertEqual(desc_node.text, fset["description"]) + if fset.get("css_extra_klass"): + self.assertEqual(fset_node.attrib["class"], fset["css_extra_klass"]) + # check all fields are contained into it + self.assert_match_inputs(fset_node, fset["fields"]) + + def test_render_form_fieldsets_protected_field(self): + """No field, no fieldset.""" + user = self.env.ref("base.user_demo") + form = self.get_form("cms.form.test_fieldsets", sudo_uid=user.id) + html = form.form_render() + node = self.to_xml_node(html)[0] + # we still get all the fields + expected_fields = ( + "csrf_token", + "a_char", + "a_float", + "a_number", + "a_many2one", + "a_many2many", + "a_one2many", + ) + # all fields are rendered + self.assertEqual(len(node[0].xpath("//input|//select")), len(expected_fields)) + self.assert_match_inputs(node, expected_fields) + # protected field is not there + self.assertFalse(node[0].xpath('//input[@name="ihaveagroup"]')) + # also, since the field was the only one in the fieldset + # the fieldset as well should be gone + self.assertFalse(node[0].xpath('//fieldset[@id="protected"]')) diff --git a/cms_form/tests/test_form_search.py b/cms_form/tests/test_form_search.py new file mode 100644 index 000000000..6a7367bd6 --- /dev/null +++ b/cms_form/tests/test_form_search.py @@ -0,0 +1,257 @@ +# Copyright 2017 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from unittest import mock + +from .common import FormTestCase +from .utils import fake_request + + +class TestCMSSearchForm(FormTestCase): + @staticmethod + def _get_test_models(): + from .fake_models.fake_partner_form import FakePartnerForm + from .fake_models.fake_search_partner_form import ( + FakeSearchPartnerForm, + FakeSearchPartnerFormMulti, + ) + + return ( + FakePartnerForm, + FakeSearchPartnerForm, + FakeSearchPartnerFormMulti, + ) + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls._setup_records() + + def tearDown(self): + self.FakeSearchPartnerForm._set_test_record_ids([]) + super().tearDown() + + @classmethod + def _setup_records(cls): + cls.partner_model = cls.env["res.partner"].with_context(tracking_disable=True) + + cls.expected_partners = [] + cls.expected_partners_ids = [] + cls._expected_partners = ( + ( + "Salmo", + cls.env.ref("base.it").id, + ), + ( + "Marracash", + cls.env.ref("base.it").id, + ), + ( + "Notorious BIG", + cls.env.ref("base.us").id, + ), + ( + "Dr. Dre", + cls.env.ref("base.us").id, + ), + ( + "NTM", + cls.env.ref("base.fr").id, + ), + ) + + for name, country_id in cls._expected_partners: + cls.expected_partners_ids.append( + cls.partner_model.create({"name": name, "country_id": country_id}).id + ) + cls.expected_partners = cls.partner_model.browse(cls.expected_partners_ids) + + def assert_results(self, form, count, expected): + self.assertTrue("results" in form.form_search_results) + self.assertTrue("count" in form.form_search_results) + self.assertTrue("pager" in form.form_search_results) + self.assertEqual(len(form.form_search_results["results"]), count) + self.assertEqual(form.form_search_results["count"], count) + self.assertEqual( + sorted(form.form_search_results["results"].mapped("id")), + sorted(expected.mapped("id")), + ) + + def get_search_form(self, data, form_model="cms.form.search.res.partner", **kw): + request = fake_request(form_data=data) + form = self.get_form(form_model, req=request, **kw) + # restrict search results to these ids + self.FakeSearchPartnerForm._set_test_record_ids(self.expected_partners_ids) + return form + + def test_form_base_attrs(self): + form = self.get_search_form({}) + self.assertEqual(form.form_mode, "search") + self.assertEqual(form.form_title, "Search Contact") + + def test_search_domain(self): + form = self.get_search_form({}) + self.FakeSearchPartnerForm._set_test_record_ids([]) + form.form_search_domain_rules = { + "float_field": lambda field, value, search_values: ( + "float_field", + ">", + value, + ) + } + + def mock_fields(form): + return { + "char_field": {"type": "char"}, + "text_field": {"type": "text"}, + "int_field": {"type": "integer"}, + "float_field": {"type": "float"}, + "m2o_field": {"type": "many2one"}, + "bool_field": {"type": "boolean"}, + "date_field": {"type": "date"}, + "datetime_field": {"type": "date"}, + "o2m_field": {"type": "one2many"}, + "m2m_field": {"type": "many2many"}, + } + + with mock.patch.object(type(form), "form_fields_get", mock_fields): + search_values = { + "char_field": "foo", + "text_field": "", + "int_field": 2, + "float_field": 1.0, + "m2o_field": 3, + "bool_field": "on", + "date_field": "2019-01-26", + "datetime_field": "", + "o2m_field": [1, 2, 3], + "m2m_field": "", + } + expected = [ + ("char_field", "ilike", "%foo%"), + ("int_field", "=", 2), + ("float_field", ">", 1.0), + ("m2o_field", "=", 3), + ("bool_field", "=", True), + ("date_field", "=", "2019-01-26"), + ("o2m_field", "in", [1, 2, 3]), + ] + self.assertEqual( + sorted(form.form_search_domain(search_values)), + sorted(expected), + ) + + def test_search(self): + data = { + "name": "Salmo", + } + form = self.get_search_form(data) + form.form_process() + self.assert_results(form, 1, self.expected_partners[:1]) + + data = { + "name": "Marracash", + } + form = self.get_search_form(data) + form.form_process() + self.assert_results(form, 1, self.expected_partners[1:2]) + + data = { + "country_id": self.env.ref("base.it").id, + } + form = self.get_search_form(data) + form.form_process() + self.assert_results(form, 2, self.expected_partners[:2]) + + data = { + "country_id": self.env.ref("base.fr").id, + } + form = self.get_search_form(data) + form.form_process() + self.assert_results(form, 1, self.expected_partners[4:]) + + data = { + "name": "", + "country_id": self.env.ref("base.fr").id, + } + form = self.get_search_form(data) + form.form_process() + self.assert_results(form, 1, self.expected_partners[4:]) + + def test_search_no_result(self): + form = self.get_search_form({}, form_show_results_no_submit=False) + form.form_process() + self.assertEqual(form.form_search_results, {}) + + def test_pager_url(self): + data = { + "name": "Salmo", + } + form = self.get_search_form(data) + # value from rendering + render_values = {"extra_args": {"pager_url": "/foo"}} + self.assertEqual(form._form_get_url_for_pager(render_values), "/foo") + # value from model's `cms_search_url` if available + form = self.get_search_form(data) + # add fake cms_search_url on current model + # NOTE: when website_partner is installed this mocking is useless + # but we need it here just to demonstrate we are handling it + with mock.patch.object( + type(form.form_model), + "cms_search_url", + property(lambda x: "/cms/search/res.partner"), + create=True, + ): + self.assertEqual( + form._form_get_url_for_pager({}), "/cms/search/res.partner" + ) + # value from request path + request = fake_request(url="/search/custom/page/1?foo=baz") + form = self.get_form("cms.form.search.res.partner", req=request) + # on the contrary, here, we make sure `cms_search_url` is empty + # so that the default via request path is used + with mock.patch.object( + type(form.form_model), + "cms_search_url", + property(lambda x: ""), + create=True, + ): + self.assertEqual(form._form_get_url_for_pager({}), "/search/custom") + + def test_search_multi(self): + countries = [ + self.env.ref("base.it").id, + self.env.ref("base.fr").id, + ] + data = {"country_id": ",".join(map(str, countries))} + form = self.get_search_form( + data, form_model="cms.form.search.res.partner.multicountry" + ) + form.form_process() + expected = self.expected_partners.filtered( + lambda x: x.country_id.id in countries + ) + self.assert_results(form, 3, expected) + + def test_search_form_bypass_security_check(self): + form = self.get_search_form({}, sudo_uid=self.env.ref("base.public_user").id) + self.assertTrue(form.form_check_permission()) + + def test_search_custom_rules(self): + data = { + "country_id": "Italy", + } + form = self.get_search_form(data) + form.form_process() + # we find them all since domain is mocked to include all test partners + self.assert_results(form, 5, self.expected_partners) + # apply custom rules + data = { + "country_id": "Italy", + } + form = self.get_search_form( + data, + form_search_domain_rules={"country_id": ("country_id.name", "ilike", "")}, + ) + form.form_process() + self.assert_results(form, 2, self.expected_partners[:2]) diff --git a/cms_form/tests/test_form_wizard.py b/cms_form/tests/test_form_wizard.py new file mode 100644 index 000000000..4873b93fa --- /dev/null +++ b/cms_form/tests/test_form_wizard.py @@ -0,0 +1,181 @@ +# Copyright 2017 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from .common import FormSessionTestCase +from .utils import fake_request + +# TODO: add tests w/ real session +# to make sure there's no regression between versions + + +class TestCMSFormWizard(FormSessionTestCase): + @staticmethod + def _get_test_models(): + from .fake_models.fake_wizard_form import ( + FakeWiz, + FakeWizStep1Country, + FakeWizStep2Partner, + FakeWizStep3Partner, + ) + + return ( + FakeWiz, + FakeWizStep1Country, + FakeWizStep2Partner, + FakeWizStep3Partner, + ) + + def tearDown(self): + self.FakeWiz.FAKE_STORAGE.clear() + super().tearDown() + + def test_wiz_base_attrs(self): + form = self.get_form(self.FakeWizStep1Country._name) + # check wrapper class override + self.assertEqual( + form.form_wrapper_css_klass, + # wrapper klass, normalized wizard step model + "cms_form_wrapper fake_wiz_step1_country " + # normalized model, mode, normalized wizard model + "res_country mode_wizard fake_wiz", + ) + # check default storage key + self.assertEqual(form._wiz_storage_key, "fake.wiz") + # check default storage + self.assertEqual( + form._wiz_storage, + { + "fake.wiz": { + "steps": {1: {}, 2: {}, 3: {}}, + "current": 1, + "next": 2, + "prev": None, + } + }, + ) + self.assertEqual(form.wiz_steps, [1, 2, 3]) + + def test_wiz_use_session_by_default(self): + req = fake_request(session=self.session) + form = self.get_form("cms.form.wizard", req=req) + self.assertEqual(form._wiz_storage.__class__.__name__, "Session") + + def test_wiz_configure_steps(self): + form = self.get_form("cms.form.wizard") + self.assertEqual(form.wiz_configure_steps(), {}) + + def test_wiz_step_info(self): + form = self.get_form(self.FakeWizStep1Country._name) + with self.assertRaises(ValueError) as err: + form.wiz_get_step_info(100) + self.assertEqual(str(err.exception), "Step `100` does not exists.") + self.assertEqual( + form.wiz_get_step_info(1), {"form_model": "fake.wiz.step1.country"} + ) + self.assertEqual( + form.wiz_get_step_info(2), {"form_model": "fake.wiz.step2.partner"} + ) + self.assertEqual( + form.wiz_get_step_info(3), {"form_model": "fake.wiz.step3.partner"} + ) + + def test_wiz_save_step(self): + form = self.get_form(self.FakeWizStep1Country._name) + # no step passed, use current one (1) + form.wiz_save_step({"foo": "baz"}) + self.assertEqual(form.wiz_load_step(1), {"foo": "baz"}) + form.wiz_save_step({"boo": "waz"}, step=3) + self.assertEqual(form.wiz_load_step(3), {"boo": "waz"}) + # corner case whereas a step in the storage has been removed + form.wiz_storage_get()["steps"].pop(2) + form.wiz_save_step({"get": "back"}, step=2) + self.assertEqual(form.wiz_load_step(2), {"get": "back"}) + + def test_wiz_init(self): + form = self.get_form(self.FakeWizStep1Country._name) + self.assertEqual(form.wiz_storage_get()["current"], 1) + self.assertEqual(form.wiz_storage_get()["next"], 2) + self.assertEqual(form.wiz_storage_get()["prev"], None) + self.assertEqual(len(form.wiz_storage_get()["steps"]), 3) + + def test_wiz_init_from_another_page(self): + form = self.get_form(self.FakeWizStep1Country._name, page=2) + self.assertEqual(form.wiz_storage_get()["current"], 2) + self.assertEqual(form.wiz_storage_get()["next"], 3) + self.assertEqual(form.wiz_storage_get()["prev"], 1) + + def test_wiz_next_prev1(self): + form = self.get_form(self.FakeWizStep1Country._name) + self.assertEqual(form.wiz_prev_step(), None) + self.assertEqual(form.wiz_current_step(), 1) + self.assertEqual(form.wiz_next_step(), 2) + + def test_wiz_next_prev2(self): + form = self.get_form(self.FakeWizStep2Partner._name, page=2) + self.assertEqual(form.wiz_prev_step(), 1) + self.assertEqual(form.wiz_current_step(), 2) + self.assertEqual(form.wiz_next_step(), 3) + + def test_wiz_next_prev3(self): + form = self.get_form(self.FakeWizStep3Partner._name, page=3) + self.assertEqual(form.wiz_prev_step(), 2) + self.assertEqual(form.wiz_current_step(), 3) + self.assertEqual(form.wiz_next_step(), None) + + def test_wiz_next_prev_url1(self): + form = self.get_form(self.FakeWizStep1Country._name) + self.assertEqual(form.form_next_url(), "/cms/wiz/fake.wiz/page/2") + # simulate click on prev button + req = fake_request(form_data={"wiz_submit": "prev"}, method="POST") + form = self.get_form(self.FakeWizStep1Country._name, req=req) + # when step is none we default to initial one + self.assertEqual(form.form_next_url(), "/cms/wiz/fake.wiz/page/1") + + def test_wiz_next_prev_url2(self): + form = self.get_form(self.FakeWizStep2Partner._name, page=2) + self.assertEqual(form.form_next_url(), "/cms/wiz/fake.wiz/page/3") + req = fake_request(form_data={"wiz_submit": "prev"}, method="POST") + form = self.get_form(self.FakeWizStep2Partner._name, page=2, req=req) + self.assertEqual(form.form_next_url(), "/cms/wiz/fake.wiz/page/1") + + def test_wiz_next_prev_url3(self): + form = self.get_form(self.FakeWizStep3Partner._name, page=3) + # 3 it's the last step so next url defaults to initial one + self.assertEqual(form.form_next_url(), "/cms/wiz/fake.wiz/page/1") + req = fake_request(form_data={"wiz_submit": "prev"}, method="POST") + form = self.get_form(self.FakeWizStep2Partner._name, page=3, req=req) + self.assertEqual(form.form_next_url(), "/cms/wiz/fake.wiz/page/2") + + def test_wiz_stored_fields(self): + data = { + "name": "John Doe", + "to_be_stored": "Whatever", + } + req = fake_request(form_data=data, method="POST") + form = self.get_form( + self.FakeWizStep2Partner._name, + req=req, + ) + main_object = form.form_create_or_update() + self.assertEqual(main_object.name, "John Doe") + step_values = form.wiz_load_step() + self.assertDictEqual(step_values, {"to_be_stored": "Whatever"}) + + def test_wiz_stored_fields_all(self): + data = { + "name": "John Doe", + "to_be_stored": "Whatever", + } + req = fake_request(form_data=data, method="POST") + form = self.get_form( + self.FakeWizStep2Partner._name, + req=req, + form_step_store_all_fields=True, + form_step_stored_fields="[]", + ) + main_object = form.form_create_or_update() + self.assertEqual(main_object.name, "John Doe") + step_values = form.wiz_load_step() + self.assertDictEqual( + step_values, {"name": "John Doe", "to_be_stored": "Whatever"} + ) diff --git a/cms_form/tests/test_loaders.py b/cms_form/tests/test_loaders.py new file mode 100644 index 000000000..8b9cc3afe --- /dev/null +++ b/cms_form/tests/test_loaders.py @@ -0,0 +1,4 @@ +# Copyright 2017 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +# TODO diff --git a/cms_form/tests/test_marshallers.py b/cms_form/tests/test_marshallers.py new file mode 100644 index 000000000..339bb0167 --- /dev/null +++ b/cms_form/tests/test_marshallers.py @@ -0,0 +1,147 @@ +# Copyright 2018 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +import unittest + +from werkzeug.datastructures import FileMultiDict, Headers, MultiDict + +from .. import marshallers +from .utils import fake_file_from_request, file_as_stream + + +class TestMarshallers(unittest.TestCase): + def test_plain_values(self): + data = MultiDict([("a", "1"), ("b", "2"), ("c", "3")]) + marshalled = marshallers.marshal_request_values(data) + self.assertEqual(marshalled["a"], "1") + self.assertEqual(marshalled["b"], "2") + self.assertEqual(marshalled["c"], "3") + + def test_skip_csrf_token(self): + data = MultiDict([("csrf_token", "whatever")]) + marshalled = marshallers.marshal_request_values(data) + self.assertEqual(marshalled, {}) + + def test_marshal_list(self): + data = MultiDict( + [("a", "1"), ("b:list", "1"), ("b:list", "2"), ("b:list", "3"), ("c", "3")] + ) + marshalled = marshallers.marshal_request_values(data) + self.assertEqual(marshalled["a"], "1") + self.assertListEqual(marshalled["b"], ["1", "2", "3"]) + self.assertEqual(marshalled["c"], "3") + + def test_marshal_int(self): + data = MultiDict([("a", "1"), ("b:int", "2"), ("c:int", "3"), ("d:int", "bad")]) + marshalled = marshallers.marshal_request_values(data) + self.assertEqual(marshalled["a"], "1") + self.assertEqual(marshalled["b"], 2) + self.assertEqual(marshalled["c"], 3) + self.assertEqual(marshalled["d"], None) + + def test_marshal_float(self): + data = MultiDict( + [ + ("a", "1"), + ("b:float", "2"), + ("c:float", "3.0"), + ("d:float", "4,0"), + ("e:float", "bad"), + ] + ) + marshalled = marshallers.marshal_request_values(data) + self.assertEqual(marshalled["a"], "1") + self.assertEqual(marshalled["b"], 2.0) + self.assertEqual(marshalled["c"], 3.0) + self.assertEqual(marshalled["d"], 4.0) + self.assertEqual(marshalled["e"], None) + + def test_marshal_dict(self): + data = MultiDict( + [ + ("a", "1"), + ("b.x:dict", "1"), + ("b.y:dict", "2"), + ("b.z:dict", "3"), + ("c", "3"), + ] + ) + marshalled = marshallers.marshal_request_values(data) + self.assertEqual(marshalled["a"], "1") + self.assertDictEqual(marshalled["b"], {"x": "1", "y": "2", "z": "3"}) + self.assertEqual(marshalled["c"], "3") + + def test_marshal_esc(self): + data = MultiDict( + [ + ("a:esc", "I'm bad"), + ("b", "I'm bad but I don't care"), + ] + ) + marshalled = marshallers.marshal_request_values(data) + self.assertEqual(marshalled["a"], "<span>I'm bad</span>") + self.assertEqual(marshalled["b"], "I'm bad but I don't care") + self.assertNotIn("a:esc", marshalled) + + def test_marshal_dict_list(self): + data = MultiDict( + [ + ("a", "1"), + ("b.1.x:dict:list", "b1x"), + ("b.1.y:dict:list", "b1y"), + ("b.1.z:dict:list", "b1z"), + ("b.2.x:dict:list", "b2x"), + ("b.2.y:dict:list", "b2y"), + ("b.2.z:dict:list", "b2z"), + ("b.3.x:dict:list", "b3x"), + ("b.3.y:dict:list", "b3y"), + ("b.3.z:dict:list", "b3z"), + ("c", "3"), + ] + ) + marshalled = marshallers.marshal_request_values(data) + self.assertEqual(marshalled["a"], "1") + self.assertEqual( + marshalled["b"], + [ + {"x": "b1x", "y": "b1y", "z": "b1z"}, + {"x": "b2x", "y": "b2y", "z": "b2z"}, + {"x": "b3x", "y": "b3y", "z": "b3z"}, + ], + ) + self.assertEqual(marshalled["c"], "3") + self.assertNotIn("b.1.x:dict:list", marshalled) + self.assertNotIn("b.2.y:dict:list", marshalled) + + def test_marshal_file(self): + data = FileMultiDict() + content = b"a,b,c\n1,2,3\n4,5,6" + with file_as_stream(content) as stream: + data.add_file( + "one:file", + fake_file_from_request( + "one", + stream=stream, + filename="one.csv", + content_type="text/csv", + content_length=len(content), + ), + ) + marshalled = marshallers.marshal_request_values(data) + self.assertEqual( + marshalled["one"], + { + "_from_request": True, + "content_length": 17, + "content_type": "text/csv", + "filename": "one.csv", + "headers": Headers( + [("Content-Type", "text/csv"), ("Content-Length", "17")] + ), + "mimetype": "text/csv", + "mimetype_params": {}, + "raw_value": "YSxiLGMKMSwyLDMKNCw1LDY=", + "value": "YSxiLGMKMSwyLDMKNCw1LDY=", + }, + ) + self.assertNotIn("one:file", marshalled) diff --git a/cms_form/tests/test_utils.py b/cms_form/tests/test_utils.py new file mode 100644 index 000000000..1eac3b6a9 --- /dev/null +++ b/cms_form/tests/test_utils.py @@ -0,0 +1,59 @@ +# Copyright 2018 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +import unittest + +from .. import utils + + +class TestUtils(unittest.TestCase): + def test_safe_to_integer(self): + self.assertEqual(utils.safe_to_integer(""), None) + self.assertEqual(utils.safe_to_integer(False), 0) + self.assertEqual(utils.safe_to_integer("abc"), None) + self.assertEqual(utils.safe_to_integer("10.0"), None) + self.assertEqual(utils.safe_to_integer("0"), 0) + self.assertEqual(utils.safe_to_integer("10"), 10) + + def test_safe_to_float(self): + self.assertEqual(utils.safe_to_float(""), None) + self.assertEqual(utils.safe_to_float(False), 0.0) + self.assertEqual(utils.safe_to_float("abc"), None) + self.assertEqual(utils.safe_to_float("10,0"), 10.0) + self.assertEqual(utils.safe_to_float("10.0"), 10.0) + self.assertEqual(utils.safe_to_float("0"), 0.0) + self.assertEqual(utils.safe_to_float("10"), 10.0) + + def test_safe_to_date(self): + self.assertEqual(utils.safe_to_date(""), None) + self.assertEqual(utils.safe_to_date("2019-01-13"), "2019-01-13") + + def test_string_to_bool(self): + for val in ( + "on", + "yes", + "ok", + "true", + True, + 1, + "1", + ): + self.assertTrue(utils.string_to_bool(val)) + for val in ("", " ", "2", "no", "whatever is not true"): + self.assertFalse(utils.string_to_bool(val)) + + def test_data_merge(self): + a = {"a": 1, "b": {"foo": "bar"}, "d": [1, 2], "e": [5, 6]} + b = {"a": 2, "b": {"baz": "taz"}, "c": "yo", "d": [3, 4], "e": 8} + expected = { + "a": 2, + "b": {"foo": "bar", "baz": "taz"}, + "c": "yo", + "d": [1, 2, 3, 4], + "e": [5, 6, 8], + } + self.assertEqual(utils.data_merge(a, b), expected) + with self.assertRaises(ValueError): + utils.data_merge({"a": {"x": 1, "y": 2}}, {"a": ["not", "compat"]}) + with self.assertRaises(NotImplementedError): + utils.data_merge({"a": {1, 2, 3}}, {"a": {4}}) diff --git a/cms_form/tests/utils.py b/cms_form/tests/utils.py new file mode 100644 index 000000000..cccd64791 --- /dev/null +++ b/cms_form/tests/utils.py @@ -0,0 +1,127 @@ +# Copyright 2017 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +import base64 +import io +import urllib.parse +from contextlib import contextmanager +from unittest import mock + +from werkzeug.datastructures import FileStorage +from werkzeug.wrappers import Request + +from odoo import api, http +from odoo.tests.common import get_db_name +from odoo.tools import DotDict +from odoo.tools._vendor.sessions import SessionStore + +from odoo.addons.website.tools import MockRequest + + +def fake_request( + form_data=None, + query_string=None, + url="/fake/path", + method="GET", + content_type=None, + session=None, +): + data = urllib.parse.urlencode(form_data or {}) + content_type = content_type or "application/x-www-form-urlencoded" + # werkzeug request + w_req = Request.from_values( + url, + query_string=query_string, + content_length=len(data), + input_stream=io.StringIO(data), + content_type=content_type, + method=method, + ) + # odoo request + o_req = http.Request(w_req) + o_req.csrf_token = mock.MagicMock() + o_req.httprequest = w_req + o_req.session = session if session is not None else mock.MagicMock() + o_req.__testing__ = True + return o_req + + +@contextmanager +def mock_request( + env, + request=None, + httprequest=None, + extra_headers=None, + request_attrs=None, + httprequest_attrs=None, + **kw +): + # TODO: refactor this ctx mngr from website to: + # - make it independent + # - use real request and session as per fake_request above + with MockRequest(env, **kw) as mocked_request: + if httprequest: + if isinstance(httprequest, dict): + httprequest = DotDict(httprequest) + mocked_request.httprequest = httprequest + headers = {} + headers.update(extra_headers or {}) + mocked_request.httprequest.headers = headers + request_attrs = request_attrs or {} + for k, v in request_attrs.items(): + setattr(mocked_request, k, v) + httprequest_attrs = httprequest_attrs or {} + for k in ("args", "form", "files", "_cms_form_files_processed"): + if k not in httprequest_attrs: + httprequest_attrs[k] = {} + for k, v in httprequest_attrs.items(): + setattr(mocked_request.httprequest, k, v) + mocked_request.make_response = lambda data, **kw: data + mocked_request.registry._init_modules = set() + mocked_request.session.touch = lambda: True + yield mocked_request + + +class FakeSessionStore(SessionStore): + def delete(self, session): + session.clear() + del session + + +session_store = FakeSessionStore(session_class=http.Session) + + +def fake_session(env, **kw): + db = get_db_name() + env = api.Environment(env.cr, env.uid, {}) + session = session_store.new() + session.db = db + session.uid = env.uid + session.login = env.user.login + session.password = "" + session.context = dict(env.context) + session.context["uid"] = env.uid + for k, v in kw.items(): + if hasattr(session, k): + setattr(session, k, v) + session.__testing__ = True + return session + + +@contextmanager +def file_as_stream(content): + stream = io.BytesIO() + stream.write(content) + stream.seek(0) + yield stream + stream.close() + + +@contextmanager +def b64_as_stream(b64_content): + with file_as_stream(base64.b64decode(b64_content)) as stream: + yield stream + + +def fake_file_from_request(input_name, stream, **kw): + return FileStorage(name=input_name, stream=stream, **kw) diff --git a/cms_form/tests/widgets/__init__.py b/cms_form/tests/widgets/__init__.py new file mode 100644 index 000000000..e2c3a4cfa --- /dev/null +++ b/cms_form/tests/widgets/__init__.py @@ -0,0 +1,12 @@ +from . import test_widget_base +from . import test_widget_text +from . import test_widget_hidden +from . import test_widget_integer +from . import test_widget_float +from . import test_widget_selection +from . import test_widget_radio +from . import test_widget_boolean +from . import test_widget_date +from . import test_widget_many2one +from . import test_widget_x2many +from . import test_widget_binary diff --git a/cms_form/tests/widgets/common.py b/cms_form/tests/widgets/common.py new file mode 100644 index 000000000..4f97b2f9f --- /dev/null +++ b/cms_form/tests/widgets/common.py @@ -0,0 +1,83 @@ +# Copyright 2018 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + +from odoo.tests.common import TransactionCase + +from ..common import HTMLRenderMixin +from ..utils import fake_request + + +def fake_form(env, main_object=None, **data): + """Get a mocked fake form. + + :param data: kw args for setting form values + """ + form = env["cms.form"].form_init( + fake_request(), main_object=main_object, form_data=data + ) + return form + + +def fake_field(name, **kw): + """Get fake field specs `form_fields` compliant. + + :param name: field name + :param kw: kw args to override some values + """ + info = { + "type": "char", + "required": False, + "string": name.capitalize().replace("_", " "), + "readonly": False, + "help": "Help for %s" % name, + } + info.update(kw) + return name, info + + +def get_widget(env, fname, field, form=None, widget_model=None, **kw): + """Retrieve and initialize widget. + + :param fname: field name + :param field: field info matching `form_fields` schema + :param form: an instance of a cms_form + :param widget_model: if you don't pass a form you must pass a w model + """ + assert form or widget_model + if not form: + form = fake_form(env) + if not widget_model: + widget_model = form._form_get_default_widget_model(fname, field) + return env[widget_model].widget_init(form, fname, field, **kw) + + +class TestWidgetCase(TransactionCase, HTMLRenderMixin): + + at_install = False + post_install = True + + @classmethod + def get_widget(cls, fname, field, **kw): + return get_widget(cls.env, fname, field, **kw) + + def _test_widget_attributes(self, widget, tag, expected, text=None): + node = self.to_xml_node(widget.render())[0] + node_input = self.find_input_name(node, expected["name"]) + self.assertEqual(len(node_input), 1) + node_input = node_input[0] + self._test_element_attributes(node_input, tag, expected, text=text) + # return node for further testing + return node_input + + def _test_element_attributes(self, node, tag, expected, text=None): + self.assertEqual(node.tag, tag) + for attr_name, attr_value in expected.items(): + self.assertEqual(node.attrib[attr_name], attr_value) + # special attrs that should be set or not completely + for attr_name in ("required", "checked"): + if expected.get(attr_name): + self.assertIn(attr_name, node.attrib) + else: + self.assertNotIn(attr_name, node.attrib) + if text: + self.assertEqual(node.text.strip(), text) diff --git a/cms_form/tests/widgets/test_widget_base.py b/cms_form/tests/widgets/test_widget_base.py new file mode 100644 index 000000000..8cefeb10e --- /dev/null +++ b/cms_form/tests/widgets/test_widget_base.py @@ -0,0 +1,97 @@ +# Copyright 2018 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). +from ..common import FakeModelMixin, get_form +from .common import TestWidgetCase, fake_field + + +class TestWidgetBase(TestWidgetCase, FakeModelMixin): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls._setup_models(cls) + + @classmethod + def tearDownClass(cls): + cls._teardown_models(cls) + super().tearDownClass() + + @staticmethod + def _get_test_models(): + from ..fake_models.fake_partner_form import FakePartnerForm + + return (FakePartnerForm,) + + def test_widget_init(self): + form = get_form(self.env, "cms.form.res.partner") + field = form.form_fields_get()["custom"] + widget = self.get_widget("custom", field, form=form) + self.assertEqual(widget.w_form, form) + self.assertEqual(widget.w_form_model, form.form_model) + self.assertEqual(widget.w_record, form.main_object) + self.assertEqual(widget.w_form_values, form.form_data) + self.assertEqual(widget.w_fname, "custom") + self.assertDictEqual(widget.w_field, field) + self.assertEqual(widget.w_field_value, "oh yeah!") + self.assertEqual(widget.w_data, {}) + self.assertEqual(widget.w_subfields, {}) + + def test_w_load(self): + name, field = fake_field("foo") + widget = self.get_widget(name, field, widget_model="cms.form.widget.char") + # no value from default, nor from request, nor from record + self.assertEqual(widget.w_load(), None) + # get value from default. Default values from ORM + # are stored into internal key `_default` by `cms.form.mixin` + field["_default"] = "oh yeah" + widget = self.get_widget(name, field, widget_model="cms.form.widget.char") + self.assertEqual(widget.w_load(), "oh yeah") + # get value from request and it has precedence over default + self.assertEqual(widget.w_load(foo="daje"), "daje") + # get value from record (faked here) + widget.w_record = {"foo": "value coming from record"} + self.assertEqual(widget.w_load(), "value coming from record") + # still, value from request takes precedence + self.assertEqual( + widget.w_load(foo="I am more important"), "I am more important" + ) + + def test_w_extract(self): + name, field = fake_field("foo") + widget = self.get_widget(name, field, widget_model="cms.form.widget.char") + # no val in request + self.assertEqual(widget.w_extract(), None) + self.assertEqual(widget.w_extract(foo="yo!"), "yo!") + + def test_w_ids_from_input(self): + name, field = fake_field("foo") + widget = self.get_widget(name, field, widget_model="cms.form.widget.char") + self.assertEqual(widget.w_ids_from_input(""), []) + # not valid values are skipped + self.assertEqual( + widget.w_ids_from_input("1,2,3,#4, 70, 1XX, 200"), + [1, 2, 3, 70, 200], + ) + + def test_subfields_get(self): + form = get_form( + self.env, + "cms.form.res.partner", + form_sub_fields={"name": {"_all": ("custom",)}}, + ) + fields = form.form_fields_get() + widget = self.get_widget("name", fields["name"], form=form) + self.assertEqual(widget.w_subfields_by_value(), {"custom": fields["custom"]}) + + def test_html_name(self): + form = get_form(self.env, "cms.form.res.partner") + for fname, field in form.form_fields_get().items(): + widget = self.get_widget(fname, field, form=form) + self.assertEqual(widget.html_fname, fname) + form = get_form( + self.env, + "cms.form.res.partner", + form_fname_pattern="pre_{widget.w_fname}_post", + ) + for fname, field in form.form_fields_get().items(): + widget = self.get_widget(fname, field, form=form) + self.assertEqual(widget.html_fname, f"pre_{fname}_post") diff --git a/cms_form/tests/widgets/test_widget_binary.py b/cms_form/tests/widgets/test_widget_binary.py new file mode 100644 index 000000000..09be988b1 --- /dev/null +++ b/cms_form/tests/widgets/test_widget_binary.py @@ -0,0 +1,259 @@ +# Copyright 2019 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). +from odoo.addons.cms_form.marshallers import Marshaller # pylint: disable=W8150 +from odoo.addons.cms_form.tests.utils import ( # pylint: disable=W8150 + b64_as_stream, + fake_file_from_request, +) + +from .common import TestWidgetCase, fake_field, fake_form + +TEST_IMAGE_GIF = ( + "iVBORw0KGgoAAAANSUhEUgAAAA4AAAAPCAYAAADUFP50AAAABmJLR0QA/wD/AP+gvaeTAAAAC" + "XBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4wENCQc7YpV2jQAAAbtJREFUKM+dkr9rU3EUxT" + "/3fb/vvfxoTKIJtlpoiZGkqQriFgsuipvQWdEi/j39N1zcXZRgFwc3h4JDIEqjtZXSGny/37s" + "O0cEt9SwHLudwLude6fU7yn/A0aKgXK2RF1CvVeZDv8p6p0sYZWzcuEkaZly53KbVvkT5Qovm" + "ko8Meqv68P42yUoXHe3ydmpoDndo7+9x++oPlu4+xk0Nw3tNXu6+Zuv5MziZ4ORiqXXXGcgxr" + "/ZjACSPOJgeki1vwOGYnwl8OfhKmmWM9j7w7s0I6fU7miYJaMGt4QNWGh7jTx/5PD0iy4UkCv" + "A8lzwHYx2kyElVkO71NW00GsxmM9IkJi8Ua12sNf+UkaYFrufAnyodAFVFVXE9H9/3sdagOlf" + "85e2dHu5pxKOnd1grJcjm4Jr65SpFkREEIaVKhTgM8P0SURxTKfkEYUSeKdYz1DvLrB5PcJIs" + "RwTCMEJEiMMQEOI4RoAwihERrOuAKvor5CS1WFVFRBY+/Nm3U87E4BhjyNw6FwkWMoqAAI4qH" + "E3GbD15cb6X8z1LyYTMvjsU51jZSdIM09rEDd7P4xeE9Pod1TyjcCxm8UB+A5x0uaR5zMPmAA" + "AAAElFTkSuQmCC" +) + +TEST_IMAGE_JPG = ( + "/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMD" + "AsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFB" + "QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wgARCAA" + "KAAoDAREAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAgMEB//EABkBAAIDAQAAAAAAAAAA" + "AAAAAAMGAQIFCP/aAAwDAQACEAMQAAABz9M6UGapOvVB3f/EABkQAAMBAQEAAAAAAAAAAAAAA" + "AECAwQRI//aAAgBAQABBQKYZ6UBSgPvtHNn/8QAJxEAAQIEAgsAAAAAAAAAAAAAAQIEAAMRIU" + "FCBQYSFCIxUWFxsbL/2gAIAQMBAT8BdCW2bTOIitcSTyJtU9AbAiGLjbaSlVyjHt4jWBakuW6" + "QbFM74jRyRuUm2VPqP//EAB8RAAIBAgcAAAAAAAAAAAAAAAABAgMEERIiIzJhsf/aAAgBAgEB" + "PwGDzTWJUjrZbcW+16VXuSP/xAAcEAACAgIDAAAAAAAAAAAAAAABAgADESFxcrH/2gAIAQEAB" + "j8CUKN8RlbTA4Ilcv7n2f/EABwQAQACAQUAAAAAAAAAAAAAAAEAESFBUXGBkf/aAAgBAQABPy" + "G8BcDtiGrbbB1iAXhuzyAEKAwOU//aAAwDAQACAAMAAAAQpZ//xAAbEQEBAAMAAwAAAAAAAAA" + "AAAABEQAhMUFR4f/aAAgBAwEBPxBh1D3QEEqNBAiklwkg1dtdjqhX2oXuPXKYLGWU8x5eZy75" + "c//EAB8RAQABBAEFAAAAAAAAAAAAAAERACFBUWExgZGhsf/aAAgBAgEBPxBIIYTAHe3tnmozN" + "uDfDHi1AYVyknUy/a//xAAYEAEBAQEBAAAAAAAAAAAAAAABESEAMf/aAAgBAQABPxAVEtAlQU" + "RHUNG2bw1MmiKgmSI8QkJA4764GxsIAQA7/9k=" +) + + +class TestWidgetBinary(TestWidgetCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.partner = cls.env["res.partner"].search([], limit=1) + cls.maxDiff = None + + def setUp(self): + super().setUp() + self.form = fake_form(self.env, main_object=self.partner) + + # TODO: we have only an image widget ATM -> add a file widget and test it + def test_widget_binary_base(self): + w_name, w_field = fake_field( + "image", + type="binary", + ) + widget = self.get_widget( + w_name, + w_field, + form=self.form, + widget_model="cms.form.widget.image", + ) + node_items = self.to_xml_node(widget.render()) + self.assertEqual(len(node_items), 1) + node_wrapper = node_items[0] + expected_attrs = { + "class": "image-widget-wrapper", + } + self._test_element_attributes( + node_wrapper, + "div", + expected_attrs, + ) + # no existing value so we get only the input + self.assertEqual(len(node_wrapper.getchildren()), 1) + node_input = node_wrapper.getchildren()[0] + expected_attrs = { + "type": "file", + "id": "image", + "name": "image", + "class": "form-control", + "capture": "camera", + "accept": "image/*", + } + self._test_element_attributes( + node_input, + "input", + expected_attrs, + ) + + def test_widget_binary_load_from_record(self): + w_name, w_field = fake_field( + "image_1024", + type="binary", + ) + widget = self.get_widget( + w_name, + w_field, + form=self.form, + widget_model="cms.form.widget.image", + ) + # test conversion + self.assertEqual(widget.w_load(image_1024=False), False) + # set value on partner image + self.partner.image_1024 = TEST_IMAGE_GIF + self.assertEqual( + widget.w_load(), + { + "value": "data:image/png;base64,{}".format(TEST_IMAGE_GIF), + "raw_value": TEST_IMAGE_GIF, + "mimetype": "image/png", + "content_type": "image/png", + "content_lenght": len(TEST_IMAGE_GIF), + }, + ) + + def test_widget_binary_load_from_request(self): + w_name, w_field = fake_field( + "image_1024", + type="binary", + ) + widget = self.get_widget( + w_name, + w_field, + form=self.form, + widget_model="cms.form.widget.image", + ) + # test conversion + self.assertEqual(widget.w_load(image_1024=False), False) + + with b64_as_stream(TEST_IMAGE_JPG) as stream: + req_image = Marshaller._filedata_from_filestorage( + fake_file_from_request( + "image_1024", + stream=stream, + filename="foo.jpg", + content_type="image/jpeg", + ) + ) + res = widget.w_load(image_1024=req_image) + expected = dict( + req_image, value="data:image/jpg;base64,{}".format(TEST_IMAGE_JPG) + ) + for k, v in res.items(): + self.assertEqual(v, expected[k], f"{k} not matching") + + def test_widget_binary_extract_string(self): + w_name, w_field = fake_field( + "image", + type="binary", + ) + widget = self.get_widget( + w_name, + w_field, + form=self.form, + widget_model="cms.form.widget.image", + ) + + # no value in request -> None + self.assertEqual(widget.w_extract(), None) + # req value can come as string + req_val = "data:image/jpeg;base64,{}".format(TEST_IMAGE_JPG) + # value in request but no check flag -> None + self.assertEqual(widget.w_extract(image=req_val), TEST_IMAGE_JPG) + # value in request but keep flag is ON -> None + self.assertEqual(widget.w_extract(image=req_val, image_keepcheck="yes"), None) + # value in request but keep flag is ON -> None + self.assertEqual( + widget.w_extract(image=req_val, image_keepcheck="no"), + TEST_IMAGE_JPG, + ) + + def test_widget_binary_extract_filestorage(self): + w_name, w_field = fake_field( + "image", + type="binary", + ) + widget = self.get_widget( + w_name, + w_field, + form=self.form, + widget_model="cms.form.widget.image", + ) + + # value in request but no check flag -> None + with b64_as_stream(TEST_IMAGE_JPG) as stream: + req_image = Marshaller._filedata_from_filestorage( + fake_file_from_request( + "image", + stream=stream, + filename="foo.jpg", + content_type="image/jpeg", + ) + ) + self.assertEqual( + widget.w_extract(image=req_image, image_keepcheck="no"), + TEST_IMAGE_JPG, + ) + + def test_widget_binary_check_empty(self): + w_name, w_field = fake_field( + "image", + type="binary", + ) + widget = self.get_widget( + w_name, + w_field, + form=self.form, + widget_model="cms.form.widget.image", + ) + + # no value at all -> empty + self.assertIs(widget.w_check_empty_value(""), True) + self.assertIs(widget.w_check_empty_value(False), True) + # behavior w/ file value + with b64_as_stream(TEST_IMAGE_JPG) as stream: + req_val = fake_file_from_request( + "image", stream=stream, content_type="image/jpeg" + ) + # no filename -> empty + self.assertIs(widget.w_check_empty_value(req_val), True) + # no filename and keep flag -> empty, since we want to preserve + self.assertIs( + widget.w_check_empty_value(req_val, image_keepcheck="yes"), + False, + ) + req_val = fake_file_from_request( + "image", + stream=stream, + filename="foo.jpg", + content_type="image/jpeg", + ) + # got file w/ filename and no keep flag -> not empty + self.assertIs(widget.w_check_empty_value(req_val), False) + req_val = fake_file_from_request( + "image", + stream=stream, + filename="foo.jpg", + content_type="image/jpeg", + ) + # got file w/ filename and yes keep flag -> not empty + self.assertIs( + widget.w_check_empty_value(req_val, image_keepcheck="yes"), + False, + ) + # got file w/ filename and no keep flag -> not empty + self.assertIs( + widget.w_check_empty_value(req_val, image_keepcheck="no"), + False, + ) + # got file w/ filename and yes keep flag -> not empty + self.assertIs( + widget.w_check_empty_value(req_val, image_keepcheck="yes"), + False, + ) diff --git a/cms_form/tests/widgets/test_widget_boolean.py b/cms_form/tests/widgets/test_widget_boolean.py new file mode 100644 index 000000000..f5165a3a5 --- /dev/null +++ b/cms_form/tests/widgets/test_widget_boolean.py @@ -0,0 +1,64 @@ +# Copyright 2018 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). +from .common import TestWidgetCase, fake_field, fake_form + + +class TestWidgetBoolean(TestWidgetCase): + def _get_widget(self, field_value=False): + """Initialize form w/ given value and return the widget.""" + form = fake_form(self.env, a_boolean_field=field_value) + w_name, w_field = fake_field( + "a_boolean_field", + type="boolean", + ) + return self.get_widget( + w_name, w_field, form=form, widget_model="cms.form.widget.boolean" + ) + + def test_widget_boolean_input(self): + widget = self._get_widget() + self.assertFalse(widget.w_field_value) + expected_attrs = { + "type": "checkbox", + "id": "a_boolean_field", + "name": "a_boolean_field", + "class": "form-check-input ", + } + self._test_widget_attributes(widget, "input", expected_attrs) + + def test_widget_boolean_input_required(self): + widget = self._get_widget() + self.assertFalse(widget.w_field_value) + widget.w_field["required"] = True + expected_attrs = { + "type": "checkbox", + "id": "a_boolean_field", + "name": "a_boolean_field", + "class": "form-check-input ", + "required": "1", + } + self._test_widget_attributes(widget, "input", expected_attrs) + + def test_widget_boolean_input_checked(self): + widget = self._get_widget(field_value=True) + self.assertTrue(widget.w_field_value) + expected_attrs = { + "type": "checkbox", + "id": "a_boolean_field", + "name": "a_boolean_field", + "class": "form-check-input ", + "checked": "checked", + } + self._test_widget_attributes(widget, "input", expected_attrs) + + def test_widget_boolean_input_extract(self): + widget = self._get_widget() + self.assertIs(widget.w_extract(a_boolean_field="1"), True) + self.assertIs(widget.w_extract(a_boolean_field="ok"), True) + self.assertIs(widget.w_extract(a_boolean_field="true"), True) + self.assertIs(widget.w_extract(a_boolean_field="yes"), True) + self.assertIs(widget.w_extract(a_boolean_field=True), True) + self.assertIs(widget.w_extract(a_boolean_field=1), True) + # any other value give us False + self.assertIs(widget.w_extract(a_boolean_field=""), False) + self.assertIs(widget.w_extract(a_boolean_field="no"), False) diff --git a/cms_form/tests/widgets/test_widget_date.py b/cms_form/tests/widgets/test_widget_date.py new file mode 100644 index 000000000..c2884dc55 --- /dev/null +++ b/cms_form/tests/widgets/test_widget_date.py @@ -0,0 +1,101 @@ +# Copyright 2019 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). +import json + +from .common import TestWidgetCase, fake_field, fake_form + + +class TestWidgetDate(TestWidgetCase): + def setUp(self): + super().setUp() + self.form = fake_form(self.env, a_date_field="2019-01-12", type="date") + self.w_name, self.w_field = fake_field("a_date_field") + self.widget = self.get_widget( + self.w_name, + self.w_field, + form=self.form, + widget_model="cms.form.widget.date", + ) + + def test_widget_date_input(self): + node = self.to_xml_node(self.widget.render())[0] + # we have 2 inputs, display one + node_input_disp = self.find_input_name(node, self.w_name + "_display") + self.assertEqual(len(node_input_disp), 1) + expected_attrs = { + "type": "text", + "id": "a_date_field_display", + "name": "a_date_field_display", + "class": "form-control js_datepicker ", + "placeholder": "", + } + self._test_element_attributes(node_input_disp[0], "input", expected_attrs) + + # TODO: check on json params on other widgets too + self.assertEqual( + json.loads(node_input_disp[0].attrib["data-params"]), + {"defaultToday": True, "name": "a_date_field"}, + ) + # and the real one holding the value which is hidden + node_input = self.find_input_name(node, self.w_name) + self.assertEqual(len(node_input), 1) + expected_attrs = { + "type": "hidden", + "id": "a_date_field", + "name": "a_date_field", + "value": "2019-01-12", + } + self._test_element_attributes(node_input[0], "input", expected_attrs) + + def test_widget_date_input_required(self): + self.widget.w_field["required"] = True + expected_attrs = { + "type": "text", + "id": "a_date_field_display", + "name": "a_date_field_display", + "class": "form-control js_datepicker ", + "required": "1", + } + self._test_widget_attributes(self.widget, "input", expected_attrs) + + def test_widget_date_input_custom_dp_attrs(self): + widget = self.get_widget( + self.w_name, + self.w_field, + form=self.form, + widget_model="cms.form.widget.date", + format="%m.%Y", + placeholder="Custom", + ) + self.assertEqual(widget.w_placeholder, "Custom") + self.assertEqual( + widget.w_data_json(), + '{"defaultToday": true, "dp": {"format": "%m.%Y"}, "name": "a_date_field"}', + ) + + def test_widget_date_input_all_elems(self): + node = self.to_xml_node(self.widget.render())[0] + self._test_element_attributes( + node, + "div", + {"class": "input-group"}, + ) + self.assertEqual(len(node.getchildren()), 3) + self._test_element_attributes(node.getchildren()[0], "input", {}) + self._test_element_attributes( + node.getchildren()[1], "input", {"type": "hidden"} + ) + self._test_element_attributes( + node.getchildren()[2], + "span", + {"class": "input-group-addon js_datepicker_trigger"}, + ) + self._test_element_attributes( + node.getchildren()[2].getchildren()[0], + "span", + {"class": "fa fa-calendar"}, + ) + + def test_widget_date_input_extract_default_format(self): + self.assertEqual(self.widget.w_extract(a_date_field=""), None) + self.assertEqual(self.widget.w_extract(a_date_field="2019-01-12"), "2019-01-12") diff --git a/cms_form/tests/widgets/test_widget_float.py b/cms_form/tests/widgets/test_widget_float.py new file mode 100644 index 000000000..fc874aa4c --- /dev/null +++ b/cms_form/tests/widgets/test_widget_float.py @@ -0,0 +1,55 @@ +# Copyright 2019 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). +from .common import TestWidgetCase, fake_field, fake_form + + +class TestWidgetFloat(TestWidgetCase): + def setUp(self): + super().setUp() + form = fake_form(self.env, a_float_field=2.0) + self.w_name, self.w_field = fake_field("a_float_field", type="float") + self.widget = self.get_widget( + self.w_name, + self.w_field, + form=form, + widget_model="cms.form.widget.float", + ) + + def test_widget_float_input(self): + node = self.to_xml_node(self.widget.render())[0] + node_input = self.find_input_name(node, self.w_name) + self.assertEqual(len(node_input), 1) + expected_attrs = { + "type": "text", + "id": "a_float_field", + "name": "a_float_field", + "placeholder": "A float field...", + "value": "2.0", + "class": "form-control ", + } + for attr_name, attr_value in expected_attrs.items(): + self.assertEqual(node_input[0].attrib[attr_name], attr_value) + self.assertNotIn("required", node_input[0].attrib) + + def test_widget_float_input_required(self): + self.widget.w_field["required"] = True + node = self.to_xml_node(self.widget.render())[0] + node_input = self.find_input_name(node, self.w_name) + self.assertEqual(len(node_input), 1) + expected_attrs = { + "type": "text", + "id": "a_float_field", + "name": "a_float_field", + "placeholder": "A float field...", + "value": "2.0", + "class": "form-control ", + "required": "1", + } + for attr_name, attr_value in expected_attrs.items(): + self.assertEqual(node_input[0].attrib[attr_name], attr_value) + + def test_widget_float_input_extract(self): + self.assertEqual(self.widget.w_extract(a_float_field="1"), 1.0) + self.assertEqual(self.widget.w_extract(a_float_field="2.0"), 2.0) + self.assertEqual(self.widget.w_extract(a_float_field="2,0"), 2.0) + self.assertEqual(self.widget.w_extract(a_float_field=""), None) diff --git a/cms_form/tests/widgets/test_widget_hidden.py b/cms_form/tests/widgets/test_widget_hidden.py new file mode 100644 index 000000000..160af9367 --- /dev/null +++ b/cms_form/tests/widgets/test_widget_hidden.py @@ -0,0 +1,155 @@ +# Copyright 2018 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). +from .common import TestWidgetCase, fake_field, fake_form + + +class TestWidgetHidden(TestWidgetCase): + def setUp(self): + super().setUp() + self.form = fake_form( + self.env, + # fake defaults + char_field="abc", + int_field=5, + float_field=5.0, + selection_str_field="1", + selection_integer_field=2, + selection_float_field=4.0, + many2one_field=10, + ) + + def test_widget_char_input_hidden(self): + """Test char field hidden.""" + w_name, w_field = fake_field("char_field") + widget = self.get_widget( + w_name, + w_field, + form=self.form, + widget_model="cms.form.widget.hidden", + ) + expected_attrs = { + "type": "hidden", + "id": "char_field", + "name": "char_field", + "value": "abc", + } + self._test_widget_attributes(widget, "input", expected_attrs) + # make it required + # we'll test this only here: behavior is the same for each field type + widget.w_field["required"] = True + expected_attrs["required"] = "1" + self._test_widget_attributes(widget, "input", expected_attrs) + + def test_widget_integer_input_hidden(self): + """Test int field hidden.""" + w_name, w_field = fake_field("int_field", type="integer") + widget = self.get_widget( + w_name, + w_field, + form=self.form, + widget_model="cms.form.widget.hidden", + ) + expected_attrs = { + "type": "hidden", + "id": "int_field", + "name": "int_field:int", + "value": "5", + } + self._test_widget_attributes(widget, "input", expected_attrs) + + def test_widget_float_input_hidden(self): + """Test float field hidden.""" + w_name, w_field = fake_field("float_field", type="float") + widget = self.get_widget( + w_name, + w_field, + form=self.form, + widget_model="cms.form.widget.hidden", + ) + expected_attrs = { + "type": "hidden", + "id": "float_field", + "name": "float_field:float", + "value": "5.0", + } + self._test_widget_attributes(widget, "input", expected_attrs) + + def test_widget_selection_string_input_hidden(self): + """Test selection field hidden with string values.""" + w_name, w_field = fake_field( + "selection_str_field", + type="selection", + selection=[("1", "A"), ("2", "B")], + ) + widget = self.get_widget( + w_name, + w_field, + form=self.form, + widget_model="cms.form.widget.hidden", + ) + expected_attrs = { + "type": "hidden", + "id": "selection_str_field", + "name": "selection_str_field", + "value": "1", + } + self._test_widget_attributes(widget, "input", expected_attrs) + + def test_widget_selection_integer_input_hidden(self): + """Test selection field hidden with integer values.""" + w_name, w_field = fake_field( + "selection_integer_field", + type="selection", + selection=[(1, "A"), (2, "B")], + ) + widget = self.get_widget( + w_name, + w_field, + form=self.form, + widget_model="cms.form.widget.hidden", + ) + expected_attrs = { + "type": "hidden", + "id": "selection_integer_field", + "name": "selection_integer_field:int", + "value": "2", + } + self._test_widget_attributes(widget, "input", expected_attrs) + + def test_widget_selection_float_input_hidden(self): + """Test selection field hidden with float values.""" + w_name, w_field = fake_field( + "selection_float_field", + type="selection", + selection=[(4.0, "A"), (8.0, "B")], + ) + widget = self.get_widget( + w_name, + w_field, + form=self.form, + widget_model="cms.form.widget.hidden", + ) + expected_attrs = { + "type": "hidden", + "id": "selection_float_field", + "name": "selection_float_field:float", + "value": "4.0", + } + self._test_widget_attributes(widget, "input", expected_attrs) + + def test_widget_many2one_input_hidden(self): + """Test many2one field hidden.""" + w_name, w_field = fake_field("many2one_field", type="many2one") + widget = self.get_widget( + w_name, + w_field, + form=self.form, + widget_model="cms.form.widget.hidden", + ) + expected_attrs = { + "type": "hidden", + "id": "many2one_field", + "name": "many2one_field:int", + "value": "10", + } + self._test_widget_attributes(widget, "input", expected_attrs) diff --git a/cms_form/tests/widgets/test_widget_integer.py b/cms_form/tests/widgets/test_widget_integer.py new file mode 100644 index 000000000..fd3c371d9 --- /dev/null +++ b/cms_form/tests/widgets/test_widget_integer.py @@ -0,0 +1,52 @@ +# Copyright 2019 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). +from .common import TestWidgetCase, fake_field, fake_form + + +class TestWidgetInteger(TestWidgetCase): + + # TODO: test extraction and conversion to proper field value + # on EVERY widget and not just rely on the marshallers. + # Of course we have to switch to `html_fname` approach as hidden widget. + # This implies that we test w/ a full request too. + + def setUp(self): + super().setUp() + form = fake_form(self.env, an_int_field=10) + self.w_name, self.w_field = fake_field("an_int_field", type="integer") + self.widget = self.get_widget( + self.w_name, + self.w_field, + form=form, + widget_model="cms.form.widget.integer", + ) + + def test_widget_integer_input(self): + expected_attrs = { + "type": "number", + "id": "an_int_field", + "name": "an_int_field", + "placeholder": "An int field...", + "value": "10", + "class": "form-control ", + } + self._test_widget_attributes(self.widget, "input", expected_attrs) + + def test_widget_integer_input_required(self): + self.widget.w_field["required"] = True + expected_attrs = { + "type": "number", + "id": "an_int_field", + "name": "an_int_field", + "placeholder": "An int field...", + "value": "10", + "class": "form-control ", + "required": "1", + } + self._test_widget_attributes(self.widget, "input", expected_attrs) + + def test_widget_integer_input_extract(self): + self.assertEqual(self.widget.w_extract(an_int_field="1"), 1) + self.assertEqual(self.widget.w_extract(an_int_field="4"), 4) + self.assertEqual(self.widget.w_extract(an_int_field="2.0"), None) + self.assertEqual(self.widget.w_extract(an_int_field=""), None) diff --git a/cms_form/tests/widgets/test_widget_many2one.py b/cms_form/tests/widgets/test_widget_many2one.py new file mode 100644 index 000000000..39a4c34e2 --- /dev/null +++ b/cms_form/tests/widgets/test_widget_many2one.py @@ -0,0 +1,165 @@ +# Copyright 2019 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). +import json + +from .common import TestWidgetCase, fake_field, fake_form + + +class TestWidgetM2O(TestWidgetCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.partners = cls.env["res.partner"].search([], limit=4) + + def setUp(self): + super().setUp() + self.form = fake_form( + self.env, + # fake defaults + m2o_field=self.partners.ids[0], + ) + + def test_widget_many2one_base(self): + w_name, w_field = fake_field( + "m2o_field", + type="many2one", + relation="res.partner", + domain=[("id", "in", self.partners.ids)], + ) + widget = self.get_widget( + w_name, + w_field, + form=self.form, + widget_model="cms.form.widget.many2one", + ) + self.assertEqual(widget.w_comodel, self.env["res.partner"]) + self.assertEqual(widget.w_domain, [("id", "in", self.partners.ids)]) + self.assertEqual(widget.w_option_items, self.partners) + + def test_widget_many2one_base_load(self): + # TODO: test load value from form record + w_name, w_field = fake_field( + "m2o_field", + type="many2one", + relation="res.partner", + ) + widget = self.get_widget( + w_name, + w_field, + form=self.form, + widget_model="cms.form.widget.many2one", + ) + + self.assertEqual(widget.w_load(m2o_field="1"), 1) + self.assertEqual(widget.w_load(m2o_field="0"), None) + self.assertEqual(widget.w_load(m2o_field="a"), None) + self.assertEqual(widget.w_load(m2o_field=1), 1) + self.assertEqual(widget.w_load(m2o_field=self.partners[0]), self.partners[0].id) + self.assertEqual(widget.w_load(m2o_field=""), None) + self.assertEqual(widget.w_load(m2o_field=None), None) + self.assertEqual(widget.w_load(m2o_field=False), False) + + def test_widget_many2one_base_extract(self): + w_name, w_field = fake_field( + "m2o_field", + type="many2one", + relation="res.partner", + ) + widget = self.get_widget( + w_name, + w_field, + form=self.form, + widget_model="cms.form.widget.many2one", + ) + + self.assertEqual(widget.w_extract(m2o_field="1"), 1) + self.assertEqual(widget.w_extract(m2o_field="0"), None) + self.assertEqual(widget.w_extract(m2o_field="a"), None) + self.assertEqual(widget.w_extract(m2o_field=1), 1) + self.assertEqual( + widget.w_extract(m2o_field=self.partners[0]), self.partners[0].id + ) + self.assertEqual(widget.w_extract(m2o_field=""), None) + # none to ignore any change + self.assertEqual(widget.w_extract(m2o_field=None), None) + # false to flush the field + self.assertEqual(widget.w_extract(m2o_field=False), None) + + def test_widget_many2one_base_render(self): + w_name, w_field = fake_field( + "m2o_field", + type="many2one", + relation="res.partner", + ) + widget = self.get_widget( + w_name, + w_field, + form=self.form, + widget_model="cms.form.widget.many2one", + ) + expected_attrs = { + "id": "m2o_field", + "name": "m2o_field", + } + self._test_widget_attributes(widget, "select", expected_attrs) + widget.w_field["required"] = True + expected_attrs["required"] = "1" + self._test_widget_attributes(widget, "select", expected_attrs) + + def test_widget_many2one_multi(self): + w_name, w_field = fake_field( + "m2o_field", + type="many2one", + relation="res.partner", + ) + widget = self.get_widget( + w_name, + w_field, + form=self.form, + widget_model="cms.form.widget.many2one.multi", + ) + expected_attrs = { + "id": "m2o_field", + "name": "m2o_field", + "class": "form-control js_select2_m2m_widget m2o", + "placeholder": "M2o field...", + "data-init-value": str(self.partners.ids[0]), + "data-model": "res.partner", + "data-domain": "[]", + "data-fields": '["name"]', + } + self._test_widget_attributes(widget, "input", expected_attrs) + + def test_widget_many2one_multi_load(self): + w_name, w_field = fake_field( + "m2o_field", + type="many2one", + relation="res.partner", + ) + widget = self.get_widget( + w_name, + w_field, + form=self.form, + widget_model="cms.form.widget.many2one.multi", + ) + # test conversion + self.assertEqual(widget.w_load(m2o_field=False), "[]") + self.assertEqual( + widget.w_load(m2o_field="{},{}".format(*self.partners.ids[0:2])), + json.dumps(self.partners[0:2].read(["name"])), + ) + + def test_widget_many2one_multi_extract(self): + w_name, w_field = fake_field( + "m2o_field", + type="many2one", + relation="res.partner", + ) + widget = self.get_widget( + w_name, + w_field, + form=self.form, + widget_model="cms.form.widget.many2one.multi", + ) + # test conversion + self.assertEqual(widget.w_extract(m2o_field="1,2,3"), [1, 2, 3]) diff --git a/cms_form/tests/widgets/test_widget_radio.py b/cms_form/tests/widgets/test_widget_radio.py new file mode 100644 index 000000000..b2fc3b44c --- /dev/null +++ b/cms_form/tests/widgets/test_widget_radio.py @@ -0,0 +1,69 @@ +# Copyright 2019 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). +from .common import TestWidgetCase, fake_field, fake_form + + +class TestWidgetRadio(TestWidgetCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.form = fake_form( + cls.env, + # fake defaults + radio_field="opt2", + ) + + def test_widget_radio_base(self): + select_options = [ + ("opt1", "Option 1"), + ("opt2", "Option 2"), + ("opt3", "Option 3"), + ] + w_name, w_field = fake_field( + "radio_field", + type="selection", + selection=select_options, + ) + widget = self.get_widget( + w_name, + w_field, + form=self.form, + widget_model="cms.form.widget.radio", + ) + node = self.to_xml_node(widget.render())[0] + self.assertIn("radio-select", node.attrib["class"]) + # we have only 3 options + self.assertEqual(len(node.getchildren()), 3) + for i, node_option in enumerate(node.getchildren()): + self._test_element_attributes( + node_option, "div", {"class": "radio option-item"} + ) + # we should have only the label wrapping the input + self.assertEqual(len(node_option.getchildren()), 1) + node_label = node_option.getchildren()[0] + self._test_element_attributes( + node_label, "label", {"for": "radio_field_%s" % i} + ) + # we should have only the input and the span w/ text here + self.assertEqual(len(node_label.getchildren()), 2) + node_input = node_label.getchildren()[0] + expected_attrs = { + "type": "radio", + "name": "radio_field", + "id": "radio_field_%s" % i, + "value": "opt%s" % (i + 1), + } + if i == 1: + expected_attrs["checked"] = "checked" + self._test_element_attributes( + node_input, + "input", + expected_attrs, + ) + node_span = node_label.getchildren()[1] + self._test_element_attributes( + node_span, + "span", + {}, + text="Option %s" % (i + 1), + ) diff --git a/cms_form/tests/widgets/test_widget_selection.py b/cms_form/tests/widgets/test_widget_selection.py new file mode 100644 index 000000000..d427f096a --- /dev/null +++ b/cms_form/tests/widgets/test_widget_selection.py @@ -0,0 +1,172 @@ +# Copyright 2019 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). +from .common import TestWidgetCase, fake_field, fake_form + + +class TestWidgetSelection(TestWidgetCase): + def setUp(self): + super().setUp() + self.form = fake_form( + self.env, + # fake defaults + selection_char_field="opt1", + selection_integer_field=2, + selection_float_field=3.0, + ) + + def test_widget_selection_base(self): + select_options = [ + ("opt1", "Option 1"), + ] + w_name, w_field = fake_field( + "selection_char_field", + type="selection", + selection=select_options, + ) + widget = self.get_widget( + w_name, + w_field, + form=self.form, + widget_model="cms.form.widget.selection", + ) + expected_attrs = { + "id": "selection_char_field", + "name": "selection_char_field", + } + self._test_widget_attributes(widget, "select", expected_attrs) + widget.w_field["required"] = True + expected_attrs["required"] = "1" + self._test_widget_attributes(widget, "select", expected_attrs) + + def test_widget_selection_char(self): + select_options = [ + ("opt1", "Option 1"), + ("opt2", "Option 2"), + ("opt3", "Option 3"), + ] + w_name, w_field = fake_field( + "selection_char_field", + type="selection", + selection=select_options, + ) + widget = self.get_widget( + w_name, + w_field, + form=self.form, + widget_model="cms.form.widget.selection", + ) + expected_attrs = { + "id": "selection_char_field", + "name": "selection_char_field", + } + node = self._test_widget_attributes(widget, "select", expected_attrs) + node_children = node.getchildren() + self.assertEqual(len(node_children), 4) + self.assertEqual(node_children[0].attrib, {"value": "", "class": "empty_item"}) + self.assertEqual(node_children[0].text.strip(), "Selection char field...") + for i in range(1, 4): + expected_attrs = {"value": "opt%s" % i} + if i == 1: + expected_attrs["selected"] = "selected" + self.assertEqual(node_children[i].attrib, expected_attrs) + self.assertEqual(node_children[i].text.strip(), "Option %s" % i) + + # test conversion + extracted = widget.w_extract(selection_char_field="opt2") + self.assertTrue(isinstance(extracted, str)) + + def test_widget_selection_integer(self): + select_options = [ + (1, "Option 1"), + (2, "Option 2"), + (3, "Option 3"), + ] + w_name, w_field = fake_field( + "selection_integer_field", + type="selection", + selection=select_options, + ) + widget = self.get_widget( + w_name, + w_field, + form=self.form, + widget_model="cms.form.widget.selection", + ) + expected_attrs = { + "id": "selection_integer_field", + "name": "selection_integer_field", + } + node = self._test_widget_attributes(widget, "select", expected_attrs) + node_children = node.getchildren() + self.assertEqual(len(node_children), 4) + self.assertEqual(node_children[0].attrib, {"value": "", "class": "empty_item"}) + self.assertEqual(node_children[0].text.strip(), "Selection integer field...") + for i in range(1, 4): + expected_attrs = {"value": str(i)} + if i == 2: + expected_attrs["selected"] = "selected" + self.assertEqual(node_children[i].attrib, expected_attrs) + self.assertEqual(node_children[i].text.strip(), "Option %s" % i) + + # test conversion + extracted = widget.w_extract(selection_integer_field="2") + self.assertTrue(isinstance(extracted, int)) + + def test_widget_selection_float(self): + select_options = [ + (1.0, "Option 1"), + (2.0, "Option 2"), + (3.0, "Option 3"), + ] + w_name, w_field = fake_field( + "selection_float_field", + type="selection", + selection=select_options, + ) + widget = self.get_widget( + w_name, + w_field, + form=self.form, + widget_model="cms.form.widget.selection", + ) + expected_attrs = { + "id": "selection_float_field", + "name": "selection_float_field", + } + node = self._test_widget_attributes(widget, "select", expected_attrs) + node_children = node.getchildren() + self.assertEqual(len(node_children), 4) + self._test_element_attributes( + node_children[0], + "option", + {"value": "", "class": "empty_item"}, + text="Selection float field...", + ) + for i in range(1, 4): + expected_attrs = {"value": "%s.0" % i} + if i == 3: + expected_attrs["selected"] = "selected" + self._test_element_attributes( + node_children[i], + "option", + expected_attrs, + text="Option %s" % i, + ) + # test conversion + extracted = widget.w_extract(selection_float_field="3.0") + self.assertTrue(isinstance(extracted, float)) + + def test_widget_selection_non_selection_field(self): + w_name, w_field = fake_field( + "selection_char_field", + type="selection", + # do not pass `selection` + ) + widget = self.get_widget( + w_name, + w_field, + form=self.form, + widget_model="cms.form.widget.selection", + ) + # no selection found: should not fail and give back an empty list + self.assertEqual(widget.w_option_items, []) diff --git a/cms_form/tests/widgets/test_widget_text.py b/cms_form/tests/widgets/test_widget_text.py new file mode 100644 index 000000000..15335d67f --- /dev/null +++ b/cms_form/tests/widgets/test_widget_text.py @@ -0,0 +1,94 @@ +# Copyright 2018 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). +from .common import TestWidgetCase, fake_field, fake_form + +TXT = """Lorem ipsum dolor sit amet, mea te propriae verterem. +Soluta viderer no vis. Ut populo suscipit vel. +Usu ea timeam utamur consectetuer, no simul dolorum vel. +Duo illum dolore id, ea mei error gloriatur voluptaria.""" + + +class TestWidgetTxt(TestWidgetCase): + def setUp(self): + super().setUp() + self.form = fake_form( + self.env, + a_char_field="Just a string", + a_text_field=TXT, + ) + + def test_widget_char_input(self): + w_name, w_field = fake_field("a_char_field") + widget = self.get_widget( + w_name, + w_field, + form=self.form, + widget_model="cms.form.widget.char", + ) + expected_attrs = { + "type": "text", + "id": "a_char_field", + "name": "a_char_field", + "placeholder": "A char field...", + "value": "Just a string", + "class": "form-control ", + } + self._test_widget_attributes(widget, "input", expected_attrs) + widget.w_field["required"] = True + expected_attrs["required"] = "1" + self._test_widget_attributes(widget, "input", expected_attrs) + + def test_widget_text_input(self): + w_name, w_field = fake_field("a_text_field", type="text") + widget = self.get_widget( + w_name, + w_field, + form=self.form, + widget_model="cms.form.widget.text", + ) + expected_attrs = { + "id": "a_text_field", + "name": "a_text_field", + "class": "form-control ", + } + self._test_widget_attributes(widget, "textarea", expected_attrs, text=TXT) + widget.w_field["required"] = True + expected_attrs["required"] = "1" + self._test_widget_attributes(widget, "textarea", expected_attrs, text=TXT) + + def test_widget_text_input_maxlength(self): + w_name, w_field = fake_field("a_text_field", type="text", maxlength=100) + widget = self.get_widget( + w_name, + w_field, + form=self.form, + widget_model="cms.form.widget.text", + ) + node_items = self.to_xml_node(widget.render()) + self.assertEqual(len(node_items), 2) + node_textarea = node_items[0] + expected_attrs = { + "id": "a_text_field", + "name": "a_text_field", + "class": "form-control ", + "maxlength": "100", + } + self._test_element_attributes( + node_textarea, + "textarea", + expected_attrs, + text=TXT, + ) + node_counter = node_items[1] + expected_attrs = { + "type": "text", + "id": "a_text_field_counter", + "name": "a_text_field_counter", + "size": "3", + "class": "form-control text-counter", + } + self._test_element_attributes( + node_counter, + "input", + expected_attrs, + ) diff --git a/cms_form/tests/widgets/test_widget_x2many.py b/cms_form/tests/widgets/test_widget_x2many.py new file mode 100644 index 000000000..3c1ab6914 --- /dev/null +++ b/cms_form/tests/widgets/test_widget_x2many.py @@ -0,0 +1,150 @@ +# Copyright 2019 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). +import json + +from .common import TestWidgetCase, fake_field, fake_form + + +class TestWidgetX2M(TestWidgetCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.partners = cls.env["res.partner"].search([], limit=4) + + def setUp(self): + super().setUp() + self.form = fake_form( + self.env, + # fake defaults + # behavior of o2m or m2m ATM is the same + m2m_field=self.partners.ids[0:2], + ) + + def test_widget_x2many_base(self): + w_name, w_field = fake_field( + "m2m_field", + type="many2many", + relation="res.partner", + domain=[("id", "in", self.partners.ids)], + ) + widget = self.get_widget( + w_name, + w_field, + form=self.form, + widget_model="cms.form.widget.many2many", + ) + self.assertEqual(widget.w_comodel, self.env["res.partner"]) + self.assertEqual(widget.w_domain, [("id", "in", self.partners.ids)]) + + def test_widget_x2many(self): + w_name, w_field = fake_field( + "m2m_field", + type="many2many", + relation="res.partner", + ) + widget = self.get_widget( + w_name, + w_field, + form=self.form, + widget_model="cms.form.widget.many2many", + ) + expected_attrs = { + "id": "m2m_field", + "name": "m2m_field", + "class": "form-control js_select2_m2m_widget ", + "placeholder": "M2m field...", + "data-init-value": json.dumps(self.partners.ids[0:2]), + "data-model": "res.partner", + "data-domain": "[]", + "data-fields": '["name"]', + } + self._test_widget_attributes(widget, "input", expected_attrs) + + def test_widget_x2many_base_load(self): + w_name, w_field = fake_field( + "m2m_field", + type="many2many", + relation="res.partner", + ) + widget = self.get_widget( + w_name, + w_field, + form=self.form, + widget_model="cms.form.widget.many2many", + ) + # test conversion + self.assertEqual(widget.w_load(m2m_field=False), "[]") + self.assertEqual( + widget.w_load(m2m_field="{},{}".format(*self.partners.ids[0:2])), + json.dumps(self.partners[0:2].read(["display_name", "name"])), + ) + + def test_widget_x2many_base_load_from_record(self): + categs = self.env["res.partner.category"].search([], limit=3) + partner = self.partners[0] + form = fake_form( + self.env, + # category_id=categs, + main_object=partner, + ) + w_name, w_field = fake_field( + "category_id", + type="many2many", + relation=categs._name, + ) + widget = self.get_widget( + w_name, + w_field, + form=form, + widget_model="cms.form.widget.many2many", + ) + # flush categories if any + partner.category_id = False + # flushed, no value + self.assertEqual(widget.w_load(), "[]") + # set some value + partner.category_id = categs[0:2] + # no value override from request: load from record + self.assertEqual( + widget.w_load(), + json.dumps(partner.category_id.read(["display_name", "name"])), + ) + self.assertEqual( + # pass new value via request + widget.w_load(category_id=str(categs[-1].id)), + json.dumps(categs[-1].read(["display_name", "name"])), + ) + + def test_widget_x2many_base_extract(self): + w_name, w_field = fake_field( + "m2m_field", + type="many2many", + relation="res.partner", + ) + widget = self.get_widget( + w_name, + w_field, + form=self.form, + widget_model="cms.form.widget.many2many", + ) + # test conversion + self.assertEqual(widget.w_extract(m2m_field="1,2,3"), [(6, False, [1, 2, 3])]) + self.assertEqual(widget.w_extract(m2m_field=""), [(5,)]) + self.form.form_extract_value_mode = "read" + self.assertEqual(widget.w_extract(m2m_field="1,2,3"), [1, 2, 3]) + + def test_widget_x2many_load_no_value(self): + w_name, w_field = fake_field( + "m2m_field", + type="many2many", + relation="res.partner", + ) + widget = self.get_widget( + w_name, + w_field, + form=self.form, + widget_model="cms.form.widget.many2many", + ) + self.assertEqual(widget.w_load(m2m_field=""), "[]") + # empty value from default_get + self.assertEqual(widget.w_load(m2m_field=[(6, 0, [])]), "[]") diff --git a/cms_form/utils.py b/cms_form/utils.py new file mode 100644 index 000000000..0265de197 --- /dev/null +++ b/cms_form/utils.py @@ -0,0 +1,85 @@ +# Copyright 2017 Simone Orsi +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). + + +def safe_to_integer(value, **kw): + """Convert to integer safely.""" + try: + return int(value) + except (ValueError, TypeError, AttributeError): + return None + + +def safe_to_float(value, **kw): + if value is False: + return 0.0 + try: + return float(value.replace(",", ".")) + except (ValueError, TypeError, AttributeError): + return None + + +def safe_to_date(value, **kw): + if not value: + # 1. make sure we do not return empty string which breaks the ORM + # 2. return `None` so that request extractor ignores the field + # if it's not required + return None + return value + + +TRUE_VALUES = ( + "on", + "yes", + "ok", + "true", + True, + 1, + "1", +) + + +def string_to_bool(value, true_values=TRUE_VALUES): + return value in true_values + + +def data_merge(a, b): + """Merges `b` into `a` and return merged result + + NOTE: tuples and arbitrary objects are not handled + as it is totally ambiguous what should happen. + + Thanks to http://stackoverflow.com/a/15836901/647924 + """ + key = None + try: + if a is None or isinstance(a, (str, int, float)): + # border case for first run or if a is a primitive + a = b + elif isinstance(a, list): + # lists can be only appended + if isinstance(b, list): + # merge lists + a.extend(b) + else: + # append to list + a.append(b) + elif isinstance(a, dict): + # dicts must be merged + if isinstance(b, dict): + for key in b: + if key in a: + a[key] = data_merge(a[key], b[key]) + else: + a[key] = b[key] + else: + raise ValueError( + 'Cannot merge non-dict "{}" into dict "{}"'.format(b, a) + ) + else: + raise NotImplementedError('NOT IMPLEMENTED "{}" into "{}"'.format(b, a)) + except TypeError as e: # pragma: no cover + raise TypeError( + '"{}" in key "{}" when merging "{}" into "{}"'.format(e, key, b, a) + ) from e + return a diff --git a/setup/cms_form/odoo/addons/cms_form b/setup/cms_form/odoo/addons/cms_form new file mode 120000 index 000000000..7a2c1e039 --- /dev/null +++ b/setup/cms_form/odoo/addons/cms_form @@ -0,0 +1 @@ +../../../../cms_form \ No newline at end of file diff --git a/setup/cms_form/setup.py b/setup/cms_form/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/cms_form/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)