[MOV] website_portal, portal: move customer portal to portal module

This commit moves the whole customer portal to the portal module.
It now completely uses portal and http_routing features and is not
dependent on website anymore.

An override of web controller is added in portal in order to redirect
portal users to /my instead of /web. That way once having the customer
portal installed all share users are correctly redirected to their
account.

All modules defining customer portal templates and controllers are
updated accordingly.
This commit is contained in:
Thibault Delavallée
2017-08-08 14:58:49 +02:00
parent 86aa1b81bc
commit ea27c1b7a3
40 changed files with 472 additions and 500 deletions
+1
View File
@@ -2,3 +2,4 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import controllers
from . import wizard
+2
View File
@@ -16,8 +16,10 @@ This module contains most code coming from odoo v10 website_portal. Purpose
of this module is to allow the display of a customer portal without having
a dependency towards website edition and customization capabilities.""",
'data': [
'data/portal_data.xml',
'views/assets.xml',
'views/portal_templates.xml',
'wizard/portal_wizard_views.xml',
],
'depends': ['http_routing', 'mail'],
}
+1
View File
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import web
from . import portal
+120 -2
View File
@@ -5,9 +5,10 @@ import math
from werkzeug import urls
from odoo import fields as odoo_fields, tools, _
from odoo.exceptions import ValidationError
from odoo.http import Controller, request, route
# --------------------------------------------------
# Misc tools
# --------------------------------------------------
@@ -71,14 +72,131 @@ def pager(url, total, page=1, step=30, scope=5, url_args=None):
}
def get_records_pager(ids, current):
if current.id in ids:
idx = ids.index(current.id)
return {
'prev_record': idx != 0 and current.browse(ids[idx - 1]).website_url,
'next_record': idx < len(ids) - 1 and current.browse(ids[idx + 1]).website_url
}
return {}
class CustomerPortal(Controller):
MANDATORY_BILLING_FIELDS = ["name", "phone", "email", "street", "city", "country_id"]
OPTIONAL_BILLING_FIELDS = ["zipcode", "state_id", "vat", "company_name"]
_items_per_page = 20
def _get_archive_groups(self, model, domain=None, fields=None, groupby="create_date", order="create_date desc"):
if not model:
return []
if domain is None:
domain = []
if fields is None:
fields = ['name', 'create_date']
groups = []
for group in request.env[model]._read_group_raw(domain, fields=fields, groupby=groupby, orderby=order):
dates, label = group[groupby]
date_begin, date_end = dates.split('/')
groups.append({
'date_begin': odoo_fields.Date.to_string(odoo_fields.Date.from_string(date_begin)),
'date_end': odoo_fields.Date.to_string(odoo_fields.Date.from_string(date_end)),
'name': label,
'item_count': group[groupby + '_count']
})
return groups
def _prepare_portal_layout_values(self):
# get customer sales rep
sales_user = False
partner = request.env.user.partner_id
if partner.user_id and not partner.user_id._is_public():
sales_user = partner.user_id
return {
'sales_user': sales_user,
'page_name': 'home',
'archive_groups': [],
}
@route(['/my', '/my/home'], type='http', auth="user", website=True)
def home(self, **kw):
values = self._prepare_portal_layout_values()
return request.render("portal.portal_layout", values)
return request.render("portal.portal_my_home", values)
@route(['/my/account'], type='http', auth='user', website=True)
def account(self, redirect=None, **post):
partner = request.env.user.partner_id
values = {
'error': {},
'error_message': []
}
if post:
error, error_message = self.details_form_validate(post)
values.update({'error': error, 'error_message': error_message})
values.update(post)
if not error:
values = {key: post[key] for key in self.MANDATORY_BILLING_FIELDS}
values.update({key: post[key] for key in self.OPTIONAL_BILLING_FIELDS if key in post})
values.update({'zip': values.pop('zipcode', '')})
partner.sudo().write(values)
if redirect:
return request.redirect(redirect)
return request.redirect('/my/home')
countries = request.env['res.country'].sudo().search([])
states = request.env['res.country.state'].sudo().search([])
values.update({
'partner': partner,
'countries': countries,
'states': states,
'has_check_vat': hasattr(request.env['res.partner'], 'check_vat'),
'redirect': redirect,
'page_name': 'my_details',
})
response = request.render("portal.portal_my_details", values)
response.headers['X-Frame-Options'] = 'DENY'
return response
def details_form_validate(self, data):
error = dict()
error_message = []
# Validation
for field_name in self.MANDATORY_BILLING_FIELDS:
if not data.get(field_name):
error[field_name] = 'missing'
# email validation
if data.get('email') and not tools.single_email_re.match(data.get('email')):
error["email"] = 'error'
error_message.append(_('Invalid Email! Please enter a valid email address.'))
# vat validation
partner = request.env["res.partner"]
if data.get("vat") and hasattr(partner, "check_vat"):
partner_dummy = partner.new({
'vat': data['vat'],
'country_id': (int(data['country_id'])
if data.get('country_id') else False),
})
try:
partner_dummy.check_vat()
except ValidationError:
error["vat"] = 'error'
# error message for empty required fields
if [err for err in tools.pycompat.values(error) if err == 'missing']:
error_message.append(_('Some required fields are empty.'))
unknown = [k for k in data if k not in self.MANDATORY_BILLING_FIELDS + self.OPTIONAL_BILLING_FIELDS]
if unknown:
error['common'] = 'Unknown field'
error_message.append("Unknown field '%s'" % ','.join(unknown))
return error, error_message
+20
View File
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import http
from odoo.addons.web.controllers.main import Home
from odoo.http import request
class Home(Home):
@http.route()
def index(self, *args, **kw):
if request.session.uid and not request.env['res.users'].sudo().browse(request.session.uid).has_group('base.group_user'):
return http.local_redirect('/my', query=request.params, keep_hash=True)
return super(Home, self).index(*args, **kw)
def _login_redirect(self, uid, redirect=None):
if not redirect and not request.env['res.users'].sudo().browse(uid).has_group('base.group_user'):
return '/my'
return super(Home, self)._login_redirect(uid, redirect=redirect)
@@ -4,7 +4,7 @@
<record id="mail_template_data_portal_welcome" model="mail.template">
<field name="name">Portal: new user</field>
<field name="model_id" ref="website_portal.model_portal_wizard_user"/>
<field name="model_id" ref="portal.model_portal_wizard_user"/>
<field name="email_to">${object.partner_id.email}</field>
<field name="subject">Your Odoo account at ${user.company_id.name}</field>
<field name="lang">${object.partner_id.lang}</field>
@@ -1,7 +1,8 @@
odoo.define('website_portal', function(require) {
'use strict';
require('website.website');
odoo.define('portal.portal', function(require) {
'use strict';
$(document).ready(function () {
if(!$('.o_portal').length) {
return $.Deferred().reject("DOM doesn't contain '.o_portal'");
@@ -45,3 +46,4 @@ odoo.define('website_portal', function(require) {
});
}
});
});
@@ -1,13 +1,11 @@
odoo.define('website_portal.tour', function (require) {
odoo.define('portal.tour', function (require) {
'use strict';
var tour = require("web_tour.tour");
var base = require("web_editor.base");
tour.register('portal_load_homepage', {
test: true,
url: '/my',
wait_for: base.ready()
},
[
{
+6 -1
View File
@@ -1,10 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="assets_frontend" inherit_id="web.assets_frontend" name="Portal Assets">
<xpath expr="//script[last()]" position="after">
<xpath expr="//link[last()]" position="after">
<link rel="stylesheet" type="text/less" href="/portal/static/src/less/website.ui.less"/>
<link rel="stylesheet" type="text/less" href="/portal/static/src/less/website.wrapwrap.less"/>
<link rel="stylesheet" type="text/less" href="/portal/static/src/less/bootstrap.extend.less"/>
</xpath>
<xpath expr="//link[last()]" position="after">
<script type="text/javascript" src="/portal/static/src/js/portal.js"></script>
<script type="text/javascript" src="/portal/static/src/js/portal_tour.js"></script>
</xpath>
</template>
</odoo>
+253
View File
@@ -62,11 +62,258 @@
<template id="portal_layout" name="Portal Layout">
<t t-call="portal.frontend_layout">
<div class="container mt16 o_portal">
<div class="oe_structure">
<div t-if="not no_breadcrumbs" class="row">
<div class="col-sm-12">
<ol class="breadcrumb o_portal_submenu">
<li><a href="/my/home"><i class="fa fa-home"/></a></li>
<li t-if="page_name == 'my_details'">Your Details</li>
</ol>
</div>
</div>
</div>
</div>
<div id="wrap" class='portal-wrap'>
<div class="container mb64">
<t t-if="my_details">
<div class="row">
<div t-attf-class="col-md-9 col-lg-8">
<t t-raw="0"/>
</div>
<div id="o_my_sidebar" class="col-md-3 col-lg-offset-1 o_my_sidebar">
<div class="o_my_contact" t-if="sales_user">
<t t-call="portal.portal_contact"/>
</div>
<div class="o_portal_my_details">
<h3 class="page-header">Your Details <a href="/my/account" class="btn btn-default btn-xs">Change</a></h3>
<div t-field="user_id.partner_id" t-options='{"widget": "contact", "fields": ["email", "phone", "address", "name"]}'/>
</div>
<div class="o_my_archives" t-if="archive_groups">
<t t-call="portal.portal_archive_groups"/>
</div>
</div>
</div>
</t>
<t t-else="">
<t t-raw="0"/>
</t>
</div>
</div>
</t>
</template>
<template id="portal_show_sign_in" inherit_id="portal.frontend_layout" name="Show Sign In">
<xpath expr="//ul[@id='top_menu']" position="inside">
<li class="divider" groups="base.group_public"/>
<li groups="base.group_public">
<a t-attf-href="/web/login">
<b>Sign in</b>
</a>
</li>
</xpath>
</template>
<template id="portal_my_home" name="My Portal">
<t t-call="portal.portal_layout">
<t t-set="my_details" t-value="True"/>
<div class="o_my_home_content">
<div class="oe_structure"></div>
<h3 class="page-header">Your Documents</h3>
<ul class="o_portal_docs list-group">
</ul>
</div>
<div class="col-md-12 oe_structure"/>
</t>
</template>
<!--
The search bar is composed of 2 buttons : a "filter by" and a "sort by". Changing the 'sortby'
criteria will keep the number of page, query params, ... Changing the 'filterby' param will
redirect the user to the beginning of document list, keeping query parameters.
These 2 buttons can be prepended by a advanced search input, to activate it, search_input need
to be initialized at 'True' and the content of the t-call is the list of li elements searchable.
:param dict searchbar_sortings : containing the sort criteria like
{'date': {'label': _('Newest'), 'order': 'create_date desc'}}
:param string sortby : name of the sort criteria
:param dict searchbar_filters : containing the filter criteria like
{'open': {'label': _('In Progress'), 'domain': [('state', '=', 'open')]}}
:param string filterby : name of the filter criteria
:param default_url : the base url of the pages (like '/my/orders')
:param boolean o_portal_search_panel : set to True to active the input search
:param html $0 : content of the t-call, <li>a</li><li>b</li>
-->
<template id="portal_searchbar" name="Portal Search Bar">
<t t-if='searchbar_inputs'>
<div class='hidden-xs pull-right o_portal_search_panel'>
<div class="input-group">
<div class="input-group-btn">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
<span id="search_label">Search</span> <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li t-foreach='searchbar_inputs' t-as='input' t-att-class="search_in == searchbar_inputs[input]['input'] and 'active'">
<a t-att-href="'#' + searchbar_inputs[input]['input']"><span t-raw="searchbar_inputs[input]['label']"/></a>
</li>
</ul>
</div>
<input type="text" class="form-control search_panel_fixed_width" t-att-value='search' name="search"/>
<span class="input-group-btn">
<button class="btn btn-default search-submit" type="button"><span class="fa fa-search"></span></button>
</span>
</div>
</div>
</t>
<div t-if="searchbar_sortings" class="dropdown pull-right mr4">
<button id="portal_searchbar_sortby" class="search_panel_fixed_width btn btn-default" type="button" data-toggle="dropdown">
<span class="fa fa-sort fa-lg" />
<span class='hidden-xs hidden-sm hidden-md' t-esc="searchbar_sortings[sortby].get('label', 'Newest')"/>
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="portal_searchbar_sortby">
<li t-foreach="searchbar_sortings" t-as="option" t-attf-class="#{sortby == option and 'active'}">
<a t-att-href="request.httprequest.path + '?' + keep_query('*', sortby=option)">
<span t-esc="searchbar_sortings[option].get('label')"/>
</a>
</li>
</ul>
</div>
<div t-if="searchbar_filters" class="dropdown pull-right mr4">
<button id="portal_searchbar_filters" class="search_panel_fixed_width btn btn-default" type="button" data-toggle="dropdown">
<span class="fa fa-filter fa-lg" />
<span class='hidden-xs hidden-sm hidden-md' t-esc="searchbar_filters[filterby].get('label', 'All')"/>
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="portal_searchbar_filters">
<li t-foreach="searchbar_filters" t-as="option" t-att-class="filterby == option and 'active'">
<a t-att-href="default_url + '?' + keep_query('*', filterby=option)">
<span t-esc="searchbar_filters[option].get('label')"/>
</a>
</li>
</ul>
</div>
</template>
<template id="portal_contact" name="Contact">
<h3 class="page-header"><t t-esc="title"/></h3>
<h5 t-esc="sales_user.name"/>
<p><a t-att-href="'mailto:'+sales_user.email" t-esc="sales_user.email"/></p>
<p t-esc="sales_user.phone"/>
<p t-esc="sales_user.city"/>
</template>
<template id="portal_archive_groups" name="Portal Archive Groups">
<h3 class="page-header">Archives</h3>
<ul class="nav nav-pills nav-stacked">
<t t-foreach="archive_groups" t-as="group">
<t t-if="group['date_begin'] == date">
<li class="active">
<a t-att-href="default_url" t-ignore="True"><t t-esc="group['name']"/><span class="pull-right badge" t-esc="group['item_count']"/></a>
</li>
</t>
<t t-if="group['date_begin'] != date">
<li>
<a t-ignore="True" t-attf-href="#{default_url}?date_begin=#{group['date_begin']}&amp;date_end=#{group['date_end']}"><t t-esc="group['name']"/><span class="pull-right badge" t-esc="group['item_count']"/></a>
</li>
</t>
</t>
</ul>
</template>
<template id="portal_my_details">
<t t-call="portal.portal_layout">
<t t-set="additional_title">Your Contact Details</t>
<h3>Your Details</h3>
<form action="/my/account" method="post">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<div class="row o_portal_details">
<div class="col-md-8">
<div class="row">
<div class="col-md-12">
<div t-if="error_message" class="alert alert-danger">
<t t-foreach="error_message" t-as="err"><t t-esc="err"/><br /></t>
</div>
</div>
<div t-attf-class="form-group #{error.get('name') and 'has-error' or ''} col-lg-6">
<label class="control-label" for="name">Your Name</label>
<input type="text" name="name" class="form-control" t-att-value="name or partner.name" />
</div>
<div t-attf-class="form-group #{error.get('email') and 'has-error' or ''} col-lg-6">
<label class="control-label" for="email">Email</label>
<input type="email" name="email" class="form-control" t-att-value="email or partner.email" />
</div>
<div t-attf-class="form-group #{error.get('phone') and 'has-error' or ''} col-lg-6">
<label class="control-label" for="phone">Phone</label>
<input type="tel" name="phone" class="form-control" t-att-value="phone or partner.phone" />
</div>
<div class="clearfix" />
<div t-attf-class="form-group #{error.get('company_name') and 'has-error' or ''} col-lg-6">
<label class="control-label label-optional" for="company_name">Company Name</label>
<input type="text" name="company_name" class="form-control" t-att-value="company_name or partner.company_name"/>
</div>
<div t-if="has_check_vat" t-attf-class="form-group #{error.get('vat') and 'has-error' or ''} col-lg-6">
<label class="control-label label-optional" for="vat">VAT Number</label>
<input type="text" name="vat" class="form-control" t-att-value="vat or partner.vat" />
</div>
<div class="clearfix" />
<div t-attf-class="form-group #{error.get('street') and 'has-error' or ''} col-lg-6">
<label class="control-label label-optional" for="street">Street</label>
<input type="text" name="street" class="form-control" t-att-value="street or partner.street"/>
</div>
<div t-attf-class="form-group #{error.get('city') and 'has-error' or ''} col-lg-6">
<label class="control-label" for="city">City</label>
<input type="text" name="city" class="form-control" t-att-value="city or partner.city" />
</div>
<div t-attf-class="form-group #{error.get('zip') and 'has-error' or ''} col-lg-6">
<label class="control-label" for="zipcode">Zip / Postal Code</label>
<input type="text" name="zipcode" class="form-control" t-att-value="zipcode or partner.zip" />
</div>
<div t-attf-class="form-group #{error.get('country_id') and 'has-error' or ''} col-lg-6">
<label class="control-label" for="country_id">Country</label>
<select name="country_id" class="form-control">
<option value="">Country...</option>
<t t-foreach="countries or []" t-as="country">
<option t-att-value="country.id" t-att-selected="country.id == partner.country_id.id">
<t t-esc="country.name" />
</option>
</t>
</select>
</div>
<div t-attf-class="form-group #{error.get('state_id') and 'has-error' or ''} col-lg-6">
<label class="control-label label-optional" for="state_id">State / Province</label>
<select name="state_id" class="form-control">
<option value="">select...</option>
<t t-foreach="states or []" t-as="state">
<option t-att-value="state.id" style="display:none;" t-att-data-country_id="state.country_id.id" t-att-selected="state.id == partner.state_id.id">
<t t-esc="state.name" />
</option>
</t>
</select>
</div>
<input type="hidden" name="redirect" t-att-value="redirect"/>
</div>
<div class="clearfix">
<button type="submit" class="btn btn-primary pull-right mb32 ">
Confirm
<span class="fa fa-long-arrow-right" />
</button>
</div>
</div>
</div>
</form>
</t>
</template>
<template id="record_pager" name="Portal Record Pager">
<t t-if='prev_record or next_record'>
<div class="ml8 record_pager btn-group btn-group-xs pull-right" role="group">
<a t-att-class="'btn btn-default %s' % ('disabled' if not prev_record else '')" t-att-href="prev_record or '#'" ><i class="fa fa-arrow-left" aria-hidden="true"></i></a>
<a t-att-class="'btn btn-default %s' % ('disabled' if not next_record else '')" t-att-href="next_record or '#'" ><i class="fa fa-arrow-right" aria-hidden="true"></i></a>
</div>
</t>
</template>
@@ -83,4 +330,10 @@
</li>
</ul>
</template>
<template id="my_account_link" name="Link to frontend portal" inherit_id="portal.frontend_layout">
<xpath expr="//li[@id='o_logout']" position="before">
<li><a href="/my/home" role="menuitem">My Account</a></li>
</xpath>
</template>
</odoo>
@@ -182,7 +182,7 @@ class PortalWizardUser(models.TransientModel):
raise UserError(_('You must have an email address in your User Preferences to send emails.'))
# determine subject and body in the portal user's language
template = self.env.ref('website_portal.mail_template_data_portal_welcome')
template = self.env.ref('portal.mail_template_data_portal_welcome')
for wizard_line in self:
lang = wizard_line.user_id.lang
partner = wizard_line.user_id.partner_id
+4 -3
View File
@@ -458,6 +458,9 @@ class Home(http.Controller):
ensure_db()
return werkzeug.utils.redirect(redirect, 303)
def _login_redirect(self, uid, redirect=None):
return redirect if redirect else '/web'
@http.route('/web/login', type='http', auth="none")
def web_login(self, redirect=None, **kw):
ensure_db()
@@ -479,9 +482,7 @@ class Home(http.Controller):
uid = request.session.authenticate(request.session.db, request.params['login'], request.params['password'])
if uid is not False:
request.params['login_success'] = True
if not redirect:
redirect = '/web'
return http.redirect_with_hash(redirect)
return http.redirect_with_hash(self._login_redirect(uid, redirect=redirect))
request.uid = old_uid
values['error'] = _("Wrong login/password")
else:
+2
View File
@@ -241,6 +241,8 @@
<t t-call="web.less_helpers"/>
<link rel="stylesheet" type="text/less" href="/web/static/src/less/import_bootstrap.less"/>
<link rel='stylesheet' type="text/less" href='/web/static/src/less/portal.less'/>
<script type="text/javascript" src="/web/static/src/js/services/session.js"></script>
</template>
@@ -216,17 +216,6 @@
</xpath>
</template>
<template id="show_sign_in" customize_show="True" inherit_id="website.layout" name="Show Sign In">
<xpath expr="//ul[@id='top_menu']" position="inside">
<li class="divider" groups="base.group_public"/>
<li groups="base.group_public">
<a t-attf-href="/web/login">
<b>Sign in</b>
</a>
</li>
</xpath>
</template>
<template id="footer_custom" inherit_id="website.layout" name="Footer">
<xpath expr="//div[@id='footer']" position="replace">
<div class="oe_structure" id="footer" t-if='not no_footer'>
@@ -1,5 +1,5 @@
<odoo>
<template id="portal_my_home_menu_invoice" name="Portal layout : invoice menu entries" inherit_id="website_portal.portal_layout" priority="30">
<template id="portal_my_home_menu_invoice" name="Portal layout : invoice menu entries" inherit_id="portal.portal_layout" priority="30">
<xpath expr="//ol[contains(@class,'o_portal_submenu')]" position="inside">
<li t-if="page_name == 'invoice'" class="active">
Invoices
@@ -7,7 +7,7 @@
</xpath>
</template>
<template id="portal_my_home_invoice" name="Portal My Home : invoice entries" inherit_id="website_portal.portal_my_home" priority="30">
<template id="portal_my_home_invoice" name="Portal My Home : invoice entries" inherit_id="portal.portal_my_home" priority="30">
<xpath expr="//ul[contains(@class,'o_portal_docs')]" position="inside">
<li t-if="invoice_count" class="list-group-item">
<span class="badge" t-esc="invoice_count"/>
@@ -17,9 +17,9 @@
</template>
<template id="portal_my_invoices" name="My Invoices and Payments">
<t t-call="website_portal.portal_layout">
<t t-call="portal.portal_layout">
<h3>Invoices
<t t-call="website_portal.portal_searchbar"/>
<t t-call="portal.portal_searchbar"/>
</h3>
<t t-if="not invoices">
<p>There are currently no invoices and payments for your account.</p>
@@ -12,7 +12,7 @@ Publish and Assign Partner
==========================
""",
'depends': ['base_geolocalize', 'crm', 'account',
'website_partner', 'website_google_map', 'website_portal'],
'website_partner', 'website_google_map', 'portal'],
'data': [
'data/crm_partner_assign_data.xml',
'security/ir.model.access.csv',
@@ -11,15 +11,14 @@ from odoo import fields
from odoo import http
from odoo.http import request
from odoo.addons.http_routing.models.ir_http import slug, unslug
from odoo.addons.portal.controllers.portal import CustomerPortal
from odoo.addons.website_partner.controllers.main import WebsitePartnerPage
from odoo.tools import pycompat
from odoo.tools.translate import _
from odoo.addons.website_portal.controllers.main import website_account
class WebsiteAccount(website_account):
class WebsiteAccount(CustomerPortal):
def get_domain_my_lead(self, user):
return [
@@ -192,7 +192,7 @@
<script type="text/javascript" src="/website_crm_partner_assign/static/src/js/crm_partner_assign.js" />
</xpath>
</template>
<template id="portal_my_home_menu_lead" name="Portal layout : lead menu entry" inherit_id="website_portal.portal_layout" priority="15">
<template id="portal_my_home_menu_lead" name="Portal layout : lead menu entry" inherit_id="portal.portal_layout" priority="15">
<xpath expr="//ol[contains(@class,'o_portal_submenu')]" position="inside">
<li t-if="page_name == 'lead' or lead">
<a t-if="lead" t-attf-href="/my/leads?{{ keep_query() }}">
@@ -219,9 +219,9 @@
</xpath>
</template>
<template id="portal_my_home_lead" name="Portal My Home : lead entries" inherit_id="website_portal.portal_my_home" priority="15">
<template id="portal_my_home_lead" name="Portal My Home : lead entries" inherit_id="portal.portal_my_home" priority="15">
<xpath expr="//div[contains(@class,'o_my_home_content')]" position="inside">
<t t-if="user.commercial_partner_id.grade_id">
<t t-if="user_id.commercial_partner_id.grade_id">
<h3 t-if="lead_count" class="page-header">
<a href="/my/leads">My Leads
<span class="badge ml8" t-esc="lead_count"/>
@@ -240,10 +240,10 @@
</template>
<template id="portal_my_leads" name="My Leads">
<t t-call="website_portal.portal_layout">
<t t-call="portal.portal_layout">
<h3 class="page-header">
My Leads
<t t-call="website_portal.portal_searchbar"/>
<t t-call="portal.portal_searchbar"/>
</h3>
<div t-if="not leads" class="alert alert-warning mt8" role="alert">
There are no leads.
@@ -275,10 +275,10 @@
</template>
<template id="portal_my_opportunities" name="My Opportunities">
<t t-call="website_portal.portal_layout">
<t t-call="portal.portal_layout">
<h3 class="page-header">
My Opportunities
<t t-call="website_portal.portal_searchbar"/>
<t t-call="portal.portal_searchbar"/>
<button name='new_opp' data-toggle="modal" data-target=".modal_new_opp" class="btn btn-default pull-right mr8"><i class="fa fa-plus fa-lg" /></button>
</h3>
<div class="modal fade modal_new_opp" role="form" aria-hidden="true">
@@ -350,7 +350,7 @@
</template>
<template id="portal_my_lead" name="My Lead">
<t t-call="website_portal.portal_layout">
<t t-call="portal.portal_layout">
<div class="panel panel-default">
<div class="panel-heading">
<div class="row">
@@ -516,7 +516,7 @@
</template>
<template id="portal_my_opportunity" name="My Opportunity">
<t t-call="website_portal.portal_layout">
<t t-call="portal.portal_layout">
<div class="panel panel-default">
<div class="panel-heading">
<div class="row">
+1 -1
View File
@@ -9,7 +9,7 @@
'depends': [
'website',
'payment',
'website_portal',
'portal',
],
'data': [
'views/website_payment_view.xml',
@@ -70,8 +70,8 @@
</t>
</template>
<template id="pay_meth_link" inherit_id="website_portal.portal_layout" customize_show="True">
<xpath expr="//div[@class='o_my_details']" position="inside">
<template id="pay_meth_link" inherit_id="portal.portal_layout" customize_show="True">
<xpath expr="//div[@class='o_portal_my_details']" position="inside">
<t t-if="request.env['payment.acquirer'].search([('website_published', '=', True), ('registration_view_template_id', '!=', False)])">
<div class='manage_payment_method mt16'><a href="/my/payment_method">Manage your payment acquirers</a></div>
</t>
-3
View File
@@ -1,5 +1,2 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import controllers
from . import wizard
-3
View File
@@ -13,8 +13,5 @@ Allows your customers to manage their account from a beautiful web interface.
'website', 'auth_signup'
],
'data': [
'views/website_portal_templates.xml',
'wizard/portal_wizard_views.xml',
'data/website_portal_data.xml',
],
}
@@ -1,4 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import main
-145
View File
@@ -1,145 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import http
from odoo.http import request
from odoo import tools
from odoo.tools import pycompat
from odoo.tools.translate import _
from odoo.exceptions import ValidationError
from odoo.fields import Date
def get_records_pager(ids, current):
if current.id in ids:
idx = ids.index(current.id)
return {
'prev_record': idx != 0 and current.browse(ids[idx - 1]).website_url,
'next_record': idx < len(ids) - 1 and current.browse(ids[idx + 1]).website_url
}
return {}
class website_account(http.Controller):
MANDATORY_BILLING_FIELDS = ["name", "phone", "email", "street", "city", "country_id"]
OPTIONAL_BILLING_FIELDS = ["zipcode", "state_id", "vat", "company_name"]
_items_per_page = 20
def _prepare_portal_layout_values(self):
""" prepare the values to render portal layout template. This returns the
data displayed on every portal pages.
"""
partner = request.env.user.partner_id
# get customer sales rep
if partner.user_id:
sales_rep = partner.user_id
else:
sales_rep = False
values = {
'sales_rep': sales_rep,
'company': request.website.company_id,
'user': request.env.user
}
return values
def _get_archive_groups(self, model, domain=None, fields=None, groupby="create_date", order="create_date desc"):
if not model:
return []
if domain is None:
domain = []
if fields is None:
fields = ['name', 'create_date']
groups = []
for group in request.env[model]._read_group_raw(domain, fields=fields, groupby=groupby, orderby=order):
dates, label = group[groupby]
date_begin, date_end = dates.split('/')
groups.append({
'date_begin': Date.to_string(Date.from_string(date_begin)),
'date_end': Date.to_string(Date.from_string(date_end)),
'name': label,
'item_count': group[groupby + '_count']
})
return groups
@http.route(['/my', '/my/home'], type='http', auth="user", website=True)
def account(self, **kw):
values = self._prepare_portal_layout_values()
return request.render("website_portal.portal_my_home", values)
@http.route(['/my/account'], type='http', auth='user', website=True)
def details(self, redirect=None, **post):
partner = request.env.user.partner_id
values = {
'error': {},
'error_message': []
}
if post:
error, error_message = self.details_form_validate(post)
values.update({'error': error, 'error_message': error_message})
values.update(post)
if not error:
values = {key: post[key] for key in self.MANDATORY_BILLING_FIELDS}
values.update({key: post[key] for key in self.OPTIONAL_BILLING_FIELDS if key in post})
values.update({'zip': values.pop('zipcode', '')})
partner.sudo().write(values)
if redirect:
return request.redirect(redirect)
return request.redirect('/my/home')
countries = request.env['res.country'].sudo().search([])
states = request.env['res.country.state'].sudo().search([])
values.update({
'partner': partner,
'countries': countries,
'states': states,
'has_check_vat': hasattr(request.env['res.partner'], 'check_vat'),
'redirect': redirect,
'page_name': 'my_details',
})
response = request.render("website_portal.details", values)
response.headers['X-Frame-Options'] = 'DENY'
return response
def details_form_validate(self, data):
error = dict()
error_message = []
# Validation
for field_name in self.MANDATORY_BILLING_FIELDS:
if not data.get(field_name):
error[field_name] = 'missing'
# email validation
if data.get('email') and not tools.single_email_re.match(data.get('email')):
error["email"] = 'error'
error_message.append(_('Invalid Email! Please enter a valid email address.'))
# vat validation
partner = request.env["res.partner"]
if data.get("vat") and hasattr(partner, "check_vat"):
partner_dummy = partner.new({
'vat': data['vat'],
'country_id': (int(data['country_id'])
if data.get('country_id') else False),
})
try:
partner_dummy.check_vat()
except ValidationError:
error["vat"] = 'error'
# error message for empty required fields
if [err for err in pycompat.values(error) if err == 'missing']:
error_message.append(_('Some required fields are empty.'))
unknown = [k for k in data if k not in self.MANDATORY_BILLING_FIELDS + self.OPTIONAL_BILLING_FIELDS]
if unknown:
error['common'] = 'Unknown field'
error_message.append("Unknown field '%s'" % ','.join(unknown))
return error, error_message
@@ -1,264 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="assets_frontend" inherit_id="website.assets_frontend" name="Account Manager">
<xpath expr="." position="inside">
<link rel='stylesheet' href='/website_portal/static/src/less/website_portal.less'/>
<script type="text/javascript" src="/website_portal/static/src/js/website_portal.js"></script>
<script type="text/javascript" src="/website_portal/static/src/js/website_portal_tour.js"></script>
</xpath>
</template>
<template id="portal_archive_groups" name="Portal Archive Groups">
<h3 class="page-header">Archives</h3>
<ul class="nav nav-pills nav-stacked">
<t t-foreach="archive_groups" t-as="group">
<t t-if="group['date_begin'] == date">
<li class="active">
<a t-att-href="default_url" t-ignore="True"><t t-esc="group['name']"/><span class="pull-right badge" t-esc="group['item_count']"/></a>
</li>
</t>
<t t-if="group['date_begin'] != date">
<li>
<a t-ignore="True" t-attf-href="#{default_url}?date_begin=#{group['date_begin']}&amp;date_end=#{group['date_end']}"><t t-esc="group['name']"/><span class="pull-right badge" t-esc="group['item_count']"/></a>
</li>
</t>
</t>
</ul>
</template>
<!--
The search bar is composed of 2 buttons : a "filter by" and a "sort by". Changing the 'sortby'
criteria will keep the number of page, query params, ... Changing the 'filterby' param will
redirect the user to the beginning of document list, keeping query parameters.
These 2 buttons can be prepended by a advanced search input, to activate it, search_input need
to be initialized at 'True' and the content of the t-call is the list of li elements searchable.
:param dict searchbar_sortings : containing the sort criteria like
{'date': {'label': _('Newest'), 'order': 'create_date desc'}}
:param string sortby : name of the sort criteria
:param dict searchbar_filters : containing the filter criteria like
{'open': {'label': _('In Progress'), 'domain': [('state', '=', 'open')]}}
:param string filterby : name of the filter criteria
:param default_url : the base url of the pages (like '/my/orders')
:param boolean o_portal_search_panel : set to True to active the input search
:param html $0 : content of the t-call, <li>a</li><li>b</li>
-->
<template id="portal_searchbar" name="Portal Search Bar">
<t t-if='searchbar_inputs'>
<div class='hidden-xs pull-right o_portal_search_panel'>
<div class="input-group">
<div class="input-group-btn">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
<span id="search_label">Search</span> <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li t-foreach='searchbar_inputs' t-as='input' t-att-class="search_in == searchbar_inputs[input]['input'] and 'active'">
<a t-att-href="'#' + searchbar_inputs[input]['input']"><span t-raw="searchbar_inputs[input]['label']"/></a>
</li>
</ul>
</div>
<input type="text" class="form-control search_panel_fixed_width" t-att-value='search' name="search"/>
<span class="input-group-btn">
<button class="btn btn-default search-submit" type="button"><span class="fa fa-search"></span></button>
</span>
</div>
</div>
</t>
<div t-if="searchbar_sortings" class="dropdown pull-right mr4">
<button id="portal_searchbar_sortby" class="search_panel_fixed_width btn btn-default" type="button" data-toggle="dropdown">
<span class="fa fa-sort fa-lg" />
<span class='hidden-xs hidden-sm hidden-md' t-esc="searchbar_sortings[sortby].get('label', 'Newest')"/>
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="portal_searchbar_sortby">
<li t-foreach="searchbar_sortings" t-as="option" t-attf-class="#{sortby == option and 'active'}">
<a t-att-href="request.httprequest.path + '?' + keep_query('*', sortby=option)">
<span t-esc="searchbar_sortings[option].get('label')"/>
</a>
</li>
</ul>
</div>
<div t-if="searchbar_filters" class="dropdown pull-right mr4">
<button id="portal_searchbar_filters" class="search_panel_fixed_width btn btn-default" type="button" data-toggle="dropdown">
<span class="fa fa-filter fa-lg" />
<span class='hidden-xs hidden-sm hidden-md' t-esc="searchbar_filters[filterby].get('label', 'All')"/>
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="portal_searchbar_filters">
<li t-foreach="searchbar_filters" t-as="option" t-att-class="filterby == option and 'active'">
<a t-att-href="default_url + '?' + keep_query('*', filterby=option)">
<span t-esc="searchbar_filters[option].get('label')"/>
</a>
</li>
</ul>
</div>
</template>
<template id="portal_layout" name="Portal Layout">
<t t-call="website.layout">
<div class="container mt16 o_portal">
<div class="oe_structure">
<div t-if="not no_breadcrumbs" class="row">
<div class="col-sm-12">
<ol class="breadcrumb o_portal_submenu">
<li><a href="/my/home"><i class="fa fa-home"/></a></li>
<li t-if="page_name == 'my_details'">Your Details</li>
</ol>
</div>
</div>
</div>
</div>
<div id="wrap" class='portal-wrap'>
<div class="container mb64">
<t t-if="my_details">
<div class="row">
<div t-attf-class="col-md-9 col-lg-8">
<t t-raw="0"/>
</div>
<div id="o_my_sidebar" class="col-md-3 col-lg-offset-1 o_my_sidebar">
<div class="o_my_contact" t-if="sales_rep">
<t t-call="website_portal.contact"/>
</div>
<div class="o_my_details">
<h3 class="page-header">Your Details <a href="/my/account" class="btn btn-default btn-xs">Change</a></h3>
<div t-field="user.partner_id" t-options='{"widget": "contact", "fields": ["email", "phone", "address", "name"]}'/>
</div>
<div class="o_my_archives" t-if="archive_groups">
<t t-call="website_portal.portal_archive_groups"/>
</div>
</div>
</div>
</t>
<t t-else="">
<t t-raw="0"/>
</t>
</div>
</div>
</t>
</template>
<template id="portal_my_home" name="My Portal">
<t t-call="website_portal.portal_layout">
<t t-set="my_details" t-value="True"/>
<div class="o_my_home_content">
<div class="oe_structure"></div>
<h3 class="page-header">Your Documents</h3>
<ul class="o_portal_docs list-group">
</ul>
</div>
<div class="col-md-12 oe_structure"/>
</t>
</template>
<template id="contact" name="Contact">
<h3 class="page-header">Salesperson</h3>
<h5 t-esc="sales_rep.name"/>
<p><a t-att-href="'mailto:'+sales_rep.email" t-esc="sales_rep.email"/></p>
<p t-esc="sales_rep.phone"/>
<p t-esc="sales_rep.city"/>
</template>
<template id="details">
<t t-call="website_portal.portal_layout">
<t t-set="additional_title">Your Contact Details</t>
<h3>Your Details</h3>
<form action="/my/account" method="post">
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
<div class="row o_portal_details">
<div class="col-md-8">
<div class="row">
<div class="col-md-12">
<div t-if="error_message" class="alert alert-danger">
<t t-foreach="error_message" t-as="err"><t t-esc="err"/><br /></t>
</div>
</div>
<div t-attf-class="form-group #{error.get('name') and 'has-error' or ''} col-lg-6">
<label class="control-label" for="name">Your Name</label>
<input type="text" name="name" class="form-control" t-att-value="name or partner.name" />
</div>
<div t-attf-class="form-group #{error.get('email') and 'has-error' or ''} col-lg-6">
<label class="control-label" for="email">Email</label>
<input type="email" name="email" class="form-control" t-att-value="email or partner.email" />
</div>
<div t-attf-class="form-group #{error.get('phone') and 'has-error' or ''} col-lg-6">
<label class="control-label" for="phone">Phone</label>
<input type="tel" name="phone" class="form-control" t-att-value="phone or partner.phone" />
</div>
<div class="clearfix" />
<div t-attf-class="form-group #{error.get('company_name') and 'has-error' or ''} col-lg-6">
<label class="control-label label-optional" for="company_name">Company Name</label>
<input type="text" name="company_name" class="form-control" t-att-value="company_name or partner.company_name"/>
</div>
<div t-if="has_check_vat" t-attf-class="form-group #{error.get('vat') and 'has-error' or ''} col-lg-6">
<label class="control-label label-optional" for="vat">VAT Number</label>
<input type="text" name="vat" class="form-control" t-att-value="vat or partner.vat" />
</div>
<div class="clearfix" />
<div t-attf-class="form-group #{error.get('street') and 'has-error' or ''} col-lg-6">
<label class="control-label label-optional" for="street">Street</label>
<input type="text" name="street" class="form-control" t-att-value="street or partner.street"/>
</div>
<div t-attf-class="form-group #{error.get('city') and 'has-error' or ''} col-lg-6">
<label class="control-label" for="city">City</label>
<input type="text" name="city" class="form-control" t-att-value="city or partner.city" />
</div>
<div t-attf-class="form-group #{error.get('zip') and 'has-error' or ''} col-lg-6">
<label class="control-label" for="zipcode">Zip / Postal Code</label>
<input type="text" name="zipcode" class="form-control" t-att-value="zipcode or partner.zip" />
</div>
<div t-attf-class="form-group #{error.get('country_id') and 'has-error' or ''} col-lg-6">
<label class="control-label" for="country_id">Country</label>
<select name="country_id" class="form-control">
<option value="">Country...</option>
<t t-foreach="countries or []" t-as="country">
<option t-att-value="country.id" t-att-selected="country.id == partner.country_id.id">
<t t-esc="country.name" />
</option>
</t>
</select>
</div>
<div t-attf-class="form-group #{error.get('state_id') and 'has-error' or ''} col-lg-6">
<label class="control-label label-optional" for="state_id">State / Province</label>
<select name="state_id" class="form-control">
<option value="">select...</option>
<t t-foreach="states or []" t-as="state">
<option t-att-value="state.id" style="display:none;" t-att-data-country_id="state.country_id.id" t-att-selected="state.id == partner.state_id.id">
<t t-esc="state.name" />
</option>
</t>
</select>
</div>
<input type="hidden" name="redirect" t-att-value="redirect"/>
</div>
<div class="clearfix">
<button type="submit" class="btn btn-primary pull-right mb32 ">
Confirm
<span class="fa fa-long-arrow-right" />
</button>
</div>
</div>
</div>
</form>
</t>
</template>
<template id="my_account_link" name="Link to frontend portal" inherit_id="website.layout">
<xpath expr="//li[@id='o_logout']" position="before">
<li><a href="/my/home" role="menuitem">My Account</a></li>
</xpath>
</template>
<template id="record_pager" name="Portal Record Pager">
<t t-if='prev_record or next_record'>
<div class="ml8 record_pager btn-group btn-group-xs pull-right" role="group">
<a t-att-class="'btn btn-default %s' % ('disabled' if not prev_record else '')" t-att-href="prev_record or '#'" ><i class="fa fa-arrow-left" aria-hidden="true"></i></a>
<a t-att-class="'btn btn-default %s' % ('disabled' if not next_record else '')" t-att-href="next_record or '#'" ><i class="fa fa-arrow-right" aria-hidden="true"></i></a>
</div>
</t>
</template>
</odoo>
@@ -11,7 +11,8 @@ Display purchase orders on your website. Customers will be able to access their
""",
'depends': [
'purchase',
'website_portal',
'website',
'portal',
],
'data': [
'views/website_portal_purchase_templates.xml',
@@ -8,11 +8,10 @@ from odoo.exceptions import AccessError
from odoo.http import request
from odoo.tools import pycompat
from odoo.tools.translate import _
from odoo.addons.website_portal.controllers.main import website_account, get_records_pager
from odoo.addons.portal.controllers.portal import get_records_pager, CustomerPortal
class WebsitePortal(website_account):
class WebsitePortal(CustomerPortal):
def _prepare_portal_layout_values(self):
values = super(WebsitePortal, self)._prepare_portal_layout_values()
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="portal_my_home_menu_purchase" name="Portal layout : purchase menu entries" inherit_id="website_portal.portal_layout" priority="25">
<template id="portal_my_home_menu_purchase" name="Portal layout : purchase menu entries" inherit_id="portal.portal_layout" priority="25">
<xpath expr="//ol[contains(@class,'o_portal_submenu')]" position="inside">
<li t-if="page_name == 'purchase' or purchase_order">
<a t-if="purchase_order" t-attf-href="/my/purchase?{{ keep_query() }}">Purchase Orders</a>
@@ -13,7 +13,7 @@
</xpath>
</template>
<template id="portal_my_home_purchase" name="Portal My Home : purchase entry" inherit_id="website_portal.portal_my_home" priority="25">
<template id="portal_my_home_purchase" name="Portal My Home : purchase entry" inherit_id="portal.portal_my_home" priority="25">
<xpath expr="//ul[contains(@class,'o_portal_docs')]" position="inside">
<li t-if="purchase_count" class="list-group-item">
<span class="badge" t-esc="purchase_count"/>
@@ -23,9 +23,9 @@
</template>
<template id="portal_my_purchase_orders" name="Portal: My Purchase Orders">
<t t-call="website_portal.portal_layout">
<t t-call="portal.portal_layout">
<h3>Purchase Orders
<t t-call="website_portal.portal_searchbar"/>
<t t-call="portal.portal_searchbar"/>
</h3>
<div t-if="orders" class="panel panel-default">
<div class="table-responsive"><table class="table table-hover o_my_status_table">
@@ -61,7 +61,7 @@
</template>
<template id="portal_my_purchase_order" name="Portal: My Purchase Order">
<t t-call="website_portal.portal_layout">
<t t-call="portal.portal_layout">
<t t-set="purchase_order" t-value="order"/>
<div id="optional_placeholder"></div>
<div class="container">
@@ -70,7 +70,7 @@
<div class="row">
<div class="col-md-12">
<h4>
<t t-call='website_portal.record_pager'/>
<t t-call='portal.record_pager'/>
Purchase Order
<span t-esc="order.name"/>
</h4>
@@ -6,7 +6,8 @@ from odoo.exceptions import AccessError
from odoo.http import request
from odoo.tools import consteq
from odoo.addons.website_portal.controllers.main import website_account, get_records_pager
from odoo.addons.portal.controllers.portal import get_records_pager
from odoo.addons.website_portal.controllers.main import website_account
class website_account(website_account):
@@ -6,7 +6,7 @@
</xpath>
</template>
<template id="portal_my_home_menu_sale" name="Portal layout : sales menu entries" inherit_id="website_portal.portal_layout" priority="20">
<template id="portal_my_home_menu_sale" name="Portal layout : sales menu entries" inherit_id="portal.portal_layout" priority="20">
<xpath expr="//ol[contains(@class,'o_portal_submenu')]" position="inside">
<li t-if="page_name == 'quote' or sales_order and sales_order.state == 'sent'">
<a t-if="sales_order" t-attf-href="/my/quotes?{{ keep_query() }}">Quotations</a>
@@ -24,7 +24,7 @@
</xpath>
</template>
<template id="portal_my_home_sale" name="Portal My Home : sales entries" inherit_id="website_portal.portal_my_home" priority="20">
<template id="portal_my_home_sale" name="Portal My Home : sales entries" inherit_id="portal.portal_my_home" priority="20">
<xpath expr="//ul[contains(@class,'o_portal_docs')]" position="inside">
<li t-if="quotation_count" class="list-group-item">
<span class="badge" t-esc="quotation_count"/>
@@ -38,9 +38,9 @@
</template>
<template id="portal_my_quotations" name="My Quotations">
<t t-call="website_portal.portal_layout">
<t t-call="portal.portal_layout">
<h3>Quotations
<t t-call="website_portal.portal_searchbar"/>
<t t-call="portal.portal_searchbar"/>
</h3>
<t t-if="not quotations">
<p>There are currently no quotations for your account.</p>
@@ -84,9 +84,9 @@
</template>
<template id="portal_my_orders" name="My Sales Orders">
<t t-call="website_portal.portal_layout">
<t t-call="portal.portal_layout">
<h3>Sales Orders
<t t-call="website_portal.portal_searchbar"/>
<t t-call="portal.portal_searchbar"/>
</h3>
<t t-if="not orders">
<p>There are currently no orders for your account.</p>
@@ -136,7 +136,7 @@
</template>
<template id="orders_followup" name="Sales Order">
<t t-call="website_portal.portal_layout">
<t t-call="portal.portal_layout">
<t t-set="sales_order" t-value="order"/> <!-- rename to ensure breadcrumb compatibility (differentiate with purchase orders) -->
<div id="optional_placeholder"></div>
<div class="container">
@@ -146,7 +146,7 @@
<div class="row">
<div class="col-md-12">
<h4>
<t t-call='website_portal.record_pager'/>
<t t-call='portal.record_pager'/>
<t t-if="order.state == 'sent'">
Quotation
</t>
+1 -1
View File
@@ -7,7 +7,7 @@ Website portal for Project and Tasks
====================================
""",
'category': 'Website',
'depends': ['project', 'website_portal'],
'depends': ['project', 'website', 'portal'],
'data': [
'security/project_security.xml',
'security/ir.model.access.csv',
+2 -3
View File
@@ -5,13 +5,12 @@ from collections import OrderedDict
from odoo import http, _
from odoo.http import request
from odoo.addons.website_portal.controllers.main import website_account, get_records_pager
from odoo.addons.portal.controllers.portal import get_records_pager, CustomerPortal
from odoo.osv.expression import OR
from odoo.tools import pycompat
class WebsiteAccount(website_account):
class WebsiteAccount(CustomerPortal):
def _prepare_portal_layout_values(self):
values = super(WebsiteAccount, self)._prepare_portal_layout_values()
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="portal_layout" name="Portal layout: project menu entry" inherit_id="website_portal.portal_layout" priority="40">
<template id="portal_layout" name="Portal layout: project menu entry" inherit_id="portal.portal_layout" priority="40">
<xpath expr="//ol[contains(@class,'o_portal_submenu')]" position="inside">
<li t-if="page_name == 'project' or project">
<a t-if="project" t-attf-href="/my/projects?{{ keep_query() }}">Projects</a>
@@ -19,7 +19,7 @@
</xpath>
</template>
<template id="portal_my_home" name="Portal My Home: project entries" inherit_id="website_portal.portal_my_home" priority="40">
<template id="portal_my_home" name="Portal My Home: project entries" inherit_id="portal.portal_my_home" priority="40">
<xpath expr="//ul[contains(@class,'o_portal_docs')]" position="inside">
<li t-if="project_count" class="list-group-item">
<span class="badge" t-esc="project_count"/>
@@ -33,9 +33,9 @@
</template>
<template id="my_projects" name="My Projects">
<t t-call="website_portal.portal_layout">
<t t-call="portal.portal_layout">
<h3>Projects
<t t-call="website_portal.portal_searchbar"/>
<t t-call="portal.portal_searchbar"/>
</h3>
<t t-if="not projects">
<div class="alert alert-warning mt8" role="alert">
@@ -72,13 +72,13 @@
</template>
<template id="my_project" name="My Project">
<t t-call="website_portal.portal_layout">
<t t-call="portal.portal_layout">
<div class="panel panel-default">
<div class="panel-heading">
<div class="row">
<div class="col-md-12">
<h4>
<t t-call='website_portal.record_pager'/>
<t t-call='portal.record_pager'/>
Project - <span t-field="project.name"/>
</h4>
</div>
@@ -126,9 +126,9 @@
</template>
<template id="my_tasks" name="My Tasks">
<t t-call="website_portal.portal_layout">
<t t-call="portal.portal_layout">
<h3>Tasks
<t t-call="website_portal.portal_searchbar"/>
<t t-call="portal.portal_searchbar"/>
</h3>
<t t-if="not tasks">
<div class="alert alert-warning mt8" role="alert">
@@ -164,13 +164,13 @@
</template>
<template id="my_task" name="My Task">
<t t-call="website_portal.portal_layout">
<t t-call="portal.portal_layout">
<div class="panel panel-default">
<div class="panel-heading">
<div class="row">
<div class="col-md-12">
<h4>
<t t-call='website_portal.record_pager'/>
<t t-call='portal.record_pager'/>
Task - <span t-field="task.name"/>
<span t-field="task.stage_id.name" class="pull-right label label-info" title="Current stage of this task"/>
<a class="btn btn-info" t-att-href="'/web#return_label=Website&amp;model=project.task&amp;id=%s&amp;view_type=form' % (task.id)" groups="project.group_project_user">Edit Task</a>
+1 -1
View File
@@ -5,8 +5,8 @@ import werkzeug
from odoo import fields, http, _
from odoo.http import request
from odoo.addons.portal.controllers.portal import get_records_pager
from odoo.addons.website_mail.controllers.main import _message_post_helper
from odoo.addons.website_portal.controllers.main import get_records_pager
class sale_quote(http.Controller):
@@ -166,7 +166,7 @@
<!-- Complete page of the quotation -->
<template id="so_quotation" name="Product Quotation">
<t t-call="website_portal.portal_layout">
<t t-call="portal.portal_layout">
<t t-set="sales_order" t-value="quotation"/> <!-- rename to ensure breadcrumb compatibility with website_portal_sale's portal_layout inheritance -->
<body data-spy="scroll" data-target=".navspy" data-offset="50">
<div class="container o_website_quote">
@@ -470,7 +470,7 @@
<a class="btn btn-info" target="_blank" t-att-href="'/quote/%s/%s' % (quotation.id,quotation.access_token)+'?pdf=True'">Print</a>
</li>
<li>
<t t-call='website_portal.record_pager'/>
<t t-call='portal.record_pager'/>
</li>
</ul>
</div>