123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362 |
- # -*- coding: utf-8 -*-
- # Part of Odoo. See LICENSE file for full copyright and licensing details.
- import werkzeug
- from odoo import http, fields, tools
- from odoo.addons.http_routing.models.ir_http import slug
- from odoo.addons.portal.controllers.portal import pager as portal_pager
- from odoo.exceptions import AccessError
- from odoo.http import request
- from odoo.osv import expression
- class PortalMailGroup(http.Controller):
- _thread_per_page = 20
- _replies_per_page = 5
- def _get_website_domain(self):
- # Base group domain in addition to the security access rules
- # Do not show rejected message on the portal view even for admin
- return [('moderation_status', '!=', 'rejected')]
- def _get_archives(self, group_id):
- """Return the different date range and message count for the group messages."""
- domain = expression.AND([self._get_website_domain(), [('mail_group_id', '=', group_id)]])
- results = request.env['mail.group.message']._read_group_raw(
- domain,
- ['subject', 'create_date'],
- groupby=['create_date'], orderby='create_date')
- date_groups = []
- for result in results:
- (dates_range, label) = result['create_date']
- start, end = dates_range.split('/')
- date_groups.append({
- 'date': label,
- 'date_begin': fields.Date.to_string(fields.Date.to_date(start)),
- 'date_end': fields.Date.to_string(fields.Date.to_date(end)),
- 'messages_count': result['create_date_count'],
- })
- thread_domain = expression.AND([domain, [('group_message_parent_id', '=', False)]])
- threads_count = request.env['mail.group.message'].search_count(thread_domain)
- return {
- 'threads_count': threads_count,
- 'threads_time_data': date_groups,
- }
- # ------------------------------------------------------------
- # MAIN PAGE
- # ------------------------------------------------------------
- @http.route('/groups', type='http', auth='public', sitemap=True, website=True)
- def groups_index(self, email='', **kw):
- """View of the group lists. Allow the users to subscribe and unsubscribe."""
- if kw.get('group_id') and kw.get('token'):
- group_id = int(kw.get('group_id'))
- token = kw.get('token')
- group = request.env['mail.group'].browse(group_id).exists().sudo()
- if not group:
- raise werkzeug.exceptions.NotFound()
- if token != group._generate_group_access_token():
- raise werkzeug.exceptions.NotFound()
- mail_groups = group
- else:
- mail_groups = request.env['mail.group'].search([]).sudo()
- if not request.env.user._is_public():
- # Force the email if the user is logged
- email_normalized = request.env.user.email_normalized
- partner_id = request.env.user.partner_id.id
- else:
- email_normalized = tools.email_normalize(email)
- partner_id = None
- members_data = mail_groups._find_members(email_normalized, partner_id)
- return request.render('mail_group.mail_groups', {
- 'mail_groups': [{
- 'group': group,
- 'is_member': bool(members_data.get(group.id, False)),
- } for group in mail_groups],
- 'email': email_normalized,
- 'is_mail_group_manager': request.env.user.has_group('mail_group.group_mail_group_manager'),
- })
- # ------------------------------------------------------------
- # THREAD DISPLAY / MANAGEMENT
- # ------------------------------------------------------------
- @http.route([
- '/groups/<model("mail.group"):group>',
- '/groups/<model("mail.group"):group>/page/<int:page>',
- ], type='http', auth='public', sitemap=True, website=True)
- def group_view_messages(self, group, page=1, mode='thread', date_begin=None, date_end=None, **post):
- GroupMessage = request.env['mail.group.message']
- domain = expression.AND([self._get_website_domain(), [('mail_group_id', '=', group.id)]])
- if mode == 'thread':
- domain = expression.AND([domain, [('group_message_parent_id', '=', False)]])
- if date_begin and date_end:
- domain = expression.AND([domain, [('create_date', '>', date_begin), ('create_date', '<=', date_end)]])
- # SUDO after the search to apply access rules but be able to read attachments
- messages_sudo = GroupMessage.search(
- domain, limit=self._thread_per_page,
- offset=(page - 1) * self._thread_per_page).sudo()
- pager = portal_pager(
- url=f'/groups/{slug(group)}',
- total=GroupMessage.search_count(domain),
- page=page,
- step=self._thread_per_page,
- scope=5,
- url_args={'date_begin': date_begin, 'date_end': date_end, 'mode': mode}
- )
- self._generate_attachments_access_token(messages_sudo)
- return request.render('mail_group.group_messages', {
- 'page_name': 'groups',
- 'group': group,
- 'messages': messages_sudo,
- 'archives': self._get_archives(group.id),
- 'date_begin': date_begin,
- 'date_end': date_end,
- 'pager': pager,
- 'replies_per_page': self._replies_per_page,
- 'mode': mode,
- })
- @http.route('/groups/<model("mail.group"):group>/<model("mail.group.message"):message>',
- type='http', auth='public', sitemap=True, website=True)
- def group_view_message(self, group, message, mode='thread', date_begin=None, date_end=None, **post):
- if group != message.mail_group_id:
- raise werkzeug.exceptions.NotFound()
- GroupMessage = request.env['mail.group.message']
- base_domain = expression.AND([
- self._get_website_domain(),
- [('mail_group_id', '=', group.id),
- ('group_message_parent_id', '=', message.group_message_parent_id.id)],
- ])
- next_message = GroupMessage.search(
- expression.AND([base_domain, [('id', '>', message.id)]]),
- order='id ASC', limit=1)
- prev_message = GroupMessage.search(
- expression.AND([base_domain, [('id', '<', message.id)]]),
- order='id DESC', limit=1)
- message_sudo = message.sudo()
- self._generate_attachments_access_token(message_sudo)
- values = {
- 'page_name': 'groups',
- 'message': message_sudo,
- 'group': group,
- 'mode': mode,
- 'archives': self._get_archives(group.id),
- 'date_begin': date_begin,
- 'date_end': date_end,
- 'replies_per_page': self._replies_per_page,
- 'next_message': next_message,
- 'prev_message': prev_message,
- }
- return request.render('mail_group.group_message', values)
- @http.route('/groups/<model("mail.group"):group>/<model("mail.group.message"):message>/get_replies',
- type='json', auth='public', methods=['POST'], website=True)
- def group_message_get_replies(self, group, message, last_displayed_id, **post):
- if group != message.mail_group_id:
- raise werkzeug.exceptions.NotFound()
- replies_domain = expression.AND([
- self._get_website_domain(),
- [('id', '>', int(last_displayed_id)), ('group_message_parent_id', '=', message.id)],
- ])
- # SUDO after the search to apply access rules but be able to read attachments
- replies_sudo = request.env['mail.group.message'].search(replies_domain, limit=self._replies_per_page).sudo()
- message_count = request.env['mail.group.message'].search_count(replies_domain)
- if not replies_sudo:
- return
- message_sudo = message.sudo()
- self._generate_attachments_access_token(message_sudo | replies_sudo)
- values = {
- 'group': group,
- 'parent_message': message_sudo,
- 'messages': replies_sudo,
- 'msg_more_count': message_count - self._replies_per_page,
- 'replies_per_page': self._replies_per_page,
- }
- return request.env['ir.qweb']._render('mail_group.messages_short', values)
- # ------------------------------------------------------------
- # SUBSCRIPTION
- # ------------------------------------------------------------
- @http.route('/group/subscribe', type='json', auth='public', website=True)
- def group_subscribe(self, group_id=0, email=None, token=None, **kw):
- """Subscribe the current logged user or the given email address to the mailing list.
- If the user is logged, the action is automatically done.
- But if the user is not logged (public user) an email will be send with a token
- to confirm the action.
- :param group_id: Id of the group
- :param email: Email to add in the member list
- :param token: An access token to bypass the <mail.group> access rule
- :return:
- 'added'
- if the member was added in the mailing list
- 'email_sent'
- if we send a confirmation email
- 'is_already_member'
- if we try to subscribe but we are already member
- """
- group_sudo, is_member, partner_id = self._group_subscription_get_group(group_id, email, token)
- if is_member:
- return 'is_already_member'
- if not request.env.user._is_public():
- # For logged user, automatically join / leave without sending a confirmation email
- group_sudo._join_group(request.env.user.email, partner_id)
- return 'added'
- # For non-logged user, send an email with a token to confirm the action
- group_sudo._send_subscribe_confirmation_email(email)
- return 'email_sent'
- @http.route('/group/unsubscribe', type='json', auth='public', website=True)
- def group_unsubscribe(self, group_id=0, email=None, token=None, **kw):
- """Unsubscribe the current logged user or the given email address to the mailing list.
- If the user is logged, the action is automatically done.
- But if the user is not logged (public user) an email will be send with a token
- to confirm the action.
- :param group_id: Id of the group
- :param email: Email to add in the member list
- :param token: An access token to bypass the <mail.group> access rule
- :return:
- 'removed'
- if the member was removed from the mailing list
- 'email_sent'
- if we send a confirmation email
- 'is_not_member'
- if we try to unsubscribe but we are not member
- """
- group_sudo, is_member, partner_id = self._group_subscription_get_group(group_id, email, token)
- if not is_member:
- return 'is_not_member'
- if not request.env.user._is_public():
- # For logged user, automatically join / leave without sending a confirmation email
- group_sudo._leave_group(request.env.user.email, partner_id)
- return 'removed'
- # For non-logged user, send an email with a token to confirm the action
- group_sudo._send_unsubscribe_confirmation_email(email)
- return 'email_sent'
- def _group_subscription_get_group(self, group_id, email, token):
- """Check the given token and return,
- :return:
- - The group sudo-ed
- - True if the email is member of the group
- - The partner of the current user
- :raise NotFound: if the given token is not valid
- """
- group = request.env['mail.group'].browse(int(group_id)).exists()
- if not group:
- raise werkzeug.exceptions.NotFound()
- # SUDO to have access to field of the many2one
- group_sudo = group.sudo()
- if token and token != group_sudo._generate_group_access_token():
- raise werkzeug.exceptions.NotFound()
- elif not token:
- try:
- # Check that the current user has access to the group
- group.check_access_rights('read')
- group.check_access_rule('read')
- except AccessError:
- raise werkzeug.exceptions.NotFound()
- partner_id = None
- if not request.env.user._is_public():
- partner_id = request.env.user.partner_id.id
- is_member = bool(group_sudo._find_member(email, partner_id))
- return group_sudo, is_member, partner_id
- @http.route('/group/subscribe-confirm', type='http', auth='public', website=True)
- def group_subscribe_confirm(self, group_id, email, token, **kw):
- """Confirm the subscribe / unsubscribe action which was sent by email."""
- group = self._group_subscription_confirm_get_group(group_id, email, token, 'subscribe')
- if not group:
- return request.render('mail_group.invalid_token_subscription')
- partners = request.env['mail.thread'].sudo()._mail_find_partner_from_emails([email])
- partner_id = partners[0].id if partners else None
- group._join_group(email, partner_id)
- return request.render('mail_group.confirmation_subscription', {
- 'group': group,
- 'email': email,
- 'subscribing': True,
- })
- @http.route('/group/unsubscribe-confirm', type='http', auth='public', website=True)
- def group_unsubscribe_confirm(self, group_id, email, token, **kw):
- """Confirm the subscribe / unsubscribe action which was sent by email."""
- group = self._group_subscription_confirm_get_group(group_id, email, token, 'unsubscribe')
- if not group:
- return request.render('mail_group.invalid_token_subscription')
- group._leave_group(email, all_members=True)
- return request.render('mail_group.confirmation_subscription', {
- 'group': group,
- 'email': email,
- 'subscribing': False,
- })
- def _group_subscription_confirm_get_group(self, group_id, email, token, action):
- """Retrieve the group and check the token use to perform the given action."""
- if not group_id or not email or not token:
- return False
- # Here we can SUDO because the token will be checked
- group = request.env['mail.group'].browse(int(group_id)).exists().sudo()
- if not group:
- raise werkzeug.exceptions.NotFound()
- excepted_token = group._generate_action_token(email, action)
- return group if token == excepted_token else False
- def _generate_attachments_access_token(self, messages):
- for message in messages:
- if message.attachment_ids:
- message.attachment_ids.generate_access_token()
- self._generate_attachments_access_token(message.group_message_child_ids)
|