Exemplo n.º 1
0
 def action_submit_rating(self, token, **kwargs):
     rate = int(kwargs.get('rate'))
     assert rate in (1, 3, 5), "Incorrect rating"
     rating = request.env['helpdesk.ticket'].sudo().search([('access_token',
                                                             '=', token)])
     if not rating:
         return request.not_found()
     feedback = (kwargs.get('feedback'))
     rating.rating_last_value = float(rate)
     if feedback:
         message = plaintext2html(feedback)
     post_values = {
         'res_model': 'helpdesk.ticket',
         'res_id': rating.id,
         'message': feedback,
         'send_after_commit': False,
         'attachment_ids': False,  # will be added afterward
     }
     message = _message_post_helper(**post_values)
     lang = rating.partner_id.lang or get_lang(request.env).code
     return request.env['ir.ui.view'].with_context(
         lang=lang)._render_template(
             'helpdesk_basic.rating_external_page_view', {
                 'web_base_url':
                 request.env['ir.config_parameter'].sudo().get_param(
                     'web.base.url'),
                 'rating_value':
                 rating,
             })
Exemplo n.º 2
0
 def _sync_activities(self, fields):
     # update activities
     for event in self:
         if event.activity_ids:
             activity_values = {}
             if 'name' in fields:
                 activity_values['summary'] = event.name
             if 'description' in fields:
                 activity_values[
                     'note'] = event.description and tools.plaintext2html(
                         event.description)
             if 'start' in fields:
                 # self.start is a datetime UTC *only when the event is not allday*
                 # activty.date_deadline is a date (No TZ, but should represent the day in which the user's TZ is)
                 # See 72254129dbaeae58d0a2055cba4e4a82cde495b7 for the same issue, but elsewhere
                 deadline = event.start
                 user_tz = self.env.context.get('tz')
                 if user_tz and not event.allday:
                     deadline = pytz.UTC.localize(deadline)
                     deadline = deadline.astimezone(pytz.timezone(user_tz))
                 activity_values['date_deadline'] = deadline.date()
             if 'user_id' in fields:
                 activity_values['user_id'] = event.user_id.id
             if activity_values.keys():
                 event.activity_ids.write(activity_values)
Exemplo n.º 3
0
 def _timesheet_create_task_prepare_values(self):
     self.ensure_one()
     project = self._timesheet_find_project()
     planned_hours = self._convert_qty_company_hours()
     return {
         'name':
         '%s:%s' % (self.order_id.name or '', self.name.split('\n')[0]
                    or self.product_id.name),
         'planned_hours':
         planned_hours,
         'remaining_hours':
         planned_hours,
         'partner_id':
         self.order_id.partner_id.id,
         'description':
         plaintext2html(self.name) if self.name else False,
         'project_id':
         project.id,
         'sale_line_id':
         self.id,
         'company_id':
         self.company_id.id,
         'email_from':
         self.order_id.partner_id.email,
         'user_id':
         False,  # force non assigned task, as created as sudo()
     }
Exemplo n.º 4
0
 def rating_apply(self, rate, token=None, feedback=None, subtype=None):
     """ Apply a rating given a token. If the current model inherits from
     mail.thread mixing, a message is posted on its chatter.
     :param rate : the rating value to apply
     :type rate : float
     :param token : access token
     :param feedback : additional feedback
     :type feedback : string
     :param subtype : subtype for mail
     :type subtype : string
     :returns rating.rating record
     """
     Rating, rating = self.env['rating.rating'], None
     if token:
         rating = self.env['rating.rating'].search([('access_token', '=', token)], limit=1)
     else:
         rating = Rating.search([('res_model', '=', self._name), ('res_id', '=', self.ids[0])], limit=1)
     if rating:
         rating.write({'rating': rate, 'feedback': feedback, 'consumed': True})
         if hasattr(self, 'message_post'):
             feedback = tools.plaintext2html(feedback or '')
             self.message_post(
                 body="<img src='/rating/static/src/img/rating_%s.png' alt=':rating_%s' style='width:20px;height:20px;float:left;margin-right: 5px;'/>%s"
                 % (rate, rate, feedback),
                 subtype=subtype or "mail.mt_comment",
                 author_id=rating.partner_id and rating.partner_id.id or None  # None will set the default author in mail_thread.py
             )
         if hasattr(self, 'stage_id') and self.stage_id and hasattr(self.stage_id, 'auto_validation_kanban_state') and self.stage_id.auto_validation_kanban_state:
             if rating.rating > 5:
                 self.write({'kanban_state': 'done'})
             if rating.rating < 5:
                 self.write({'kanban_state': 'blocked'})
     return rating
Exemplo n.º 5
0
 def portal_chatter_post(self, res_model, res_id, message, **kw):
     url = request.httprequest.referrer
     if message:
         # message is received in plaintext and saved in html
         message = plaintext2html(message)
         _message_post_helper(res_model, int(res_id), message, **kw)
         url = url + "#discussion"
     return request.redirect(url)
Exemplo n.º 6
0
    def event_track_proposal_post(self, event, **post):
        if not event.can_access_from_current_website():
            raise NotFound()

        tags = []
        for tag in event.allowed_track_tag_ids:
            if post.get('tag_' + str(tag.id)):
                tags.append(tag.id)

        track = request.env['event.track'].sudo().create({
            'name':
            post['track_name'],
            'partner_name':
            post['partner_name'],
            'partner_email':
            post['email_from'],
            'partner_phone':
            post['phone'],
            'partner_biography':
            plaintext2html(post['biography']),
            'event_id':
            event.id,
            'tag_ids': [(6, 0, tags)],
            'user_id':
            False,
            'description':
            plaintext2html(post['description']),
            'image':
            base64.b64encode(post['image'].read())
            if post.get('image') else False
        })
        if request.env.user != request.website.user_id:
            track.sudo().message_subscribe(
                partner_ids=request.env.user.partner_id.ids)
        else:
            partner = request.env['res.partner'].sudo().search([
                ('email', '=', post['email_from'])
            ])
            if partner:
                track.sudo().message_subscribe(partner_ids=partner.ids)
        return request.render("website_event_track.event_track_proposal", {
            'track': track,
            'event': event
        })
Exemplo n.º 7
0
 def test_plaintext2html(self):
     cases = [
         ("First \nSecond \nThird\n \nParagraph\n\r--\nSignature paragraph", 'div',
          "<div><p>First <br/>Second <br/>Third</p><p>Paragraph</p><p>--<br/>Signature paragraph</p></div>"),
         ("First<p>It should be escaped</p>\nSignature", False,
          "<p>First&lt;p&gt;It should be escaped&lt;/p&gt;<br/>Signature</p>")
     ]
     for content, container_tag, expected in cases:
         html = plaintext2html(content, container_tag)
         self.assertEqual(html, expected, 'plaintext2html is broken')
Exemplo n.º 8
0
    def _message_sms(self,
                     body,
                     subtype_id=False,
                     partner_ids=False,
                     number_field=False,
                     sms_numbers=None,
                     sms_pid_to_number=None,
                     **kwargs):
        """ Main method to post a message on a record using SMS-based notification
        method.

        :param body: content of SMS;
        :param subtype_id: mail.message.subtype used in mail.message associated
          to the sms notification process;
        :param partner_ids: if set is a record set of partners to notify;
        :param number_field: if set is a name of field to use on current record
          to compute a number to notify;
        :param sms_numbers: see ``_notify_record_by_sms``;
        :param sms_pid_to_number: see ``_notify_record_by_sms``;
        """
        self.ensure_one()
        sms_pid_to_number = sms_pid_to_number if sms_pid_to_number is not None else {}

        if number_field or (partner_ids is False and sms_numbers is None):
            info = self._sms_get_recipients_info(
                force_field=number_field)[self.id]
            info_partner_ids = info['partner'].ids if info['partner'] else False
            info_number = info['sanitized'] if info['sanitized'] else info[
                'number']
            if info_partner_ids and info_number:
                sms_pid_to_number[info_partner_ids[0]] = info_number
            if info_partner_ids:
                partner_ids = info_partner_ids + (partner_ids or [])
            if not info_partner_ids:
                if info_number:
                    sms_numbers = [info_number] + (sms_numbers or [])
                    # will send a falsy notification allowing to fix it through SMS wizards
                elif not sms_numbers:
                    sms_numbers = [False]

        if subtype_id is False:
            subtype_id = self.env['ir.model.data'].xmlid_to_res_id(
                'mail.mt_note')

        return self.message_post(
            body=plaintext2html(html2plaintext(body)),
            partner_ids=partner_ids
            or [],  # TDE FIXME: temp fix otherwise crash mail_thread.py
            message_type='sms',
            subtype_id=subtype_id,
            sms_numbers=sms_numbers,
            sms_pid_to_number=sms_pid_to_number,
            **kwargs)
Exemplo n.º 9
0
    def rating_apply(self,
                     rate,
                     token=None,
                     feedback=None,
                     subtype_xmlid=None):
        """ Apply a rating given a token. If the current model inherits from
        mail.thread mixin, a message is posted on its chatter. User going through
        this method should have at least employee rights because of rating
        manipulation (either employee, either sudo-ed in public controllers after
        security check granting access).

        :param float rate : the rating value to apply
        :param string token : access token
        :param string feedback : additional feedback
        :param string subtype_xmlid : xml id of a valid mail.message.subtype

        :returns rating.rating record
        """
        rating = None
        if token:
            rating = self.env['rating.rating'].search(
                [('access_token', '=', token)], limit=1)
        else:
            rating = self.env['rating.rating'].search(
                [('res_model', '=', self._name), ('res_id', '=', self.ids[0])],
                limit=1)
        if rating:
            rating.write({
                'rating': rate,
                'feedback': feedback,
                'consumed': True
            })
            if hasattr(self, 'message_post'):
                feedback = tools.plaintext2html(feedback or '')
                self.message_post(
                    body=
                    "<img src='/rating/static/src/img/rating_%s.png' alt=':%s/5' style='width:18px;height:18px;float:left;margin-right: 5px;'/>%s"
                    % (rate, rate, feedback),
                    subtype_xmlid=subtype_xmlid or "mail.mt_comment",
                    author_id=rating.partner_id and rating.partner_id.id or
                    None  # None will set the default author in mail_thread.py
                )
            if hasattr(self, 'stage_id') and self.stage_id and hasattr(
                    self.stage_id, 'auto_validation_kanban_state'
            ) and self.stage_id.auto_validation_kanban_state:
                if rating.rating > 2:
                    self.write({'kanban_state': 'done'})
                else:
                    self.write({'kanban_state': 'blocked'})
        return rating
Exemplo n.º 10
0
    def mail_chat_post(self, uuid, message_content, **kwargs):
        mail_channel = request.env["mail.channel"].sudo().search([('uuid', '=', uuid)], limit=1)
        if not mail_channel:
            return False

        # find the author from the user session
        if request.session.uid:
            author = request.env['res.users'].sudo().browse(request.session.uid).partner_id
            author_id = author.id
            email_from = author.email_formatted
        else:  # If Public User, use catchall email from company
            author_id = False
            email_from = mail_channel.anonymous_name or mail_channel.create_uid.company_id.catchall_formatted
        # post a message without adding followers to the channel. email_from=False avoid to get author from email data
        body = tools.plaintext2html(message_content)
        message = mail_channel.with_context(mail_create_nosubscribe=True).message_post(author_id=author_id,
                                                                                       email_from=email_from, body=body,
                                                                                       message_type='comment',
                                                                                       subtype_xmlid='mail.mt_comment')
        return message and message.id or False
Exemplo n.º 11
0
    def portal_chatter_post(self, res_model, res_id, message, redirect=None, attachment_ids='', attachment_tokens='', **kw):
        """Create a new `mail.message` with the given `message` and/or
        `attachment_ids` and redirect the user to the newly created message.

        The message will be associated to the record `res_id` of the model
        `res_model`. The user must have access rights on this target document or
        must provide valid identifiers through `kw`. See `_message_post_helper`.
        """
        url = redirect or (request.httprequest.referrer and request.httprequest.referrer + "#discussion") or '/my'

        res_id = int(res_id)

        attachment_ids = [int(attachment_id) for attachment_id in attachment_ids.split(',') if attachment_id]
        attachment_tokens = [attachment_token for attachment_token in attachment_tokens.split(',') if attachment_token]
        self._portal_post_check_attachments(attachment_ids, attachment_tokens)

        if message or attachment_ids:
            # message is received in plaintext and saved in html
            if message:
                message = plaintext2html(message)
            post_values = {
                'res_model': res_model,
                'res_id': res_id,
                'message': message,
                'send_after_commit': False,
                'attachment_ids': False,  # will be added afterward
            }
            post_values.update((fname, kw.get(fname)) for fname in self._portal_post_filter_params())
            message = _message_post_helper(**post_values)

            if attachment_ids:
                # sudo write the attachment to bypass the read access
                # verification in mail message
                record = request.env[res_model].browse(res_id)
                message_values = {'res_id': res_id, 'model': res_model}
                attachments = record._message_post_process_attachments([], attachment_ids, message_values)

                if attachments.get('attachment_ids'):
                    message.sudo().write(attachments)

        return request.redirect(url)
Exemplo n.º 12
0
    def mail_update_message(self,
                            res_model,
                            res_id,
                            message,
                            message_id,
                            redirect=None,
                            attachment_ids='',
                            attachment_tokens='',
                            **post):
        # keep this mechanism intern to slide currently (saas 12.5) as it is
        # considered experimental
        if res_model != 'slide.channel':
            raise Forbidden()
        res_id = int(res_id)

        attachment_ids = [
            int(attachment_id) for attachment_id in attachment_ids.split(',')
            if attachment_id
        ]
        attachment_tokens = [
            attachment_token
            for attachment_token in attachment_tokens.split(',')
            if attachment_token
        ]
        self._portal_post_check_attachments(attachment_ids, attachment_tokens)

        pid = int(post['pid']) if post.get('pid') else False
        if not _check_special_access(res_model,
                                     res_id,
                                     token=post.get('token'),
                                     _hash=post.get('hash'),
                                     pid=pid):
            raise Forbidden()

        # fetch and update mail.message
        message_id = int(message_id)
        message_body = plaintext2html(message)
        domain = [('model', '=', res_model), ('res_id', '=', res_id),
                  ('is_internal', '=', False),
                  ('author_id', '=', request.env.user.partner_id.id),
                  ('message_type', '=', 'comment'),
                  ('id', '=', message_id)]  # restrict to the given message_id
        message = request.env['mail.message'].search(domain, limit=1)
        if not message:
            raise NotFound()
        message.sudo().write({
            'body':
            message_body,
            'attachment_ids': [(4, aid) for aid in attachment_ids],
        })

        # update rating
        if post.get('rating_value'):
            domain = [('res_model', '=', res_model), ('res_id', '=', res_id),
                      ('is_internal', '=', False),
                      ('message_id', '=', message.id)]
            rating = request.env['rating.rating'].sudo().search(
                domain, order='write_date DESC', limit=1)
            rating.write({
                'rating': float(post['rating_value']),
                'feedback': html2plaintext(message.body),
            })

        # redirect to specified or referrer or simply channel page as fallback
        redirect_url = redirect or (request.httprequest.referrer
                                    and request.httprequest.referrer +
                                    '#review') or '/slides/%s' % res_id
        return werkzeug.utils.redirect(redirect_url, 302)
Exemplo n.º 13
0
    def message_post(self,
                     body='',
                     subject=None,
                     message_type='notification',
                     subtype=None,
                     parent_id=False,
                     attachments=None,
                     content_subtype='html',
                     **kwargs):
        if attachments is None:
            attachments = {}
        if self.ids and not self.ensure_one():
            raise exceptions.UserError(
                _('Invalid record set: should be called as model (without records) or on single-record recordset'
                  ))
        # inception
        context = self._context.copy()
        if not context.get('from_composer', False):
            context.update({
                'mail_post_autofollow': False,
                'mail_create_nosubscribe': True,
            })
        # /inception

        # if we're processing a message directly coming from the gateway, the destination model was
        # set in the context.
        model = False
        if self.ids:
            self.ensure_one()
            model = self._context.get(
                'thread_model',
                False) if self._name == 'mail.thread' else self._name
            if model and model != self._name and hasattr(
                    self.env[model], 'message_post'):
                RecordModel = self.env[model].with_context(
                    thread_model=None)  # TDE: was removing the key ?
                return RecordModel.browse(self.ids).message_post(
                    body=body,
                    subject=subject,
                    message_type=message_type,
                    subtype=subtype,
                    parent_id=parent_id,
                    attachments=attachments,
                    content_subtype=content_subtype,
                    **kwargs)

        # 0: Find the message's author, because we need it for private discussion
        author_id = kwargs.get('author_id')
        if author_id is None:  # keep False values
            author_id = self.env['mail.message']._get_default_author().id

        # 1: Handle content subtype: if plaintext, converto into HTML
        if content_subtype == 'plaintext':
            body = tools.plaintext2html(body)

        # 2: Private message: add recipients (recipients and author of parent message) - current author
        #   + legacy-code management (! we manage only 4 and 6 commands)
        partner_ids = set()
        kwargs_partner_ids = kwargs.pop('partner_ids', [])
        for partner_id in kwargs_partner_ids:
            if isinstance(
                    partner_id,
                (list, tuple)) and partner_id[0] == 4 and len(partner_id) == 2:
                partner_ids.add(partner_id[1])
            if isinstance(
                    partner_id,
                (list, tuple)) and partner_id[0] == 6 and len(partner_id) == 3:
                partner_ids |= set(partner_id[2])
            elif isinstance(partner_id, pycompat.integer_types):
                partner_ids.add(partner_id)
            else:
                pass  # we do not manage anything else
        if parent_id and not model:
            parent_message = self.env['mail.message'].browse(parent_id)
            private_followers = set(
                [partner.id for partner in parent_message.partner_ids])
            if parent_message.author_id:
                private_followers.add(parent_message.author_id.id)
            private_followers -= set([author_id])
            partner_ids |= private_followers

        # 4: mail.message.subtype
        subtype_id = kwargs.get('subtype_id', False)
        if not subtype_id:
            subtype = subtype or 'mt_note'
            if '.' not in subtype:
                subtype = 'mail.%s' % subtype
            subtype_id = self.env['ir.model.data'].xmlid_to_res_id(subtype)
        # inception
        if context.get('put_this_subtype_instead', False):
            subtype = context.get('put_this_subtype_instead')
            subtype_id = self.env['ir.model.data'].xmlid_to_res_id(subtype)
        # / inception

        # automatically subscribe recipients if asked to
        if self._context.get(
                'mail_post_autofollow') and self.ids and partner_ids:
            partner_to_subscribe = partner_ids
            if self._context.get('mail_post_autofollow_partner_ids'):
                partner_to_subscribe = [
                    p for p in partner_ids if p in self._context.get(
                        'mail_post_autofollow_partner_ids')
                ]
            self.message_subscribe(list(partner_to_subscribe), force=False)

        # _mail_flat_thread: automatically set free messages to the first posted message
        MailMessage = self.env['mail.message']
        if self._mail_flat_thread and model and not parent_id and self.ids:
            messages = MailMessage.search([
                '&', ('res_id', '=', self.ids[0]), ('model', '=', model),
                ('message_type', '=', 'email')
            ],
                                          order="id ASC",
                                          limit=1)
            if not messages:
                messages = MailMessage.search(
                    ['&', ('res_id', '=', self.ids[0]), ('model', '=', model)],
                    order="id ASC",
                    limit=1)
            parent_id = messages and messages[0].id or False
        # we want to set a parent: force to set the parent_id to the oldest ancestor, to avoid having more than 1
        #  level of thread
        elif parent_id:
            messages = MailMessage.sudo().search([('id', '=', parent_id),
                                                  ('parent_id', '!=', False)],
                                                 limit=1)
            # avoid loops when finding ancestors
            processed_list = []
            if messages:
                message = messages[0]
                while (message.parent_id
                       and message.parent_id.id not in processed_list):
                    processed_list.append(message.parent_id.id)
                    message = message.parent_id
                parent_id = message.id

        values = kwargs
        values.update({
            'author_id': author_id,
            'model': model,
            'res_id': model and self.ids[0] or False,
            'body': body,
            'subject': subject or False,
            'message_type': message_type,
            'parent_id': parent_id,
            'subtype_id': subtype_id,
            'partner_ids': [(4, pid) for pid in partner_ids],
        })

        # 3. Attachments
        #   - HACK TDE FIXME: Chatter: attachments linked to the document (not done JS-side), load the message
        attachment_ids = self._message_post_process_attachments(
            attachments, kwargs.pop('attachment_ids', []), values)
        values['attachment_ids'] = attachment_ids

        # Avoid warnings about non-existing fields
        for x in ('from', 'to', 'cc'):
            values.pop(x, None)

        # Post the message
        new_message = MailMessage.create(values)

        # Post-process: subscribe author, update message_last_post
        # Note: the message_last_post mechanism is no longer used.  This
        # will be removed in a later version.
        if (self._context.get('mail_save_message_last_post') and model
                and model != 'mail.thread' and self.ids and subtype_id):
            subtype_rec = self.env['mail.message.subtype'].sudo().browse(
                subtype_id)
            if not subtype_rec.internal:
                # done with SUPERUSER_ID, because on some models users can post only with read access,
                # not necessarily write access
                self.sudo().write({'message_last_post': fields.Datetime.now()})
        if author_id and model and self.ids and message_type != 'notification' and not \
                self._context.get('mail_create_nosubscribe'):
            self.message_subscribe([author_id], force=False)

            self._message_post_after_hook(new_message)

        return new_message