summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
Diffstat (limited to 'addons')
-rw-r--r--addons/web/static/src/js/framework/ajax.js47
-rw-r--r--addons/website_crm/__init__.py2
-rw-r--r--addons/website_crm/__openerp__.py6
-rw-r--r--addons/website_crm/controllers/main.py109
-rw-r--r--addons/website_crm/data/website_crm_data.xml7
-rw-r--r--addons/website_crm/models/__init__.py1
-rw-r--r--addons/website_crm/models/crm_lead.py8
-rw-r--r--addons/website_crm/static/src/js/website_crm_tour.js103
-rw-r--r--addons/website_crm/tests/__init__.py1
-rw-r--r--addons/website_crm/tests/test_website_crm.py7
-rw-r--r--addons/website_crm/views/website_crm.xml173
-rw-r--r--addons/website_crm_score/controllers/main.py43
-rw-r--r--addons/website_form/__init__.py2
-rw-r--r--addons/website_form/__openerp__.py18
-rw-r--r--addons/website_form/controllers/__init__.py (renamed from addons/website_crm/controllers/__init__.py)0
-rw-r--r--addons/website_form/controllers/main.py217
-rw-r--r--addons/website_form/models/__init__.py1
-rw-r--r--addons/website_form/models/models.py47
-rw-r--r--addons/website_form/static/src/js/website_form.js168
-rw-r--r--addons/website_form/static/src/less/website_form.less54
-rw-r--r--addons/website_form/static/src/xml/website_form.xml25
-rw-r--r--addons/website_form/views/assets.xml17
-rw-r--r--addons/website_hr_recruitment/__openerp__.py2
-rw-r--r--addons/website_hr_recruitment/controllers/main.py37
-rw-r--r--addons/website_hr_recruitment/data/config_data.xml6
-rw-r--r--addons/website_hr_recruitment/models/__init__.py1
-rw-r--r--addons/website_hr_recruitment/models/hr_applicant.py11
-rw-r--r--addons/website_hr_recruitment/static/src/js/website_hr_recruitment_tour.js92
-rw-r--r--addons/website_hr_recruitment/tests/__init__.py1
-rw-r--r--addons/website_hr_recruitment/tests/test_website_hr_recruitment.py7
-rw-r--r--addons/website_hr_recruitment/views/templates.xml106
-rw-r--r--addons/website_issue/__init__.py0
-rw-r--r--addons/website_issue/__openerp__.py18
-rw-r--r--addons/website_issue/data/website_issue_data.xml15
34 files changed, 1047 insertions, 305 deletions
diff --git a/addons/web/static/src/js/framework/ajax.js b/addons/web/static/src/js/framework/ajax.js
index 444784ef0fd..f8392287b26 100644
--- a/addons/web/static/src/js/framework/ajax.js
+++ b/addons/web/static/src/js/framework/ajax.js
@@ -2,6 +2,7 @@ odoo.define('web.ajax', function (require) {
"use strict";
var time = require('web.time');
+var utils = require('web.utils');
function genericJsonRpc (fct_name, params, fct) {
var data = {
@@ -274,6 +275,51 @@ function get_file(options) {
timer = setTimeout(waitLoop, CHECK_INTERVAL);
};
+function post (controller_url, data) {
+
+ var progressHandler = function (deferred) {
+ return function (state) {
+ if(state.lengthComputable) {
+ deferred.notify({
+ h_loaded: utils.human_size(state.loaded),
+ h_total : utils.human_size(state.total),
+ loaded : state.loaded,
+ total : state.total,
+ pcent : Math.round((state.loaded/state.total)*100)
+ });
+ }
+ };
+ };
+
+ var Def = $.Deferred();
+ var compatibility = !(typeof(FormData));
+ var postData = compatibility ? new FormDataCompatibility() : new FormData();
+
+ $.each(data, function(i,val) {
+ postData.append(i, val);
+ });
+
+ var xhr = new XMLHttpRequest();
+ if(compatibility) {
+ postData.setContentTypeHeader(xhr);
+ postData = postData.buildBody();
+ }
+
+ if(xhr.upload) xhr.upload.addEventListener('progress', progressHandler(Def), false);
+
+ var ajaxDef = $.ajax(controller_url, {
+ xhr: function() {return xhr;},
+ data: postData,
+ processData: false,
+ contentType: false,
+ type: 'POST'
+ }).then(function (data) {Def.resolve(data);})
+ .fail(function (data) {Def.reject(data);});
+
+ return Def;
+}
+
+
var loadXML = (function () {
var loading = false;
@@ -315,6 +361,7 @@ return {
loadJS: loadJS,
loadXML: loadXML,
get_file: get_file,
+ post: post,
};
});
diff --git a/addons/website_crm/__init__.py b/addons/website_crm/__init__.py
index ee5959455ad..bff786c0885 100644
--- a/addons/website_crm/__init__.py
+++ b/addons/website_crm/__init__.py
@@ -1 +1 @@
-import controllers
+import models
diff --git a/addons/website_crm/__openerp__.py b/addons/website_crm/__openerp__.py
index b265efde01b..98eb8000673 100644
--- a/addons/website_crm/__openerp__.py
+++ b/addons/website_crm/__openerp__.py
@@ -3,17 +3,17 @@
'category': 'Website',
'website': 'https://www.odoo.com/page/website-builder',
'summary': 'Create Leads From Contact Form',
- 'version': '1.0',
+ 'version': '2.0',
'description': """
OpenERP Contact Form
====================
""",
- 'depends': ['website_partner', 'crm'],
+ 'depends': ['website_form','website_partner', 'crm'],
'data': [
'data/website_crm_data.xml',
'views/website_crm.xml',
],
'installable': True,
- 'auto_install': False,
+ 'auto_install': True,
}
diff --git a/addons/website_crm/controllers/main.py b/addons/website_crm/controllers/main.py
deleted file mode 100644
index e92a2483f37..00000000000
--- a/addons/website_crm/controllers/main.py
+++ /dev/null
@@ -1,109 +0,0 @@
-# -*- coding: utf-8 -*-
-import base64
-
-import werkzeug
-import werkzeug.urls
-
-from openerp import http, SUPERUSER_ID
-from openerp.http import request
-from openerp.tools.translate import _
-
-
-class contactus(http.Controller):
-
- def generate_google_map_url(self, street, city, city_zip, country_name):
- url = "http://maps.googleapis.com/maps/api/staticmap?center=%s&sensor=false&zoom=8&size=298x298" % werkzeug.url_quote_plus(
- '%s, %s %s, %s' % (street, city, city_zip, country_name)
- )
- return url
-
- @http.route(['/page/website.contactus', '/page/contactus'], type='http', auth="public", website=True)
- def contact(self, **kwargs):
- values = {}
- for field in ['description', 'partner_name', 'phone', 'contact_name', 'email_from', 'name']:
- if kwargs.get(field):
- values[field] = kwargs.pop(field)
- values.update(kwargs=kwargs.items())
- return request.website.render("website.contactus", values)
-
- def create_lead(self, request, values, kwargs):
- """ Allow to be overrided """
- cr, context = request.cr, request.context
- return request.registry['crm.lead'].create(cr, SUPERUSER_ID, values, context=dict(context, mail_create_nosubscribe=True))
-
- def preRenderThanks(self, values, kwargs):
- """ Allow to be overrided """
- company = request.website.company_id
- return {
- 'google_map_url': self.generate_google_map_url(company.street, company.city, company.zip, company.country_id and company.country_id.name_get()[0][1] or ''),
- '_values': values,
- '_kwargs': kwargs,
- }
-
- def get_contactus_response(self, values, kwargs):
- values = self.preRenderThanks(values, kwargs)
- return request.website.render(kwargs.get("view_callback", "website_crm.contactus_thanks"), values)
-
- @http.route(['/crm/contactus'], type='http', auth="public", website=True)
- def contactus(self, **kwargs):
- def dict_to_str(title, dictvar):
- ret = "\n\n%s" % title
- for field in dictvar:
- ret += "\n%s" % field
- return ret
-
- _TECHNICAL = ['show_info', 'view_from', 'view_callback'] # Only use for behavior, don't stock it
- _BLACKLIST = ['id', 'create_uid', 'create_date', 'write_uid', 'write_date', 'user_id', 'active'] # Allow in description
- _REQUIRED = ['name', 'contact_name', 'email_from', 'description'] # Could be improved including required from model
-
- post_file = [] # List of file to add to ir_attachment once we have the ID
- post_description = [] # Info to add after the message
- values = {}
-
- values['medium_id'] = request.registry['ir.model.data'].xmlid_to_res_id(request.cr, SUPERUSER_ID, 'crm.crm_medium_website')
-
- for field_name, field_value in kwargs.items():
- if hasattr(field_value, 'filename'):
- post_file.append(field_value)
- elif field_name in request.registry['crm.lead']._fields and field_name not in _BLACKLIST:
- values[field_name] = field_value
- elif field_name not in _TECHNICAL: # allow to add some free fields or blacklisted field like ID
- post_description.append("%s: %s" % (field_name, field_value))
-
- if "name" not in kwargs and values.get("contact_name"): # if kwarg.name is empty, it's an error, we cannot copy the contact_name
- values["name"] = values.get("contact_name")
- # fields validation : Check that required field from model crm_lead exists
- error = set(field for field in _REQUIRED if not values.get(field))
-
- if error:
- values = dict(values, error=error, kwargs=kwargs.items())
- return request.website.render(kwargs.get("view_from", "website.contactus"), values)
-
- # description is required, so it is always already initialized
- if post_description:
- values['description'] += dict_to_str(_("Custom Fields: "), post_description)
-
- if kwargs.get("show_info"):
- post_description = []
- environ = request.httprequest.headers.environ
- post_description.append("%s: %s" % ("IP", environ.get("REMOTE_ADDR")))
- post_description.append("%s: %s" % ("USER_AGENT", environ.get("HTTP_USER_AGENT")))
- post_description.append("%s: %s" % ("ACCEPT_LANGUAGE", environ.get("HTTP_ACCEPT_LANGUAGE")))
- post_description.append("%s: %s" % ("REFERER", environ.get("HTTP_REFERER")))
- values['description'] += dict_to_str(_("Environ Fields: "), post_description)
-
- lead_id = self.create_lead(request, dict(values, user_id=False), kwargs)
- values.update(lead_id=lead_id)
- if lead_id:
- for field_value in post_file:
- attachment_value = {
- 'name': field_value.filename,
- 'res_name': field_value.filename,
- 'res_model': 'crm.lead',
- 'res_id': lead_id,
- 'datas': base64.encodestring(field_value.read()),
- 'datas_fname': field_value.filename,
- }
- request.registry['ir.attachment'].create(request.cr, SUPERUSER_ID, attachment_value, context=request.context)
-
- return self.get_contactus_response(values, kwargs)
diff --git a/addons/website_crm/data/website_crm_data.xml b/addons/website_crm/data/website_crm_data.xml
index 8d6c916fffa..a116a6ae411 100644
--- a/addons/website_crm/data/website_crm_data.xml
+++ b/addons/website_crm/data/website_crm_data.xml
@@ -1,12 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="1">
+ <record id="crm.model_crm_lead" model="ir.model">
+ <field name="website_form_default_field_id" ref="crm.field_crm_lead_description" />
+ <field name="website_form_access">True</field>
+ <field name="website_form_label">Create a lead</field>
+ </record>
<record id="action_open_website" model="ir.actions.act_url">
<field name="name">Website Contact Form</field>
<field name="target">self</field>
<field name="url">/page/contactus</field>
</record>
+
<record id="base.open_menu" model="ir.actions.todo">
<field name="action_id" ref="action_open_website"/>
<field name="state">open</field>
@@ -19,6 +25,5 @@
<field name="use_leads">True</field>
<field name="use_opportunities" eval="False"/>
</record>
-
</data>
</openerp>
diff --git a/addons/website_crm/models/__init__.py b/addons/website_crm/models/__init__.py
new file mode 100644
index 00000000000..4970d37d01b
--- /dev/null
+++ b/addons/website_crm/models/__init__.py
@@ -0,0 +1 @@
+import crm_lead
diff --git a/addons/website_crm/models/crm_lead.py b/addons/website_crm/models/crm_lead.py
new file mode 100644
index 00000000000..ce1fad60cdd
--- /dev/null
+++ b/addons/website_crm/models/crm_lead.py
@@ -0,0 +1,8 @@
+from openerp import models, SUPERUSER_ID
+
+class Lead(models.Model):
+ _inherit = 'crm.lead'
+
+ def website_form_input_filter(self, request, values):
+ values.setdefault('medium_id', request.registry['ir.model.data'].xmlid_to_res_id(request.cr, SUPERUSER_ID, 'crm.crm_medium_website'))
+ return values
diff --git a/addons/website_crm/static/src/js/website_crm_tour.js b/addons/website_crm/static/src/js/website_crm_tour.js
new file mode 100644
index 00000000000..e17c0dafac6
--- /dev/null
+++ b/addons/website_crm/static/src/js/website_crm_tour.js
@@ -0,0 +1,103 @@
+odoo.define('website_crm.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_crm_tour',
+ name: "Test the contact us form",
+ path: '/page/contactus',
+ mode: 'test',
+ steps: [
+ {
+ title: "Complete name",
+ element: "input[name=contact_name]",
+ sampleText: "John Smith",
+ },
+ {
+ title: "Complete phone number",
+ element: "input[name=phone]",
+ sampleText: "118.218"
+ },
+ {
+ title: "Complete Email",
+ element: "input[name=email_from]",
+ sampleText: "john@smith.com"
+ },
+ {
+ title: "Complete Company",
+ element: "input[name=partner_name]",
+ sampleText: "Odoo S.A."
+ },
+ {
+ title: "Complete Subject",
+ element: "input[name=name]",
+ sampleText: "Useless message"
+ },
+ {
+ title: "Complete Subject",
+ element: "textarea[name=description]",
+ sampleText: "Even more useless message"
+ },
+ {
+ title: "Send the form",
+ element: ".o_website_form_send"
+ },
+ {
+ title: "Check we were redirected to the success page",
+ waitFor: "#wrap:has(h1:contains('Thanks')):has(div.alert-success)"
+ }
+ ]
+ });
+
+ Tour.register({
+ id: 'website_crm_tour_results',
+ name: "Test the contact us form created records",
+ path: '',
+ mode: 'test',
+ steps: [
+ {
+ title: "Check crm.lead record has been created",
+ waitFor: ".o-apps",
+ onload: function (tour) {
+ var leadDef = new Model("crm.lead").call(
+ "search_read",
+ [
+ [
+ ['contact_name', '=' , 'John Smith'],
+ ['phone' , '=' , '118.218'],
+ ['email_from' , '=' , 'john@smith.com'],
+ ['partner_name', '=' , 'Odoo S.A.'],
+ ['name' , '=' , 'Useless message'],
+ ['description' , 'like', 'Even more useless message']
+ ],
+ []
+ ]
+ );
+ var success = function(model, data) {
+ if(data.length) {
+ $('body').append('<div id="website_form_success_test_tour_'+model+'"></div>');
+ }
+ };
+ leadDef.then(_.bind(success, this, 'crm_lead'));
+ }
+ },
+ {
+ title: "Check crm.lead record has been created",
+ waitFor: "#website_form_success_test_tour_crm_lead"
+ },
+ {
+ title: "Final Step",
+ waitFor: "html"
+ }
+ ]
+ });
+ });
+
+ return {};
+});
diff --git a/addons/website_crm/tests/__init__.py b/addons/website_crm/tests/__init__.py
new file mode 100644
index 00000000000..7c1af2a6172
--- /dev/null
+++ b/addons/website_crm/tests/__init__.py
@@ -0,0 +1 @@
+import test_website_crm
diff --git a/addons/website_crm/tests/test_website_crm.py b/addons/website_crm/tests/test_website_crm.py
new file mode 100644
index 00000000000..ddf627d9fbe
--- /dev/null
+++ b/addons/website_crm/tests/test_website_crm.py
@@ -0,0 +1,7 @@
+import openerp.tests
+@openerp.tests.common.at_install(False)
+@openerp.tests.common.post_install(True)
+class TestWebsiteCrm(openerp.tests.HttpCase):
+ def test_tour(self):
+ self.phantom_js("/", "odoo.__DEBUG__.services['web.Tour'].run('website_crm_tour', 'test')", "odoo.__DEBUG__.services['web.Tour'].tours.website_crm_tour", login="admin")
+ self.phantom_js("/", "odoo.__DEBUG__.services['web.Tour'].run('website_crm_tour_results', 'test')", "odoo.__DEBUG__.services['web.Tour'].tours.website_crm_tour_results", login="admin") \ No newline at end of file
diff --git a/addons/website_crm/views/website_crm.xml b/addons/website_crm/views/website_crm.xml
index 124a2ac6cc6..baee365ae01 100644
--- a/addons/website_crm/views/website_crm.xml
+++ b/addons/website_crm/views/website_crm.xml
@@ -1,95 +1,94 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
-<data>
-
-<template id="contactus_form" name="Contact Form" inherit_id="website.contactus" customize_show="True">
- <xpath expr="//div[@name='mail_button']" position="replace">
- <form action="/crm/contactus" method="post" class="form-horizontal mt32 js_website_submit_form" enctype="multipart/form-data">
- <div t-attf-class="form-group #{error and 'contact_name' in error and 'has-error' or ''}">
- <label class="col-md-3 col-sm-4 control-label" for="contact_name">Your Name</label>
- <div class="col-md-7 col-sm-8">
- <input type="text" class="form-control" name="contact_name" required="True" t-attf-value="#{contact_name or ''}"/>
- </div>
- </div>
- <div t-attf-class="form-group #{error and 'phone' in error and 'has-error' or ''}">
- <label class="col-md-3 col-sm-4 control-label" for="phone">Phone Number</label>
- <div class="col-md-7 col-sm-8">
- <input type="text" class="form-control" name="phone" t-attf-value="#{phone or ''}"/>
- </div>
- </div>
- <div name="email_from_container" t-attf-class="form-group #{error and 'email_from' in error and 'has-error' or ''}">
- <label class="col-md-3 col-sm-4 control-label" for="email_from">Email</label>
- <div class="col-md-7 col-sm-8">
- <input type="email" class="form-control" name="email_from" required="True" t-attf-value="#{email_from or ''}"/>
- </div>
- </div>
- <div t-attf-class="form-group #{error and 'name' in error and 'has-error' or ''}">
- <label class="col-md-3 col-sm-4 control-label" for="name">Subject</label>
- <div class="col-md-7 col-sm-8">
- <input type="text" class="form-control" name="name" t-attf-value="#{name or ''}"/>
- </div>
- </div>
- <div t-attf-class="form-group #{error and 'description' in error and 'has-error' or ''}">
- <label class="col-md-3 col-sm-4 control-label" for="description">Your Question</label>
- <div class="col-md-7 col-sm-8">
- <textarea class="form-control" name="description" style="min-height: 120px" required="True"><t t-esc="description or ''"/></textarea>
- </div>
- </div>
- <t t-foreach="kwargs" t-as="kwarg">
- <input type="hidden" t-att-name="kwarg[0]" t-att-value="kwarg[1]"/>
- </t>
-
- <div class="form-group">
- <div class="col-md-offset-3 col-sm-offset-4 col-sm-8 col-md-7">
- <button type="submit" class="btn btn-primary btn-lg">Send</button>
+ <data>
+ <template id="contactus_form" name="Contact Form" inherit_id="website.contactus" customize_show="True">
+ <xpath expr="//div[@name='mail_button']" position="replace">
+ <div>
+ <form action="/website_form/" method="post" data-model_name="crm.lead" data-success_page="/page/website_crm.contactus_thanks" class="s_website_form form-horizontal container-fluid mt32" enctype="multipart/form-data">
+ <div class="form-group form-field o_website_form_required_custom">
+ <label class="col-md-3 col-sm-4 control-label" for="contact_name">Your Name</label>
+ <div class="col-md-7 col-sm-8">
+ <input type="text" class="form-control o_website_form_input" name="contact_name" required=""/>
+ </div>
+ </div>
+ <div class="form-group form-field">
+ <label class="col-md-3 col-sm-4 control-label" for="phone">Phone Number</label>
+ <div class="col-md-7 col-sm-8">
+ <input type="text" class="form-control o_website_form_input" placeholder="e.g. (+32).81.81.37.00" name="phone"/>
+ </div>
+ </div>
+ <div class="form-group form-field o_website_form_required_custom">
+ <label class="col-md-3 col-sm-4 control-label" for="email_from">Email</label>
+ <div class="col-md-7 col-sm-8">
+ <input type="text" class="form-control o_website_form_input" name="email_from" required=""/>
+ </div>
+ </div>
+ <div class="form-group form-field o_website_form_required_custom">
+ <label class="col-md-3 col-sm-4 control-label" for="partner_name">Your Company</label>
+ <div class="col-md-7 col-sm-8">
+ <input type="text" class="form-control o_website_form_input" name="partner_name" required=""/>
+ </div>
+ </div>
+ <div class="form-group form-field o_website_form_required">
+ <label class="col-md-3 col-sm-4 control-label" for="name">Subject</label>
+ <div class="col-md-7 col-sm-8">
+ <input type="text" class="form-control o_website_form_input" name="name" required=""/>
+ </div>
+ </div>
+ <div class="form-group form-field o_website_form_required_custom">
+ <label class="col-md-3 col-sm-4 control-label" for="description">Your Question</label>
+ <div class="col-md-7 col-sm-8">
+ <textarea class="form-control o_website_form_input" name="description" required=""></textarea>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-md-offset-3 col-sm-offset-4 col-sm-8 col-md-7">
+ <button class="btn btn-primary btn-lg o_website_form_send">Send</button>
+ <span id="o_website_form_result"></span>
+ </div>
+ </div>
+ </form>
</div>
- </div>
- </form>
- </xpath>
-</template>
-
-<template id="contactus_form_company_name" name="Company Name" inherit_id="website_crm.contactus_form" customize_show="True">
- <xpath expr="//div[@name='email_from_container']" position="after">
- <div t-attf-class="form-group #{error and 'partner_name' in error and 'has-error' or ''}">
- <label class="col-md-3 col-sm-4 control-label" for="partner_name">My Company</label>
- <div class="col-md-7 col-sm-8">
- <input type="text" class="form-control" name="partner_name" required="True" t-attf-value="#{partner_name or ''}"/>
- </div>
- </div>
- </xpath>
-</template>
+ </xpath>
+ </template>
-<template id="contactus_thanks" name="Contact us">
- <t t-call="website.layout">
- <div id="wrap">
- <div class="oe_structure"/>
- <div class="container">
- <h1>Thanks!</h1>
- <div class="row">
- <div class="col-md-8">
- <div class="alert alert-success">
- Your message has been sent successfully.
- <button type="button" class="close" data-dismiss="alert">&amp;times;</button>
+ <template id="contactus_thanks" name="Contact us">
+ <t t-call="website.layout">
+ <div id="wrap">
+ <div class="oe_structure"/>
+ <div class="container">
+ <h1>Thanks!</h1>
+ <div class="row">
+ <div class="col-md-8">
+ <div class="alert alert-success">
+ Your message has been sent successfully.
+ <button type="button" class="close" data-dismiss="alert">&amp;times;</button>
+ </div>
+ <p>
+ We will get back to you shortly.
+ </p><p class="mt64">
+ If you have an emergency, do not hesitate to contact us by phone:
+ </p>
+ <ul class="list-unstyled">
+ <li><i class="fa fa-phone"></i> : <span t-field="res_company.phone"/></li>
+ <li><i class="fa fa-envelope"></i> : <span t-field="res_company.email"/></li>
+ </ul>
+ </div>
+ <div class="col-md-4">
+ <t t-call="website.company_description"/>
+ </div>
</div>
- <p>
- We will get back to you shortly.
- </p><p class="mt64">
- If you have an emergency, do not hesitate to contact us by phone:
- </p>
- <ul class="list-unstyled">
- <li><i class="fa fa-phone"></i> : <span t-field="res_company.phone"/></li>
- <li><i class="fa fa-envelope"></i> : <span t-field="res_company.email"/></li>
- </ul>
</div>
- <div class="col-md-4">
- <t t-call="website.company_description"/>
- </div>
- </div>
- </div>
- <div class="oe_structure"/>
- </div>
- </t>
-</template>
+ <div class="oe_structure"/>
+ </div>
+ </t>
+ </template>
+
+ <template id="website_crm_tests" name="Website CRM Tests" inherit_id="web.assets_common">
+ <xpath expr="." position="inside">
+ <script type="text/javascript" src="/website_crm/static/src/js/website_crm_tour.js"></script>
+ </xpath>
+ </template>
-</data>
+ </data>
</openerp>
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/<string:model_name>', 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 += '<br/><b>%s</b>: <b>%s</b>' % (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('<a href="%s" target="_blank"><b>%s</b></a>' % (url_encoded, url_encoded))
body = '<br/>'.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
index 8ee9bae18d9..8ee9bae18d9 100644
--- a/addons/website_crm/controllers/__init__.py
+++ b/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 <model>
+ @http.route('/website_form/<string:model_name>', type='http', auth="public", methods=['POST'], website=True)
+ def website_form(self, model_name, **kwargs):
+ model_record = request.env['ir.model'].search([('model', '=', model_name), ('website_form_access', '=', True)])
+
+ if not model_record:
+ return json.dumps(False)
+
+ try:
+ data = self.extract_data(model_record, ** kwargs)
+
+ # If we encounter an issue while extracting data
+ except ValidationError, e:
+ # I couldn't find a cleaner way to pass data to an exception
+ return json.dumps({'error_fields' : e.args[0]})
+
+ try:
+ id_record = self.insert_record(request, model_record, data['record'], data['custom'], data.get('meta'))
+ if id_record:
+ self.insert_attachment(model_record, id_record, data['attachments'])
+
+ # Some fields have additionnal SQL constraints that we can't check generically
+ # Ex: crm.lead.probability which is a float between 0 and 1
+ # TODO: How to get the name of the erroneous field ?
+ except IntegrityError:
+ return json.dumps(False)
+
+ request.session['form_builder_model'] = model_record.name
+ request.session['form_builder_id'] = id_record
+
+ return json.dumps({'id': id_record})
+
+ # Constants string to make custom info and metadata readable on a text field
+
+ _custom_label = "%s\n___________\n\n" % _("Custom infos") # Title for custom fields
+ _meta_label = "%s\n________\n\n" % _("Metadata") # Title for meta data
+
+ # Dict of dynamically called filters following type of field to be fault tolerent
+
+ def identity(self, field_label, field_input):
+ return field_input
+
+ def integer(self, field_label, field_input):
+ return int(field_input)
+
+ def floating(self, field_label, field_input):
+ return float(field_input)
+
+ def boolean(self, field_label, field_input):
+ return bool(field_input)
+
+ def 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': _('<p>Attached files : </p>'),
+ 'model': model.model,
+ 'message_type': 'comment',
+ 'no_auto_thread': False,
+ 'res_id': id_record,
+ 'attachment_ids': [(6, 0, orphan_attachment_ids)],
+ }
+ mail_id = request.env['mail.message'].sudo().create(values)
+ else:
+ # If the model is mail.mail then we have no other choice but to
+ # attach the custom binary field files on the attachment_ids field.
+ for attachment_id_id in orphan_attachment_ids:
+ record.attachment_ids = [(4, attachment_id_id)]
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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates xml:space="preserve">
+
+ <!-- Success status -->
+ <t t-name="website_form.status_success">
+ <span id="o_website_form_result" class="text-success ml8">
+ <i class="fa fa-check mr4"/>The form has been sent successfully.
+ </span>
+ </t>
+
+ <!-- Missing field status -->
+ <t t-name="website_form.status_invalid">
+ <span id="o_website_form_result" class="text-danger ml8">
+ <i class="fa fa-close mr4"/>Please fill in the form correctly.
+ </span>
+ </t>
+
+ <!-- Error status -->
+ <t t-name="website_form.status_error">
+ <span id="o_website_form_result" class="text-danger ml8">
+ <i class="fa fa-close mr4"/>An error has occured, the form has not been sent.
+ </span>
+ </t>
+
+</templates>
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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data>
+
+ <template id="assets_frontend" name="Website Form Assets Frontend" inherit_id="website.assets_frontend">
+ <xpath expr="." position="inside">
+ <link rel='stylesheet' href='/website_form/static/src/less/website_form.less'/>
+ <script type="text/javascript" src="/website_form/static/src/js/website_form.js"/>
+
+ <!-- Bootstrap datetimepicker is currently only included in the backend -->
+ <link rel="stylesheet" href="/web/static/lib/bootstrap-datetimepicker/css/bootstrap-datetimepicker.css"/>
+ <script type="text/javascript" src="/web/static/lib/bootstrap-datetimepicker/src/js/bootstrap-datetimepicker.js"/>
+ </xpath>
+ </template>
+
+ </data>
+</openerp>
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 @@
<field name="action_id" ref="action_open_website"/>
<field name="state">open</field>
</record>
-
+ <record id="hr_recruitment.model_hr_applicant" model="ir.model">
+ <field name="website_form_default_field_id" ref="hr_recruitment.field_hr_applicant_description" />
+ <field name="website_form_access">True</field>
+ <field name="website_form_label">Apply for a Job</field>
+ </record>
</data>
</openerp>
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('<div id="website_form_success_test_tour_'+model+'"></div>');
+ }
+ };
+ 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 @@
<template id="apply">
<t t-call="website.layout">
<t t-set="additional_title">Apply Job</t>
- <div id="wrap">
- <div class="container">
- <h1 class="text-center">
- Job Application Form
- </h1>
- <h2 t-if="job" class="text-center text-muted">
- <span t-field="job.name"/>
- </h2>
+
+ <div id="wrap" class="container">
+ <h1 class="text-center">
+ Job Application Form
+ </h1>
+ <h2 t-if="job" class="text-center text-muted">
+ <span t-field="job.name"/>
+ </h2>
- <div class="row">
- <section id="forms">
- <!-- TODO Multilingual form action support ? -->
- <form class="form-horizontal mt32 js_website_submit_form" action="/jobs/thankyou" method="post" enctype="multipart/form-data">
- <input type="hidden" t-att-value="job and job.department_id.id or False" name="department_id"/>
- <input type="hidden" t-att-value="job and job.id or False" name="job_id"/>
- <div t-attf-class="form-group #{error.get('partner_name') and 'has-error' or ''}">
- <label class="col-md-3 col-sm-4 control-label" for="partner_name">Your Name</label>
- <div class="col-md-7 col-sm-8">
- <input type="text" t-att-value="default.get('partner_name')" class="form-control" name="partner_name" required="True"/>
- </div>
+ <div class="row">
+ <section id="forms">
+ <form action="/website_form/" method="post" class="s_website_form form-horizontal container-fluid mt32" enctype="multipart/form-data" data-model_name="hr.applicant" data-success_page="/page/website_hr_recruitment.thankyou" t-att-data-form_field_department_id="job and job.department_id.id or False" t-att-data-form_field_job_id="job and job.id or False">
+ <div class="form-group form-field o_website_form_required_custom">
+ <div class="col-md-3 col-sm-4 text-right">
+ <label class="control-label" for="partner_name">Your Name</label>
</div>
- <div t-attf-class="form-group #{error.get('email_from') and 'has-error' or ''}">
- <label class="col-md-3 col-sm-4 control-label" for="email_from">Your Email</label>
- <div class="col-md-7 col-sm-8">
- <input type="email" t-att-value="default.get('email_from')" class="form-control" name="email_from" required="True"/>
- </div>
+ <div class="col-md-7 col-sm-8">
+ <input type="text" class="form-control o_website_form_input" name="partner_name" required=""/>
</div>
- <div t-attf-class="form-group #{error.get('phone') and 'has-error' or ''}">
- <label class="col-md-3 col-sm-4 control-label" for="phone">Your Phone</label>
- <div class="col-md-7 col-sm-8">
- <input type="text" t-att-value="default.get('phone')" class="form-control" name="phone" required="True"/>
- </div>
+ </div>
+ <div class="form-group form-field o_website_form_required_custom">
+ <div class="col-md-3 col-sm-4 text-right">
+ <label class="control-label" for="email_from">Your Email</label>
</div>
- <div class="form-group">
- <label class="col-md-3 col-sm-4 control-label" for="description">Short Introduction</label>
- <div class="col-md-7 col-sm-8">
- <textarea class="form-control" t-esc="default.get('description')" name="description" style="min-height: 120px"/>
- </div>
+ <div class="col-md-7 col-sm-8">
+ <input type="text" class="form-control o_website_form_input" name="email_from" required=""/>
</div>
- <div class="form-group">
- <label class="col-md-3 col-sm-4 control-label" for="ufile">Resume</label>
- <div class="col-md-7 col-sm-8">
- <input class="input-file" id="fileInput" type="file" name="ufile"/>
- <span t-if="error.get('ufile')">Please send again your resume.</span>
- </div>
+ </div>
+ <div class="form-group form-field o_website_form_required_custom">
+ <div class="col-md-3 col-sm-4 text-right">
+ <label class="control-label" for="partner_phone">Your Phone Number</label>
</div>
- <div class="form-group">
- <div class="col-md-offset-3 col-sm-offset-4 col-sm-8 col-md-7">
- <button type="submit" class="btn btn-primary">Submit</button>
- </div>
+ <div class="col-md-7 col-sm-8">
+ <input type="text" class="form-control o_website_form_input" name="partner_phone" required=""/>
</div>
- </form>
- </section>
- </div>
+ </div>
+ <div class="form-group form-field">
+ <div class="col-md-3 col-sm-4 text-right">
+ <label class="control-label" for="description">Short Introduction</label>
+ </div>
+ <div class="col-md-7 col-sm-8">
+ <textarea class="form-control o_website_form_input" name="description"></textarea>
+ </div>
+ </div>
+ <div class="form-group form-field o_website_form_custom">
+ <div class="col-md-3 col-sm-4 text-right">
+ <label class="control-label" for="Resume">Resume</label>
+ </div>
+ <div class="col-md-7 col-sm-8">
+ <input type="file" class="form-control o_website_form_input" name="Resume"/>
+ </div>
+ </div>
+ <div class="form-group">
+ <div class="col-md-offset-3 col-sm-offset-4 col-sm-8 col-md-7">
+ <button class="btn btn-primary btn-lg o_website_form_send">Submit</button>
+ <span id="o_website_form_result"></span>
+ </div>
+ </div>
+ </form>
+ </section>
</div>
</div>
</t>
@@ -292,5 +298,11 @@
</xpath>
</template>
+<template id="website_hr_recuitment_tests" name="Website HR Recruitment Tests" inherit_id="web.assets_common">
+ <xpath expr="." position="inside">
+ <script type="text/javascript" src="/website_hr_recruitment/static/src/js/website_hr_recruitment_tour.js"></script>
+ </xpath>
+</template>
+
</data>
</openerp>
diff --git a/addons/website_issue/__init__.py b/addons/website_issue/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/addons/website_issue/__init__.py
diff --git a/addons/website_issue/__openerp__.py b/addons/website_issue/__openerp__.py
new file mode 100644
index 00000000000..b6efff77bc8
--- /dev/null
+++ b/addons/website_issue/__openerp__.py
@@ -0,0 +1,18 @@
+{
+ 'name': 'Issues Form',
+ 'category': 'Bridge',
+ 'summary': 'Create Issues From Contact Form',
+ 'version': '1.0',
+ 'description': """
+OpenERP Contact Form
+====================
+
+ """,
+ 'author': 'OpenERP SA',
+ 'depends': ['website_form','project_issue'],
+ 'data': [
+ 'data/website_issue_data.xml',
+ ],
+ 'installable': True,
+ 'auto_install': True,
+}
diff --git a/addons/website_issue/data/website_issue_data.xml b/addons/website_issue/data/website_issue_data.xml
new file mode 100644
index 00000000000..a588449d30d
--- /dev/null
+++ b/addons/website_issue/data/website_issue_data.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+ <data noupdate="1">
+ <record id="project_issue.model_project_issue" model="ir.model">
+ <field name="website_form_default_field_id" ref="project_issue.field_project_issue_description" />
+ <field name="website_form_access">True</field>
+ <field name="website_form_label">Create an issue</field>
+ </record>
+<!-- Template to blacklist field
+ <record id="project_issue.field_project_issue_{field_name}" model="ir.model.fields">
+ <field name="website_form_blacklisted">True</field>
+ </record>
+-->
+ </data>
+</openerp>