[MERGE] portal,project,website_*: move portal code

This merge mainly does 2 things:
- move frontend chatter feature from website_mail
into portal, since chatter is required for some
business model in the customer portal. This chatter
is still also responsible of displaying comments
for website object (slides, blog, ...)
- bring project customer portal into project,
and make project module depends on portal, like
it was already done for sale and account.
hr_timesheet now contains the dislaying of timesheet
on task in portal.

website_project is removed, and we do not use
website.published.mixin on project, since this
is only used to display its rating. projects are
not public object.
This commit is contained in:
Jérome Maes
2017-08-16 16:09:28 +02:00
77 changed files with 811 additions and 2433 deletions
+1
View File
@@ -27,6 +27,7 @@ up a management by affair.
'views/hr_timesheet_views.xml',
'views/hr_timesheet_config_settings_views.xml',
'views/project_views.xml',
'views/project_portal_templates.xml',
'report/hr_timesheet_report_view.xml',
'report/report_timesheet_templates.xml',
'views/hr_views.xml',
@@ -51,6 +51,21 @@
<field name="perm_read" eval="1"/>
</record>
<record id="timesheet_rule_portal" model="ir.rule">
<field name="name">Project/Task Timesheets: frontend access</field>
<field name="model_id" ref="analytic.model_account_analytic_line"/>
<field name="domain_force">[
'|',
'&amp;',
('task_id.project_id.privacy_visibility', '=', 'portal'),
('task_id.project_id.message_partner_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
'&amp;',
('task_id.project_id.privacy_visibility', '=', 'portal'),
('task_id.message_partner_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
]</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
</record>
</data>
</odoo>
@@ -1,8 +1,9 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_account_analytic_line_user,analytic.account.analytic.line.timesheet.user,analytic.model_account_analytic_line,hr_timesheet.group_hr_timesheet_user,1,1,1,1
access_account_analytic_line_portal,access_timesheets_portal,analytic.model_account_analytic_line,base.group_portal,1,0,0,0
access_account_analytic_user,analytic.account.analytic.timesheet.user,analytic.model_account_analytic_account,hr_timesheet.group_hr_timesheet_user,1,1,0,0
access_product_product_user,product.product.timesheet.user,product.model_product_product,hr_timesheet.group_hr_timesheet_user,1,1,1,0
access_product_template_timesheet_user,product.template.timesheet.user,product.model_product_template,hr_timesheet.group_hr_timesheet_user,1,1,1,0
access_product_uom_hr_timesheet,product.uom.timesheet.user,product.model_product_uom,hr_timesheet.group_hr_timesheet_user,1,0,0,0
access_project_project,project.project.timesheet.user,model_project_project,hr_timesheet.group_hr_timesheet_user,1,0,0,0
access_project_task,project.task.timesheet.user,model_project_task,hr_timesheet.group_hr_timesheet_user,1,1,0,0
access_project_task,project.task.timesheet.user,model_project_task,hr_timesheet.group_hr_timesheet_user,1,1,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_account_analytic_line_user analytic.account.analytic.line.timesheet.user analytic.model_account_analytic_line hr_timesheet.group_hr_timesheet_user 1 1 1 1
3 access_account_analytic_line_portal access_timesheets_portal analytic.model_account_analytic_line base.group_portal 1 0 0 0
4 access_account_analytic_user analytic.account.analytic.timesheet.user analytic.model_account_analytic_account hr_timesheet.group_hr_timesheet_user 1 1 0 0
5 access_product_product_user product.product.timesheet.user product.model_product_product hr_timesheet.group_hr_timesheet_user 1 1 1 0
6 access_product_template_timesheet_user product.template.timesheet.user product.model_product_template hr_timesheet.group_hr_timesheet_user 1 1 1 0
7 access_product_uom_hr_timesheet product.uom.timesheet.user product.model_product_uom hr_timesheet.group_hr_timesheet_user 1 0 0 0
8 access_project_project project.project.timesheet.user model_project_project hr_timesheet.group_hr_timesheet_user 1 0 0 0
9 access_project_task project.task.timesheet.user model_project_task hr_timesheet.group_hr_timesheet_user 1 1 0 0
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="my_task" inherit_id="website_project.my_task" name="Project Task Timesheets">
<template id="portal_my_task" inherit_id="project.portal_my_task" name="Portal: My Task with imesheets">
<xpath expr="//div[hasclass('panel-body')]" position="inside">
<div class="container" t-if="task.timesheet_ids">
<hr/>
+10
View File
@@ -178,6 +178,16 @@ class IrHttp(models.AbstractModel):
rerouting_limit = 10
@classmethod
def _get_converters(cls):
""" Get the converters list for custom url pattern werkzeug need to
match Rule. This override adds the website ones.
"""
return dict(
super(IrHttp, cls)._get_converters(),
model=ModelConverter,
)
@classmethod
def _get_languages(cls):
return request.env['res.lang'].search([])
+1
View File
@@ -3,3 +3,4 @@
from . import web
from . import portal
from . import mail
+103
View File
@@ -0,0 +1,103 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from werkzeug.exceptions import NotFound, Forbidden
from odoo import http
from odoo.http import request
from odoo.tools import consteq
def _special_access_object(res_model, res_id, token='', token_field=''):
record = request.env[res_model].browse(res_id).sudo()
if token and record and getattr(record, token_field, None) and consteq(getattr(record, token_field), token):
return True
return False
def _message_post_helper(res_model='', res_id=None, message='', token='', token_field='token', nosubscribe=True, **kw):
""" Generic chatter function, allowing to write on *any* object that inherits mail.thread.
If a token is specified, all logged in users will be able to write a message regardless
of access rights; if the user is the public user, the message will be posted under the name
of the partner_id of the object (or the public user if there is no partner_id on the object).
:param string res_model: model name of the object
:param int res_id: id of the object
:param string message: content of the message
optional keywords arguments:
:param string token: access token if the object's model uses some kind of public access
using tokens (usually a uuid4) to bypass access rules
:param string token_field: name of the field that contains the token on the object (defaults to 'token')
:param bool nosubscribe: set False if you want the partner to be set as follower of the object when posting (default to True)
The rest of the kwargs are passed on to message_post()
"""
record = request.env[res_model].browse(res_id)
author_id = request.env.user.partner_id.id if request.env.user.partner_id else False
if token_field and token:
access_as_sudo = _special_access_object(res_model, res_id, token=token, token_field=token_field)
if access_as_sudo:
record = record.sudo()
if request.env.user == request.env.ref('base.public_user'):
author_id = record.partner_id.id if hasattr(record, 'partner_id') else author_id
else:
if not author_id:
raise NotFound()
else:
raise Forbidden()
kw.pop('csrf_token', None)
return record.with_context(mail_create_nosubscribe=nosubscribe).message_post(body=message,
message_type=kw.pop('message_type', "comment"),
subtype=kw.pop('subtype', "mt_comment"),
author_id=author_id,
**kw)
class PortalChatter(http.Controller):
@http.route(['/mail/chatter_post'], type='http', methods=['POST'], auth='public', website=True)
def portal_chatter_post(self, res_model, res_id, message, **kw):
url = request.httprequest.referrer
if message:
_message_post_helper(res_model, int(res_id), message, **kw)
url = url + "#discussion"
return request.redirect(url)
@http.route('/mail/chatter_init', type='json', auth='public', website=True)
def portal_chatter_init(self, res_model, res_id, domain=False, limit=False, **kwargs):
is_user_public = request.env.user.has_group('base.group_public')
message_data = self.portal_message_fetch(res_model, res_id, domain=domain, limit=limit, **kwargs)
display_composer = False
if kwargs.get('allow_composer'):
display_composer = kwargs.get('token') or not is_user_public
return {
'messages': message_data['messages'],
'options': {
'message_count': message_data['message_count'],
'is_user_public': is_user_public,
'is_user_publisher': request.env.user.has_group('website.group_website_publisher'),
'display_composer': display_composer,
'partner_id': request.env.user.partner_id.id
}
}
@http.route('/mail/chatter_fetch', type='json', auth='public', website=True)
def portal_message_fetch(self, res_model, res_id, domain=False, limit=10, offset=0, **kw):
if not domain:
domain = []
# Only search into website_message_ids, so apply the same domain to perform only one search
# extract domain from the 'website_message_ids' field
field_domain = request.env[res_model]._fields['website_message_ids'].domain
domain += field_domain(request.env[res_model]) if callable(field_domain) else field_domain
domain += [('res_id', '=', res_id)]
# Check access
Message = request.env['mail.message']
if kw.get('token'):
access_as_sudo = _special_access_object(res_model, res_id, token=kw.get('token'), token_field=kw.get('token_field'))
if not access_as_sudo: # if token is not correct, raise Forbidden
raise Forbidden()
Message = request.env['mail.message'].sudo()
return {
'messages': Message.search(domain, limit=limit, offset=offset).portal_message_format(),
'message_count': Message.search_count(domain)
}
+3 -1
View File
@@ -1,4 +1,6 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import portal_mixin
from . import mail_thread
from . import mail_message
from . import portal_mixin
+23
View File
@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, models
class MailMessage(models.Model):
_inherit = 'mail.message'
@api.multi
def portal_message_format(self):
return self._portal_message_format([
'id', 'body', 'date', 'author_id', 'email_from', # base message fields
'message_type', 'subtype_id', 'subject', # message specific
'model', 'res_id', 'record_name', # document related
])
@api.multi
def _portal_message_format(self, fields_list):
message_values = self.read(fields_list)
message_tree = dict((m.id, m) for m in self.sudo())
self._message_read_dict_postprocess(message_values, message_tree)
return message_values
@@ -0,0 +1,238 @@
odoo.define('portal.chatter', function(require) {
'use strict';
var base = require('web_editor.base');
var ajax = require('web.ajax');
var core = require('web.core');
var Widget = require('web.Widget');
var rpc = require('web.rpc');
var qweb = core.qweb;
var _t = core._t;
/**
* Widget PortalChatter
*
* - Fetch message fron controller
* - Display chatter: pager, total message, composer (according to access right)
* - Provider API to filter displayed messages
*/
var PortalChatter = Widget.extend({
template: 'portal.chatter',
events: {
"click .o_portal_chatter_pager_btn": '_onClickPager'
},
init: function(parent, options){
this._super.apply(this, arguments);
this.options = _.defaults(options || {}, {
'allow_composer': true,
'display_composer': false,
'csrf_token': odoo.csrf_token,
'message_count': 0,
'pager_step': 10,
'pager_scope': 5,
'pager_start': 1,
'is_user_public': true,
'is_user_publisher': false,
'domain': [],
});
this.set('messages', []);
this.set('message_count', this.options['message_count']);
this.set('pager', {});
this.set('domain', this.options['domain']);
this._current_page = this.options['pager_start'];
},
willStart: function(){
var self = this;
// load qweb template and init data
return $.when(
rpc.query({
route: '/mail/chatter_init',
params: this._messageFetchPrepareParams()
}), this._loadTemplates()
).then(function(result){
// bind events
self.on("change:messages", self, self._renderMessages);
self.on("change:message_count", self, function(){
self._renderMessageCount();
self.set('pager', self._pager(self._current_page));
});
self.on("change:pager", self, self._renderPager);
self.on("change:domain", self, self._onChangeDomain);
// set options and parameters
self.options = _.extend(self.options, result['options'] || {});
self.set('message_count', self.options['message_count']);
self.set('messages', self.preprocessMessages(result['messages']));
return result;
});
},
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
/**
* Fetch the messages and the message count from the server for the
* current page and current domain.
*
* @param {Array}
* @returns {Deferred}
*/
messageFetch: function(domain){
var self = this;
return rpc.query({
route: '/mail/chatter_fetch',
params: self._messageFetchPrepareParams()
}).then(function(result){
self.set('messages', self.preprocessMessages(result['messages']));
self.set('message_count', result['message_count']);
});
},
/**
* Update the messages format
*
* @param {Array<Object>}
* @returns {Array}
*/
preprocessMessages: function(messages){
_.each(messages, function(m){
m['author_avatar_url'] = _.str.sprintf('/web/image/%s/%s/author_avatar/50x50', 'mail.message', m.id);
m['published_date_str'] = _.str.sprintf(_t('Published on %s'), moment(m.date).format('MMMM Do YYYY, h:mm:ss a'));
});
return messages;
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* Change the current page by refreshing current domain
*
* @private
* @param {Number} page
* @param {Array} domain
*/
_changeCurrentPage: function(page, domain){
this._current_page = page;
var d = domain ? domain : _.clone(this.get('domain'));
this.set('domain', d); // trigger fetch message
},
/**
* @private
* @returns {Deferred}
*/
_loadTemplates: function(){
return ajax.loadXML('/portal/static/src/xml/portal_chatter.xml', qweb);
},
_messageFetchPrepareParams: function(){
var self = this;
var data = {
'res_model': this.options['res_model'],
'res_id': this.options['res_id'],
'limit': this.options['pager_step'],
'offset': (this._current_page-1) * this.options['pager_step'],
'allow_composer': this.options['allow_composer'],
};
// add fields to allow to post comment without being logged
_.each(['token', 'token_field'], function(field){
if(self.options[field]){
data[field] = self.options[field];
}
});
// add domain
if(this.get('domain')){
data['domain'] = this.get('domain');
}
return data;
},
/**
* Generate the pager data for the given page number
*
* @private
* @param {Number} page
* @returns {Object}
*/
_pager: function(page){
var page = page || 1;
var total = this.get('message_count');
var scope = this.options['pager_scope'];
var step = this.options['pager_step'];
// Compute Pager
var page_count = Math.ceil(parseFloat(total) / step);
var page = Math.max(1, Math.min(parseInt(page), page_count));
scope -= 1;
var pmin = Math.max(page - parseInt(Math.floor(scope/2)), 1);
var pmax = Math.min(pmin + scope, page_count);
if(pmax - scope > 0){
pmin = pmax - scope;
}else{
pmin = 1;
}
var pages = [];
_.each(_.range(pmin, pmax+1), function(index){
pages.push(index);
});
return {
"page_count": page_count,
"offset": (page - 1) * step,
"page": page,
"page_start": pmin,
"page_previous": Math.max(pmin, page - 1),
"page_next": Math.min(pmax, page + 1),
"page_end": pmax,
"pages": pages
};
},
_renderMessages: function(){
this.$('.o_portal_chatter_messages').html(qweb.render("portal.chatter_messages", {widget: this}));
},
_renderMessageCount: function(){
this.$('.o_message_counter').replaceWith(qweb.render("portal.chatter_message_count", {widget: this}));
},
_renderPager: function(){
this.$('.o_portal_chatter_pager').replaceWith(qweb.render("portal.pager", {widget: this}));
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
_onChangeDomain: function(){
var self = this;
this.messageFetch().then(function(){
var p = self._current_page;
self.set('pager', self._pager(p));
});
},
/**
* @private
* @param {MouseEvent} event
*/
_onClickPager: function(ev){
ev.preventDefault();
var page = $(ev.currentTarget).data('page');
this._changeCurrentPage(page);
},
});
base.ready().then(function () {
$('.o_website_mail_thread').each(function (index) {
var $elem = $(this);
var mail_thread = new WebsiteMailThread(null, $elem.data());
mail_thread.appendTo($elem);
});
});
return {
PortalChatter: PortalChatter,
};
});
@@ -0,0 +1,33 @@
.o_portal_chatter {
padding: 10px;
.o_portal_chatter_avatar{
width: 45px;
height: 45px;
}
.o_portal_chatter_header {
margin-bottom: 15px;
}
.o_portal_chatter_composer {
margin-bottom: 15px;
}
.o_portal_chatter_messages {
margin-bottom: 15px;
.o_portal_chatter_message_title {
p {
font-size:85%;
color:@odoo-main-color-muted;
margin: 0px;
}
}
}
.o_portal_chatter_pager {
text-align: center;
}
}
@@ -0,0 +1,117 @@
<templates id="template" xml:space="preserve">
<t t-name="portal.chatter_message_count">
<t t-set="count" t-value="widget.get('message_count')"/>
<div class="o_message_counter">
<t t-if="count">
<span class="o_message_count fa fa-comments"> <t t-esc="count"/></span>
<t t-if="count == 1">comment</t>
<t t-else="">comments</t>
</t>
<t t-else="">
There are no comments for now.
</t>
</div>
</t>
<t t-name="portal.chatter_composer">
<div class="o_portal_chatter_composer" t-if="widget.options['allow_composer']">
<t t-if="!widget.options['display_composer']">
<h4>Leave a comment</h4>
<p>You must be <a t-attf-href="/web/login?redirect=#{window.encodeURI(window.location.href + '#discussion')}">logged in</a> to post a comment.</p>
</t>
<t t-if="widget.options['display_composer']">
<div class="media">
<div class="media-left">
<img class="o_portal_chatter_avatar pull-left" t-attf-src="/web/image/res.partner/#{widget.options['partner_id']}/image_small/50x50"/>
</div>
<div class="media-body">
<form class="o_portal_chatter_composer_form" t-attf-action="/mail/chatter_post" method="POST">
<input type="hidden" name="csrf_token" t-att-value="widget.options['csrf_token']"/>
<div class="mb32">
<textarea rows="4" name="message" class="form-control" placeholder="Write a message..."></textarea>
<input type='hidden' name="res_model" t-att-value="widget.options['res_model']"/>
<input type='hidden' name="res_id" t-att-value="widget.options['res_id']"/>
<input type='hidden' name="token" t-att-value="widget.options['token']" t-if="widget.options['token']"/>
<input type='hidden' name="sha_in" t-att-value="widget.options['sha_in']" t-if="widget.options['sha_in']"/>
<input type='hidden' name="sha_time" t-att-value="widget.options['sha_time']" t-if="widget.options['sha_time']"/>
<input type='hidden' name="token_field" t-att-value="widget.options['token_field']" t-if="widget.options['token_field']"/>
<div class="alert alert-danger mt8 mb0 o_portal_chatter_composer_error" style="display:none;">
Oops! Something went wrong. Try to reload the page and log in.
</div>
<button t-attf-class="o_portal_chatter_composer_btn btn btn-primary mt8 o_website_message_post_helper" type="submit">Send</button>
</div>
</form>
</div>
</div>
</t>
<hr/>
</div>
</t>
<t t-name="portal.chatter_messages">
<div class="o_portal_chatter_messages">
<t t-foreach="widget.get('messages')" t-as="message">
<div class="media o_portal_chatter_message" t-att-id="'message-' + message.id">
<div class="media-left">
<img class="media-object o_portal_chatter_avatar" t-att-src="message.author_avatar_url" alt="avatar"/>
</div>
<div class="media-body">
<div class="o_portal_chatter_message_title">
<h5 class="media-heading">
<t t-esc="message.author_id[1]"/>
</h5>
<p class="o_portal_chatter_puslished_date"><t t-esc="message.published_date_str"/></p>
</div>
<t t-raw="message.body"/>
<div class="o_portal_chatter_attachments">
<div class="col-md-2 col-sm-3 text-center" t-foreach='message.attachment_ids' t-as='attachment'>
<a t-attf-href="/web/content/#{attachment.id}?download=true" target="_blank">
<div class='oe_attachment_embedded o_image' t-att-title="attachment.name" t-att-data-mimetype="attachment.mimetype" t-attf-data-src="/web/image/#{attachment.id}/100x80"/>
<div class='oe_attachment_name'><t t-raw='attachment.name'/></div>
</a>
</div>
</div>
</div>
</div>
</t>
</div>
</t>
<t t-name="portal.pager">
<div class="o_portal_chatter_pager">
<t t-if="!_.isEmpty(widget.get('pager'))">
<ul class="pagination" t-if="widget.get('pager')['pages'].length &gt; 1">
<li t-if="widget.get('pager')['page'] != widget.get('pager')['page_previous']" t-att-data-page="widget.get('pager')['page_previous']" class="o_portal_chatter_pager_btn">
<a href="#"><i class="fa fa-chevron-left"/></a>
</li>
<t t-foreach="widget.get('pager')['pages']" t-as="page">
<li t-att-data-page="page" t-att-class="page == widget.get('pager')['page'] ? 'o_portal_chatter_pager_btn active' : 'o_portal_chatter_pager_btn'">
<a href="#"><t t-esc="page"/></a>
</li>
</t>
<li t-if="widget.get('pager')['page'] != widget.get('pager')['page_next']" t-att-data-page="widget.get('pager')['page_next']" class="o_portal_chatter_pager_btn">
<a href="#"><i class="fa fa-chevron-right"/></a>
</li>
</ul>
</t>
</div>
</t>
<t t-name="portal.chatter">
<div class="o_portal_chatter">
<div class="o_portal_chatter_header">
<t t-call="portal.chatter_message_count"/>
</div>
<hr/>
<t t-call="portal.chatter_composer"/>
<t t-call="portal.chatter_messages"/>
<div class="o_portal_chatter_footer">
<t t-call="portal.pager"/>
</div>
</div>
</t>
</templates>
+2
View File
@@ -5,10 +5,12 @@
<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"/>
<link rel="stylesheet" type="text/less" href="/portal/static/src/less/portal_chatter.less"/>
</xpath>
<xpath expr="//script[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>
<script type="text/javascript" src="/portal/static/src/js/portal_chatter.js"></script>
</xpath>
</template>
</odoo>
+18
View File
@@ -336,4 +336,22 @@
<li><a href="/my/home" role="menuitem">My Account</a></li>
</xpath>
</template>
<!--
Generic chatter template for the frontend
This template provide the container of the chatter. The rest is done in js.
To use this template, you need to call it after setting the following variable in your template or in your controller:
:object browserecord : the mail_thread object
:message_per_page int (optional): number of message per chatter page
:token string (optional): if you want your chatter to be available for non-logged user,
you can use a token to verify the identity of the user;
the message will be posted with the identity of the partner_id of the object
:token_field string (optional): name of the field that contains the token on the object (default to 'token')
-->
<template id="message_thread">
<div id="discussion" class="hidden-print o_portal_chatter"
t-att-data-token="token" t-att-data-res_model="object._name" t-att-data-res_id="object.id" t-att-data-token_field="token_field" t-att-data-pager_step="message_per_page or 10" t-att-data-allow_composer="'0' if disable_composer else '1'">
</div>
</template>
</odoo>
+1
View File
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import controllers
from . import models
from . import report
from . import wizard
+2
View File
@@ -13,6 +13,7 @@
'product',
'analytic',
'mail',
'portal',
'resource',
'web',
'web_planner',
@@ -28,6 +29,7 @@
'views/res_partner_views.xml',
'views/project_config_settings_views.xml',
'views/project_templates.xml',
'views/project_portal_templates.xml',
'data/web_planner_data.xml',
'data/project_mail_template_data.xml',
'wizard/project_task_merge_wizard_views.xml',
@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import main
from . import portal
@@ -5,15 +5,16 @@ from collections import OrderedDict
from odoo import http, _
from odoo.http import request
from odoo.addons.portal.controllers.portal import get_records_pager, CustomerPortal
from odoo.addons.portal.controllers.portal import get_records_pager, CustomerPortal, pager as portal_pager
from odoo.osv.expression import OR
from odoo.tools import pycompat
class WebsiteAccount(CustomerPortal):
class CustomerPortal(CustomerPortal):
def _prepare_portal_layout_values(self):
values = super(WebsiteAccount, self)._prepare_portal_layout_values()
values = super(CustomerPortal, self)._prepare_portal_layout_values()
values.update({
'project_count': request.env['project.project'].search_count([('privacy_visibility', '=', 'portal')]),
'task_count': request.env['project.task'].search_count([('project_id.privacy_visibility', '=', 'portal')])
@@ -21,7 +22,7 @@ class WebsiteAccount(CustomerPortal):
return values
@http.route(['/my/projects', '/my/projects/page/<int:page>'], type='http', auth="user", website=True)
def my_projects(self, page=1, date_begin=None, date_end=None, sortby=None, **kw):
def portal_my_projects(self, page=1, date_begin=None, date_end=None, sortby=None, **kw):
values = self._prepare_portal_layout_values()
Project = request.env['project.project']
domain = [('privacy_visibility', '=', 'portal')]
@@ -41,7 +42,7 @@ class WebsiteAccount(CustomerPortal):
# projects count
project_count = Project.search_count(domain)
# pager
pager = request.website.pager(
pager = portal_pager(
url="/my/projects",
url_args={'date_begin': date_begin, 'date_end': date_end, 'sortby': sortby},
total=project_count,
@@ -64,17 +65,18 @@ class WebsiteAccount(CustomerPortal):
'searchbar_sortings': searchbar_sortings,
'sortby': sortby
})
return request.render("website_project.my_projects", values)
return request.render("project.portal_my_projects", values)
@http.route(['/my/project/<model("project.project"):project>'], type='http', auth="user", website=True)
def my_project(self, project=None, **kw):
vals = {'project': project, }
@http.route(['/my/project/<int:project_id>'], type='http', auth="user", website=True)
def portal_my_project(self, project_id=None, **kw):
project = request.env['project.project'].browse(project_id)
vals = {'project': project}
history = request.session.get('my_projects_history', [])
vals.update(get_records_pager(history, project))
return request.render("website_project.my_project", vals)
return request.render("project.portal_my_project", vals)
@http.route(['/my/tasks', '/my/tasks/page/<int:page>'], type='http', auth="user", website=True)
def my_tasks(self, page=1, date_begin=None, date_end=None, sortby=None, filterby=None, search=None, search_in='content', **kw):
def portal_my_tasks(self, page=1, date_begin=None, date_end=None, sortby=None, filterby=None, search=None, search_in='content', **kw):
values = self._prepare_portal_layout_values()
domain = [('project_id.privacy_visibility', '=', 'portal')]
@@ -131,7 +133,7 @@ class WebsiteAccount(CustomerPortal):
# task count
task_count = request.env['project.task'].search_count(domain)
# pager
pager = request.website.pager(
pager = portal_pager(
url="/my/tasks",
url_args={'date_begin': date_begin, 'date_end': date_end, 'sortby': sortby, 'filterby': filterby},
total=task_count,
@@ -158,11 +160,15 @@ class WebsiteAccount(CustomerPortal):
'searchbar_filters': OrderedDict(sorted(pycompat.items(searchbar_filters))),
'filterby': filterby,
})
return request.render("website_project.my_tasks", values)
return request.render("project.portal_my_tasks", values)
@http.route(['/my/task/<model("project.task"):task>'], type='http', auth="user", website=True)
def my_task(self, task=None, **kw):
vals = {'task': task, 'user': request.env.user}
@http.route(['/my/task/<int:task_id>'], type='http', auth="user", website=True)
def portal_my_task(self, task_id=None, **kw):
task = request.env['project.task'].browse(task_id)
vals = {
'task': task,
'user': request.env.user
}
history = request.session.get('my_tasks_history', [])
vals.update(get_records_pager(history, task))
return request.render("website_project.my_task", vals)
return request.render("project.portal_my_task", vals)
+5
View File
@@ -67,6 +67,8 @@
<field name="color">3</field>
<field name="user_id" ref="base.user_demo"/>
<field name="type_ids" eval="[(4, ref('project_stage_0')), (4, ref('project_stage_1')), (4, ref('project_stage_2')), (4, ref('project_stage_3'))]"/>
<field name="partner_id" ref="base.partner_demo_portal"/>
<field name="privacy_visibility">portal</field>
</record>
<record id="project_project_2" model="project.project">
@@ -425,5 +427,8 @@ Send it ASAP, its urgent.</field>
<field name="author_id" ref="base.partner_root"/>
</record>
<function model="project.project" name="message_subscribe"
eval="[ref('project.project_project_1')], [ref('base.partner_demo_portal')]"/>
</data>
</odoo>
+96 -5
View File
@@ -4,7 +4,7 @@
from lxml import etree
from odoo import api, fields, models, tools, SUPERUSER_ID, _
from odoo.exceptions import UserError, ValidationError
from odoo.exceptions import UserError, AccessError
from odoo.tools.safe_eval import safe_eval
@@ -46,7 +46,7 @@ class ProjectTaskType(models.Model):
class Project(models.Model):
_name = "project.project"
_description = "Project"
_inherit = ['mail.alias.mixin', 'mail.thread']
_inherit = ['mail.alias.mixin', 'mail.thread', 'portal.mixin']
_inherits = {'account.analytic.account': "analytic_account_id"}
_order = "sequence, name, id"
_period_number = 5
@@ -225,6 +225,11 @@ class Project(models.Model):
('project_date_greater', 'check(date >= date_start)', 'Error! project start-date must be lower than project end-date.')
]
def _compute_portal_url(self):
super(Project, self)._compute_portal_url()
for project in self:
project.portal_url = '/my/project/%s' % project.id
@api.multi
def map_tasks(self, new_project_id):
""" copy and map tasks from old to new project """
@@ -271,6 +276,30 @@ class Project(models.Model):
project.message_subscribe(project.partner_id.ids)
return res
@api.multi
def get_access_action(self, access_uid=None):
""" Instead of the classic form view, redirect to website for portal users
that can read the project. """
self.ensure_one()
user, record = self.env.user, self
if access_uid:
user = self.env['res.users'].sudo().browse(access_uid)
record = self.sudo(user)
if user.share:
try:
record.check_access_rule('read')
except AccessError:
pass
else:
return {
'type': 'ir.actions.act_url',
'url': '/my/project/%s' % self.id,
'target': 'self',
'res_id': self.id,
}
return super(Project, self).get_access_action(access_uid)
@api.multi
def message_subscribe(self, partner_ids=None, channel_ids=None, subtype_ids=None, force=True):
""" Subscribe to all existing active tasks when subscribing to a project """
@@ -290,6 +319,28 @@ class Project(models.Model):
self.mapped('tasks').message_unsubscribe(partner_ids=partner_ids, channel_ids=channel_ids)
return super(Project, self).message_unsubscribe(partner_ids=partner_ids, channel_ids=channel_ids)
@api.multi
def _notification_recipients(self, message, groups):
groups = super(Project, self)._notification_recipients(message, groups)
for group_name, group_method, group_data in groups:
group_data['has_button_access'] = True
return groups
@api.multi
def toggle_favorite(self):
favorite_projects = not_fav_projects = self.env['project.project'].sudo()
for project in self:
if self.env.user in project.favorite_user_ids:
favorite_projects |= project
else:
not_fav_projects |= project
# Project User has no write access for project.
not_fav_projects.write({'favorite_user_ids': [(4, self.env.uid)]})
favorite_projects.write({'favorite_user_ids': [(3, self.env.uid)]})
@api.multi
def close_dialog(self):
return {'type': 'ir.actions.act_window_close'}
@@ -299,7 +350,7 @@ class Task(models.Model):
_name = "project.task"
_description = "Task"
_date_name = "date_start"
_inherit = ['mail.thread', 'mail.activity.mixin']
_inherit = ['mail.thread', 'mail.activity.mixin', 'portal.mixin']
_mail_post_access = 'read'
_order = "priority desc, sequence, date_start, name, id"
@@ -383,7 +434,8 @@ class Task(models.Model):
default=lambda self: self.env['res.company']._company_default_get())
color = fields.Integer(string='Color Index')
user_email = fields.Char(related='user_id.email', string='User Email', readonly=True)
attachment_ids = fields.One2many('ir.attachment', 'res_id', domain=lambda self: [('res_model', '=', self._name)], auto_join=True, string='Attachments')
attachment_ids = fields.One2many('ir.attachment', compute='_compute_attachment_ids', string="Main Attachments",
help="Attachment that don't come from message.")
# In the domain of displayed_image_id, we couln't use attachment_ids because a one2many is represented as a list of commands so we used res_model & res_id
displayed_image_id = fields.Many2one('ir.attachment', domain="[('res_model', '=', 'project.task'), ('res_id', '=', id), ('mimetype', 'ilike', 'image')]", string='Cover Image')
legend_blocked = fields.Char(related='stage_id.legend_blocked', string='Kanban Blocked Explanation', readonly=True)
@@ -402,6 +454,12 @@ class Task(models.Model):
working_days_open = fields.Float(compute='_compute_elapsed', string='Working days to assign', store=True, group_operator="avg")
working_days_close = fields.Float(compute='_compute_elapsed', string='Working days to close', store=True, group_operator="avg")
def _compute_attachment_ids(self):
for task in self:
attachment_ids = self.env['ir.attachment'].search([('res_id', '=', task.id), ('res_model', '=', 'project.task')]).ids
message_attachment_ids = self.mapped('message_ids.attachment_ids').ids # from mail_thread
task.attachment_ids = list(set(attachment_ids) - set(message_attachment_ids))
@api.multi
@api.depends('create_date', 'date_end', 'date_assign')
def _compute_elapsed(self):
@@ -436,6 +494,11 @@ class Task(models.Model):
else:
task.kanban_state_label = task.legend_done
def _compute_portal_url(self):
super(Task, self)._compute_portal_url()
for task in self:
task.portal_url = '/my/task/%s' % task.id
@api.onchange('partner_id')
def _onchange_partner_id(self):
self.email_from = self.partner_id.email
@@ -601,6 +664,30 @@ class Task(models.Model):
return {'date_end': fields.Datetime.now()}
return {'date_end': False}
@api.multi
def get_access_action(self, access_uid=None):
""" Instead of the classic form view, redirect to website for portal users
that can read the task. """
self.ensure_one()
user, record = self.env.user, self
if access_uid:
user = self.env['res.users'].sudo().browse(access_uid)
record = self.sudo(user)
if user.share:
try:
record.check_access_rule('read')
except AccessError:
pass
else:
return {
'type': 'ir.actions.act_url',
'url': '/my/task/%s' % self.id,
'target': 'self',
'res_id': self.id,
}
return super(Task, self).get_access_action(access_uid)
# ---------------------------------------------------
# Mail gateway
# ---------------------------------------------------
@@ -647,7 +734,11 @@ class Task(models.Model):
'actions': project_actions,
})
return [new_group] + groups
groups = [new_group] + groups
for group_name, group_method, group_data in groups:
group_data['has_button_access'] = True
return groups
@api.model
def message_get_reply_to(self, res_ids, default=None):
+6 -1
View File
@@ -3,18 +3,23 @@ access_project_project,project.project,model_project_project,project.group_proje
access_project_project_manager,project.project,model_project_project,project.group_project_manager,1,1,1,1
access_account_analytic_account_user,account.analytic.account,analytic.model_account_analytic_account,project.group_project_user,1,0,0,0
access_account_analytic_account_manager,account.analytic.account,analytic.model_account_analytic_account,project.group_project_manager,1,1,1,1
access_account_analytic_account_portal,account_analytic_account,analytic.model_account_analytic_account,base.group_portal,1,0,0,0
access_project_task_type_user,project.task.type.user,model_project_task_type,base.group_user,1,0,0,0
access_project_task_type_project_user,project.task.type.project.user,model_project_task_type,project.group_project_user,1,0,0,0
access_project_task_type_manager,project.task.type manager,model_project_task_type,project.group_project_manager,1,1,1,1
access_project_task_type_portal,task_type_portal,project.model_project_task_type,base.group_portal,1,0,0,0
access_project_task,project.task,model_project_task,project.group_project_user,1,1,1,1
access_report_project_task_user,report.project.task.user,model_report_project_task_user,project.group_project_manager,1,1,1,1
access_partner_task user,base.res.partner user,base.model_res_partner,project.group_project_user,1,0,0,0
access_task_on_partner,project.task on partners,model_project_task,base.group_user,1,0,0,0
access_project_on_partner,project.project on partners,model_project_project,base.group_user,1,0,0,0
access_task_portal,task_portal,project.model_project_task,base.group_portal,1,0,0,0
access_project_user,project.project on partners,model_project_project,base.group_user,1,0,0,0
access_project_portal,project_portal,project.model_project_project,base.group_portal,1,0,0,0
access_account_analytic_line_project,account.analytic.line project,analytic.model_account_analytic_line,project.group_project_manager,1,1,1,1
access_resource_calendar,project.resource_calendar user,resource.model_resource_calendar,project.group_project_user,1,0,0,0
access_resource_calendar_attendance,project.resource_calendar_attendance user,resource.model_resource_calendar_attendance,project.group_project_user,1,0,0,0
access_resource_calendar_leaves_user,resource.calendar.leaves user,resource.model_resource_calendar_leaves,project.group_project_user,1,1,1,1
access_project_tags_all,project.project_tags_all,model_project_tags,,1,0,0,0
access_project_tags_manager,project.project_tags_manager,model_project_tags,project.group_project_manager,1,1,1,1
access_project_tags_portal,project_tags_portal,project.model_project_tags,base.group_portal,1,0,0,0
access_mail_alias,mail.alias,mail.model_mail_alias,project.group_project_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
3 access_project_project_manager project.project model_project_project project.group_project_manager 1 1 1 1
4 access_account_analytic_account_user account.analytic.account analytic.model_account_analytic_account project.group_project_user 1 0 0 0
5 access_account_analytic_account_manager account.analytic.account analytic.model_account_analytic_account project.group_project_manager 1 1 1 1
6 access_account_analytic_account_portal account_analytic_account analytic.model_account_analytic_account base.group_portal 1 0 0 0
7 access_project_task_type_user project.task.type.user model_project_task_type base.group_user 1 0 0 0
8 access_project_task_type_project_user project.task.type.project.user model_project_task_type project.group_project_user 1 0 0 0
9 access_project_task_type_manager project.task.type manager model_project_task_type project.group_project_manager 1 1 1 1
10 access_project_task_type_portal task_type_portal project.model_project_task_type base.group_portal 1 0 0 0
11 access_project_task project.task model_project_task project.group_project_user 1 1 1 1
12 access_report_project_task_user report.project.task.user model_report_project_task_user project.group_project_manager 1 1 1 1
13 access_partner_task user base.res.partner user base.model_res_partner project.group_project_user 1 0 0 0
14 access_task_on_partner project.task on partners model_project_task base.group_user 1 0 0 0
15 access_project_on_partner access_task_portal project.project on partners task_portal model_project_project project.model_project_task base.group_user base.group_portal 1 0 0 0
16 access_project_user project.project on partners model_project_project base.group_user 1 0 0 0
17 access_project_portal project_portal project.model_project_project base.group_portal 1 0 0 0
18 access_account_analytic_line_project account.analytic.line project analytic.model_account_analytic_line project.group_project_manager 1 1 1 1
19 access_resource_calendar project.resource_calendar user resource.model_resource_calendar project.group_project_user 1 0 0 0
20 access_resource_calendar_attendance project.resource_calendar_attendance user resource.model_resource_calendar_attendance project.group_project_user 1 0 0 0
21 access_resource_calendar_leaves_user resource.calendar.leaves user resource.model_resource_calendar_leaves project.group_project_user 1 1 1 1
22 access_project_tags_all project.project_tags_all model_project_tags 1 0 0 0
23 access_project_tags_manager project.project_tags_manager model_project_tags project.group_project_manager 1 1 1 1
24 access_project_tags_portal project_tags_portal project.model_project_tags base.group_portal 1 0 0 0
25 access_mail_alias mail.alias mail.model_mail_alias project.group_project_manager 1 1 1 1
@@ -96,5 +96,32 @@
<field name="domain_force">['|',('company_id','=',False),('company_id','child_of',[user.company_id.id])]</field>
</record>
<!-- Portal -->
<record id="project_project_rule_portal" model="ir.rule">
<field name="name">Project: portal users: portal and following</field>
<field name="model_id" ref="project.model_project_project"/>
<field name="domain_force">[
'&amp;',
('privacy_visibility', '=', 'portal'),
('message_partner_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
]</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
</record>
<record id="project_task_rule_portal" model="ir.rule">
<field name="name">Project/Task: portal users: (portal and following project) or (portal and following task)</field>
<field name="model_id" ref="project.model_project_task"/>
<field name="domain_force">[
'|',
'&amp;',
('project_id.privacy_visibility', '=', 'portal'),
('project_id.message_partner_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
'&amp;',
('project_id.privacy_visibility', '=', 'portal'),
('message_partner_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
]</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
</record>
</data>
</odoo>
+1 -1
View File
@@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
from . import test_project_base, test_project_flow, test_access_rights, test_project_ui
from . import test_project_base, test_project_flow, test_access_rights, test_project_ui, test_portal
@@ -32,7 +32,7 @@
</xpath>
</template>
<template id="my_projects" name="My Projects">
<template id="portal_my_projects" name="My Projects">
<t t-call="portal.portal_layout">
<h3>Projects
<t t-call="portal.portal_searchbar"/>
@@ -66,12 +66,12 @@
</table></div>
</div>
<div t-if="pager" class="o_portal_pager text-center">
<t t-call="website.pager"/>
<t t-call="portal.pager"/>
</div>
</t>
</template>
<template id="my_project" name="My Project">
<template id="portal_my_project" name="My Project">
<t t-call="portal.portal_layout">
<div class="panel panel-default">
<div class="panel-heading">
@@ -117,7 +117,7 @@
<h4><strong>Message and communication history</strong></h4>
</div>
<div class="col-md-10 col-md-offset-1 mt16">
<t t-call="website_mail.message_thread">
<t t-call="portal.message_thread">
<t t-set="object" t-value="project"/>
</t>
</div>
@@ -125,7 +125,7 @@
</t>
</template>
<template id="my_tasks" name="My Tasks">
<template id="portal_my_tasks" name="My Tasks">
<t t-call="portal.portal_layout">
<h3>Tasks
<t t-call="portal.portal_searchbar"/>
@@ -158,12 +158,12 @@
</table></div>
</div>
<div t-if="pager" class="o_portal_pager text-center">
<t t-call="website.pager"/>
<t t-call="portal.pager"/>
</div>
</t>
</template>
<template id="my_task" name="My Task">
<template id="portal_my_task" name="My Task">
<t t-call="portal.portal_layout">
<div class="panel panel-default">
<div class="panel-heading">
@@ -235,7 +235,7 @@
<h4><strong>Message and communication history</strong></h4>
</div>
<div class="col-md-10 col-md-offset-1 mt16">
<t t-call="website_mail.message_thread">
<t t-call="portal.message_thread">
<t t-set="object" t-value="task"/>
</t>
</div>
@@ -302,7 +302,7 @@
<template id="opt_blog_post_complete_comment" name="Allow blog post comment" inherit_id="website_blog.blog_post_complete" active="False" customize_show="True">
<xpath expr="//div[hasclass('o_blog_post_complete','o_sharing_links')]" position="after">
<section id="comments" class="read_width">
<t t-call="website_mail.message_thread">
<t t-call="portal.message_thread">
<t t-set="object" t-value="blog_post"/>
</t>
</section>
@@ -507,7 +507,7 @@
<h4><strong>Message and communication history</strong></h4>
</div>
<div class="col-md-10 col-md-offset-1 mt16">
<t t-call="website_mail.message_thread">
<t t-call="portal.message_thread">
<t t-set="object" t-value="lead"/>
</t>
</div>
@@ -772,7 +772,7 @@
<h4><strong>Message and communication history</strong></h4>
</div>
<div class="col-md-10 col-md-offset-1 mt16">
<t t-call="website_mail.message_thread">
<t t-call="portal.message_thread">
<t t-set="object" t-value="opportunity"/>
</t>
</div>
+4 -1
View File
@@ -6,7 +6,10 @@
'category': 'Hidden',
'summary': 'Website Module for Mail',
'version': '0.1',
'description': """Glue module holding mail improvements for website.""",
'description': """
Module holding mail improvements for website.
It is responsible of comments moderation for published documents (forum, slides, blog, ...)
""",
'depends': ['website', 'mail'],
'data': [
'views/website_mail_templates.xml',
-95
View File
@@ -1,56 +1,8 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from werkzeug.exceptions import NotFound, Forbidden
from odoo import http
from odoo.http import request
from odoo.tools import consteq
def _special_access_object(res_model, res_id, token='', token_field=''):
record = request.env[res_model].browse(res_id).sudo()
if token and record and getattr(record, token_field, None) and consteq(getattr(record, token_field), token):
return True
return False
def _message_post_helper(res_model='', res_id=None, message='', token='', token_field='token', nosubscribe=True, **kw):
""" Generic chatter function, allowing to write on *any* object that inherits mail.thread.
If a token is specified, all logged in users will be able to write a message regardless
of access rights; if the user is the public user, the message will be posted under the name
of the partner_id of the object (or the public user if there is no partner_id on the object).
:param string res_model: model name of the object
:param int res_id: id of the object
:param string message: content of the message
optional keywords arguments:
:param string token: access token if the object's model uses some kind of public access
using tokens (usually a uuid4) to bypass access rules
:param string token_field: name of the field that contains the token on the object (defaults to 'token')
:param bool nosubscribe: set False if you want the partner to be set as follower of the object when posting (default to True)
The rest of the kwargs are passed on to message_post()
"""
record = request.env[res_model].browse(res_id)
author_id = request.env.user.partner_id.id if request.env.user.partner_id else False
if token_field and token:
access_as_sudo = _special_access_object(res_model, res_id, token=token, token_field=token_field)
if access_as_sudo:
record = record.sudo()
if request.env.user == request.env.ref('base.public_user'):
author_id = record.partner_id.id if hasattr(record, 'partner_id') else author_id
else:
if not author_id:
raise NotFound()
else:
raise Forbidden()
kw.pop('csrf_token', None)
return record.with_context(mail_create_nosubscribe=nosubscribe).message_post(body=message,
message_type=kw.pop('message_type', "comment"),
subtype=kw.pop('subtype', "mt_comment"),
author_id=author_id,
**kw)
class WebsiteMail(http.Controller):
@@ -108,50 +60,3 @@ class WebsiteMail(http.Controller):
('partner_id', '=', partner.id)
]))
return values
@http.route(['/website_mail/post/post'], type='http', methods=['POST'], auth='public', website=True)
def website_chatter_post(self, res_model, res_id, message, **kw):
url = request.httprequest.referrer
if message:
_message_post_helper(res_model, int(res_id), message, **kw)
url = url + "#discussion"
return request.redirect(url)
@http.route('/website_mail/init', type='json', auth='public', website=True)
def website_chatter_init(self, res_model, res_id, domain=False, limit=False, **kwargs):
is_user_public = bool(request.env.user == request.website.user_id)
message_data = self.website_message_fetch(res_model, res_id, domain=domain, limit=limit, **kwargs)
display_composer = False
if kwargs.get('allow_composer'):
display_composer = kwargs.get('token') or not is_user_public
return {
'messages': message_data['messages'],
'options': {
'message_count': message_data['message_count'],
'is_user_public': is_user_public,
'is_user_publisher': request.env.user.has_group('website.group_website_publisher'),
'display_composer': display_composer,
'partner_id': request.env.user.partner_id.id
}
}
@http.route('/website_mail/fetch', type='json', auth='public', website=True)
def website_message_fetch(self, res_model, res_id, domain=False, limit=10, offset=0, **kw):
if not domain:
domain = []
# Only search into website_message_ids, so apply the same domain to perform only one search
# extract domain from the 'website_message_ids' field
field_domain = request.env[res_model]._fields['website_message_ids'].domain
domain += field_domain(request.env[res_model]) if callable(field_domain) else field_domain
domain += [('res_id', '=', res_id)]
# Check access
Message = request.env['mail.message']
if kw.get('token'):
access_as_sudo = _special_access_object(res_model, res_id, token=kw.get('token'), token_field=kw.get('token_field'))
if not access_as_sudo: # if token is not correct, raise Forbidden
raise Forbidden()
Message = request.env['mail.message'].sudo()
return {
'messages': Message.search(domain, limit=limit, offset=offset).website_message_format(),
'message_count': Message.search_count(domain)
}
-1
View File
@@ -1,6 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import mail_thread
from . import mail_message
from . import update
+4 -11
View File
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, SUPERUSER_ID, _
from odoo import api, fields, models, _
from odoo.osv import expression
from odoo.tools import html2plaintext
from odoo.exceptions import AccessError
@@ -58,13 +58,6 @@ class MailMessage(models.Model):
return super(MailMessage, self).check_access_rule(operation=operation)
@api.multi
def website_message_format(self):
message_values = self.read([
'id', 'body', 'date', 'author_id', 'email_from', # base message fields
'message_type', 'subtype_id', 'subject', # message specific
'model', 'res_id', 'record_name', # document related
'website_published',
])
message_tree = dict((m.id, m) for m in self.sudo())
self._message_read_dict_postprocess(message_values, message_tree)
return message_values
def _portal_message_format(self, fields_list):
fields_list += ['website_published']
return super(MailMessage, self)._portal_message_format(fields_list)
@@ -4,7 +4,7 @@
<record id="mail_message_rule_public" model="ir.rule">
<field name="name">mail.message: portal/public: read published messages</field>
<field name="model_id" ref="model_mail_message"/>
<field name="model_id" ref="mail.model_mail_message"/>
<field name="domain_force">[('website_published', '=', True)]</field>
<field name="groups" eval="[(4, ref('base.group_portal')), (4, ref('base.group_public'))]"/>
</record>
@@ -2,37 +2,3 @@
.js_follow[data-follow='off'] .js_unfollow_btn {
display: none;
}
.o_website_mail_chatter {
padding: 10px;
.o_website_mail_avatar{
width: 45px;
height: 45px;
}
.o_website_mail_header {
margin-bottom: 15px;
}
.o_website_mail_composer {
margin-bottom: 15px;
}
.o_website_mail_messages {
margin-bottom: 15px;
.o_website_mail_message_title {
p {
font-size:85%;
color:@odoo-main-color-muted;
margin: 0px;
}
}
}
.o_website_mail_pager {
text-align: center;
}
}
@@ -1,237 +1,20 @@
odoo.define('website_mail.thread', function (require) {
odoo.define('website_mail.thread', function(require) {
'use strict';
var base = require('web_editor.base');
var ajax = require('web.ajax');
var core = require('web.core');
var Widget = require('web.Widget');
var rpc = require('web.rpc');
var qweb = core.qweb;
var _t = core._t;
var PortalChatter = require('portal.chatter').PortalChatter;
/**
* Widget WebsiteMailThread
*
* - Fetch message fron controller
* - Display chatter: pager, total message, composer (according to access right)
* - Provider API to filter displayed messages
* Extends Frontend Chatter to handle rating
*/
var WebsiteMailThread = Widget.extend({
template: 'website_mail.chatter',
events: {
"click .o_website_mail_pager_btn": '_onClickPager'
},
init: function (parent, options){
this._super.apply(this, arguments);
this.options = _.defaults(options || {}, {
'allow_composer': true,
'display_composer': false,
'csrf_token': odoo.csrf_token,
'message_count': 0,
'pager_step': 10,
'pager_scope': 5,
'pager_start': 1,
'is_user_public': true,
'is_user_publisher': false,
'domain': [],
});
this.set('messages', []);
this.set('message_count', this.options['message_count']);
this.set('pager', {});
this.set('domain', this.options['domain']);
this._current_page = this.options['pager_start'];
},
willStart: function (){
var self = this;
// load qweb template and init data
return $.when(
rpc.query({
route: '/website_mail/init',
params: this._messageFetchPrepareParams()
}), this._loadTemplates()
).then(function (result){
// bind events
self.on("change:messages", self, self._renderMessages);
self.on("change:message_count", self, function () {
self._renderMessageCount();
self.set('pager', self._pager(self._current_page));
});
self.on("change:pager", self, self._renderPager);
self.on("change:domain", self, self._onChangeDomain);
// set options and parameters
self.options = _.extend(self.options, result['options'] || {});
self.set('message_count', self.options['message_count']);
self.set('messages', self.preprocessMessages(result['messages']));
return result;
});
},
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
/**
* Fetch the messages and the message count from the server for the
* current page and current domain.
*
* @param {Array}
* @returns {Deferred}
*/
messageFetch: function (domain){
var self = this;
return rpc.query({
route: '/website_mail/fetch',
params: self._messageFetchPrepareParams()
}).then(function (result){
self.set('messages', self.preprocessMessages(result['messages']));
self.set('message_count', result['message_count']);
});
},
/**
* Update the messages format
*
* @param {Array<Object>}
* @returns {Array}
*/
preprocessMessages: function (messages){
_.each(messages, function (m){
m['author_avatar_url'] = _.str.sprintf('/web/image/%s/%s/author_avatar/50x50', 'mail.message', m.id);
m['published_date_str'] = _.str.sprintf(_t('Published on %s'), moment(m.date).format('MMMM Do YYYY, h:mm:ss a'));
});
return messages;
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* Change the current page by refreshing current domain
*
* @private
* @param {Number} page
* @param {Array} domain
*/
_changeCurrentPage: function (page, domain){
this._current_page = page;
var d = domain ? domain : _.clone(this.get('domain'));
this.set('domain', d); // trigger fetch message
},
/**
* @private
* @returns {Deferred}
*/
_loadTemplates: function () {
return ajax.loadXML('/website_mail/static/src/xml/website_mail.xml', qweb);
},
_messageFetchPrepareParams: function () {
var self = this;
var data = {
'res_model': this.options['res_model'],
'res_id': this.options['res_id'],
'limit': this.options['pager_step'],
'offset': (this._current_page-1) * this.options['pager_step'],
'allow_composer': this.options['allow_composer'],
};
// add fields to allow to post comment without being logged
_.each(['token', 'token_field'], function (field) {
if (self.options[field]){
data[field] = self.options[field];
}
});
// add domain
if (this.get('domain')){
data['domain'] = this.get('domain');
}
return data;
},
/**
* Generate the pager data for the given page number
*
* @private
* @param {Number} page
* @returns {Object}
*/
_pager: function (page) {
page = page || 1;
var total = this.get('message_count');
var scope = this.options['pager_scope'];
var step = this.options['pager_step'];
// Compute Pager
var page_count = Math.ceil(parseFloat(total) / step);
page = Math.max(1, Math.min(parseInt(page), page_count));
scope -= 1;
var pmin = Math.max(page - parseInt(Math.floor(scope/2)), 1);
var pmax = Math.min(pmin + scope, page_count);
if (pmax - scope > 0){
pmin = pmax - scope;
} else {
pmin = 1;
}
var pages = [];
_.each(_.range(pmin, pmax+1), function (index) {
pages.push(index);
});
return {
"page_count": page_count,
"offset": (page - 1) * step,
"page": page,
"page_start": pmin,
"page_previous": Math.max(pmin, page - 1),
"page_next": Math.min(pmax, page + 1),
"page_end": pmax,
"pages": pages
};
},
_renderMessages: function () {
this.$('.o_website_mail_messages').html(qweb.render("website_mail.chatter_messages", {widget: this}));
},
_renderMessageCount: function () {
this.$('.o_message_counter').replaceWith(qweb.render("website_mail.chatter_message_count", {widget: this}));
},
_renderPager: function () {
this.$('.o_website_mail_pager').replaceWith(qweb.render("website_mail.pager", {widget: this}));
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
_onChangeDomain: function () {
var self = this;
this.messageFetch().then(function () {
var p = self.get('current_page');
self.set('pager', self._pager(p));
});
},
/**
* @private
* @param {MouseEvent} event
*/
_onClickPager: function (ev) {
ev.preventDefault();
var page = $(ev.currentTarget).data('page');
this._changeCurrentPage(page);
PortalChatter.include({
_loadTemplates: function(){
return $.when(this._super(), ajax.loadXML('/website_mail/static/src/xml/website_mail.xml', qweb));
},
});
base.ready().then(function () {
$('.o_website_mail_thread').each(function (index) {
var $elem = $(this);
var mail_thread = new WebsiteMailThread(null, $elem.data());
mail_thread.appendTo($elem);
});
});
return {
WebsiteMailThread: WebsiteMailThread,
};
});
@@ -9,124 +9,15 @@
</t>
</t>
<t t-name="website_mail.chatter_message_count">
<t t-set="count" t-value="widget.get('message_count')"/>
<div class="o_message_counter">
<t t-if="count">
<span class="o_message_count fa fa-comments"> <t t-esc="count"/></span>
<t t-if="count == 1">comment</t>
<t t-else="">comments</t>
<t t-extend="portal.chatter_messages">
<t t-jquery=".o_portal_chatter_message_title" t-operation="before">
<t t-call="website_mail.publish_short">
<t t-set="res_model" t-value="'mail.message'"/>
<t t-set="res_id" t-value="message.id"/>
<t t-set="object" t-value="message"/>
<t t-set="is_publisher" t-value="widget.options['is_user_publisher']"/>
</t>
<t t-else="">
There are no comments for now.
</t>
</div>
</t>
</t>
<t t-name="website_mail.chatter_composer">
<div class="o_website_mail_composer" t-if="widget.options['allow_composer']">
<t t-if="!widget.options['display_composer']">
<h4>Leave a comment</h4>
<p>You must be <a t-attf-href="/web/login?redirect=#{window.encodeURI(window.location.href + '#discussion')}">logged in</a> to post a comment.</p>
</t>
<t t-if="widget.options['display_composer']">
<div class="media">
<div class="media-left">
<img class="o_website_mail_avatar pull-left" t-attf-src="/web/image/res.partner/#{widget.options['partner_id']}/image_small/50x50"/>
</div>
<div class="media-body">
<form class="o_website_mail_composer_form" t-attf-action="/website_mail/post/post" method="POST">
<input type="hidden" name="csrf_token" t-att-value="widget.options['csrf_token']"/>
<div class="mb32">
<textarea rows="4" name="message" class="form-control" placeholder="Write a message..."></textarea>
<input type='hidden' name="res_model" t-att-value="widget.options['res_model']"/>
<input type='hidden' name="res_id" t-att-value="widget.options['res_id']"/>
<input type='hidden' name="token" t-att-value="widget.options['token']" t-if="widget.options['token']"/>
<input type='hidden' name="sha_in" t-att-value="widget.options['sha_in']" t-if="widget.options['sha_in']"/>
<input type='hidden' name="sha_time" t-att-value="widget.options['sha_time']" t-if="widget.options['sha_time']"/>
<input type='hidden' name="token_field" t-att-value="widget.options['token_field']" t-if="widget.options['token_field']"/>
<div class="alert alert-danger mt8 mb0 o_website_mail_composer_error" style="display:none;">
Oops! Something went wrong. Try to reload the page and log in.
</div>
<button t-attf-class="o_website_mail_composer_btn btn btn-primary mt8 o_website_message_post_helper" type="submit">Send</button>
</div>
</form>
</div>
</div>
</t>
<hr/>
</div>
</t>
<t t-name="website_mail.chatter_messages">
<div class="o_website_mail_messages">
<t t-foreach="widget.get('messages')" t-as="message">
<div class="media o_website_mail_message" t-att-id="'message-' + message.id">
<div class="media-left">
<img class="media-object o_website_mail_avatar" t-att-src="message.author_avatar_url" alt="avatar"/>
</div>
<div class="media-body">
<t t-call="website_mail.publish_short">
<t t-set="res_model" t-value="'mail.message'"/>
<t t-set="res_id" t-value="message.id"/>
<t t-set="object" t-value="message"/>
<t t-set="is_publisher" t-value="widget.options['is_user_publisher']"/>
</t>
<div class="o_website_mail_message_title">
<h5 class="media-heading">
<t t-esc="message.author_id[1]"/>
</h5>
<p class="o_website_mail_puslished_date"><t t-esc="message.published_date_str"/></p>
</div>
<t t-raw="message.body"/>
<div class="o_website_mail_attachments">
<div class="col-md-2 col-sm-3 text-center" t-foreach='message.attachment_ids' t-as='attachment'>
<a t-attf-href="/web/content/#{attachment.id}?download=true" target="_blank">
<div class='oe_attachment_embedded o_image' t-att-title="attachment.name" t-att-data-mimetype="attachment.mimetype" t-attf-data-src="/web/image/#{attachment.id}/100x80"/>
<div class='oe_attachment_name'><t t-raw='attachment.name'/></div>
</a>
</div>
</div>
</div>
</div>
</t>
</div>
</t>
<t t-name="website_mail.pager">
<div class="o_website_mail_pager">
<t t-if="!_.isEmpty(widget.get('pager'))">
<ul class="pagination" t-if="widget.get('pager')['pages'].length &gt; 1">
<li t-if="widget.get('pager')['page'] != widget.get('pager')['page_previous']" t-att-data-page="widget.get('pager')['page_previous']" class="o_website_mail_pager_btn">
<a href="#"><i class="fa fa-chevron-left"/></a>
</li>
<t t-foreach="widget.get('pager')['pages']" t-as="page">
<li t-att-data-page="page" t-att-class="page == widget.get('pager')['page'] ? 'o_website_mail_pager_btn active' : 'o_website_mail_pager_btn'">
<a href="#"><t t-esc="page"/></a>
</li>
</t>
<li t-if="widget.get('pager')['page'] != widget.get('pager')['page_next']" t-att-data-page="widget.get('pager')['page_next']" class="o_website_mail_pager_btn">
<a href="#"><i class="fa fa-chevron-right"/></a>
</li>
</ul>
</t>
</div>
</t>
<t t-name="website_mail.chatter">
<div class="o_website_mail_chatter">
<div class="o_website_mail_header">
<t t-call="website_mail.chatter_message_count"/>
</div>
<hr/>
<t t-call="website_mail.chatter_composer"/>
<t t-call="website_mail.chatter_messages"/>
<div class="o_website_mail_footer">
<t t-call="website_mail.pager"/>
</div>
</div>
</t>
</templates>
@@ -25,21 +25,4 @@
</xpath>
</template>
<!--
Generic chatter template for the frontend
This template provide the container of the chatter. The rest is done in js.
To use this template, you need to call it after setting the following variable in your template or in your controller:
:object browserecord : the mail_thread object
:message_per_page int (optional): number of message per chatter page
:token string (optional): if you want your chatter to be available for non-logged user,
you can use a token to verify the identity of the user;
the message will be posted with the identity of the partner_id of the object
:token_field string (optional): name of the field that contains the token on the object (default to 'token')
-->
<template id="message_thread">
<div id="discussion" class="hidden-print o_website_mail_thread"
t-att-data-token="token" t-att-public-user="website.env.user == request.website.user_id" t-att-data-res_model="object._name" t-att-data-res_id="object.id" t-att-data-token_field="token_field" t-att-data-pager_step="message_per_page or 10" t-att-data-allow_composer="'0' if disable_composer else '1'">
</div>
</template>
</odoo>
-5
View File
@@ -1,5 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import controllers
from . import models
-20
View File
@@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': "Website Project",
'description': """
Website portal for Project and Tasks
====================================
""",
'category': 'Website',
'depends': ['project', 'website', 'portal'],
'data': [
'security/project_security.xml',
'security/ir.model.access.csv',
'views/project_templates.xml',
],
'demo': [
'demo/project_demo.xml',
],
'auto_install': True,
}
@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="project.project_project_1" model="project.project">
<field name="partner_id" ref="base.partner_demo_portal"/>
<field name="privacy_visibility">portal</field>
</record>
<function model="project.project" name="message_subscribe"
eval="[ref('project.project_project_1')], [ref('base.partner_demo_portal')]"/>
</data>
</odoo>
-189
View File
@@ -1,189 +0,0 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * website_project
#
# Translators:
# Antonio Trueba <atgayol@gmail.com>, 2016
# Roberto Lizana <robertolizana@trey.es>, 2016
# José Vicente <txusev@gmail.com>, 2016
# Martin Trigaux <mat@odoo.com>, 2016
# oihane <oihanecruce@gmail.com>, 2016
# Alejandro Die Sanchis <marketing@domatix.com>, 2016
# Gustavo Orozco H. <gustavo.orozco.h@app-247.com>, 2016
# José Antonio Cuello <inactive+Yopli2k@transifex.com>, 2017
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 10.saas~14\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-15 09:50+0000\n"
"PO-Revision-Date: 2017-02-15 09:50+0000\n"
"Last-Translator: José Antonio Cuello <inactive+Yopli2k@transifex.com>, 2017\n"
"Language-Team: Spanish (https://www.transifex.com/odoo/teams/41243/es/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: es\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Assigned to</strong>"
msgstr "<strong>Asignado a</strong>"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_project
msgid "<strong>Customer</strong>"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Date:</strong>"
msgstr "<strong>Fecha:</strong>"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Deadline:</strong>"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Message and communication history</strong>"
msgstr "<strong>Histórico de mensajes y comunicaciones</strong>"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_project
msgid "<strong>Project Manager</strong>"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Project:</strong>"
msgstr "<strong>Proyecto:</strong>"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Reported by</strong>"
msgstr "<strong>Notificado por</strong>"
#. module: website_project
#: code:addons/website_project/controllers/main.py:84
#, python-format
msgid "All"
msgstr "Todos"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_tasks
msgid "Current stage of the task"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "Current stage of this task"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "Edit Task"
msgstr "Editar Tarea"
#. module: website_project
#: code:addons/website_project/controllers/main.py:81
#, python-format
msgid "Last Stage Update"
msgstr "última actualización de la etapa"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_project
msgid "My Projects"
msgstr "Mis proyectos"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "My Tasks"
msgstr "Mis tareas"
#. module: website_project
#: code:addons/website_project/controllers/main.py:31
#: code:addons/website_project/controllers/main.py:79
#: model:ir.ui.view,arch_db:website_project.my_projects
#, python-format
msgid "Name"
msgstr "Nombre"
#. module: website_project
#: code:addons/website_project/controllers/main.py:30
#: code:addons/website_project/controllers/main.py:78
#, python-format
msgid "Newest"
msgstr "Más moderno"
#. module: website_project
#: model:ir.model,name:website_project.model_project_project
msgid "Project"
msgstr "Proyecto"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_project
msgid "Project -"
msgstr "Proyecto -"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.portal_layout
msgid "Projects"
msgstr "Proyectos"
#. module: website_project
#: code:addons/website_project/controllers/main.py:80
#: model:ir.ui.view,arch_db:website_project.my_tasks
#, python-format
msgid "Stage"
msgstr "Etapa"
#. module: website_project
#: model:ir.model,name:website_project.model_project_task
#: model:ir.ui.view,arch_db:website_project.my_tasks
msgid "Task"
msgstr "Tarea"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "Task -"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.portal_layout
msgid "Tasks"
msgstr "Tareas"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.portal_my_home
msgid "There are currently no projects."
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.portal_my_home
msgid "There are currently no quotes."
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_projects
msgid "There are no projects."
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_tasks
msgid "There are no tasks."
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_projects
#: model:ir.ui.view,arch_db:website_project.portal_my_home
msgid "Your Projects"
msgstr "Sus Proyectos"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_tasks
#: model:ir.ui.view,arch_db:website_project.portal_my_home
msgid "Your Tasks"
msgstr ""
-190
View File
@@ -1,190 +0,0 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * website_project
#
# Translators:
# Xavier Belmere <Info@cartmeleon.com>, 2016
# Frédéric LIETART <stuff@tifred.fr>, 2016
# Miguel Vidali <mvidali129@gmail.com>, 2016
# Martin Trigaux <mat@odoo.com>, 2016
# Leo Schmitt <inactive+lschmitt@transifex.com>, 2016
# Maxime Chambreuil <mchambreuil@ursainfosystems.com>, 2016
# Henri Cornuau <hcornuau@gmail.com>, 2016
# Jérémy Donas <LeDistordu@users.noreply.github.com>, 2016
# Foo Bar <mat+test@odoo.com>, 2017
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 10.saas~14\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-15 09:50+0000\n"
"PO-Revision-Date: 2017-02-15 09:50+0000\n"
"Last-Translator: Foo Bar <mat+test@odoo.com>, 2017\n"
"Language-Team: French (https://www.transifex.com/odoo/teams/41243/fr/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: fr\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Assigned to</strong>"
msgstr "<strong>Assigné à</strong>"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_project
msgid "<strong>Customer</strong>"
msgstr "<strong>Client</strong>"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Date:</strong>"
msgstr "<strong>Date :</strong>"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Deadline:</strong>"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Message and communication history</strong>"
msgstr "<strong>Historique des messages et communications</strong>"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_project
msgid "<strong>Project Manager</strong>"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Project:</strong>"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Reported by</strong>"
msgstr "<strong>Rapporté par</strong>"
#. module: website_project
#: code:addons/website_project/controllers/main.py:84
#, python-format
msgid "All"
msgstr "Tous"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_tasks
msgid "Current stage of the task"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "Current stage of this task"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "Edit Task"
msgstr "Modifié la Tâche"
#. module: website_project
#: code:addons/website_project/controllers/main.py:81
#, python-format
msgid "Last Stage Update"
msgstr "Dernière étape de mise à jour"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_project
msgid "My Projects"
msgstr "Mes projets"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "My Tasks"
msgstr "Mes tâches par zone de temps"
#. module: website_project
#: code:addons/website_project/controllers/main.py:31
#: code:addons/website_project/controllers/main.py:79
#: model:ir.ui.view,arch_db:website_project.my_projects
#, python-format
msgid "Name"
msgstr "Nom"
#. module: website_project
#: code:addons/website_project/controllers/main.py:30
#: code:addons/website_project/controllers/main.py:78
#, python-format
msgid "Newest"
msgstr "Le plus récent"
#. module: website_project
#: model:ir.model,name:website_project.model_project_project
msgid "Project"
msgstr "Projet"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_project
msgid "Project -"
msgstr "Projet -"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.portal_layout
msgid "Projects"
msgstr "Projets"
#. module: website_project
#: code:addons/website_project/controllers/main.py:80
#: model:ir.ui.view,arch_db:website_project.my_tasks
#, python-format
msgid "Stage"
msgstr "Étape"
#. module: website_project
#: model:ir.model,name:website_project.model_project_task
#: model:ir.ui.view,arch_db:website_project.my_tasks
msgid "Task"
msgstr "Tâche"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "Task -"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.portal_layout
msgid "Tasks"
msgstr "Tâches"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.portal_my_home
msgid "There are currently no projects."
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.portal_my_home
msgid "There are currently no quotes."
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_projects
msgid "There are no projects."
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_tasks
msgid "There are no tasks."
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_projects
#: model:ir.ui.view,arch_db:website_project.portal_my_home
msgid "Your Projects"
msgstr "Vos projets"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_tasks
#: model:ir.ui.view,arch_db:website_project.portal_my_home
msgid "Your Tasks"
msgstr ""
-183
View File
@@ -1,183 +0,0 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * website_project
#
# Translators:
# krnkris <krnkris@freemail.hu>, 2016
# Martin Trigaux <mat@odoo.com>, 2016
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 10.saas~14\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-15 09:50+0000\n"
"PO-Revision-Date: 2017-02-15 09:50+0000\n"
"Last-Translator: Martin Trigaux <mat@odoo.com>, 2016\n"
"Language-Team: Hungarian (https://www.transifex.com/odoo/teams/41243/hu/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: hu\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Assigned to</strong>"
msgstr "<strong>Hozzárendelve</strong>"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_project
msgid "<strong>Customer</strong>"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Date:</strong>"
msgstr "<strong>Dátum:</strong>"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Deadline:</strong>"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Message and communication history</strong>"
msgstr "<strong>Üzenetek és kommunikáció történet</strong>"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_project
msgid "<strong>Project Manager</strong>"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Project:</strong>"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Reported by</strong>"
msgstr "<strong>Által jelentve</strong>"
#. module: website_project
#: code:addons/website_project/controllers/main.py:84
#, python-format
msgid "All"
msgstr "Összes"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_tasks
msgid "Current stage of the task"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "Current stage of this task"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "Edit Task"
msgstr "Feladat szerkesztése"
#. module: website_project
#: code:addons/website_project/controllers/main.py:81
#, python-format
msgid "Last Stage Update"
msgstr "Utolsó szint frissítése"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_project
msgid "My Projects"
msgstr "Projekt témáim"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "My Tasks"
msgstr "Feladataim"
#. module: website_project
#: code:addons/website_project/controllers/main.py:31
#: code:addons/website_project/controllers/main.py:79
#: model:ir.ui.view,arch_db:website_project.my_projects
#, python-format
msgid "Name"
msgstr "Név"
#. module: website_project
#: code:addons/website_project/controllers/main.py:30
#: code:addons/website_project/controllers/main.py:78
#, python-format
msgid "Newest"
msgstr "Legújabb"
#. module: website_project
#: model:ir.model,name:website_project.model_project_project
msgid "Project"
msgstr "Projekt téma"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_project
msgid "Project -"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.portal_layout
msgid "Projects"
msgstr "Projekt témák"
#. module: website_project
#: code:addons/website_project/controllers/main.py:80
#: model:ir.ui.view,arch_db:website_project.my_tasks
#, python-format
msgid "Stage"
msgstr "Értékesítési szint"
#. module: website_project
#: model:ir.model,name:website_project.model_project_task
#: model:ir.ui.view,arch_db:website_project.my_tasks
msgid "Task"
msgstr "Fladat"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "Task -"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.portal_layout
msgid "Tasks"
msgstr "Feladatok"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.portal_my_home
msgid "There are currently no projects."
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.portal_my_home
msgid "There are currently no quotes."
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_projects
msgid "There are no projects."
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_tasks
msgid "There are no tasks."
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_projects
#: model:ir.ui.view,arch_db:website_project.portal_my_home
msgid "Your Projects"
msgstr "Projekt témái"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_tasks
#: model:ir.ui.view,arch_db:website_project.portal_my_home
msgid "Your Tasks"
msgstr ""
-185
View File
@@ -1,185 +0,0 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * website_project
#
# Translators:
# Yenthe Van Ginneken <yenthespam@gmail.com>, 2016
# Cas Vissers <casvissers@brahoo.nl>, 2016
# Martin Trigaux <mat@odoo.com>, 2016
# Erwin van der Ploeg <erwin@odooexperts.nl>, 2016
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 10.saas~14\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-15 09:50+0000\n"
"PO-Revision-Date: 2017-02-15 09:50+0000\n"
"Last-Translator: Erwin van der Ploeg <erwin@odooexperts.nl>, 2016\n"
"Language-Team: Dutch (https://www.transifex.com/odoo/teams/41243/nl/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: nl\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Assigned to</strong>"
msgstr "<strong>Toegewezen aan</strong>"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_project
msgid "<strong>Customer</strong>"
msgstr "<strong>Klant</strong>"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Date:</strong>"
msgstr "<strong>Datum:</strong>"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Deadline:</strong>"
msgstr "<strong>Deadline:</strong>"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Message and communication history</strong>"
msgstr "<strong>Berichten en communicatie historie</strong>"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_project
msgid "<strong>Project Manager</strong>"
msgstr "<strong>Projectleider</strong>"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Project:</strong>"
msgstr "<strong>Project:</strong>"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Reported by</strong>"
msgstr "<strong>Gerapporteerd door</strong>"
#. module: website_project
#: code:addons/website_project/controllers/main.py:84
#, python-format
msgid "All"
msgstr "Alle"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_tasks
msgid "Current stage of the task"
msgstr "Huidige fase van de taak"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "Current stage of this task"
msgstr "Huidige fase van deze taak"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "Edit Task"
msgstr "Wijzig taak"
#. module: website_project
#: code:addons/website_project/controllers/main.py:81
#, python-format
msgid "Last Stage Update"
msgstr "Laatste fase aanpassing"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_project
msgid "My Projects"
msgstr "Mijn projecten"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "My Tasks"
msgstr "Mijn taken"
#. module: website_project
#: code:addons/website_project/controllers/main.py:31
#: code:addons/website_project/controllers/main.py:79
#: model:ir.ui.view,arch_db:website_project.my_projects
#, python-format
msgid "Name"
msgstr "Naam"
#. module: website_project
#: code:addons/website_project/controllers/main.py:30
#: code:addons/website_project/controllers/main.py:78
#, python-format
msgid "Newest"
msgstr "Nieuwste"
#. module: website_project
#: model:ir.model,name:website_project.model_project_project
msgid "Project"
msgstr "Project"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_project
msgid "Project -"
msgstr "Project -"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.portal_layout
msgid "Projects"
msgstr "Projecten"
#. module: website_project
#: code:addons/website_project/controllers/main.py:80
#: model:ir.ui.view,arch_db:website_project.my_tasks
#, python-format
msgid "Stage"
msgstr "Fase"
#. module: website_project
#: model:ir.model,name:website_project.model_project_task
#: model:ir.ui.view,arch_db:website_project.my_tasks
msgid "Task"
msgstr "Taak"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "Task -"
msgstr "Taak -"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.portal_layout
msgid "Tasks"
msgstr "Taken"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.portal_my_home
msgid "There are currently no projects."
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.portal_my_home
msgid "There are currently no quotes."
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_projects
msgid "There are no projects."
msgstr "Er zijn geen projecten."
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_tasks
msgid "There are no tasks."
msgstr "Er zijn geen taken."
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_projects
#: model:ir.ui.view,arch_db:website_project.portal_my_home
msgid "Your Projects"
msgstr "Uw projecten"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_tasks
#: model:ir.ui.view,arch_db:website_project.portal_my_home
msgid "Your Tasks"
msgstr "Uw taken"
-187
View File
@@ -1,187 +0,0 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * website_project
#
# Translators:
# Mateus Lopes <mateus1@gmail.com>, 2016
# falexandresilva <falexandresilva@gmail.com>, 2016
# Martin Trigaux <mat@odoo.com>, 2016
# Peter Leaf <pablleaf@gmail.com>, 2016
# grazziano <gra.negocia@gmail.com>, 2016
# Anderson Fratuci <ander.frat@gmail.com>, 2016
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 10.saas~14\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-15 09:50+0000\n"
"PO-Revision-Date: 2017-02-15 09:50+0000\n"
"Last-Translator: Anderson Fratuci <ander.frat@gmail.com>, 2016\n"
"Language-Team: Portuguese (Brazil) (https://www.transifex.com/odoo/teams/41243/pt_BR/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: pt_BR\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Assigned to</strong>"
msgstr "<strong>Atribuído a</strong>"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_project
msgid "<strong>Customer</strong>"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Date:</strong>"
msgstr "<strong>Data:</strong>"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Deadline:</strong>"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Message and communication history</strong>"
msgstr "<strong>Histórico de mensagem e comunicação</strong>"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_project
msgid "<strong>Project Manager</strong>"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Project:</strong>"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Reported by</strong>"
msgstr "<strong>Reportado por</strong>"
#. module: website_project
#: code:addons/website_project/controllers/main.py:84
#, python-format
msgid "All"
msgstr "Tudo"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_tasks
msgid "Current stage of the task"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "Current stage of this task"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "Edit Task"
msgstr ""
#. module: website_project
#: code:addons/website_project/controllers/main.py:81
#, python-format
msgid "Last Stage Update"
msgstr "Última Atualização de Estágio"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_project
msgid "My Projects"
msgstr "Meus Projetos"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "My Tasks"
msgstr "Minhas Tarefas"
#. module: website_project
#: code:addons/website_project/controllers/main.py:31
#: code:addons/website_project/controllers/main.py:79
#: model:ir.ui.view,arch_db:website_project.my_projects
#, python-format
msgid "Name"
msgstr "Nome"
#. module: website_project
#: code:addons/website_project/controllers/main.py:30
#: code:addons/website_project/controllers/main.py:78
#, python-format
msgid "Newest"
msgstr "Mais Recente"
#. module: website_project
#: model:ir.model,name:website_project.model_project_project
msgid "Project"
msgstr "Projeto"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_project
msgid "Project -"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.portal_layout
msgid "Projects"
msgstr "Projetos"
#. module: website_project
#: code:addons/website_project/controllers/main.py:80
#: model:ir.ui.view,arch_db:website_project.my_tasks
#, python-format
msgid "Stage"
msgstr "Estágio"
#. module: website_project
#: model:ir.model,name:website_project.model_project_task
#: model:ir.ui.view,arch_db:website_project.my_tasks
msgid "Task"
msgstr "Tarefa"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "Task -"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.portal_layout
msgid "Tasks"
msgstr "Tarefas"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.portal_my_home
msgid "There are currently no projects."
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.portal_my_home
msgid "There are currently no quotes."
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_projects
msgid "There are no projects."
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_tasks
msgid "There are no tasks."
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_projects
#: model:ir.ui.view,arch_db:website_project.portal_my_home
msgid "Your Projects"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_tasks
#: model:ir.ui.view,arch_db:website_project.portal_my_home
msgid "Your Tasks"
msgstr ""
@@ -1,215 +0,0 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * website_project
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 10.saas~16\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-07-25 10:04+0000\n"
"PO-Revision-Date: 2017-07-25 10:04+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Assigned to</strong>"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_project
msgid "<strong>Customer</strong>"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Date:</strong>"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Deadline:</strong>"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Message and communication history</strong>"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_project
msgid "<strong>Project Manager</strong>"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Project:</strong>"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Reported by</strong>"
msgstr ""
#. module: website_project
#: code:addons/website_project/controllers/main.py:88
#, python-format
msgid "All"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_tasks
msgid "Current stage of the task"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "Current stage of this task"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "Edit Task"
msgstr ""
#. module: website_project
#: code:addons/website_project/controllers/main.py:85
#, python-format
msgid "Last Stage Update"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_project
msgid "My Projects"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "My Tasks"
msgstr ""
#. module: website_project
#: code:addons/website_project/controllers/main.py:31
#: model:ir.ui.view,arch_db:website_project.my_projects
#, python-format
msgid "Name"
msgstr ""
#. module: website_project
#: code:addons/website_project/controllers/main.py:30
#: code:addons/website_project/controllers/main.py:82
#, python-format
msgid "Newest"
msgstr ""
#. module: website_project
#: model:ir.model,name:website_project.model_project_project
msgid "Project"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_project
msgid "Project -"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.portal_layout
msgid "Projects"
msgstr ""
#. module: website_project
#: code:addons/website_project/controllers/main.py:91
#, python-format
msgid "Search <span class=\"nolabel\"> (in Content)</span>"
msgstr ""
#. module: website_project
#: code:addons/website_project/controllers/main.py:95
#, python-format
msgid "Search in All"
msgstr ""
#. module: website_project
#: code:addons/website_project/controllers/main.py:93
#, python-format
msgid "Search in Customer"
msgstr ""
#. module: website_project
#: code:addons/website_project/controllers/main.py:92
#, python-format
msgid "Search in Messages"
msgstr ""
#. module: website_project
#: code:addons/website_project/controllers/main.py:94
#, python-format
msgid "Search in Stages"
msgstr ""
#. module: website_project
#: code:addons/website_project/controllers/main.py:84
#: model:ir.ui.view,arch_db:website_project.my_tasks
#, python-format
msgid "Stage"
msgstr ""
#. module: website_project
#: model:ir.model,name:website_project.model_project_task
#: model:ir.ui.view,arch_db:website_project.my_tasks
msgid "Task"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "Task -"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.portal_layout
msgid "Tasks"
msgstr ""
#. module: website_project
#: model:ir.model.fields,help:website_project.field_project_task_website_url
msgid "The full URL to access the document through the website."
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_projects
msgid "There are no projects."
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_tasks
msgid "There are no tasks."
msgstr ""
#. module: website_project
#: code:addons/website_project/controllers/main.py:83
#, python-format
msgid "Title"
msgstr ""
#. module: website_project
#: model:ir.model.fields,field_description:website_project.field_project_task_website_url
msgid "Website URL"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_projects
#: model:ir.ui.view,arch_db:website_project.portal_my_home
msgid "Your Projects"
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_tasks
#: model:ir.ui.view,arch_db:website_project.portal_my_home
msgid "Your Tasks"
msgstr ""
-188
View File
@@ -1,188 +0,0 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * website_project
#
# Translators:
# Jeffery CHEN <jeffery9@gmail.com>, 2016
# Gary Wei <Gary.wei@elico-corp.com>, 2016
# Martin Trigaux <mat@odoo.com>, 2016
# Talway <9010446@qq.com>, 2016
# Luke <cialuo@qq.com>, 2016
# 苏州远鼎 <tiexinliu@126.com>, 2016
# waveyeung <waveyeung@qq.com>, 2016
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 10.saas~14\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-15 09:50+0000\n"
"PO-Revision-Date: 2017-02-15 09:50+0000\n"
"Last-Translator: waveyeung <waveyeung@qq.com>, 2016\n"
"Language-Team: Chinese (China) (https://www.transifex.com/odoo/teams/41243/zh_CN/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: zh_CN\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Assigned to</strong>"
msgstr "<strong>分配给</strong>"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_project
msgid "<strong>Customer</strong>"
msgstr "<strong>客户</strong>"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Date:</strong>"
msgstr "<strong>日期:</strong>"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Deadline:</strong>"
msgstr "<strong>截至日期:</strong>"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Message and communication history</strong>"
msgstr "<strong>消息和交流历史</strong>"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_project
msgid "<strong>Project Manager</strong>"
msgstr "<strong>项目管理</strong>"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Project:</strong>"
msgstr "<strong>项目:</strong>"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "<strong>Reported by</strong>"
msgstr "<strong>汇报</strong>"
#. module: website_project
#: code:addons/website_project/controllers/main.py:84
#, python-format
msgid "All"
msgstr "All"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_tasks
msgid "Current stage of the task"
msgstr "任务的当前阶段"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "Current stage of this task"
msgstr "任务的当前阶段"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "Edit Task"
msgstr "编辑任务"
#. module: website_project
#: code:addons/website_project/controllers/main.py:81
#, python-format
msgid "Last Stage Update"
msgstr "最后阶段更新"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_project
msgid "My Projects"
msgstr "我的项目"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "My Tasks"
msgstr "我的任务"
#. module: website_project
#: code:addons/website_project/controllers/main.py:31
#: code:addons/website_project/controllers/main.py:79
#: model:ir.ui.view,arch_db:website_project.my_projects
#, python-format
msgid "Name"
msgstr "名称"
#. module: website_project
#: code:addons/website_project/controllers/main.py:30
#: code:addons/website_project/controllers/main.py:78
#, python-format
msgid "Newest"
msgstr "最新"
#. module: website_project
#: model:ir.model,name:website_project.model_project_project
msgid "Project"
msgstr "项目"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_project
msgid "Project -"
msgstr "项目 -"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.portal_layout
msgid "Projects"
msgstr "项目"
#. module: website_project
#: code:addons/website_project/controllers/main.py:80
#: model:ir.ui.view,arch_db:website_project.my_tasks
#, python-format
msgid "Stage"
msgstr "阶段"
#. module: website_project
#: model:ir.model,name:website_project.model_project_task
#: model:ir.ui.view,arch_db:website_project.my_tasks
msgid "Task"
msgstr "任务"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_task
msgid "Task -"
msgstr "任务 -"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.portal_layout
msgid "Tasks"
msgstr "任务"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.portal_my_home
msgid "There are currently no projects."
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.portal_my_home
msgid "There are currently no quotes."
msgstr ""
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_projects
msgid "There are no projects."
msgstr "没有项目。"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_tasks
msgid "There are no tasks."
msgstr "没有任务。"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_projects
#: model:ir.ui.view,arch_db:website_project.portal_my_home
msgid "Your Projects"
msgstr "你的项目"
#. module: website_project
#: model:ir.ui.view,arch_db:website_project.my_tasks
#: model:ir.ui.view,arch_db:website_project.portal_my_home
msgid "Your Tasks"
msgstr "你的任务"
@@ -1,4 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import project
-93
View File
@@ -1,93 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, exceptions, models, fields
class Project(models.Model):
_inherit = ['project.project']
@api.multi
def get_access_action(self, access_uid=None):
""" Instead of the classic form view, redirect to website for portal users
that can read the project. """
self.ensure_one()
user, record = self.env.user, self
if access_uid:
user = self.env['res.users'].sudo().browse(access_uid)
record = self.sudo(user)
if user.share:
try:
record.check_access_rule('read')
except exceptions.AccessError:
pass
else:
return {
'type': 'ir.actions.act_url',
'url': '/my/project/%s' % self.id,
'target': 'self',
'res_id': self.id,
}
return super(Project, self).get_access_action(access_uid)
@api.multi
def _notification_recipients(self, message, groups):
groups = super(Project, self)._notification_recipients(message, groups)
for group_name, group_method, group_data in groups:
group_data['has_button_access'] = True
return groups
class Task(models.Model):
_inherit = ['project.task']
website_url = fields.Char('Website URL', compute='_compute_website_url', help='The full URL to access the document through the website.')
attachment_ids = fields.One2many('ir.attachment', compute='_compute_attachment_ids', string="Main Attachments",
help="Attachment that don't come from message.")
def _compute_attachment_ids(self):
for task in self:
attachment_ids = self.env['ir.attachment'].search([('res_id', '=', task.id), ('res_model', '=', 'project.task')]).ids
message_attachment_ids = self.mapped('message_ids.attachment_ids').ids # from mail_thread
task.attachment_ids = list(set(attachment_ids) - set(message_attachment_ids))
def _compute_website_url(self):
for task in self:
task.website_url = '/my/task/%s' % task.id
@api.multi
def get_access_action(self, access_uid=None):
""" Instead of the classic form view, redirect to website for portal users
that can read the task. """
self.ensure_one()
user, record = self.env.user, self
if access_uid:
user = self.env['res.users'].sudo().browse(access_uid)
record = self.sudo(user)
if user.share:
try:
record.check_access_rule('read')
except exceptions.AccessError:
pass
else:
return {
'type': 'ir.actions.act_url',
'url': self.website_url,
'target': 'self',
'res_id': self.id,
}
return super(Task, self).get_access_action(access_uid)
@api.multi
def _notification_recipients(self, message, groups):
groups = super(Task, self)._notification_recipients(message, groups)
for group_name, group_method, group_data in groups:
group_data['has_button_access'] = True
return groups
@@ -1,6 +0,0 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_account_analytic_account_portal,account_analytic_account,analytic.model_account_analytic_account,base.group_portal,1,0,0,0
access_project_tags_portal,project_tags_portal,project.model_project_tags,base.group_portal,1,0,0,0
access_project_portal,project__portal,project.model_project_project,base.group_portal,1,0,0,0
access_task_portal,task_portal,project.model_project_task,base.group_portal,1,0,0,0
access_task_type_portal,task_type_portal,project.model_project_task_type,base.group_portal,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_account_analytic_account_portal account_analytic_account analytic.model_account_analytic_account base.group_portal 1 0 0 0
3 access_project_tags_portal project_tags_portal project.model_project_tags base.group_portal 1 0 0 0
4 access_project_portal project__portal project.model_project_project base.group_portal 1 0 0 0
5 access_task_portal task_portal project.model_project_task base.group_portal 1 0 0 0
6 access_task_type_portal task_type_portal project.model_project_task_type base.group_portal 1 0 0 0
@@ -1,31 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- Portal -->
<record id="portal_project_rule" model="ir.rule">
<field name="name">Project: portal users: portal and following</field>
<field name="model_id" ref="project.model_project_project"/>
<field name="domain_force">[
'&amp;',
('privacy_visibility', '=', 'portal'),
('message_partner_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
]</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
</record>
<record id="portal_task_rule" model="ir.rule">
<field name="name">Project/Task: portal users: (portal and following project) or (portal and following task)</field>
<field name="model_id" ref="project.model_project_task"/>
<field name="domain_force">[
'|',
'&amp;',
('project_id.privacy_visibility', '=', 'portal'),
('project_id.message_partner_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
'&amp;',
('project_id.privacy_visibility', '=', 'portal'),
('message_partner_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
]</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
</record>
</data>
</odoo>
-4
View File
@@ -1,4 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import test_access_rights
@@ -1,17 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': 'Timesheet in Website Portal',
'category': 'Website',
'description': """
Display Timesheet on Task in the Website Portal
===============================================
""",
'depends': ['website_project', 'hr_timesheet'],
'data': [
'security/ir.model.access.csv',
'security/account_analytic_line_security.xml',
'views/account_analytic_line_templates.xml',
],
'auto_install': True,
}
@@ -1,41 +0,0 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * website_project_timesheet
#
# Translators:
# Martin Trigaux <mat@odoo.com>, 2016
# Pedro M. Baeza <pedro.baeza@gmail.com>, 2016
# José Vicente <txusev@gmail.com>, 2016
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 10.saas~14\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-15 09:50+0000\n"
"PO-Revision-Date: 2017-02-15 09:50+0000\n"
"Last-Translator: José Vicente <txusev@gmail.com>, 2016\n"
"Language-Team: Spanish (https://www.transifex.com/odoo/teams/41243/es/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: es\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. module: website_project_timesheet
#: model:ir.ui.view,arch_db:website_project_timesheet.my_task
msgid "Date"
msgstr "Fecha"
#. module: website_project_timesheet
#: model:ir.ui.view,arch_db:website_project_timesheet.my_task
msgid "Description"
msgstr "Descripción"
#. module: website_project_timesheet
#: model:ir.ui.view,arch_db:website_project_timesheet.my_task
msgid "Duration"
msgstr "Duración"
#. module: website_project_timesheet
#: model:ir.ui.view,arch_db:website_project_timesheet.my_task
msgid "Responsible"
msgstr "Responsable"
@@ -1,42 +0,0 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * website_project_timesheet
#
# Translators:
# Martin Trigaux <mat@odoo.com>, 2016
# Olivier Lenoir <olivier.lenoir@free.fr>, 2016
# Fabien Pinckaers <fp@openerp.com>, 2016
# Melanie Bernard <mbe@odoo.com>, 2016
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 10.saas~14\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-15 09:50+0000\n"
"PO-Revision-Date: 2017-02-15 09:50+0000\n"
"Last-Translator: Melanie Bernard <mbe@odoo.com>, 2016\n"
"Language-Team: French (https://www.transifex.com/odoo/teams/41243/fr/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: fr\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#. module: website_project_timesheet
#: model:ir.ui.view,arch_db:website_project_timesheet.my_task
msgid "Date"
msgstr "Date "
#. module: website_project_timesheet
#: model:ir.ui.view,arch_db:website_project_timesheet.my_task
msgid "Description"
msgstr "Description"
#. module: website_project_timesheet
#: model:ir.ui.view,arch_db:website_project_timesheet.my_task
msgid "Duration"
msgstr "Durée"
#. module: website_project_timesheet
#: model:ir.ui.view,arch_db:website_project_timesheet.my_task
msgid "Responsible"
msgstr "Responsable"
@@ -1,40 +0,0 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * website_project_timesheet
#
# Translators:
# Martin Trigaux <mat@odoo.com>, 2016
# krnkris <krnkris@freemail.hu>, 2016
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 10.saas~14\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-15 09:50+0000\n"
"PO-Revision-Date: 2017-02-15 09:50+0000\n"
"Last-Translator: krnkris <krnkris@freemail.hu>, 2016\n"
"Language-Team: Hungarian (https://www.transifex.com/odoo/teams/41243/hu/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: hu\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. module: website_project_timesheet
#: model:ir.ui.view,arch_db:website_project_timesheet.my_task
msgid "Date"
msgstr "Dátum"
#. module: website_project_timesheet
#: model:ir.ui.view,arch_db:website_project_timesheet.my_task
msgid "Description"
msgstr "Leírás"
#. module: website_project_timesheet
#: model:ir.ui.view,arch_db:website_project_timesheet.my_task
msgid "Duration"
msgstr "Időtartam"
#. module: website_project_timesheet
#: model:ir.ui.view,arch_db:website_project_timesheet.my_task
msgid "Responsible"
msgstr "Felelős"
@@ -1,39 +0,0 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * website_project_timesheet
#
# Translators:
# Yenthe Van Ginneken <yenthespam@gmail.com>, 2016
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 10.saas~14\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-15 09:50+0000\n"
"PO-Revision-Date: 2017-02-15 09:50+0000\n"
"Last-Translator: Yenthe Van Ginneken <yenthespam@gmail.com>, 2016\n"
"Language-Team: Dutch (https://www.transifex.com/odoo/teams/41243/nl/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: nl\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. module: website_project_timesheet
#: model:ir.ui.view,arch_db:website_project_timesheet.my_task
msgid "Date"
msgstr "Datum"
#. module: website_project_timesheet
#: model:ir.ui.view,arch_db:website_project_timesheet.my_task
msgid "Description"
msgstr "Omschrijving"
#. module: website_project_timesheet
#: model:ir.ui.view,arch_db:website_project_timesheet.my_task
msgid "Duration"
msgstr "Tijdsduur"
#. module: website_project_timesheet
#: model:ir.ui.view,arch_db:website_project_timesheet.my_task
msgid "Responsible"
msgstr "Verantwoordelijke"
@@ -1,41 +0,0 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * website_project_timesheet
#
# Translators:
# Martin Trigaux <mat@odoo.com>, 2016
# Mateus Lopes <mateus1@gmail.com>, 2016
# falexandresilva <falexandresilva@gmail.com>, 2016
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 10.saas~14\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-15 09:50+0000\n"
"PO-Revision-Date: 2017-02-15 09:50+0000\n"
"Last-Translator: falexandresilva <falexandresilva@gmail.com>, 2016\n"
"Language-Team: Portuguese (Brazil) (https://www.transifex.com/odoo/teams/41243/pt_BR/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: pt_BR\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#. module: website_project_timesheet
#: model:ir.ui.view,arch_db:website_project_timesheet.my_task
msgid "Date"
msgstr "Data"
#. module: website_project_timesheet
#: model:ir.ui.view,arch_db:website_project_timesheet.my_task
msgid "Description"
msgstr "Descrição"
#. module: website_project_timesheet
#: model:ir.ui.view,arch_db:website_project_timesheet.my_task
msgid "Duration"
msgstr "Duração"
#. module: website_project_timesheet
#: model:ir.ui.view,arch_db:website_project_timesheet.my_task
msgid "Responsible"
msgstr "Responsável"
@@ -1,37 +0,0 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * website_project_timesheet
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 10.saas~14\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-15 09:50+0000\n"
"PO-Revision-Date: 2017-02-15 09:50+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: website_project_timesheet
#: model:ir.ui.view,arch_db:website_project_timesheet.my_task
msgid "Date"
msgstr ""
#. module: website_project_timesheet
#: model:ir.ui.view,arch_db:website_project_timesheet.my_task
msgid "Description"
msgstr ""
#. module: website_project_timesheet
#: model:ir.ui.view,arch_db:website_project_timesheet.my_task
msgid "Duration"
msgstr ""
#. module: website_project_timesheet
#: model:ir.ui.view,arch_db:website_project_timesheet.my_task
msgid "Responsible"
msgstr ""
@@ -1,40 +0,0 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * website_project_timesheet
#
# Translators:
# Jeffery CHEN <jeffery9@gmail.com>, 2016
# Martin Trigaux <mat@odoo.com>, 2016
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 10.saas~14\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-15 09:50+0000\n"
"PO-Revision-Date: 2017-02-15 09:50+0000\n"
"Last-Translator: Martin Trigaux <mat@odoo.com>, 2016\n"
"Language-Team: Chinese (China) (https://www.transifex.com/odoo/teams/41243/zh_CN/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Language: zh_CN\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#. module: website_project_timesheet
#: model:ir.ui.view,arch_db:website_project_timesheet.my_task
msgid "Date"
msgstr "日期"
#. module: website_project_timesheet
#: model:ir.ui.view,arch_db:website_project_timesheet.my_task
msgid "Description"
msgstr "说明"
#. module: website_project_timesheet
#: model:ir.ui.view,arch_db:website_project_timesheet.my_task
msgid "Duration"
msgstr "持续时间"
#. module: website_project_timesheet
#: model:ir.ui.view,arch_db:website_project_timesheet.my_task
msgid "Responsible"
msgstr "负责人"
@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="portal_task_timesheet_rule" model="ir.rule">
<field name="name">Project/Task Timesheets: frontend access</field>
<field name="model_id" ref="analytic.model_account_analytic_line"/>
<field name="domain_force">['|',
'&amp;',
('task_id.project_id.privacy_visibility', '=', 'portal'),
('task_id.project_id.message_partner_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
'&amp;',
('task_id.project_id.privacy_visibility', '=', 'portal'),
('task_id.message_partner_ids', 'child_of', [user.partner_id.commercial_partner_id.id]),
]</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
</record>
</data>
</odoo>
@@ -1,2 +0,0 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_timesheets_portal,access_timesheets_portal,analytic.model_account_analytic_line,base.group_portal,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_timesheets_portal access_timesheets_portal analytic.model_account_analytic_line base.group_portal 1 0 0 0
+1 -1
View File
@@ -6,7 +6,7 @@ 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.portal.controllers.mail import _message_post_helper
class sale_quote(http.Controller):
@@ -430,7 +430,7 @@
<xpath expr="//div[@id='quote_content']" position="inside">
<h1 class="page-header">History</h1>
<!-- Options:Quotation Chatter: user can reply -->
<t t-call="website_mail.message_thread">
<t t-call="portal.message_thread">
<t t-set="object" t-value="quotation"/>
<t t-set="token_field" t-value="'access_token'"/>
</t>
@@ -1,19 +1,17 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from werkzeug.exceptions import NotFound
from odoo import http
from odoo.http import request
from odoo.addons.website_mail.controllers.main import WebsiteMail
from odoo.addons.portal.controllers.mail import PortalChatter
class WebsiteRating(WebsiteMail):
class WebsiteRating(PortalChatter):
@http.route()
def website_chatter_init(self, res_model, res_id, domain=False, limit=False, **kwargs):
result = super(WebsiteRating, self).website_chatter_init(res_model, res_id, domain=domain, limit=limit, **kwargs)
def portal_chatter_init(self, res_model, res_id, domain=False, limit=False, **kwargs):
result = super(WebsiteRating, self).portal_chatter_init(res_model, res_id, domain=domain, limit=limit, **kwargs)
# get the rating statistics about the record
if kwargs.get('rating_include'):
record = request.env[res_model].browse(res_id)
@@ -22,10 +20,10 @@ class WebsiteRating(WebsiteMail):
return result
@http.route()
def website_message_fetch(self, res_model, res_id, domain=False, limit=False, offset=False, **kw):
# add 'rating_include' in context, to fetch them in website_message_format
def portal_message_fetch(self, res_model, res_id, domain=False, limit=False, offset=False, **kw):
# add 'rating_include' in context, to fetch them in portal_message_format
if kw.get('rating_include'):
context = dict(request.context)
context['rating_include'] = True
request.context = context
return super(WebsiteRating, self).website_message_fetch(res_model, res_id, domain=domain, limit=limit, offset=offset, **kw)
return super(WebsiteRating, self).portal_message_fetch(res_model, res_id, domain=domain, limit=limit, offset=offset, **kw)
+3 -7
View File
@@ -8,12 +8,8 @@ class MailMessage(models.Model):
_inherit = 'mail.message'
@api.multi
def website_message_format(self):
result = super(MailMessage, self).website_message_format()
def _portal_message_format(self, field_list):
# inlude rating value in data if requested
if self._context.get('rating_include'):
message_data = self.read(['rating_value'])
message_tree = dict((m['id'], m['rating_value']) for m in message_data)
for message_vals in result:
message_vals['rating_value'] = message_tree.get(message_vals['id'], False) # False for no rating
return result
field_list += ['rating_value']
return super(MailMessage, self)._portal_message_format(field_list)
@@ -8,13 +8,13 @@ odoo.define('website_rating.thread', function(require) {
var qweb = core.qweb;
var _t = core._t;
var WebsiteMailThread = require('website_mail.thread').WebsiteMailThread;
var PortalChatter = require('portal.chatter').PortalChatter;
/**
* Extends Frontend Chatter to handle rating
*/
WebsiteMailThread.include({
events: _.extend({}, WebsiteMailThread.prototype.events, {
PortalChatter.include({
events: _.extend({}, PortalChatter.prototype.events, {
"mousemove .stars i" : "_onMoveStar",
"mouseleave .stars i" : "_onMoveOutStar",
"click .stars" : "_onClickStar",
@@ -27,7 +27,7 @@
<t t-call="website_rating.rating_stars_static">
<t t-set="val" t-value="widget.get('rating_card_values')['avg'] || 0"/>
</t>
<t t-call="website_mail.chatter_message_count"/>
<t t-call="portal.chatter_message_count"/>
</div>
</div>
<div class="col-md-6" t-if="!_.isEmpty(widget.get('rating_card_values'))">
@@ -76,15 +76,15 @@
</t>
<!--
Inherited templates from website_mail to custom chatter with rating
Inherited templates from portal to custom chatter with rating
-->
<t t-extend="website_mail.chatter_composer">
<t t-jquery=".o_website_mail_composer_form input[name='csrf_token']" t-operation="after">
<t t-extend="portal.chatter_composer">
<t t-jquery=".o_portal_chatter_composer_form input[name='csrf_token']" t-operation="after">
<t t-call="website_rating.rating_star_input"/>
</t>
</t>
<t t-extend="website_mail.chatter_messages">
<t t-extend="portal.chatter_messages">
<t t-jquery="t[t-raw='message.body']" t-operation="before">
<t t-if="message['rating_value']">
<t t-call="website_rating.rating_stars_static">
@@ -94,13 +94,13 @@
</t>
</t>
<t t-extend="website_mail.chatter">
<t t-jquery="t[t-call='website_mail.chatter_message_count']" t-operation="replace">
<t t-extend="portal.chatter">
<t t-jquery="t[t-call='portal.chatter_message_count']" t-operation="replace">
<t t-if="widget.options['display_rating']">
<t t-call="website_rating.rating_card"/>
</t>
<t t-if="!widget.options['display_rating']">
<t t-call="website_mail.chatter_message_count"/>
<t t-call="portal.chatter_message_count"/>
</t>
</t>
</t>
@@ -8,7 +8,7 @@
</xpath>
</template>
<template id="message_thread" inherit_id="website_mail.message_thread">
<template id="message_thread" inherit_id="portal.message_thread">
<xpath expr="//div[@id='discussion']" position="attributes">
<attribute name="t-att-data-display_rating">display_rating or False</attribute>
</xpath>
@@ -10,8 +10,8 @@ This module display project customer satisfaction on your website.
==================================================================
""",
'depends': [
'website_project',
'rating_project'
'website',
'rating_project',
],
'data': [
'views/website_rating_project.xml',
@@ -13,7 +13,7 @@ class WebsiteRatingProject(http.Controller):
@http.route(['/project/rating/'], type='http', auth="public", website=True)
def index(self, **kw):
projects = request.env['project.project'].sudo().search([('rating_status', '!=', 'no'), ('website_published', '=', True)])
projects = request.env['project.project'].sudo().search([('rating_status', '!=', 'no'), ('portal_show_rating', '=', True)])
values = {'projects': projects}
return request.render('website_rating_project.index', values)
@@ -23,7 +23,7 @@ class WebsiteRatingProject(http.Controller):
project = request.env['project.project'].sudo().browse(project_id)
# to avoid giving any access rights on projects to the public user, let's use sudo
# and check if the user should be able to view the project (project managers only if it's unpublished or has no rating)
if not ((project.rating_status!='no') and project.website_published) and not user.sudo(user).has_group('project.group_project_manager'):
if not ((project.rating_status!='no') and project.portal_show_rating) and not user.sudo(user).has_group('project.group_project_manager'):
raise NotFound()
values = {
'project': project,
+12 -14
View File
@@ -1,27 +1,25 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, models
from odoo import api, fields, models
class Project(models.Model):
_name = "project.project"
_inherit = ['project.project', 'website.published.mixin']
_inherit = ['project.project']
portal_show_rating = fields.Boolean('Rating visible in Website', copy=False, oldname='website_published')
@api.multi
def action_view_all_rating(self):
""" Override this method without calling parent to redirect to rating website project page """
self.ensure_one()
return {
'type': 'ir.actions.act_url',
'name': "Redirect to the Website Projcet Rating Page",
'target': 'self',
'url': "/project/rating/%s" % (self.id,)
}
@api.multi
def _compute_website_url(self):
super(Project, self)._compute_website_url()
for project in self:
project.website_url = "/project/rating/%s" % project.id
if self.portal_show_rating:
return {
'type': 'ir.actions.act_url',
'name': "Redirect to the Website Projcet Rating Page",
'target': 'self',
'url': "/project/rating/%s" % (self.id,)
}
return super(Project, self).action_view_all_rating()
@@ -1,17 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="project_project_view_form" model="ir.ui.view">
<field name="name">project.project.inherit.form</field>
<field name="model">project.project</field>
<field name="inherit_id" ref="project.edit_project"/>
<field name="inherit_id" ref="rating_project.view_project_project_rating_form"/>
<field name="arch" type="xml">
<xpath expr='//div[@name="button_box"]/*[1]' position='before'>
<button class="oe_stat_button" name="website_publish_button"
type="object" icon="fa-globe" attrs="{'invisible': [('rating_status', '=', 'no')]}">
<field name="website_published" widget="website_button"/>
</button>
<xpath expr="//field[@name='rating_status_period']" position="after">
<field name="portal_show_rating" attrs="{'invisible': [('rating_status','==','no')]}"/>
</xpath>
</field>
</record>
</odoo>
@@ -115,13 +115,6 @@
<div class="container oe_website_rating_project">
<h1 t-esc="project.name" class="text-center"/>
<div class="row mb32">
<div id="website_published_button" class="" groups="project.group_project_manager">
<t t-call="website.publish_management">
<t t-set="object" t-value="project"/>
<t t-set="publish_edit" t-value="True"/>
<t t-set="action" t-value="'project.open_view_project_all'"/>
</t>
</div>
<div class="col-md-8">
<t t-set="type" t-value="'Tasks'"/>
<t t-set="data" t-value="task_data"/>
+1 -1
View File
@@ -552,7 +552,7 @@
<hr/>
<div class="row">
<div class="col-md-8 col-md-offset-2">
<t t-call="website_mail.message_thread">
<t t-call="portal.message_thread">
<t t-set="object" t-value="product"/>
<t t-set="display_rating" t-value="True"/>
</t>
@@ -543,7 +543,7 @@
</h4>
</div>
<div t-att-class="comments and 'tab-pane fade active in' or 'tab-pane fade'" id="discuss">
<t t-call="website_mail.message_thread">
<t t-call="portal.message_thread">
<t t-set="object" t-value="slide"/>
</t>
</div>