- Your message has been sent successfully.
-
+
+
+
+
+
+
Thanks!
+
+
+
+ Your message has been sent successfully.
+
+
+
+ We will get back to you shortly.
+
+ If you have an emergency, do not hesitate to contact us by phone:
+
+
+
:
+
:
+
+
+
+
+
-
- We will get back to you shortly.
-
- If you have an emergency, do not hesitate to contact us by phone:
-
-
-
:
-
:
-
-
-
-
-
-
-
-
-
-
+
+
+
+
-
+
+
+
+
+
+
+
diff --git a/addons/website_crm_score/controllers/main.py b/addons/website_crm_score/controllers/main.py
index 3707a8f648f..fe983c98d37 100644
--- a/addons/website_crm_score/controllers/main.py
+++ b/addons/website_crm_score/controllers/main.py
@@ -1,9 +1,10 @@
# -*- coding: utf-8 -*-
+import simplejson
from openerp import http, SUPERUSER_ID, fields
from openerp.http import request
from openerp.tools import html_escape
from openerp.addons.website.controllers.main import Website
-from openerp.addons.website_crm.controllers.main import contactus
+from openerp.addons.website_form.controllers.main import WebsiteForm
class PageController(Website):
@@ -33,27 +34,31 @@ class PageController(Website):
return response
-class ContactController(contactus):
+class ContactController(WebsiteForm):
+
+ @http.route('/website_form/', type='http', auth="public", methods=['POST'], website=True)
+ def website_form(self, model_name, **kwargs):
+ response = super(ContactController, self).website_form(model_name, **kwargs)
+ if model_name != 'crm.lead':
+ return response
- @http.route(['/crm/contactus'], type='http', auth="public", website=True)
- def contactus(self, **kwargs):
- response = super(ContactController, self).contactus(**kwargs)
# the cookie is written here because the response is not available in the create_lead function
- if '_values' in response.qcontext: # contactus error : fields validation not passed
- # NOTE: this requires for the lead_id to be added to values in the parent controller
- lead_id = response.qcontext.get('_values').get('lead_id')
- if lead_id: # a new lead has been created
- lead_model = request.registry['crm.lead']
- # sign the lead_id
- sign = lead_model.encode(lead_id)
- response.set_cookie('lead_id', sign, domain=lead_model.get_score_domain_cookies())
+ response_data = simplejson.loads(response.data) # controller is json now
+ if 'id' in response_data: # a new lead has been created
+ lead_model = request.registry['crm.lead']
+ # sign the lead_id
+ sign = lead_model.encode(response_data['id'])
+ response.set_cookie('lead_id', sign, domain=lead_model.get_score_domain_cookies())
return response
- def create_real_lead(self, request, values, kwargs):
- """ Make this function overridable to create a lead with another function that create_lead from webiste_crm """
- return super(ContactController, self).create_lead(request, values, kwargs)
+ def create_real_lead(self, request, model, values, custom, meta=None):
+ """ Make this function overridable to create a lead with another function than insert_record from website_form """
+ return super(ContactController, self).insert_record(request, model, values, custom, meta)
+
+ def insert_record(self, request, model, values, custom, meta=None):
+ if model.model != 'crm.lead':
+ return super(ContactController, self).insert_record(request, model, values, custom, meta)
- def create_lead(self, request, values, kwargs):
cr, context = request.cr, request.context
lead_model = request.registry["crm.lead"]
@@ -91,6 +96,8 @@ class ContactController(contactus):
body += ' %s: %s' % (fieldname, html_escape(changed_values[fieldname]))
request.registry['crm.lead'].message_post(cr, SUPERUSER_ID, [lead_id], body=body, subject="Field value changed", context=context)
+ return lead_id
+
else:
# either no lead_id cookie OR the lead_id doesn't exist in db OR the current one is closed -> a lead is created
lang = context.get('lang', False)
@@ -115,7 +122,7 @@ class ContactController(contactus):
urls.append('%s' % (url_encoded, url_encoded))
body = ' '.join(urls)
- new_lead_id = self.create_real_lead(request, values, kwargs)
+ new_lead_id = self.create_real_lead(request, model, values, custom, meta)
# if pages were seen, a message is posted
if body:
diff --git a/addons/website_form/__init__.py b/addons/website_form/__init__.py
new file mode 100644
index 00000000000..9f86759e32b
--- /dev/null
+++ b/addons/website_form/__init__.py
@@ -0,0 +1,2 @@
+import controllers
+import models
diff --git a/addons/website_form/__openerp__.py b/addons/website_form/__openerp__.py
new file mode 100644
index 00000000000..7fef0b7fd63
--- /dev/null
+++ b/addons/website_form/__openerp__.py
@@ -0,0 +1,18 @@
+{
+ 'name': 'Website Form Builder',
+ 'category': 'Website',
+ 'summary': 'Build custom web forms using the website builder',
+ 'version': '1.0',
+ 'description': """
+Odoo Form Builder
+====================
+
+Allows you to build web forms on the website using the website builder.
+ """,
+ 'depends': ['website', 'mail'],
+ 'data': [
+ 'views/assets.xml',
+ ],
+ 'installable': True,
+ 'auto_install': False,
+}
diff --git a/addons/website_crm/controllers/__init__.py b/addons/website_form/controllers/__init__.py
similarity index 100%
rename from addons/website_crm/controllers/__init__.py
rename to addons/website_form/controllers/__init__.py
diff --git a/addons/website_form/controllers/main.py b/addons/website_form/controllers/main.py
new file mode 100644
index 00000000000..214a8647e07
--- /dev/null
+++ b/addons/website_form/controllers/main.py
@@ -0,0 +1,217 @@
+# -*- coding: utf-8 -*-
+import base64
+
+import json
+from psycopg2 import IntegrityError
+from openerp import http, SUPERUSER_ID
+from openerp.http import request
+from openerp.tools.translate import _
+from openerp.exceptions import ValidationError
+from openerp.addons.base.ir.ir_qweb import nl2br
+
+class WebsiteForm(http.Controller):
+
+ # Check and insert values from the form on the model
+ @http.route('/website_form/', type='http', auth="public", methods=['POST'], website=True)
+ def website_form(self, model_name, **kwargs):
+ model_record = request.env['ir.model'].search([('model', '=', model_name), ('website_form_access', '=', True)])
+
+ if not model_record:
+ return json.dumps(False)
+
+ try:
+ data = self.extract_data(model_record, ** kwargs)
+
+ # If we encounter an issue while extracting data
+ except ValidationError, e:
+ # I couldn't find a cleaner way to pass data to an exception
+ return json.dumps({'error_fields' : e.args[0]})
+
+ try:
+ id_record = self.insert_record(request, model_record, data['record'], data['custom'], data.get('meta'))
+ if id_record:
+ self.insert_attachment(model_record, id_record, data['attachments'])
+
+ # Some fields have additionnal SQL constraints that we can't check generically
+ # Ex: crm.lead.probability which is a float between 0 and 1
+ # TODO: How to get the name of the erroneous field ?
+ except IntegrityError:
+ return json.dumps(False)
+
+ request.session['form_builder_model'] = model_record.name
+ request.session['form_builder_id'] = id_record
+
+ return json.dumps({'id': id_record})
+
+ # Constants string to make custom info and metadata readable on a text field
+
+ _custom_label = "%s\n___________\n\n" % _("Custom infos") # Title for custom fields
+ _meta_label = "%s\n________\n\n" % _("Metadata") # Title for meta data
+
+ # Dict of dynamically called filters following type of field to be fault tolerent
+
+ def identity(self, field_label, field_input):
+ return field_input
+
+ def integer(self, field_label, field_input):
+ return int(field_input)
+
+ def floating(self, field_label, field_input):
+ return float(field_input)
+
+ def boolean(self, field_label, field_input):
+ return bool(field_input)
+
+ def binary(self, field_label, field_input):
+ return base64.b64encode(field_input.read())
+
+ def one2many(self, field_label, field_input):
+ return [int(i) for i in field_input.split(',')]
+
+ def many2many(self, field_label, field_input, *args):
+ return [(args[0] if args else (6,0)) + (self.one2many(field_label, field_input),)]
+
+ _input_filters = {
+ 'char': identity,
+ 'text': identity,
+ 'html': identity,
+ 'datetime': identity,
+ 'many2one': integer,
+ 'one2many': one2many,
+ 'many2many':many2many,
+ 'selection': identity,
+ 'boolean': boolean,
+ 'integer': integer,
+ 'float': floating,
+ 'binary': binary,
+ }
+
+
+ # Extract all data sent by the form and sort its on several properties
+ def extract_data(self, model, **kwargs):
+
+ data = {
+ 'record': {}, # Values to create record
+ 'attachments': [], # Attached files
+ 'custom': '', # Custom fields values
+ }
+
+ authorized_fields = model.get_authorized_fields();
+ error_fields = []
+
+ for field_name, field_value in kwargs.items():
+ # If the value of the field if a file
+ if hasattr(field_value, 'filename'):
+ # Undo file upload field name indexing
+ field_name = field_name.rsplit('[', 1)[0]
+
+ # If it's an actual binary field, convert the input file
+ # If it's not, we'll use attachments instead
+ if field_name in authorized_fields and authorized_fields[field_name]['type'] == 'binary':
+ data['record'][field_name] = base64.b64encode(field_value.read())
+ else:
+ field_value.field_name = field_name
+ data['attachments'].append(field_value)
+
+ # If it's a known field
+ elif field_name in authorized_fields:
+ try:
+ input_filter = self._input_filters[authorized_fields[field_name]['type']]
+ data['record'][field_name] = input_filter(self, field_name, field_value)
+ except ValueError:
+ error_fields.append(field_name)
+
+ # If it's a custom field
+ elif field_name != 'context':
+ data['custom'] += "%s : %s\n" % (field_name, field_value)
+
+ # Add metadata if enabled
+ environ = request.httprequest.headers.environ
+ if(request.website.website_form_enable_metadata):
+ data['meta'] += "%s : %s\n%s : %s\n%s : %s\n%s : %s\n" % (
+ "IP" , environ.get("REMOTE_ADDR"),
+ "USER_AGENT" , environ.get("HTTP_USER_AGENT"),
+ "ACCEPT_LANGUAGE" , environ.get("HTTP_ACCEPT_LANGUAGE"),
+ "REFERER" , environ.get("HTTP_REFERER")
+ )
+
+ # This function can be defined on any model to provide
+ # a model-specific filtering of the record values
+ # Example:
+ # def website_form_input_filter(self, values):
+ # values['name'] = '%s\'s Application' % values['partner_name']
+ # return values
+ dest_model = request.env[model.model]
+ if hasattr(dest_model, "website_form_input_filter"):
+ data['record'] = dest_model.website_form_input_filter(request, data['record'])
+
+ missing_required_fields = [label for label, field in authorized_fields.iteritems() if field['required'] and not label in data['record']]
+ if any(error_fields):
+ raise ValidationError(error_fields + missing_required_fields)
+
+ return data
+
+ def insert_record(self, request, model, values, custom, meta=None):
+ record = request.env[model.model].sudo().create(values)
+
+ if custom or meta:
+ default_field_name = model.website_form_default_field_id.name
+ default_field_data = values.get(default_field_name, '')
+ custom_content = (default_field_data + "\n\n" if default_field_data else '') \
+ + (self._custom_label + custom + "\n\n" if custom else '') \
+ + (self._meta_label + meta if meta else '')
+
+ # If there is a default field configured for this model, use it.
+ # If there isn't, put the custom data in a message instead
+ if default_field_name:
+ record.update({default_field_name: custom_content})
+ else:
+ values = {
+ 'body': nl2br(custom_content),
+ 'model': model.model,
+ 'message_type': 'comment',
+ 'no_auto_thread': False,
+ 'res_id': record.id,
+ }
+ mail_id = request.env['mail.message'].sudo().create(values)
+
+ return record.id
+
+ # Link all files attached on the form
+ def insert_attachment(self, model, id_record, files):
+ orphan_attachment_ids = []
+ record = model.env[model.model].browse(id_record)
+ authorized_fields = model.get_authorized_fields()
+ for file in files:
+ custom_field = file.field_name not in authorized_fields
+ attachment_value = {
+ 'name': file.field_name if custom_field else file.filename,
+ 'datas': base64.encodestring(file.read()),
+ 'datas_fname': file.filename,
+ 'res_model': model.model,
+ 'res_id': record.id,
+ }
+ attachment_id = request.env['ir.attachment'].sudo().create(attachment_value)
+ if attachment_id and not custom_field:
+ record.sudo()[file.field_name] = [(4, attachment_id.id)]
+ else:
+ orphan_attachment_ids.append(attachment_id.id)
+
+ # If some attachments didn't match a field on the model,
+ # we create a mail.message to link them to the record
+ if orphan_attachment_ids:
+ if model.name != 'mail.mail':
+ values = {
+ 'body': _('
Attached files :
'),
+ 'model': model.model,
+ 'message_type': 'comment',
+ 'no_auto_thread': False,
+ 'res_id': id_record,
+ 'attachment_ids': [(6, 0, orphan_attachment_ids)],
+ }
+ mail_id = request.env['mail.message'].sudo().create(values)
+ else:
+ # If the model is mail.mail then we have no other choice but to
+ # attach the custom binary field files on the attachment_ids field.
+ for attachment_id_id in orphan_attachment_ids:
+ record.attachment_ids = [(4, attachment_id_id)]
diff --git a/addons/website_form/models/__init__.py b/addons/website_form/models/__init__.py
new file mode 100644
index 00000000000..bff786c0885
--- /dev/null
+++ b/addons/website_form/models/__init__.py
@@ -0,0 +1 @@
+import models
diff --git a/addons/website_form/models/models.py b/addons/website_form/models/models.py
new file mode 100644
index 00000000000..8fdc5e4bf9a
--- /dev/null
+++ b/addons/website_form/models/models.py
@@ -0,0 +1,47 @@
+from openerp import tools
+from openerp import models, fields, api
+
+
+class website_form_config(models.Model):
+ _inherit = 'website'
+ website_form_enable_metadata = fields.Boolean('Write metadata',help="Enable writing metadata on form submit.")
+
+
+class website_form_model(models.Model):
+ _name = 'ir.model'
+ _inherit = 'ir.model'
+
+ website_form_access = fields.Boolean('Allowed to use in forms', help='Enable the form builder feature for this model.')
+ website_form_default_field_id = fields.Many2one('ir.model.fields', 'Field for custom form data', domain="[('model', '=', model), ('ttype', '=', 'text')]", help="Specify the field wich will contain meta and custom form fields datas.")
+ website_form_label = fields.Char("Label for form action", help="Form action label. Ex: crm.lead could be 'Send an e-mail' and project.issue could be 'Create an Issue'.")
+
+
+ def all_inherited_model_ids(self):
+ return [self.id] + [m.all_inherited_model_ids() for m in self.inherited_model_ids]
+
+ @api.multi
+ def get_authorized_fields(self):
+ model = self.env[self.model]
+ fields_get = model.fields_get()
+ domain = [('model_id', 'in', self.all_inherited_model_ids()), ('website_form_blacklisted', '=', False)]
+
+ for elem in self.env['ir.model.fields'].search(domain):
+ if elem.website_form_blacklisted:
+ fields_get.pop(elem.name, None)
+ for key, val in model._inherits.iteritems():
+ fields_get.pop(val,None)
+
+ # Unrequire fields with default values
+ default_values = model.default_get(fields_get.keys())
+ for field in [f for f in fields_get if f in default_values]:
+ fields_get[field]['required'] = False
+
+ return fields_get
+
+
+class website_form_model_fields(models.Model):
+ """ fields configuration for form builder """
+ _name = 'ir.model.fields'
+ _inherit = 'ir.model.fields'
+
+ website_form_blacklisted = fields.Boolean('Blacklisted in web forms', help='Blacklist this field for web forms')
diff --git a/addons/website_form/static/src/js/website_form.js b/addons/website_form/static/src/js/website_form.js
new file mode 100644
index 00000000000..88859643129
--- /dev/null
+++ b/addons/website_form/static/src/js/website_form.js
@@ -0,0 +1,168 @@
+odoo.define('website_form.animation', function (require) {
+'use strict';
+
+ var core = require('web.core');
+ var time = require('web.time');
+ var ajax = require('web.ajax');
+ var snippet_animation = require('web_editor.snippets.animation');
+
+ var _t = core._t;
+ var qweb = core.qweb;
+
+
+ snippet_animation.registry.form_builder_send = snippet_animation.Class.extend({
+ selector: '.s_website_form',
+
+ start: function() {
+ var self = this;
+ qweb.add_template('/website_form/static/src/xml/website_form.xml');
+ this.$target.find('.o_website_form_send').on('click',function(e) {self.send(e);});
+
+ // Initialize datetimepickers
+ var l10n = _t.database.parameters;
+ this.$target.find('.o_website_form_datetime').datetimepicker({
+ pickTime: true,
+ useSeconds: true,
+ startDate: moment({ y: 1900 }),
+ endDate: moment().add(200, "y"),
+ calendarWeeks: true,
+ icons : {
+ time: 'fa fa-clock-o',
+ date: 'fa fa-calendar',
+ up: 'fa fa-chevron-up',
+ down: 'fa fa-chevron-down'
+ },
+ language : moment.locale(),
+ format : time.strftime_to_moment_format(l10n.date_format +' '+ l10n.time_format),
+ });
+ },
+
+ stop: function() {
+ this.$target.find('button').off('click');
+ },
+
+ send: function(e) {
+ e.preventDefault(); // Prevent the default submit behavior
+
+ var self = this;
+
+ self.$target.find('#o_website_form_result').empty();
+ if (!self.check_error_fields([])) {
+ self.update_status('invalid');
+ return false;
+ }
+
+ // Prepare form inputs
+ this.form_fields = this.$target.serializeArray();
+ _.each(this.$target.find('input[type=file]'), function(input) {
+ $.each($(input).prop('files'), function(index, file) {
+ // Index field name as ajax won't accept arrays of files
+ // when aggregating multiple files into a single field value
+ self.form_fields.push({
+ name: input.name + '[' + index + ']',
+ value: file
+ });
+ });
+ });
+
+ // Serialize form inputs into a single object
+ // Aggregate multiple values into arrays
+ var form_values = {};
+ _.each(this.form_fields, function(input) {
+ if (input.name in form_values) {
+ // If a value already exists for this field,
+ // we are facing a x2many field, so we store
+ // the values in an array.
+ if (Array.isArray(form_values[input.name])) {
+ form_values[input.name].push(input.value);
+ } else {
+ form_values[input.name] = [form_values[input.name], input.value];
+ }
+ } else {
+ if (input.value != '') {
+ form_values[input.name] = input.value;
+ }
+ }
+ });
+
+ // Overwrite form_values array with values from the form tag
+ // Necessary to handle field values generated server-side, since
+ // using t-att- inside a snippet makes it non-editable !
+ for (var key in this.$target.data()) {
+ if (_.str.startsWith(key, 'form_field_')){
+ form_values[key.replace('form_field_', '')] = this.$target.data(key);
+ }
+ }
+
+ // Post form and handle result
+ ajax.post(this.$target.attr('action') + this.$target.data('model_name'), form_values)
+ .then(function(result_data) {
+ result_data = $.parseJSON(result_data);
+ if(!result_data.id) {
+ // Failure, the server didn't return the created record ID
+ self.update_status('error');
+ if (result_data.error_fields && result_data.error_fields.length) {
+ // If the server return a list of bad fields, show these fields for users
+ self.check_error_fields(result_data.error_fields);
+ }
+ } else {
+ // Success, redirect or update status
+ var success_page = self.$target.attr('data-success_page');
+ if(success_page) {
+ $(location).attr('href', success_page);
+ }
+ else {
+ self.update_status('success');
+ }
+ }
+ })
+ .fail(function(result_data){
+ self.update_status('error');
+ });
+ },
+
+ check_error_fields: function(error_fields) {
+ var form_valid = true;
+ // Loop on all fields
+ this.$target.find('.form-field').each(function(k, field){
+ var $field = $(field);
+ var $fields = self.$fields;
+ var field_name = $field.find('.control-label').attr('for')
+
+ // Validate inputs for this field
+ var field_valid = true;
+ var inputs = $field.find('.o_website_form_input');
+ var invalid_inputs = inputs.toArray().filter(function(input, k, inputs) {
+ // Special check for multiple required checkbox for same
+ // field as it seems checkValidity forces every required
+ // checkbox to be checked, instead of looking at other
+ // checkboxes with the same name and only requiring one
+ // of them to be checked.
+ if (input.required && input.type == 'checkbox') {
+ // Considering we are currently processing a single
+ // field, we can assume that all checkboxes in the
+ // inputs variable have the same name
+ var checkboxes = _.filter(inputs, function(input){
+ return input.required && input.type == 'checkbox'
+ })
+ return !_.any(checkboxes, function(checkbox){return checkbox.checked})
+ } else {
+ return !input.checkValidity();
+ }
+ })
+
+ // Update field color if invalid or erroneous
+ $field.removeClass('has-error');
+ if(invalid_inputs.length || error_fields.indexOf(field_name) >= 0){
+ $field.addClass('has-error');
+ form_valid = false;
+ }
+ });
+ return form_valid;
+ },
+
+ update_status: function(status) {
+ this.$target.find('#o_website_form_result').replaceWith(qweb.render("website_form.status_" + status))
+ },
+ });
+});
diff --git a/addons/website_form/static/src/less/website_form.less b/addons/website_form/static/src/less/website_form.less
new file mode 100644
index 00000000000..f26718da9c1
--- /dev/null
+++ b/addons/website_form/static/src/less/website_form.less
@@ -0,0 +1,54 @@
+// Radio buttons and checkboxes flex layout
+.o_website_form_flex {
+ display: flex;
+ flex-wrap: wrap;
+}
+
+.o_website_form_flex_item {
+ // col-md-4
+ flex-basis: 33%;
+
+ // col-sm-6
+ @media (max-width: @screen-sm-max) {
+ flex-basis: 50%;
+ }
+
+ // col-xs-12
+ @media (max-width: @screen-xs-max) {
+ flex-basis: 100%;
+ }
+}
+
+// Hidden field is only partially hidden in editor
+.o_website_form_field_hidden {
+ display: none;
+}
+
+.editor_enable .o_website_form_field_hidden {
+ display: block;
+ opacity: 0.5;
+}
+
+// Select inputs do not trigger the default browser behavior
+// Since we use a custom editable element
+.editor_enable .s_website_form .form-field select {
+ pointer-events: none;
+}
+
+// Required fields have a star which is not part of the field label
+.o_website_form_required, .o_website_form_required_custom {
+ .control-label:after {
+ content: ' *';
+ }
+}
+
+// Quickfix to display the editable select as a single big field
+#editable_select.form-control {
+ height: 100%;
+}
+
+// Fix for firefox browse button which is too big for Bootstrap form-field
+// http://stackoverflow.com/questions/22049739/fix-for-firefox-file-input-using-bootstrap-3-1
+.form-field input[type=file].form-control {
+ height: 100%;
+}
diff --git a/addons/website_form/static/src/xml/website_form.xml b/addons/website_form/static/src/xml/website_form.xml
new file mode 100644
index 00000000000..3a1102bb5e1
--- /dev/null
+++ b/addons/website_form/static/src/xml/website_form.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+ The form has been sent successfully.
+
+
+
+
+
+
+ Please fill in the form correctly.
+
+
+
+
+
+
+ An error has occured, the form has not been sent.
+
+
+
+
diff --git a/addons/website_form/views/assets.xml b/addons/website_form/views/assets.xml
new file mode 100644
index 00000000000..0cf95cca454
--- /dev/null
+++ b/addons/website_form/views/assets.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/website_hr_recruitment/__openerp__.py b/addons/website_hr_recruitment/__openerp__.py
index 53b6cbca8b6..372e1de127c 100644
--- a/addons/website_hr_recruitment/__openerp__.py
+++ b/addons/website_hr_recruitment/__openerp__.py
@@ -8,7 +8,7 @@ OpenERP Contact Form
====================
""",
- 'depends': ['website_partner', 'hr_recruitment', 'website_mail'],
+ 'depends': ['website_partner', 'hr_recruitment', 'website_mail', 'website_form'],
'data': [
'security/ir.model.access.csv',
'security/website_hr_recruitment_security.xml',
diff --git a/addons/website_hr_recruitment/controllers/main.py b/addons/website_hr_recruitment/controllers/main.py
index 4bef1dfe0d8..5e5dd48e18b 100644
--- a/addons/website_hr_recruitment/controllers/main.py
+++ b/addons/website_hr_recruitment/controllers/main.py
@@ -89,40 +89,3 @@ class website_hr_recruitment(http.Controller):
'error': error,
'default': default,
})
-
- @http.route('/jobs/thankyou', methods=['POST'], type='http', auth="public", website=True)
- def jobs_thankyou(self, **post):
- error = {}
- for field_name in ["partner_name", "phone", "email_from"]:
- if not post.get(field_name):
- error[field_name] = 'missing'
- if error:
- request.session['website_hr_recruitment_error'] = error
- ufile = post.pop('ufile')
- if ufile:
- error['ufile'] = 'reset'
- request.session['website_hr_recruitment_default'] = post
- return request.redirect('/jobs/apply/%s' % post.get("job_id"))
-
- # public user can't create applicants (duh)
- env = request.env(user=SUPERUSER_ID)
- value = {
- 'name': '%s\'s Application' % post.get('partner_name'),
- }
- for f in ['email_from', 'partner_name', 'description']:
- value[f] = post.get(f)
- for f in ['department_id', 'job_id']:
- value[f] = int(post.get(f) or 0)
- # Retro-compatibility for saas-3. "phone" field should be replace by "partner_phone" in the template in trunk.
- value['partner_phone'] = post.pop('phone', False)
-
- applicant = env['hr.applicant'].create(value)
- if post['ufile']:
- name = applicant.partner_name if applicant.partner_name else applicant.name
- applicant.message_post(
- body = _("%s's Application \n From: %s \n\n %s \n") % (name, applicant.email_from or "", applicant.description or ""),
- attachments = [(post['ufile'].filename, post['ufile'].read())],
- content_subtype = 'plaintext',
- subtype = "hr_recruitment.mt_applicant_hired")
-
- return request.render("website_hr_recruitment.thankyou", {})
diff --git a/addons/website_hr_recruitment/data/config_data.xml b/addons/website_hr_recruitment/data/config_data.xml
index fe6192d351f..ee934bb3ca4 100644
--- a/addons/website_hr_recruitment/data/config_data.xml
+++ b/addons/website_hr_recruitment/data/config_data.xml
@@ -16,6 +16,10 @@
open
-
+
+
+ True
+ Apply for a Job
+
diff --git a/addons/website_hr_recruitment/models/__init__.py b/addons/website_hr_recruitment/models/__init__.py
index b9c76c98f6e..331192a2437 100644
--- a/addons/website_hr_recruitment/models/__init__.py
+++ b/addons/website_hr_recruitment/models/__init__.py
@@ -1,2 +1,3 @@
import hr_job
import hr_recruitment_source
+import hr_applicant
diff --git a/addons/website_hr_recruitment/models/hr_applicant.py b/addons/website_hr_recruitment/models/hr_applicant.py
new file mode 100644
index 00000000000..de1070ad43f
--- /dev/null
+++ b/addons/website_hr_recruitment/models/hr_applicant.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+
+from openerp.osv import osv
+
+class hr_applicant(osv.Model):
+ _inherit = 'hr.applicant'
+
+ def website_form_input_filter(self, request, values):
+ if 'partner_name' in values:
+ values.setdefault('name', '%s\'s Application' % values['partner_name'])
+ return values
diff --git a/addons/website_hr_recruitment/static/src/js/website_hr_recruitment_tour.js b/addons/website_hr_recruitment/static/src/js/website_hr_recruitment_tour.js
new file mode 100644
index 00000000000..94297e88746
--- /dev/null
+++ b/addons/website_hr_recruitment/static/src/js/website_hr_recruitment_tour.js
@@ -0,0 +1,92 @@
+odoo.define('website_hr_recruitment.tour', function(require) {
+ 'use strict';
+
+ var base = require('web_editor.base');
+ var core = require('web.core');
+ var Tour = require('web.Tour');
+ var Model = require('web.Model');
+ var Session = require('web.Session');
+
+ base.ready().done(function () {
+ Tour.register({
+ id: 'website_hr_recruitment_tour',
+ name: "Test the hr applicants form",
+ path: '/jobs/apply/3',
+ mode: 'test',
+ steps: [
+ {
+ title: "Complete name",
+ element: "input[name=partner_name]",
+ sampleText: "John Smith"
+ },
+ {
+ title: "Complete Email",
+ element: "input[name=email_from]",
+ sampleText: "john@smith.com"
+ },
+ {
+ title: "Complete phone number",
+ element: "input[name=partner_phone]",
+ sampleText: "118.218"
+ },
+ {
+ title: "Complete Subject",
+ element: "textarea[name=description]",
+ sampleText: "A completely useless message"
+ },
+ // TODO: Upload a file ?
+ {
+ title: "Send the form",
+ element: ".o_website_form_send"
+ },
+ {
+ title: "Check the form is submited without errors",
+ waitFor: ".oe_structure:has(h1:contains('Thank you!'))"
+ }
+ ]
+ });
+
+ Tour.register({
+ id: 'website_hr_recruitment_tour_results',
+ name: "Check the hr applicants form created records",
+ path: '',
+ mode: 'test',
+ steps: [
+ {
+ title: "Check hr.applicant record has been created",
+ waitFor: ".o-apps",
+ onload: function (tour) {
+ var hrDef = new Model("hr.applicant").call(
+ "search_read",
+ [
+ [
+ ['partner_name' , '=' , 'John Smith'],
+ ['partner_phone' , '=' , '118.218'],
+ ['email_from' , '=' , 'john@smith.com'],
+ ['description' , 'like', 'A completely useless message']
+ ],
+ []
+ ]
+ );
+ var success = function(model, data) {
+ if(data.length) {
+ $('body').append('');
+ }
+ };
+ hrDef.then(_.bind(success, this, 'hr_applicant'));
+ }
+ },
+ {
+ title: "Check hr.applicant record has been created",
+ waitFor: "#website_form_success_test_tour_hr_applicant"
+ },
+ {
+ title: "Final Step",
+ waitFor: "html"
+ }
+ ]
+ });
+ });
+
+ return {};
+});
diff --git a/addons/website_hr_recruitment/tests/__init__.py b/addons/website_hr_recruitment/tests/__init__.py
new file mode 100644
index 00000000000..3b663956ae8
--- /dev/null
+++ b/addons/website_hr_recruitment/tests/__init__.py
@@ -0,0 +1 @@
+import test_website_hr_recruitment
\ No newline at end of file
diff --git a/addons/website_hr_recruitment/tests/test_website_hr_recruitment.py b/addons/website_hr_recruitment/tests/test_website_hr_recruitment.py
new file mode 100644
index 00000000000..d3cb0150bd9
--- /dev/null
+++ b/addons/website_hr_recruitment/tests/test_website_hr_recruitment.py
@@ -0,0 +1,7 @@
+import openerp.tests
+@openerp.tests.common.at_install(False)
+@openerp.tests.common.post_install(True)
+class TestWebsiteHrRecruitmentForm(openerp.tests.HttpCase):
+ def test_tour(self):
+ self.phantom_js("/", "odoo.__DEBUG__.services['web.Tour'].run('website_hr_recruitment_tour', 'test')", "odoo.__DEBUG__.services['web.Tour'].tours.website_hr_recruitment_tour", login="admin")
+ self.phantom_js("/", "odoo.__DEBUG__.services['web.Tour'].run('website_hr_recruitment_tour_results', 'test')", "odoo.__DEBUG__.services['web.Tour'].tours.website_hr_recruitment_tour_results", login="admin")
diff --git a/addons/website_hr_recruitment/views/templates.xml b/addons/website_hr_recruitment/views/templates.xml
index f8df63cef5c..4550da6db4e 100644
--- a/addons/website_hr_recruitment/views/templates.xml
+++ b/addons/website_hr_recruitment/views/templates.xml
@@ -140,60 +140,66 @@
Apply Job
-