summaryrefslogtreecommitdiff
path: root/addons/portal/models
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/portal/models
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/portal/models')
-rw-r--r--addons/portal/models/__init__.py9
-rw-r--r--addons/portal/models/ir_http.py20
-rw-r--r--addons/portal/models/ir_ui_view.py28
-rw-r--r--addons/portal/models/mail_message.py24
-rw-r--r--addons/portal/models/mail_thread.py39
-rw-r--r--addons/portal/models/portal_mixin.py150
-rw-r--r--addons/portal/models/res_partner.py14
7 files changed, 284 insertions, 0 deletions
diff --git a/addons/portal/models/__init__.py b/addons/portal/models/__init__.py
new file mode 100644
index 00000000..1c70d123
--- /dev/null
+++ b/addons/portal/models/__init__.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import ir_http
+from . import ir_ui_view
+from . import mail_thread
+from . import mail_message
+from . import portal_mixin
+from . import res_partner
diff --git a/addons/portal/models/ir_http.py b/addons/portal/models/ir_http.py
new file mode 100644
index 00000000..9320f6d4
--- /dev/null
+++ b/addons/portal/models/ir_http.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import models
+from odoo.http import request
+
+
+class IrHttp(models.AbstractModel):
+ _inherit = 'ir.http'
+
+ @classmethod
+ def _get_translation_frontend_modules_name(cls):
+ mods = super(IrHttp, cls)._get_translation_frontend_modules_name()
+ return mods + ['portal']
+
+ @classmethod
+ def _get_frontend_langs(cls):
+ if request and request.is_frontend:
+ return [lang[0] for lang in filter(lambda l: l[3], request.env['res.lang'].get_available())]
+ return super()._get_frontend_langs()
diff --git a/addons/portal/models/ir_ui_view.py b/addons/portal/models/ir_ui_view.py
new file mode 100644
index 00000000..ca405175
--- /dev/null
+++ b/addons/portal/models/ir_ui_view.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, models, fields
+from odoo.http import request
+from odoo.addons.http_routing.models.ir_http import url_for
+
+
+class View(models.Model):
+ _inherit = "ir.ui.view"
+
+ customize_show = fields.Boolean("Show As Optional Inherit", default=False)
+
+ @api.model
+ def _prepare_qcontext(self):
+ """ Returns the qcontext : rendering context with portal specific value (required
+ to render portal layout template)
+ """
+ qcontext = super(View, self)._prepare_qcontext()
+ if request and getattr(request, 'is_frontend', False):
+ Lang = request.env['res.lang']
+ portal_lang_code = request.env['ir.http']._get_frontend_langs()
+ qcontext.update(dict(
+ self._context.copy(),
+ languages=[lang for lang in Lang.get_available() if lang[0] in portal_lang_code],
+ url_for=url_for,
+ ))
+ return qcontext
diff --git a/addons/portal/models/mail_message.py b/addons/portal/models/mail_message.py
new file mode 100644
index 00000000..91d40d7a
--- /dev/null
+++ b/addons/portal/models/mail_message.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import models
+
+
+class MailMessage(models.Model):
+ _inherit = 'mail.message'
+
+ def portal_message_format(self):
+ return self._portal_message_format([
+ 'id', 'body', 'date', 'author_id', 'email_from', # base message fields
+ 'message_type', 'subtype_id', 'is_internal', 'subject', # message specific
+ 'model', 'res_id', 'record_name', # document related
+ ])
+
+ def _portal_message_format(self, fields_list):
+ vals_list = self._message_format(fields_list)
+ IrAttachmentSudo = self.env['ir.attachment'].sudo()
+ for vals in vals_list:
+ for attachment in vals.get('attachment_ids', []):
+ if not attachment.get('access_token'):
+ attachment['access_token'] = IrAttachmentSudo.browse(attachment['id']).generate_access_token()[0]
+ return vals_list
diff --git a/addons/portal/models/mail_thread.py b/addons/portal/models/mail_thread.py
new file mode 100644
index 00000000..0e9b58b9
--- /dev/null
+++ b/addons/portal/models/mail_thread.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import hashlib
+import hmac
+
+from odoo import api, fields, models, _
+
+
+class MailThread(models.AbstractModel):
+ _inherit = 'mail.thread'
+
+ _mail_post_token_field = 'access_token' # token field for external posts, to be overridden
+
+ website_message_ids = fields.One2many('mail.message', 'res_id', string='Website Messages',
+ domain=lambda self: [('model', '=', self._name), '|', ('message_type', '=', 'comment'), ('message_type', '=', 'email')], auto_join=True,
+ help="Website communication history")
+
+ def _sign_token(self, pid):
+ """Generate a secure hash for this record with the email of the recipient with whom the record have been shared.
+
+ This is used to determine who is opening the link
+ to be able for the recipient to post messages on the document's portal view.
+
+ :param str email:
+ Email of the recipient that opened the link.
+ """
+ self.ensure_one()
+ # check token field exists
+ if self._mail_post_token_field not in self._fields:
+ raise NotImplementedError(_(
+ "Model %(model_name)s does not support token signature, as it does not have %(field_name)s field.",
+ model_name=self._name,
+ field_name=self._mail_post_token_field
+ ))
+ # sign token
+ secret = self.env["ir.config_parameter"].sudo().get_param("database.secret")
+ token = (self.env.cr.dbname, self[self._mail_post_token_field], pid)
+ return hmac.new(secret.encode('utf-8'), repr(token).encode('utf-8'), hashlib.sha256).hexdigest()
diff --git a/addons/portal/models/portal_mixin.py b/addons/portal/models/portal_mixin.py
new file mode 100644
index 00000000..bbd9576c
--- /dev/null
+++ b/addons/portal/models/portal_mixin.py
@@ -0,0 +1,150 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+import uuid
+from werkzeug.urls import url_encode
+from odoo import api, exceptions, fields, models, _
+
+
+class PortalMixin(models.AbstractModel):
+ _name = "portal.mixin"
+ _description = 'Portal Mixin'
+
+ access_url = fields.Char(
+ 'Portal Access URL', compute='_compute_access_url',
+ help='Customer Portal URL')
+ access_token = fields.Char('Security Token', copy=False)
+
+ # to display the warning from specific model
+ access_warning = fields.Text("Access warning", compute="_compute_access_warning")
+
+ def _compute_access_warning(self):
+ for mixin in self:
+ mixin.access_warning = ''
+
+ def _compute_access_url(self):
+ for record in self:
+ record.access_url = '#'
+
+ def _portal_ensure_token(self):
+ """ Get the current record access token """
+ if not self.access_token:
+ # we use a `write` to force the cache clearing otherwise `return self.access_token` will return False
+ self.sudo().write({'access_token': str(uuid.uuid4())})
+ return self.access_token
+
+ def _get_share_url(self, redirect=False, signup_partner=False, pid=None, share_token=True):
+ """
+ Build the url of the record that will be sent by mail and adds additional parameters such as
+ access_token to bypass the recipient's rights,
+ signup_partner to allows the user to create easily an account,
+ hash token to allow the user to be authenticated in the chatter of the record portal view, if applicable
+ :param redirect : Send the redirect url instead of the direct portal share url
+ :param signup_partner: allows the user to create an account with pre-filled fields.
+ :param pid: = partner_id - when given, a hash is generated to allow the user to be authenticated
+ in the portal chatter, if any in the target page,
+ if the user is redirected to the portal instead of the backend.
+ :return: the url of the record with access parameters, if any.
+ """
+ self.ensure_one()
+ params = {
+ 'model': self._name,
+ 'res_id': self.id,
+ }
+ if share_token and hasattr(self, 'access_token'):
+ params['access_token'] = self._portal_ensure_token()
+ if pid:
+ params['pid'] = pid
+ params['hash'] = self._sign_token(pid)
+ if signup_partner and hasattr(self, 'partner_id') and self.partner_id:
+ params.update(self.partner_id.signup_get_auth_param()[self.partner_id.id])
+
+ return '%s?%s' % ('/mail/view' if redirect else self.access_url, url_encode(params))
+
+ def _notify_get_groups(self, msg_vals=None):
+ access_token = self._portal_ensure_token()
+ groups = super(PortalMixin, self)._notify_get_groups(msg_vals=msg_vals)
+ local_msg_vals = dict(msg_vals or {})
+
+ if access_token and 'partner_id' in self._fields and self['partner_id']:
+ customer = self['partner_id']
+ local_msg_vals['access_token'] = self.access_token
+ local_msg_vals.update(customer.signup_get_auth_param()[customer.id])
+ access_link = self._notify_get_action_link('view', **local_msg_vals)
+
+ new_group = [
+ ('portal_customer', lambda pdata: pdata['id'] == customer.id, {
+ 'has_button_access': False,
+ 'button_access': {
+ 'url': access_link,
+ },
+ 'notification_is_customer': True,
+ })
+ ]
+ else:
+ new_group = []
+ return new_group + groups
+
+ def get_access_action(self, access_uid=None):
+ """ Instead of the classic form view, redirect to the online document for
+ portal users or if force_website=True in the context. """
+ self.ensure_one()
+
+ user, record = self.env.user, self
+ if access_uid:
+ try:
+ record.check_access_rights('read')
+ record.check_access_rule("read")
+ except exceptions.AccessError:
+ return super(PortalMixin, self).get_access_action(access_uid)
+ user = self.env['res.users'].sudo().browse(access_uid)
+ record = self.with_user(user)
+ if user.share or self.env.context.get('force_website'):
+ try:
+ record.check_access_rights('read')
+ record.check_access_rule('read')
+ except exceptions.AccessError:
+ if self.env.context.get('force_website'):
+ return {
+ 'type': 'ir.actions.act_url',
+ 'url': record.access_url,
+ 'target': 'self',
+ 'res_id': record.id,
+ }
+ else:
+ pass
+ else:
+ return {
+ 'type': 'ir.actions.act_url',
+ 'url': record._get_share_url(),
+ 'target': 'self',
+ 'res_id': record.id,
+ }
+ return super(PortalMixin, self).get_access_action(access_uid)
+
+ @api.model
+ def action_share(self):
+ action = self.env["ir.actions.actions"]._for_xml_id("portal.portal_share_action")
+ action['context'] = {'active_id': self.env.context['active_id'],
+ 'active_model': self.env.context['active_model']}
+ return action
+
+ def get_portal_url(self, suffix=None, report_type=None, download=None, query_string=None, anchor=None):
+ """
+ Get a portal url for this model, including access_token.
+ The associated route must handle the flags for them to have any effect.
+ - suffix: string to append to the url, before the query string
+ - report_type: report_type query string, often one of: html, pdf, text
+ - download: set the download query string to true
+ - query_string: additional query string
+ - anchor: string to append after the anchor #
+ """
+ self.ensure_one()
+ url = self.access_url + '%s?access_token=%s%s%s%s%s' % (
+ suffix if suffix else '',
+ self._portal_ensure_token(),
+ '&report_type=%s' % report_type if report_type else '',
+ '&download=true' if download else '',
+ query_string if query_string else '',
+ '#%s' % anchor if anchor else ''
+ )
+ return url
diff --git a/addons/portal/models/res_partner.py b/addons/portal/models/res_partner.py
new file mode 100644
index 00000000..064fa2ff
--- /dev/null
+++ b/addons/portal/models/res_partner.py
@@ -0,0 +1,14 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import models
+
+
+class ResPartner(models.Model):
+ _inherit = 'res.partner'
+
+ def can_edit_vat(self):
+ ''' `vat` is a commercial field, synced between the parent (commercial
+ entity) and the children. Only the commercial entity should be able to
+ edit it (as in backend). '''
+ return not self.parent_id