Ejemplo n.º 1
0
class mail_send_to(models.TransientModel):
    """ Wizard to send document to partners and make them followers. """
    _name = 'mail.send.to'
    _description = 'Mail Send To'

    action_id = fields.Many2one('ir.actions.act_window', string = 'Documents to send')
    domain = fields.Char(string='Domain', help="Optional domain for further data filtering")
    
    partner_ids = fields.Many2many('res.partner', 'send_mail_to_partener_rel', 'send_mail_id','partner_id', 
                                      string='Recipients',help="List of partners that will be added as follower of the current document." )
    
    subject = fields.Char(string='Subject')
    message = fields.Html(string='Message')


    def go_step_1(self, cr, uid, ids, selected_ids, context=None):
        wizard_data = self.browse(cr,uid,ids,context)[0]

        model, res_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'deltatech_mail', 'action_mail_send_to_step1')
        action = self.pool[model].read(cr, uid, [res_id], context=context)[0]
        action['res_id'] = ids[0]
 
        action['context'] = "{'active_ids': [" +','.join(map(str,selected_ids)) + "]}"   
        return action


    @api.model
    def has_follower(self, res_model):
        document = self.env[res_model]
        res = False

        track = getattr(document, '_track','none')  
        if track != 'none':
            res = True

        return res

    @api.multi
    def do_send(self):
        for wizard in self:
            active_ids = self.env.context.get('active_ids', [])       
 
            #model = self.env[wizard.action_id.res_model]  #['ir.model'].search([('model','=', wizard.action_id.res_model)])
          
            documents = self.env[wizard.action_id.res_model].browse(active_ids)
            for document in documents: 
                new_follower_ids = [p.id for p in wizard.partner_ids]
                
                track = getattr(document, '_track','none')  
                if track != 'none':
                        document.message_subscribe(new_follower_ids)
                 
                message = self.env['mail.message'].with_context({'default_starred':True, 'mail_notify_noemail': False}).create({
                    'model': wizard.action_id.res_model,
                    'res_id': document.id,
                    'record_name': document.name_get()[0][1],
                    'email_from': self.env['mail.message']._get_default_from( ),
                    #'reply_to': self.env['mail.message']._get_default_from( ),
                    'subject': wizard.subject or '',
                    'body': wizard.message or wizard.subject,
                     
                    'message_id': self.env['mail.message']._get_message_id(  {'no_auto_thread': True} ),
                    'partner_ids': [(4, id) for id in new_follower_ids],
                    #'notified_partner_ids': [(4, id) for id in new_follower_ids]
                })
                


        return {'type': 'ir.actions.act_window_close'} 
Ejemplo n.º 2
0
class LibraryBook(models.Model):
    _name = 'library.book'
    _inherit = 'base.archive'  #
    _description = 'Library Book'  # membuat deskripsi di database
    _order = 'date_release desc, name'  #membuat urutan berdasarkan date_release kemudian name
    _rec_name = 'short_name'

    name = fields.Char('Title', required=True)
    short_name = fields.Char('Short Title')
    date_release = fields.Date('Release Date')
    notes = fields.Text('Internal Notes')
    state = fields.Selection([('draft', 'Not Available'),
                              ('available', 'Available'),
                              ('borrowed', 'Borrowec'), ('lost', 'Lost')],
                             'State')
    cover = fields.Binary('Book Cover')
    out_of_print = fields.Boolean('Out of Print?')
    date_updated = fields.Datetime('Last Updated')
    description = fields.Html(
        string='Description',
        sanitize=True,
        strip_style=False,
        translate=False,
    )
    pages = fields.Integer(
        string='Number of Pages',
        default=0,
        help='Total book page count',
        groups='base.group_user',
        states={'cancel': [('readonly', True)]},
        copy=True,
        index=False,
        readonly=False,
        required=False,
        company_dependent=False,
    )
    reader_rating = fields.Float(
        'Reader Average Rating',
        (14, 4),  # Optional precision (total, decimals)
    )
    cost_price = fields.Float('Book Cost', dp.get_precision('Book Price'))
    currency_id = fields.Many2one('res.currency', string='Currency')
    retail_price = fields.Monetary('Retail Price',
                                   # (Optional) currency_field='currency_id',
                                   )
    publisher_id = fields.Many2one(
        'res.partner',
        string='Publisher',
        # Optional:
        ondelete='set null',
        context={},
        domain=[],
    )
    publisher_city = fields.Char('Publisher City', related='publisher_id.city')
    author_ids = fields.Many2many('res.partner', string='Authors')
    age_days = fields.Float(
        string='Days Since Release',
        compute='_compute_age',
        inverse='_inverse_age',
        search='_search_age',
        store=False,
        compute_sudo=False,
    )
    date_start = fields.Date('Member Since')
    date_end = fields.Date('Termination Date')
    member_number = fields.Char()

    _sql_constraints = [('name_uniq', 'UNIQUE (name)',
                         'Book title must be uniqe..')]  #  database constrains

    # python file constrains
    @api.constrains('date_release')
    def _check_release_date(self):
        for r in self:
            if r.date_release > fields.Date.today():
                raise models.ValidationError(
                    'Release date must be in the past.')

    #add the method with the value computation logic
    @api.depends('date_release')
    def _compute_age(self):
        today = fDate.from_string(fDate.today())
        for book in self.filtered('date_release'):
            delta = (fDate.from_string(book.date_release - today))
            book.age_days = delta.days

    #add the method implementing the logic to write on the computed field
    def _inverse_age(self):
        today = fDate.from_string(fDate.today())
        for book in self.filtered('date_release'):
            d = td(days=book.age_days) - today
            book.date_release = fDate.to_string(d)

    #To implement the logic allowing you to search on the computed field
    def _search_age(self, operator, value):
        today = fDate.from_string(fDate.today())
        value_days = td(days=value)
        value_date = fDate.to_string(today - value_days)
        return [('date_release', operator, value_date)]

    #add a helper method to dynamically build the list of selectable target models
    @api.model
    def _referencable_models(self):
        models = self.env['res.request.link'].search([])
        return [(x.object, x.name) for x in models]

    #add the Reference field and use the previous function to provide the list of selectable models
    ref_doc_id = fields.Reference(selection='_referencable_models',
                                  string='Reference Document')

    # Add a helper method to check whether a state transition is allowed:
    @api.model
    def is_allowed_transition(self, old_state, new_state):
        allowed = [('draft', 'available'), ('available', 'borrowed'),
                   ('borrowed', 'available'), ('available', 'lost'),
                   ('borrowed', 'lost'), ('lost', 'available')]
        return (old_state, new_state) in allowed

    # Add a method to change the state of some books to a new one passed as an argument:
    @api.multi
    def change_state(self, new_state):
        for book in self:
            if book.is_allowed_transition(book.state, new_state):
                book.state = new_state
            else:
                continue

    @api.model
    def get_all_library_members(self):
        library_member_model = self.env['library.member']
        return library_member_model.search([])
Ejemplo n.º 3
0
class Channel(models.Model):
    """ A channel is a container of slides. It has group-based access
    configuration allowing to configure slide upload and access. Slides can be
    promoted in channels. """
    _name = 'slide.channel'
    _description = 'Channel for Slides'
    _inherit = [
        'mail.thread', 'website.seo.metadata', 'website.published.mixin'
    ]
    _order = 'sequence, id'
    _order_by_strategy = {
        'most_viewed': 'total_views desc',
        'most_voted': 'likes desc',
        'latest': 'date_published desc',
    }

    name = fields.Char('Name', translate=True, required=True)
    description = fields.Html('Description', translate=True)
    sequence = fields.Integer(default=10, help='Display order')
    category_ids = fields.One2many('slide.category',
                                   'channel_id',
                                   string="Categories")
    slide_ids = fields.One2many('slide.slide', 'channel_id', string="Slides")
    promote_strategy = fields.Selection([('none', 'No Featured Presentation'),
                                         ('latest', 'Latest Published'),
                                         ('most_voted', 'Most Voted'),
                                         ('most_viewed', 'Most Viewed'),
                                         ('custom', 'Featured Presentation')],
                                        string="Featuring Policy",
                                        default='most_voted',
                                        required=True)
    custom_slide_id = fields.Many2one('slide.slide', string='Slide to Promote')
    promoted_slide_id = fields.Many2one('slide.slide',
                                        string='Featured Slide',
                                        compute='_compute_promoted_slide_id',
                                        store=True)
    promoted_image = fields.Binary('Promoted image')

    @api.depends('custom_slide_id', 'promote_strategy', 'slide_ids.likes',
                 'slide_ids.total_views', "slide_ids.date_published")
    def _compute_promoted_slide_id(self):
        for record in self:
            if record.promote_strategy == 'none':
                record.promoted_slide_id = False
            elif record.promote_strategy == 'custom':
                record.promoted_slide_id = record.custom_slide_id
            elif record.promote_strategy:
                slides = self.env['slide.slide'].search(
                    [('website_published', '=', True),
                     ('channel_id', '=', record.id)],
                    limit=1,
                    order=self._order_by_strategy[record.promote_strategy])
                record.promoted_slide_id = slides and slides[0] or False

    nbr_presentations = fields.Integer('Number of Presentations',
                                       compute='_count_presentations',
                                       store=True)
    nbr_documents = fields.Integer('Number of Documents',
                                   compute='_count_presentations',
                                   store=True)
    nbr_videos = fields.Integer('Number of Videos',
                                compute='_count_presentations',
                                store=True)
    nbr_infographics = fields.Integer('Number of Infographics',
                                      compute='_count_presentations',
                                      store=True)
    total = fields.Integer(compute='_count_presentations', store=True)

    @api.depends('slide_ids.slide_type', 'slide_ids.website_published')
    def _count_presentations(self):
        result = dict.fromkeys(self.ids, dict())
        res = self.env['slide.slide'].read_group(
            [('website_published', '=', True), ('channel_id', 'in', self.ids)],
            ['channel_id', 'slide_type'], ['channel_id', 'slide_type'],
            lazy=False)
        for res_group in res:
            result[res_group['channel_id'][0]][res_group['slide_type']] = \
                result[res_group['channel_id'][0]].get(
                    res_group['slide_type'], 0) + res_group['__count']
        for record in self:
            record.nbr_presentations = result[record.id].get('presentation', 0)
            record.nbr_documents = result[record.id].get('document', 0)
            record.nbr_videos = result[record.id].get('video', 0)
            record.nbr_infographics = result[record.id].get('infographic', 0)
            record.total = (record.nbr_presentations + record.nbr_documents +
                            record.nbr_videos + record.nbr_infographics)

    publish_template_id = fields.Many2one(
        'email.template',
        string='Published Template',
        help="Email template to send slide publication through email",
        default=lambda self: self.env['ir.model.data'].xmlid_to_res_id(
            'website_slides.slide_template_published'))
    share_template_id = fields.Many2one(
        'email.template',
        string='Shared Template',
        help="Email template used when sharing a slide",
        default=lambda self: self.env['ir.model.data'].xmlid_to_res_id(
            'website_slides.slide_template_shared'))
    visibility = fields.Selection(
        [('public', 'Public'), ('private', 'Private'),
         ('partial', 'Show channel but restrict presentations')],
        default='public',
        required=True)
    group_ids = fields.Many2many(
        'res.groups',
        'rel_channel_groups',
        'channel_id',
        'group_id',
        string='Channel Groups',
        help="Groups allowed to see presentations in this channel")
    access_error_msg = fields.Html(
        'Error Message',
        help="Message to display when not accessible due to access rights",
        default=""
        "<p>This channel is private and its content "
        "is restricted to some users.</p>"
        "",
        translate=True)
    upload_group_ids = fields.Many2many(
        'res.groups',
        'rel_upload_groups',
        'channel_id',
        'group_id',
        string='Upload Groups',
        help=""
        "Groups allowed to upload presentations in this channel."
        " If void, every user can upload.")
    # not stored access fields, depending on each user
    can_see = fields.Boolean('Can See', compute='_compute_access')
    can_see_full = fields.Boolean('Full Access', compute='_compute_access')
    can_upload = fields.Boolean('Can Upload', compute='_compute_access')

    @api.one
    @api.depends('visibility', 'group_ids', 'upload_group_ids')
    def _compute_access(self):
        self.can_see = self.visibility in [
            'public', 'private'
        ] or bool(self.group_ids & self.env.user.groups_id)
        self.can_see_full = self.visibility == 'public' or bool(
            self.group_ids & self.env.user.groups_id)
        self.can_upload = self.can_see and (not self.upload_group_ids
                                            or bool(self.upload_group_ids
                                                    & self.env.user.groups_id))

    @api.multi
    @api.depends('name')
    def _website_url(self, name, arg):
        res = super(Channel, self)._website_url(name, arg)
        base_url = self.env['ir.config_parameter'].get_param('web.base.url')
        res.update({(channel.id, '%s/slides/%s' % (base_url, slug(channel)))
                    for channel in self})
        return res

    @api.onchange('visibility')
    def change_visibility(self):
        if self.visibility == 'public':
            self.group_ids = False
Ejemplo n.º 4
0
class Post(models.Model):

    _name = 'forum.post'
    _description = 'Forum Post'
    _inherit = ['mail.thread', 'website.seo.metadata']
    _order = "is_correct DESC, vote_count DESC, write_date DESC"

    name = fields.Char('Title')
    forum_id = fields.Many2one('forum.forum', string='Forum', required=True)
    content = fields.Html('Content', strip_style=True)
    plain_content = fields.Text('Plain Content',
                                compute='_get_plain_content',
                                store=True)

    @api.one
    @api.depends('content')
    def _get_plain_content(self):
        self.plain_content = tools.html2plaintext(
            self.content)[0:500] if self.content else False

    content_link = fields.Char('URL', help="URL of Link Articles")
    tag_ids = fields.Many2many('forum.tag',
                               'forum_tag_rel',
                               'forum_id',
                               'forum_tag_id',
                               string='Tags')
    state = fields.Selection([('active', 'Active'), ('close', 'Close'),
                              ('offensive', 'Offensive')],
                             string='Status',
                             default='active')
    views = fields.Integer('Number of Views', default=0)
    active = fields.Boolean('Active', default=True)
    post_type = fields.Selection([('question', 'Question'),
                                  ('link', 'Article'),
                                  ('discussion', 'Discussion')],
                                 string='Type',
                                 default='question',
                                 required=True)
    website_message_ids = fields.One2many(
        'mail.message',
        'res_id',
        domain=lambda self: [
            '&', ('model', '=', self._name),
            ('message_type', 'in', ['email', 'comment'])
        ],
        string='Post Messages',
        help="Comments on forum post",
    )
    # history
    create_date = fields.Datetime('Asked on', select=True, readonly=True)
    create_uid = fields.Many2one('res.users',
                                 string='Created by',
                                 select=True,
                                 readonly=True)
    write_date = fields.Datetime('Update on', select=True, readonly=True)
    bump_date = fields.Datetime(
        'Bumped on',
        readonly=True,
        help=
        "Technical field allowing to bump a question. Writing on this field will trigger"
        "a write on write_date and therefore bump the post. Directly writing on write_date"
        "is currently not supported and this field is a workaround.")
    write_uid = fields.Many2one('res.users',
                                string='Updated by',
                                select=True,
                                readonly=True)
    relevancy = fields.Float('Relevance',
                             compute="_compute_relevancy",
                             store=True)

    @api.one
    @api.depends('vote_count', 'forum_id.relevancy_post_vote',
                 'forum_id.relevancy_time_decay')
    def _compute_relevancy(self):
        if self.create_date:
            days = (datetime.today() - datetime.strptime(
                self.create_date, tools.DEFAULT_SERVER_DATETIME_FORMAT)).days
            self.relevancy = math.copysign(1, self.vote_count) * (
                abs(self.vote_count - 1)**self.forum_id.relevancy_post_vote /
                (days + 2)**self.forum_id.relevancy_time_decay)
        else:
            self.relevancy = 0

    # vote
    vote_ids = fields.One2many('forum.post.vote', 'post_id', string='Votes')
    user_vote = fields.Integer('My Vote', compute='_get_user_vote')
    vote_count = fields.Integer('Votes', compute='_get_vote_count', store=True)

    @api.multi
    def _get_user_vote(self):
        votes = self.env['forum.post.vote'].search_read(
            [('post_id', 'in', self._ids),
             ('user_id', '=', self._uid)], ['vote', 'post_id'])
        mapped_vote = dict([(v['post_id'][0], v['vote']) for v in votes])
        for vote in self:
            vote.user_vote = mapped_vote.get(vote.id, 0)

    @api.multi
    @api.depends('vote_ids.vote')
    def _get_vote_count(self):
        read_group_res = self.env['forum.post.vote'].read_group(
            [('post_id', 'in', self._ids)], ['post_id', 'vote'],
            ['post_id', 'vote'],
            lazy=False)
        result = dict.fromkeys(self._ids, 0)
        for data in read_group_res:
            result[data['post_id'][0]] += data['__count'] * int(data['vote'])
        for post in self:
            post.vote_count = result[post.id]

    # favorite
    favourite_ids = fields.Many2many('res.users', string='Favourite')
    user_favourite = fields.Boolean('Is Favourite',
                                    compute='_get_user_favourite')
    favourite_count = fields.Integer('Favorite Count',
                                     compute='_get_favorite_count',
                                     store=True)

    @api.one
    def _get_user_favourite(self):
        self.user_favourite = self._uid in self.favourite_ids.ids

    @api.one
    @api.depends('favourite_ids')
    def _get_favorite_count(self):
        self.favourite_count = len(self.favourite_ids)

    # hierarchy
    is_correct = fields.Boolean('Correct',
                                help='Correct answer or answer accepted')
    parent_id = fields.Many2one('forum.post',
                                string='Question',
                                ondelete='cascade')
    self_reply = fields.Boolean('Reply to own question',
                                compute='_is_self_reply',
                                store=True)
    child_ids = fields.One2many('forum.post', 'parent_id', string='Answers')
    child_count = fields.Integer('Number of answers',
                                 compute='_get_child_count',
                                 store=True)
    uid_has_answered = fields.Boolean('Has Answered',
                                      compute='_get_uid_has_answered')
    has_validated_answer = fields.Boolean('Is answered',
                                          compute='_get_has_validated_answer',
                                          store=True)

    @api.one
    @api.depends('create_uid', 'parent_id')
    def _is_self_reply(self):
        self.self_reply = self.parent_id.create_uid.id == self._uid

    @api.one
    @api.depends('child_ids.create_uid', 'website_message_ids')
    def _get_child_count(self):
        def process(node):
            total = len(node.website_message_ids) + len(node.child_ids)
            for child in node.child_ids:
                total += process(child)
            return total

        self.child_count = process(self)

    @api.one
    def _get_uid_has_answered(self):
        self.uid_has_answered = any(answer.create_uid.id == self._uid
                                    for answer in self.child_ids)

    @api.one
    @api.depends('child_ids.is_correct')
    def _get_has_validated_answer(self):
        self.has_validated_answer = any(answer.is_correct
                                        for answer in self.child_ids)

    # closing
    closed_reason_id = fields.Many2one('forum.post.reason', string='Reason')
    closed_uid = fields.Many2one('res.users', string='Closed by', select=1)
    closed_date = fields.Datetime('Closed on', readonly=True)
    # karma calculation and access
    karma_accept = fields.Integer('Convert comment to answer',
                                  compute='_get_post_karma_rights')
    karma_edit = fields.Integer('Karma to edit',
                                compute='_get_post_karma_rights')
    karma_close = fields.Integer('Karma to close',
                                 compute='_get_post_karma_rights')
    karma_unlink = fields.Integer('Karma to unlink',
                                  compute='_get_post_karma_rights')
    karma_comment = fields.Integer('Karma to comment',
                                   compute='_get_post_karma_rights')
    karma_comment_convert = fields.Integer(
        'Karma to convert comment to answer', compute='_get_post_karma_rights')
    can_ask = fields.Boolean('Can Ask', compute='_get_post_karma_rights')
    can_answer = fields.Boolean('Can Answer', compute='_get_post_karma_rights')
    can_accept = fields.Boolean('Can Accept', compute='_get_post_karma_rights')
    can_edit = fields.Boolean('Can Edit', compute='_get_post_karma_rights')
    can_close = fields.Boolean('Can Close', compute='_get_post_karma_rights')
    can_unlink = fields.Boolean('Can Unlink', compute='_get_post_karma_rights')
    can_upvote = fields.Boolean('Can Upvote', compute='_get_post_karma_rights')
    can_downvote = fields.Boolean('Can Downvote',
                                  compute='_get_post_karma_rights')
    can_comment = fields.Boolean('Can Comment',
                                 compute='_get_post_karma_rights')
    can_comment_convert = fields.Boolean('Can Convert to Comment',
                                         compute='_get_post_karma_rights')
    can_view = fields.Boolean('Can View', compute='_get_post_karma_rights')
    can_display_biography = fields.Boolean(
        'Can userbiography of the author be viewed',
        compute='_get_post_karma_rights')

    @api.multi
    def _get_post_karma_rights(self):
        user = self.env.user
        is_admin = user.id == SUPERUSER_ID
        # sudoed recordset instead of individual posts so values can be
        # prefetched in bulk
        for post, post_sudo in itertools.izip(self, self.sudo()):
            is_creator = post.create_uid == user

            post.karma_accept = post.forum_id.karma_answer_accept_own if post.parent_id.create_uid == user else post.forum_id.karma_answer_accept_all
            post.karma_edit = post.forum_id.karma_edit_own if is_creator else post.forum_id.karma_edit_all
            post.karma_close = post.forum_id.karma_close_own if is_creator else post.forum_id.karma_close_all
            post.karma_unlink = post.forum_id.karma_unlink_own if is_creator else post.forum_id.karma_unlink_all
            post.karma_comment = post.forum_id.karma_comment_own if is_creator else post.forum_id.karma_comment_all
            post.karma_comment_convert = post.forum_id.karma_comment_convert_own if is_creator else post.forum_id.karma_comment_convert_all

            post.can_ask = is_admin or user.karma >= post.forum_id.karma_ask
            post.can_answer = is_admin or user.karma >= post.forum_id.karma_answer
            post.can_accept = is_admin or user.karma >= post.karma_accept
            post.can_edit = is_admin or user.karma >= post.karma_edit
            post.can_close = is_admin or user.karma >= post.karma_close
            post.can_unlink = is_admin or user.karma >= post.karma_unlink
            post.can_upvote = is_admin or user.karma >= post.forum_id.karma_upvote
            post.can_downvote = is_admin or user.karma >= post.forum_id.karma_downvote
            post.can_comment = is_admin or user.karma >= post.karma_comment
            post.can_comment_convert = is_admin or user.karma >= post.karma_comment_convert
            post.can_view = is_admin or user.karma >= post.karma_close or post_sudo.create_uid.karma > 0
            post.can_display_biography = is_admin or post_sudo.create_uid.karma >= post.forum_id.karma_user_bio

    @api.one
    @api.constrains('post_type', 'forum_id')
    def _check_post_type(self):
        if (self.post_type == 'question' and not self.forum_id.allow_question) \
                or (self.post_type == 'discussion' and not self.forum_id.allow_discussion) \
                or (self.post_type == 'link' and not self.forum_id.allow_link):
            raise UserError(_('This forum does not allow %s' % self.post_type))

    def _update_content(self, content, forum_id):
        forum = self.env['forum.forum'].browse(forum_id)
        if content and self.env.user.karma < forum.karma_dofollow:
            for match in re.findall(r'<a\s.*href=".*?">', content):
                content = re.sub(match,
                                 match[:3] + 'rel="nofollow" ' + match[3:],
                                 content)

        if self.env.user.karma <= forum.karma_editor:
            filter_regexp = r'(<img.*?>)|(<a[^>]*?href[^>]*?>)|(<[a-z|A-Z]+[^>]*style\s*=\s*[\'"][^\'"]*\s*background[^:]*:[^url;]*url)'
            content_match = re.search(filter_regexp, content, re.I)
            if content_match:
                raise KarmaError(
                    'User karma not sufficient to post an image or link.')
        return content

    @api.model
    def create(self, vals):
        if 'content' in vals and vals.get('forum_id'):
            vals['content'] = self._update_content(vals['content'],
                                                   vals['forum_id'])

        post = super(Post,
                     self.with_context(mail_create_nolog=True)).create(vals)
        # deleted or closed questions
        if post.parent_id and (post.parent_id.state == 'close'
                               or post.parent_id.active is False):
            raise UserError(
                _('Posting answer on a [Deleted] or [Closed] question is not possible'
                  ))
        # karma-based access
        if not post.parent_id and not post.can_ask:
            raise KarmaError('Not enough karma to create a new question')
        elif post.parent_id and not post.can_answer:
            raise KarmaError('Not enough karma to answer to a question')
        # messaging and chatter
        base_url = self.env['ir.config_parameter'].get_param('web.base.url')
        if post.parent_id:
            body = _(
                '<p>A new answer for <i>%s</i> has been posted. <a href="%s/forum/%s/question/%s">Click here to access the post.</a></p>'
                % (post.parent_id.name, base_url, slug(
                    post.parent_id.forum_id), slug(post.parent_id)))
            post.parent_id.message_post(subject=_('Re: %s') %
                                        post.parent_id.name,
                                        body=body,
                                        subtype='website_forum.mt_answer_new')
        else:
            body = _(
                '<p>A new question <i>%s</i> has been asked on %s. <a href="%s/forum/%s/question/%s">Click here to access the question.</a></p>'
                % (post.name, post.forum_id.name, base_url, slug(
                    post.forum_id), slug(post)))
            post.message_post(subject=post.name,
                              body=body,
                              subtype='website_forum.mt_question_new')
            self.env.user.sudo().add_karma(
                post.forum_id.karma_gen_question_new)
        return post

    @api.model
    def check_mail_message_access(self, res_ids, operation, model_name=None):
        if operation in ('write', 'unlink') and (not model_name or model_name
                                                 == 'forum.post'):
            # Make sure only author or moderator can edit/delete messages
            if any(not post.can_edit for post in self.browse(res_ids)):
                raise KarmaError('Not enough karma to edit a post.')
        return super(Post,
                     self).check_mail_message_access(res_ids,
                                                     operation,
                                                     model_name=model_name)

    @api.multi
    @api.depends('name', 'post_type')
    def name_get(self):
        result = []
        for post in self:
            if post.post_type == 'discussion' and post.parent_id and not post.name:
                result.append(
                    (post.id, '%s (%s)' % (post.parent_id.name, post.id)))
            else:
                result.append((post.id, '%s' % (post.name)))
        return result

    @api.multi
    def write(self, vals):
        if 'content' in vals:
            vals['content'] = self._update_content(vals['content'],
                                                   self.forum_id.id)
        if 'state' in vals:
            if vals['state'] in ['active', 'close'] and any(not post.can_close
                                                            for post in self):
                raise KarmaError('Not enough karma to close or reopen a post.')
        if 'active' in vals:
            if any(not post.can_unlink for post in self):
                raise KarmaError(
                    'Not enough karma to delete or reactivate a post')
        if 'is_correct' in vals:
            if any(not post.can_accept for post in self):
                raise KarmaError(
                    'Not enough karma to accept or refuse an answer')
            # update karma except for self-acceptance
            mult = 1 if vals['is_correct'] else -1
            for post in self:
                if vals['is_correct'] != post.is_correct and post.create_uid.id != self._uid:
                    post.create_uid.sudo().add_karma(
                        post.forum_id.karma_gen_answer_accepted * mult)
                    self.env.user.sudo().add_karma(
                        post.forum_id.karma_gen_answer_accept * mult)
        if any(key not in [
                'state', 'active', 'is_correct', 'closed_uid', 'closed_date',
                'closed_reason_id'
        ] for key in vals.keys()) and any(not post.can_edit for post in self):
            raise KarmaError('Not enough karma to edit a post.')

        res = super(Post, self).write(vals)
        # if post content modify, notify followers
        if 'content' in vals or 'name' in vals:
            for post in self:
                if post.parent_id:
                    body, subtype = _(
                        'Answer Edited'), 'website_forum.mt_answer_edit'
                    obj_id = post.parent_id
                else:
                    body, subtype = _(
                        'Question Edited'), 'website_forum.mt_question_edit'
                    obj_id = post
                obj_id.message_post(body=body, subtype=subtype)
        return res

    @api.multi
    def reopen(self):
        if any(post.parent_id or post.state != 'close' for post in self):
            return False

        reason_offensive = self.env.ref('website_forum.reason_7')
        reason_spam = self.env.ref('website_forum.reason_8')
        for post in self:
            if post.closed_reason_id in (reason_offensive, reason_spam):
                _logger.info(
                    'Upvoting user <%s>, reopening spam/offensive question',
                    post.create_uid)
                # TODO: in master, consider making this a tunable karma parameter
                post.create_uid.sudo().add_karma(
                    post.forum_id.karma_gen_question_downvote * -5)

        self.sudo().write({'state': 'active'})

    @api.multi
    def close(self, reason_id):
        if any(post.parent_id for post in self):
            return False

        reason_offensive = self.env.ref('website_forum.reason_7').id
        reason_spam = self.env.ref('website_forum.reason_8').id
        if reason_id in (reason_offensive, reason_spam):
            for post in self:
                _logger.info(
                    'Downvoting user <%s> for posting spam/offensive contents',
                    post.create_uid)
                # TODO: in master, consider making this a tunable karma parameter
                post.create_uid.sudo().add_karma(
                    post.forum_id.karma_gen_question_downvote * 5)

        self.write({
            'state':
            'close',
            'closed_uid':
            self._uid,
            'closed_date':
            datetime.today().strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT),
            'closed_reason_id':
            reason_id,
        })
        return True

    @api.multi
    def unlink(self):
        if any(not post.can_unlink for post in self):
            raise KarmaError('Not enough karma to unlink a post')
        # if unlinking an answer with accepted answer: remove provided karma
        for post in self:
            if post.is_correct:
                post.create_uid.sudo().add_karma(
                    post.forum_id.karma_gen_answer_accepted * -1)
                self.env.user.sudo().add_karma(
                    post.forum_id.karma_gen_answer_accepted * -1)
        return super(Post, self).unlink()

    @api.multi
    def bump(self):
        """ Bump a question: trigger a write_date by writing on a dummy bump_date
        field. One cannot bump a question more than once every 10 days. """
        self.ensure_one()
        if self.forum_id.allow_bump and not self.child_ids and (datetime.today(
        ) - datetime.strptime(self.write_date,
                              tools.DEFAULT_SERVER_DATETIME_FORMAT)).days > 9:
            # write through super to bypass karma; sudo to allow public user to bump any post
            return self.sudo().write({'bump_date': fields.Datetime.now()})
        return False

    @api.multi
    def vote(self, upvote=True):
        Vote = self.env['forum.post.vote']
        vote_ids = Vote.search([('post_id', 'in', self._ids),
                                ('user_id', '=', self._uid)])
        new_vote = '1' if upvote else '-1'
        voted_forum_ids = set()
        if vote_ids:
            for vote in vote_ids:
                if upvote:
                    new_vote = '0' if vote.vote == '-1' else '1'
                else:
                    new_vote = '0' if vote.vote == '1' else '-1'
                vote.vote = new_vote
                voted_forum_ids.add(vote.post_id.id)
        for post_id in set(self._ids) - voted_forum_ids:
            for post_id in self._ids:
                Vote.create({'post_id': post_id, 'vote': new_vote})
        return {'vote_count': self.vote_count, 'user_vote': new_vote}

    @api.one
    def convert_answer_to_comment(self):
        """ Tools to convert an answer (forum.post) to a comment (mail.message).
        The original post is unlinked and a new comment is posted on the question
        using the post create_uid as the comment's author. """
        if not self.parent_id:
            return False

        # karma-based action check: use the post field that computed own/all value
        if not self.can_comment_convert:
            raise KarmaError(
                'Not enough karma to convert an answer to a comment')

        # post the message
        question = self.parent_id
        values = {
            'author_id':
            self.sudo().create_uid.partner_id.
            id,  # use sudo here because of access to res.users model
            'body':
            tools.html_sanitize(self.content,
                                strict=True,
                                strip_style=True,
                                strip_classes=True),
            'message_type':
            'comment',
            'subtype':
            'mail.mt_comment',
            'date':
            self.create_date,
        }
        new_message = self.browse(question.id).with_context(
            mail_create_nosubscribe=True).message_post(**values)

        # unlink the original answer, using SUPERUSER_ID to avoid karma issues
        self.sudo().unlink()

        return new_message

    @api.model
    def convert_comment_to_answer(self, message_id, default=None):
        """ Tool to convert a comment (mail.message) into an answer (forum.post).
        The original comment is unlinked and a new answer from the comment's author
        is created. Nothing is done if the comment's author already answered the
        question. """
        comment = self.env['mail.message'].sudo().browse(message_id)
        post = self.browse(comment.res_id)
        if not comment.author_id or not comment.author_id.user_ids:  # only comment posted by users can be converted
            return False

        # karma-based action check: must check the message's author to know if own / all
        karma_convert = comment.author_id.id == self.env.user.partner_id.id and post.forum_id.karma_comment_convert_own or post.forum_id.karma_comment_convert_all
        can_convert = self.env.user.karma >= karma_convert
        if not can_convert:
            raise KarmaError(
                'Not enough karma to convert a comment to an answer')

        # check the message's author has not already an answer
        question = post.parent_id if post.parent_id else post
        post_create_uid = comment.author_id.user_ids[0]
        if any(answer.create_uid.id == post_create_uid.id
               for answer in question.child_ids):
            return False

        # create the new post
        post_values = {
            'forum_id': question.forum_id.id,
            'content': comment.body,
            'parent_id': question.id,
        }
        # done with the author user to have create_uid correctly set
        new_post = self.sudo(post_create_uid.id).create(post_values)

        # delete comment
        comment.unlink()

        return new_post

    @api.one
    def unlink_comment(self, message_id):
        user = self.env.user
        comment = self.env['mail.message'].sudo().browse(message_id)
        if not comment.model == 'forum.post' or not comment.res_id == self.id:
            return False
        # karma-based action check: must check the message's author to know if own or all
        karma_unlink = comment.author_id.id == user.partner_id.id and self.forum_id.karma_comment_unlink_own or self.forum_id.karma_comment_unlink_all
        can_unlink = user.karma >= karma_unlink
        if not can_unlink:
            raise KarmaError('Not enough karma to unlink a comment')
        return comment.unlink()

    @api.multi
    def set_viewed(self):
        self._cr.execute(
            """UPDATE forum_post SET views = views+1 WHERE id IN %s""",
            (self._ids, ))
        return True

    @api.one
    def get_access_action(self):
        """ Override method that generated the link to access the document. Instead
        of the classic form view, redirect to the post on the website directly """
        return {
            'type': 'ir.actions.act_url',
            'url': '/forum/%s/question/%s' % (self.forum_id.id, self.id),
            'target': 'self',
            'res_id': self.id,
        }

    @api.multi
    def _notification_get_recipient_groups(self, message, recipients):
        """ Override to set the access button: everyone can see an access button
        on their notification email. It will lead on the website view of the
        post. """
        res = super(Post, self)._notification_get_recipient_groups(
            message, recipients)
        access_action = self._notification_link_helper('view',
                                                       model=message.model,
                                                       res_id=message.res_id)
        for category, data in res.iteritems():
            res[category]['button_access'] = {
                'url': access_action,
                'title': '%s %s' % (_('View'), self.post_type)
            }
        return res

    @api.cr_uid_ids_context
    def message_post(self,
                     cr,
                     uid,
                     thread_id,
                     message_type='notification',
                     subtype=None,
                     context=None,
                     **kwargs):
        if thread_id and message_type == 'comment':  # user comments have a restriction on karma
            if isinstance(thread_id, (list, tuple)):
                post_id = thread_id[0]
            else:
                post_id = thread_id
            post = self.browse(cr, uid, post_id, context=context)
            # TDE FIXME: trigger browse because otherwise the function field is not compted - check with RCO
            tmp1, tmp2 = post.karma_comment, post.can_comment
            user = self.pool['res.users'].browse(cr, uid, uid)
            tmp3 = user.karma
            # TDE END FIXME
            if not post.can_comment:
                raise KarmaError('Not enough karma to comment')
        return super(Post, self).message_post(cr,
                                              uid,
                                              thread_id,
                                              message_type=message_type,
                                              subtype=subtype,
                                              context=context,
                                              **kwargs)
Ejemplo n.º 5
0
class SimpleMeliPublishing(models.TransientModel):
    _name = "simple_meli_publishing.process_excel"

    data = fields.Binary(
        required=True,
        string="Worksheet to process"
    )
    pdata = fields.Binary(
        required=False,
        string="Worksheet processed",
    )
    name = fields.Char(
        'File Name',
    )
    state = fields.Selection(
        [('load', 'Load'),  # load spreadsheet
         ('download', 'Download'),  # download spreadsheet
         ('error', 'Error')],  # show errors
        default="load"
    )
    errors = fields.Html(
        default='_(<h1>We found the following errors</h1>)',
        readonly=True
    )

    @api.multi
    def add_error(self, tag, row=False, meli_code=False, sku=False,
                  default_code=False):
        for rec in self:
            if tag == 'not_found':
                rec.errors += _('<p>The product code %s from row %s on the '
                                'worksheet can not be found in the system.'
                                '</p>') % (meli_code, row)
            if tag == 'sku':
                rec.errors += _('<p>The sku from worksheet does not match with'
                                ' the product internal reference "%s" <> "%s" '
                                'at row %s in the worksheet.'
                                '</p>') % (sku, default_code, row)

    @api.one
    def process_data(self, fp_name):
        PUB_CODE_COL = 2
        PRICE_COL = 7
        SKU_COL = 1
        FIRST_ROW = 4

        product_obj = self.env['product.product']

        # open worksheet
        wb = openpyxl.load_workbook(filename=fp_name,
                                    read_only=False,
                                    data_only=True)
        sheet = wb.active
        for row in range(FIRST_ROW, sheet.max_row):
            meli_code = sheet.cell(column=PUB_CODE_COL, row=row).value
            sku = sheet.cell(column=SKU_COL, row=row).value
            prod = product_obj.search([('meli_code', '=', meli_code)])
            if prod:
                #if sku != prod.default_code:
                #    pass
                #    #self.state = 'error'
                #    #self.add_error('sku', row=row, sku=sku,
                #    #cd    default_code=prod.default_code)
                #else:
                sheet.cell(column=PRICE_COL, row=row, value=prod.final_price)
            else:
                self.state = 'error'
                self.add_error('not_found', row=row, meli_code=meli_code)

        if self.state != 'error':
            wb.save(fp_name)

    @api.multi
    def load_file(self):
        for rec in self:

            # escribir la data en un archivo temporario
            data = base64.decodestring(rec.data)
            (fileno, fp_name) = tempfile.mkstemp('.xlsx', 'openerp_')
            with open(fp_name, "w") as worksheet:
                worksheet.write(data)

            # procesar el archivo
            self.process_data(fp_name)

            # leer el archivo temporario para hacer download
            with open(fp_name, "r") as worksheet:
                rec.pdata = base64.encodestring(worksheet.read())

            # cambiar la vista para que descarguen el archivo
            if rec.state != 'error':
                rec.state = 'download'
Ejemplo n.º 6
0
class ResUsersAuthenticatorCreate(models.TransientModel):
    _name = 'res.users.authenticator.create'
    _description = 'MFA App/Device Creation Wizard'

    name = fields.Char(
        string='Authentication App/Device Name',
        help='A name that will help you remember this authentication'
        ' app/device',
        required=True,
        index=True,
    )
    secret_key = fields.Char(
        default=lambda s: pyotp.random_base32(),
        required=True,
    )
    qr_code_tag = fields.Html(
        compute='_compute_qr_code_tag',
        string='QR Code',
        help='Scan this image with your authentication app to add your'
        ' account',
    )
    user_id = fields.Many2one(
        comodel_name='res.users',
        default=lambda s: s._default_user_id(),
        required=True,
        string='Associated User',
        help='This is the user whose account the new authentication app/device'
        ' will be tied to',
        readonly=True,
        index=True,
        ondelete='cascade',
    )
    confirmation_code = fields.Char(
        string='Confirmation Code',
        help='Enter the latest six digit code generated by your authentication'
        ' app',
        required=True,
    )

    @api.model
    def _default_user_id(self):
        user_id = self.env.context.get('uid')
        return self.env['res.users'].browse(user_id)

    @api.multi
    @api.depends(
        'secret_key',
        'user_id.display_name',
        'user_id.company_id.display_name',
    )
    def _compute_qr_code_tag(self):
        for record in self:
            if not record.user_id:
                continue

            totp = pyotp.TOTP(record.secret_key)
            provisioning_uri = totp.provisioning_uri(
                record.user_id.display_name,
                issuer_name=record.user_id.company_id.display_name,
            )
            provisioning_uri = urllib.quote(provisioning_uri)

            qr_width = qr_height = 300
            tag_base = '<img src="/report/barcode/?type=QR&amp;'
            tag_params = 'value=%s&amp;width=%s&amp;height=%s">' % (
                provisioning_uri, qr_width, qr_height)
            record.qr_code_tag = tag_base + tag_params

    @api.multi
    def action_create(self):
        self.ensure_one()
        self._perform_validations()
        self._create_authenticator()

        action_data = self.env.ref('base.action_res_users_my').read()[0]
        action_data.update({'res_id': self.user_id.id})
        return action_data

    @api.multi
    def _perform_validations(self):
        totp = pyotp.TOTP(self.secret_key)
        if not totp.verify(self.confirmation_code):
            raise ValidationError(
                _('Your confirmation code is not correct. Please try again,'
                  ' making sure that your MFA device is set to the correct time'
                  ' and that you have entered the most recent code generated by'
                  ' your authentication app.'))

    @api.multi
    def _create_authenticator(self):
        self.env['res.users.authenticator'].create({
            'name': self.name,
            'secret_key': self.secret_key,
            'user_id': self.user_id.id,
        })
Ejemplo n.º 7
0
class account_vat_ledger(models.Model):

    _name = "account.vat.ledger"
    _description = "Account VAT Ledger"
    _inherit = ['mail.thread']
    _order = 'period_id desc'

    _columns = {'first_page': old_fields.float('Report')}

    company_id = fields.Many2one('res.company',
                                 string='Company',
                                 required=True,
                                 readonly=True,
                                 states={'draft': [('readonly', False)]},
                                 default=lambda self: self.env['res.company'].
                                 _company_default_get('account.vat.ledger'))
    fiscalyear_id = fields.Many2one('account.fiscalyear',
                                    'Fiscal Year',
                                    required=True,
                                    readonly=True,
                                    states={'draft': [('readonly', False)]},
                                    help='Keep empty for all open fiscal year')
    type = fields.Selection([('sale', 'Sale'), ('purchase', 'Purchase')],
                            "Type",
                            required=True)
    period_id = fields.Many2one(
        'account.period',
        'Period',
        required=True,
        readonly=True,
        states={'draft': [('readonly', False)]},
    )
    journal_ids = fields.Many2many(
        'account.journal',
        'account_vat_ledger_journal_rel',
        'vat_ledger_id',
        'journal_id',
        string='Journals',
        required=True,
        readonly=True,
        states={'draft': [('readonly', False)]},
    )
    # first_page = fields.Integer(
    #     "First Page", required=True,
    #     readonly=True, states={'draft': [('readonly', False)]},)
    last_page = fields.Integer(
        "Last Page",
        readonly=True,
        states={'draft': [('readonly', False)]},
    )
    presented_ledger = fields.Binary(
        "Presented Ledger",
        readonly=True,
        states={'draft': [('readonly', False)]},
    )
    state = fields.Selection([('draft', 'Draft'), ('presented', 'Presented'),
                              ('cancel', 'Cancel')],
                             'State',
                             required=True,
                             default='draft')
    note = fields.Html("Notes")
    # Computed fields
    name = fields.Char('Titile', compute='_get_name')
    invoice_ids = fields.Many2many('account.invoice',
                                   string="Invoices",
                                   compute="_get_data")
    document_class_ids = fields.Many2many('sii.document_class',
                                          string="Document Classes",
                                          compute="_get_data")
    vat_tax_code_ids = fields.Many2many('account.tax.code',
                                        string="VAT Tax Codes",
                                        compute="_get_data")
    other_tax_code_ids = fields.Many2many('account.tax.code',
                                          string="Other Tax Codes",
                                          compute="_get_data")
    responsability_ids = fields.Many2many('sii.responsability',
                                          string="Responsabilities",
                                          compute="_get_data")
    other_taxes_base = fields.Float(
        string="Other Taxes Base",
        help="Base Amount for taxes without tax code",
        compute="_get_data")
    other_taxes_amount = fields.Float(string="Other Taxes Amount",
                                      help="Amount for taxes without tax code",
                                      compute="_get_data")

    @api.one
    @api.depends('journal_ids', 'period_id')
    def _get_data(self):
        self.responsability_ids = self.env['sii.responsability'].search([])

        invoices_domain = [('state', 'not in', ['draft', 'cancel']),
                           ('journal_id', 'in', self.journal_ids.ids),
                           ('period_id', '=', self.period_id.id)]
        # Get document classes
        self.document_class_ids = self.env['sii.document_class']
        group_invoices = self.env['account.invoice'].read_group(
            invoices_domain, ['id', 'sii_document_class_id'],
            ['sii_document_class_id'])
        document_class_ids = [
            x['sii_document_class_id'][0] for x in group_invoices
        ]
        self.document_class_ids = document_class_ids

        # Get invoices
        self.invoice_ids = self.env['account.invoice']
        invoices = self.env['account.invoice'].search(invoices_domain)
        self.invoice_ids = invoices

        # Get other taxes amounts (taxes without tax codes)
        taxes_domain = [('invoice_id', 'in', invoices.ids),
                        ('tax_code_id', '=', False)]
        other_taxes = self.env['account.invoice.tax'].search(taxes_domain)
        self.other_taxes_base = sum([x.base for x in other_taxes])
        self.other_taxes_amount = sum([x.amount for x in other_taxes])

        # Get vat tax codes
        vat_taxes_domain = [
            ('invoice_id', 'in', invoices.ids),
            ('tax_code_id', '!=', False),
            ('tax_code_id.parent_id.name', '=', 'IVA'),
        ]
        self.tax_code_ids = self.env['account.tax.code']
        vat_group_taxes = self.env['account.invoice.tax'].read_group(
            vat_taxes_domain, ['id', 'tax_code_id'], ['tax_code_id'])
        vat_tax_code_ids = [x['tax_code_id'][0] for x in vat_group_taxes]
        self.vat_tax_code_ids = vat_tax_code_ids

        # Get other tax codes
        other_taxes_domain = [
            ('invoice_id', 'in', invoices.ids),
            ('tax_code_id', '!=', False),
            ('tax_code_id.parent_id.name', '!=', 'IVA'),
        ]
        self.tax_code_ids = self.env['account.tax.code']
        other_group_taxes = self.env['account.invoice.tax'].read_group(
            other_taxes_domain, ['id', 'tax_code_id'], ['tax_code_id'])
        other_tax_code_ids = [x['tax_code_id'][0] for x in other_group_taxes]
        self.other_tax_code_ids = other_tax_code_ids

    @api.one
    @api.depends('type', 'period_id')
    def _get_name(self):
        if self.type == 'sale':
            ledger_type = _('Sales')
        elif self.type == 'purchase':
            ledger_type = _('Purchases')
        name = _("%s VAT Ledger %s") % (ledger_type, self.period_id.name)
        self.name = name

    @api.one
    @api.constrains('presented_ledger', 'last_page', 'state')
    def _check_state(self):
        if self.state == 'presented':
            if not self.presented_ledger:
                raise Warning(
                    _('To set "Presented" you must upload the "Presented Ledger" first'
                      ))
            elif not self.last_page:
                raise Warning(
                    _('To set "Presented" you must set the "Last Page" first'))

    @api.onchange('company_id')
    def change_company(self):
        now = time.strftime('%Y-%m-%d')
        company_id = self.company_id.id
        domain = [('company_id', '=', company_id), ('date_start', '<', now),
                  ('date_stop', '>', now)]
        fiscalyears = self.env['account.fiscalyear'].search(domain, limit=1)
        self.fiscalyear_id = fiscalyears
        if self.type == 'sale':
            domain = [('type', 'in', ['sale', 'sale_refund'])]
        elif self.type == 'purchase':
            domain = [('type', 'in', ['purchase', 'purchase_refund'])]
        domain += [('use_documents', '=', True), '|',
                   ('company_id', '=', self.company_id.id),
                   ('company_id', 'child_of', self.company_id.id)]
        journals = self.env['account.journal'].search(domain)
        self.journal_ids = journals

    @api.onchange('fiscalyear_id')
    def change_fiscalyear(self):
        vat_ledgers = self.search(
            [('company_id', '=', self.company_id.id),
             ('fiscalyear_id', '=', self.fiscalyear_id.id),
             ('type', '=', self.type)],
            order='period_id desc',
            limit=1)
        if vat_ledgers:
            next_period = self.env['account.period'].with_context(
                company_id=self.company_id.id).next(vat_ledgers.period_id, 1)
        else:
            next_period = self.env['account.period'].search(
                [('company_id', '=', self.company_id.id),
                 ('fiscalyear_id', '=', self.fiscalyear_id.id)],
                limit=1)
        self.period_id = next_period
        self.first_page = self.last_page

    @api.multi
    def action_present(self):
        self.state = 'presented'

    @api.multi
    def action_cancel(self):
        self.state = 'cancel'

    @api.multi
    def action_to_draft(self):
        self.state = 'draft'

    @api.multi
    def action_print(self):
        assert len(
            self
        ) == 1, 'This option should only be used for a single id at a time.'
        return self.env['report'].get_action(self, 'report_account_vat_ledger')
Ejemplo n.º 8
0
class TestModel(models.Model):
    _name = 'test.model'

    _inherit = ['mail.thread']

    _columns = {}  # deprecated columns
    _defaults = {}  # deprecated defaults
    length = fields.Integer()  # Deprecated length by js errors

    name = fields.Char(
        _(u"Näme"),  # Don't need translate
        help=u"My hëlp",
        required=False,
        compute='_compute_name',  # good compute method name
        search='_search_name',  # good search method name
        inverse='_inverse_name',  # good inverse method name
    )

    # Imported openerp.fields use Char (Upper case)
    other_field = fields.char(
        name=_("Other field"),
        copy=True,
        compute='my_method_compute',  # bad compute method name
        search='my_method_search',  # bad search method name
        inverse='my_method_inverse',  # bad inverse method name
    )
    compute_none = fields.Char(compute=None)

    other_field2 = fields.char(
        'Other Field2',
        copy=True,
    )
    field_related = fields.Char('Field Related',
                                related='model_id.related_field')
    other_field_related = fields.Char(related='model_id.related_field',
                                      string='Other Field Related')

    # This is a inherit overwrite field then don't should show errors related
    # with creation of fields.
    def method_date(self):
        date = fields.Date.to_string(
            fields.Datetime.context_timestamp(self,
                                              timestamp=fields.Datetime.now()))
        self.with_context({'overwrite_context': True}).write({})
        ctx = {'overwrite_context': True}
        self.with_context(ctx).write({})
        ctx2 = ctx
        self.with_context(ctx2).write({})

        self.with_context(**ctx).write({})
        self.with_context(overwrite_context=False).write({})
        return date

    my_ok_field = fields.Float(
        "My correctly named field",
        digits=(6, 6),  # OK: Valid field parameter
        index=True,  # OK: Valid field parameter
        help="My ok field",
    )

    my_ko_field = fields.Float(
        digits_compute=lambda cr: (6, 6),  # Deprecated field parameter
        select=True,  # Deprecated field parameter
        help="My ko field",
        string="My Ko Field",  # String parameter equal to name of variable
    )
    """The name of the variable is equal to the string parameter
    Tested all fields.*"""

    boolean_variable_1 = fields.Boolean(string='Boolean Variable 1',
                                        help="Help")
    boolean_variable_2 = fields.Boolean("Boolean Variable 2", help="Help")

    char_variable_1 = fields.Char(string='Char Variable 1', help="Help")
    char_variable_2 = fields.Char("Char Variable 2", help="Help")

    text_variable_1 = fields.Text(string='Text Variable 1', help="Help")
    text_variable_2 = fields.Text("Text Variable 2", help="Help")

    html_variable_1 = fields.Html(string='Html Variable 1', help="Help")
    html_variable_2 = fields.Html("Html Variable 2", help="Help")

    integer_variable_1 = fields.Integer(string='Integer Variable 1',
                                        help="Help")
    integer_variable_2 = fields.Integer("Integer Variable 2", help="Help")

    float_variable_1 = fields.Float(string='Float Variable 1', help="Help")
    float_variable_2 = fields.Float("Float Variable 2", help="Help")

    date_variable_1 = fields.Date(string='Date Variable 1', help="Help")
    date_variable_2 = fields.Date("Date Variable 2", help="Help")

    date_time_variable_1 = fields.DateTime(string='Date Time Variable 1',
                                           help="Help")
    date_time_variable_2 = fields.DateTime("Date Time Variable 2", help="Help")

    binary_variable_1 = fields.Binary(string='Binary Variable 1', help="Help")
    binary_variable_2 = fields.Binary("Binary Variable 2", help="Help")

    selection_variable_1 = fields.Selection(selection=[('a', 'A')],
                                            string='Selection Variable 1',
                                            help="Help")
    selection_variable_2 = fields.Selection([('a', 'A')],
                                            "Selection Variable 2",
                                            help="Help")

    reference_variable_1 = fields.Reference(selection=[('res.user', 'User')],
                                            string="Reference Variable 1",
                                            help="Help")
    reference_variable_2 = fields.Reference([('res.user', 'User')],
                                            "Reference Variable 2",
                                            help="Help")

    many_2_one_variable_1 = fields.Many2one(comodel_name='res.users',
                                            string='Many 2 One Variable 1',
                                            help="Help")
    many_2_one_variable_2 = fields.Many2one('res.users',
                                            "Many 2 One Variable 2",
                                            help="Help")

    one_2_many_variable_1 = fields.One2many(comodel_name='res.users',
                                            inverse_name='rel_id',
                                            string='One 2 Many Variable 1',
                                            help="Help")
    one_2_many_variable_2 = fields.One2many('res.users',
                                            'rel_id',
                                            "One 2 Many Variable 2",
                                            help="Help")

    many_2_many_variable_1 = fields.Many2many(comodel_name='res.users',
                                              relation='table_name',
                                              column1='col_name',
                                              column2='other_col_name',
                                              string='Many 2 Many Variable 1',
                                              help="Help")
    many_2_many_variable_2 = fields.Many2many('res.users',
                                              'table_name',
                                              'col_name',
                                              'other_col_name',
                                              "Many 2 Many Variable 2",
                                              help="Help")

    field_case_sensitive = fields.Char('Field Case SENSITIVE',
                                       help="Field case sensitive")

    name_equal_to_string = fields.Float("Name equal to string",
                                        help="Name Equal To String")

    many_2_one = fields.Many2one('res.users', "Many 2 One", help="Many 2 one")

    many_2_many = fields.Many2many('res.users',
                                   'relation',
                                   'fk_column_from',
                                   'fk_column_to',
                                   "Many 2 many",
                                   help="Many 2 Many")

    def my_method1(self, variable1):
        #  Shouldn't show error of field-argument-translate
        self.my_method2(_('hello world'))

        # Message post without translation function
        self.message_post(subject='Subject not translatable',
                          body='Body not translatable %s' % variable1)
        self.message_post(subject='Subject not translatable %(variable)s' %
                          {'variable': variable1},
                          body='Body not translatable {}'.format(variable1),
                          message_type='notification')
        self.message_post('Body not translatable',
                          'Subject not translatable {a}'.format(a=variable1))
        self.message_post(
            'Body not translatable %s' % variable1,
            'Subject not translatable %(variable)s' % {'variable': variable1})
        self.message_post('Body not translatable',
                          subject='Subject not translatable')
        self.message_post(
            body='<h1>%s</h1><p>%s</p>' %
            (_('Paragraph translatable'), 'Paragraph not translatable'))

        # Message post with translation function
        self.message_post(subject=_('Subject translatable'),
                          body=_('Body translatable'))
        self.message_post(_('Body translatable'), _('Subject translatable'))
        self.message_post(_('Body translatable'),
                          subject=_('Subject translatable'))
        self.message_post(_('A CDR has been recovered for %s') % (variable1, ))
        self.message_post(_('A CDR has been recovered for %s') % variable1)
        self.message_post(_('Var {a}').format(a=variable1))
        self.message_post(_('Var %(variable)s') % {'variable': variable1})
        self.message_post(subject=_('Subject translatable'),
                          body=_('Body translatable %s') % variable1)
        self.message_post(subject=_('Subject translatable %(variable)s') %
                          {'variable': variable1},
                          message_type='notification')
        self.message_post(_('Body translatable'),
                          _('Subject translatable {a}').format(a=variable1))
        self.message_post(
            _('Body translatable %s') % variable1,
            _('Subject translatable %(variable)s') % {'variable': variable1})
        self.message_post('<p>%s</p>' % _('Body translatable'))
        self.message_post(body='<p>%s</p>' % _('Body translatable'))

        # There is no way to know if the variable is translated, then ignoring
        self.message_post(variable1)
        self.message_post(body=variable1 + variable1)
        self.message_post(body=(variable1 + variable1))
        self.message_post(body=variable1 % variable1)
        self.message_post(body=(variable1 % variable1))

        # translation function with variables in the term
        variable2 = variable1
        self.message_post(_('Variable not translatable: %s' % variable1))
        self.message_post(
            _('Variables not translatable: %s, %s' % (variable1, variable2)))
        self.message_post(body=_('Variable not translatable: %s' % variable1))
        self.message_post(body=_('Variables not translatable: %s %s' %
                                 (variable1, variable2)))
        error_msg = _('Variable not translatable: %s' % variable1)
        error_msg = _('Variables not translatable: %s, %s' %
                      (variable1, variable2))
        error_msg = _('Variable not translatable: {}'.format(variable1))
        error_msg = _('Variables not translatable: {}, {variable2}'.format(
            variable1, variable2=variable2))

        # string with parameters without name
        # so you can't change the order in the translation
        _('%s %d') % ('hello', 3)
        _('%s %s') % ('hello', 'world')

        # Valid cases
        _('%(strname)s') % {'strname': 'hello'}
        _('%(strname)s %(intname)d') % {'strname': 'hello', 'intname': 3}
        _('%s') % 'hello'
        _('%d') % 3
        return error_msg

    def my_method2(self, variable2):
        return variable2

    def my_method3(self, cr):
        cr.commit()  # Dangerous use of commit old api
        self.env.cr.commit()  # Dangerous use of commit
        self._cr.commit()  # Dangerous use of commit
        self.cr.commit()  # Dangerous use of commit
        return cr

    def my_method4(self, variable2):
        self.env.cr2.commit()  # This should not be detected
        return variable2

    def my_method5(self, variable2):
        self.env.cr.commit2()  # This should not be detected
        return variable2

    def my_method6(self):
        user_id = 1
        if user_id != 99:
            # Method without translation
            raise UserError('String without translation')

    def my_method7(self):
        user_id = 1
        if user_id != 99:
            # Method with translation
            raise UserError(_('String with translation'))

    def my_method8(self):
        user_id = 1
        if user_id != 99:
            str_error = 'String with translation 2'  # Don't check
            raise UserError(str_error)

    def my_method9(self):
        user_id = 1
        if user_id != 99:
            # Method without translation
            raise UserError("String without translation 2")

    def my_method10(self):
        # A example of built-in raise without parameters
        # Shouldn't show error from lint
        raise ZeroDivisionError
        raise ZeroDivisionError()

    def my_method11(self):
        # A example of built-in raise with parameters
        # Shouldn't show error from lint
        raise ZeroDivisionError("String without translation")
        # raise without class-exception to increase coverage
        raise
        raise "obsolete case"

    def my_method12(self):
        # Should show error
        raise exceptions.Warning(
            'String with params format {p1}'.format(p1='v1'))
        raise exceptions.Warning('qp2w String with params format %(p1)s' %
                                 {'p1': 'v1'})

    def my_method13(self):
        # Shouldn't show error
        raise exceptions.Warning(
            _('String with params format {p1}').format(p1='v1'))
        raise exceptions.Warning(
            _('String with params format {p1}'.format(p1='v1')))
        raise exceptions.Warning(
            _('String with params format %(p1)s') % {'p1': 'v1'})
        raise exceptions.Warning(
            _('String with params format %(p1)s' % {'p1': 'v1'}))

    def old_api_method_alias(self, cursor, user, ids, context=None):  # old api
        pass

    def sql_method(self, ids, cr):
        # Use of query parameters: nothing wrong here
        self._cr.execute('SELECT name FROM account WHERE id IN %s',
                         (tuple(ids), ))
        self.env.cr.execute('SELECT name FROM account WHERE id IN %s',
                            (tuple(ids), ))
        cr.execute('SELECT name FROM account WHERE id IN %s', (tuple(ids), ))
        self.cr.execute('SELECT name FROM account WHERE id IN %s',
                        (tuple(ids), ))

    def sql_injection_ignored_cases(self, ids, cr2):
        # This cr.execute2 or cr2.execute should not be detected
        self._cr.execute2('SELECT name FROM account WHERE id IN %s' %
                          (tuple(ids), ))
        cr2.execute('SELECT name FROM account WHERE id IN %s' % (tuple(ids), ))

        # Ignore when the query is built using private attributes
        self._cr.execute('DELETE FROM %s WHERE id IN %%s' % self._table,
                         (tuple(ids), ))

        # Ignore string parsed with "".format() if args are psycopg2.sql.* calls
        query = "SELECT * FROM table"
        # imported from pyscopg2 import sql
        self._cr.execute(
            sql.SQL("""CREATE or REPLACE VIEW {} as ({})""").format(
                sql.Identifier(self._table), sql.SQL(query)))
        self._cr.execute(
            sql.SQL("""CREATE or REPLACE VIEW {table} as ({query})""").format(
                table=sql.Identifier(self._table),
                query=sql.SQL(query),
            ))
        # imported from pyscopg2.sql import SQL, Identifier
        self._cr.execute(
            SQL("""CREATE or REPLACE VIEW {} as ({})""").format(
                Identifier(self._table),
                SQL(query),
            ))
        self._cr.execute(
            SQL("""CREATE or REPLACE VIEW {table} as ({query})""").format(
                table=Identifier(self._table),
                query=SQL(query),
            ))
        # imported from pyscopg2 direclty
        self._cr.execute(
            psycopg2.SQL("""CREATE or REPLACE VIEW {} as ({})""").format(
                psycopg2.sql.Identifier(self._table),
                psycopg2.sql.SQL(query),
            ))
        self._cr.execute(
            psycopg2.sql.SQL(
                """CREATE or REPLACE VIEW {table} as ({query})""").format(
                    table=Identifier(self._table),
                    query=SQL(query),
                ))
        # Variables build using pyscopg2.sql.* callers
        table = Identifier('table_name')
        sql_query = SQL(query)
        # format params
        self._cr.execute(
            SQL("""CREATE or REPLACE VIEW {} as ({})""").format(
                table,
                sql_query,
            ))
        # format dict
        self._cr.execute(
            SQL("""CREATE or REPLACE VIEW {table} as ({query})""").format(
                table=table,
                query=sql_query,
            ))

        self._cr.execute('SELECT name FROM %(table)s' % {'table': self._table})

    # old api
    def sql_injection_modulo_operator(self, cr, uid, ids, context=None):
        # Use of % operator: risky
        self._cr.execute('SELECT name FROM account WHERE id IN %s' %
                         (tuple(ids), ))
        self.env.cr.execute('SELECT name FROM account WHERE id IN %s' %
                            (tuple(ids), ))
        cr.execute('SELECT name FROM account WHERE id IN %s' % (tuple(ids), ))
        self.cr.execute('SELECT name FROM account WHERE id IN %s' %
                        (tuple(ids), ))

        operator = 'WHERE'
        # Ignore sql-injection because of there is a parameter e.g. "ids"
        self._cr.execute('SELECT name FROM account %s id IN %%s' % operator,
                         ids)

        var = 'SELECT name FROM account WHERE id IN %s'
        values = ([
            1,
            2,
            3,
        ], )
        self._cr.execute(var % values)

        self._cr.execute('SELECT name FROM account WHERE id IN %(ids)s' %
                         {'ids': ids})

    def sql_injection_executemany(self, ids, cr, v1, v2):
        # Check executemany() as well
        self.cr.executemany('INSERT INTO account VALUES (%s, %s)' % (v1, v2))

    def sql_injection_format(self, ids, cr):
        # Use of .format(): risky
        self.cr.execute('SELECT name FROM account WHERE id IN {}'.format(ids))

        var = 'SELECT name FROM account WHERE id IN {}'
        values = (1, 2, 3)
        self._cr.execute(var.format(values))

        self.cr.execute(
            'SELECT name FROM account WHERE id IN {ids}'.format(ids=ids))

    def sql_injection_plus_operator(self, ids, cr):
        # Use of +: risky
        self.cr.execute('SELECT name FROM account WHERE id IN ' +
                        str(tuple(ids)))

        operator = 'WHERE'
        # Ignore sql-injection because of there is a parameter e.g. "ids"
        self._cr.execute('SELECT name FROM account ' + operator + ' id IN %s',
                         ids)
        self.cr.execute(
            ('SELECT name FROM account ' + operator + ' id IN (1)'))
        self.cr.execute('SELECT name FROM account ' + operator + ' id IN %s' %
                        (tuple(ids), ))
        self.cr.execute(
            ('SELECT name FROM account ' + operator + ' id IN %s') %
            (tuple(ids), ))

    def sql_injection_before(self, ids):
        # query built before execute: risky as well

        var = 'SELECT name FROM account WHERE id IN %s' % tuple(ids)
        self._cr.execute(var)

        var[1] = 'SELECT name FROM account WHERE id IN %s' % tuple(ids)
        self._cr.execute(var[1])

        var = 'SELECT name FROM account WHERE id IN %(ids)s' % {
            'ids': tuple(ids)
        }
        self._cr.execute(var)

        var[1] = 'SELECT name FROM account WHERE id IN %(ids)s' % {
            'ids': tuple(ids)
        }
        self._cr.execute(var[1])

    def sql_no_injection_private_attributes(self, _variable, variable):
        # Skip sql-injection using private attributes
        self._cr.execute("CREATE VIEW %s AS (SELECT * FROM res_partner)" %
                         self._table)
        # Real sql-injection cases
        self._cr.execute("CREATE VIEW %s AS (SELECT * FROM res_partner)" %
                         self.table)
        self._cr.execute("CREATE VIEW %s AS (SELECT * FROM res_partner)" %
                         _variable)
        self._cr.execute("CREATE VIEW %s AS (SELECT * FROM res_partner)" %
                         variable)

    def sql_no_injection_private_methods(self):
        # Skip sql-injection using private methods
        self.env.cr.execute("""
            CREATE OR REPLACE VIEW %s AS (
                %s %s %s %s
            )
        """ % (
            self._table,
            self._select(),
            self._from(),
            self._where(),
            self._group_by(),
        ))

    def func(self, a):
        length = len(a)
        return length
Ejemplo n.º 9
0
class Post(models.Model):
    _name = 'forum.post'
    _description = 'Forum Post'
    _inherit = ['mail.thread', 'website.seo.metadata']
    _order = "is_correct DESC, vote_count DESC, write_date DESC"

    name = fields.Char('Title')
    forum_id = fields.Many2one('forum.forum', string='Forum', required=True)
    content = fields.Html('Content')
    content_link = fields.Char('URL', help="URL of Link Articles")
    tag_ids = fields.Many2many('forum.tag',
                               'forum_tag_rel',
                               'forum_id',
                               'forum_tag_id',
                               string='Tags')
    state = fields.Selection([('active', 'Active'), ('close', 'Close'),
                              ('offensive', 'Offensive')],
                             string='Status',
                             default='active')
    views = fields.Integer('Number of Views', default=0)
    active = fields.Boolean('Active', default=True)
    post_type = fields.Selection([('question', 'Question'),
                                  ('link', 'Article'),
                                  ('discussion', 'Discussion')],
                                 string='Type',
                                 default='question')
    website_message_ids = fields.One2many(
        'mail.message',
        'res_id',
        domain=lambda self: [
            '&', ('model', '=', self._name),
            ('type', 'in', ['email', 'comment'])
        ],
        string='Post Messages',
        help="Comments on forum post",
    )
    # history
    create_date = fields.Datetime('Asked on', select=True, readonly=True)
    create_uid = fields.Many2one('res.users',
                                 string='Created by',
                                 select=True,
                                 readonly=True)
    write_date = fields.Datetime('Update on', select=True, readonly=True)
    write_uid = fields.Many2one('res.users',
                                string='Updated by',
                                select=True,
                                readonly=True)
    relevancy = fields.Float('Relevancy',
                             compute="_compute_relevancy",
                             store=True)

    @api.one
    @api.depends('vote_count', 'forum_id.relevancy_post_vote',
                 'forum_id.relevancy_time_decay')
    def _compute_relevancy(self):
        days = (datetime.today() - datetime.strptime(
            self.create_date, tools.DEFAULT_SERVER_DATETIME_FORMAT)).days
        self.relevancy = math.copysign(1, self.vote_count) * (
            abs(self.vote_count - 1)**self.forum_id.relevancy_post_vote /
            (days + 2)**self.forum_id.relevancy_time_decay)

    # vote
    vote_ids = fields.One2many('forum.post.vote', 'post_id', string='Votes')
    user_vote = fields.Integer('My Vote', compute='_get_user_vote')
    vote_count = fields.Integer('Votes', compute='_get_vote_count', store=True)

    @api.multi
    def _get_user_vote(self):
        votes = self.env['forum.post.vote'].search_read(
            [('post_id', 'in', self._ids),
             ('user_id', '=', self._uid)], ['vote', 'post_id'])
        mapped_vote = dict([(v['post_id'][0], v['vote']) for v in votes])
        for vote in self:
            vote.user_vote = mapped_vote.get(vote.id, 0)

    @api.multi
    @api.depends('vote_ids')
    def _get_vote_count(self):
        read_group_res = self.env['forum.post.vote'].read_group(
            [('post_id', 'in', self._ids)], ['post_id', 'vote'],
            ['post_id', 'vote'],
            lazy=False)
        result = dict.fromkeys(self._ids, 0)
        for data in read_group_res:
            result[data['post_id'][0]] += data['__count'] * int(data['vote'])
        for post in self:
            post.vote_count = result[post.id]

    # favorite
    favourite_ids = fields.Many2many('res.users', string='Favourite')
    user_favourite = fields.Boolean('Is Favourite',
                                    compute='_get_user_favourite')
    favourite_count = fields.Integer('Favorite Count',
                                     compute='_get_favorite_count',
                                     store=True)

    @api.one
    def _get_user_favourite(self):
        self.user_favourite = self._uid in self.favourite_ids.ids

    @api.one
    @api.depends('favourite_ids')
    def _get_favorite_count(self):
        self.favourite_count = len(self.favourite_ids)

    # hierarchy
    is_correct = fields.Boolean('Correct',
                                help='Correct answer or answer accepted')
    parent_id = fields.Many2one('forum.post',
                                string='Question',
                                ondelete='cascade')
    self_reply = fields.Boolean('Reply to own question',
                                compute='_is_self_reply',
                                store=True)
    child_ids = fields.One2many('forum.post', 'parent_id', string='Answers')
    child_count = fields.Integer('Number of answers',
                                 compute='_get_child_count',
                                 store=True)
    uid_has_answered = fields.Boolean('Has Answered',
                                      compute='_get_uid_has_answered')
    has_validated_answer = fields.Boolean('Is answered',
                                          compute='_get_has_validated_answer',
                                          store=True)

    @api.one
    @api.depends('create_uid', 'parent_id')
    def _is_self_reply(self):
        self.self_reply = self.parent_id.create_uid.id == self._uid

    @api.one
    @api.depends('child_ids.create_uid', 'website_message_ids')
    def _get_child_count(self):
        def process(node):
            total = len(node.website_message_ids) + len(node.child_ids)
            for child in node.child_ids:
                total += process(child)
            return total

        self.child_count = process(self)

    @api.one
    def _get_uid_has_answered(self):
        self.uid_has_answered = any(answer.create_uid.id == self._uid
                                    for answer in self.child_ids)

    @api.one
    @api.depends('child_ids.is_correct')
    def _get_has_validated_answer(self):
        self.has_validated_answer = any(answer.is_correct
                                        for answer in self.child_ids)

    # closing
    closed_reason_id = fields.Many2one('forum.post.reason', string='Reason')
    closed_uid = fields.Many2one('res.users', string='Closed by', select=1)
    closed_date = fields.Datetime('Closed on', readonly=True)
    # karma calculation and access
    karma_accept = fields.Integer('Convert comment to answer',
                                  compute='_get_post_karma_rights')
    karma_edit = fields.Integer('Karma to edit',
                                compute='_get_post_karma_rights')
    karma_close = fields.Integer('Karma to close',
                                 compute='_get_post_karma_rights')
    karma_unlink = fields.Integer('Karma to unlink',
                                  compute='_get_post_karma_rights')
    karma_comment = fields.Integer('Karma to comment',
                                   compute='_get_post_karma_rights')
    karma_comment_convert = fields.Integer(
        'Karma to convert comment to answer', compute='_get_post_karma_rights')
    can_ask = fields.Boolean('Can Ask', compute='_get_post_karma_rights')
    can_answer = fields.Boolean('Can Answer', compute='_get_post_karma_rights')
    can_accept = fields.Boolean('Can Accept', compute='_get_post_karma_rights')
    can_edit = fields.Boolean('Can Edit', compute='_get_post_karma_rights')
    can_close = fields.Boolean('Can Close', compute='_get_post_karma_rights')
    can_unlink = fields.Boolean('Can Unlink', compute='_get_post_karma_rights')
    can_upvote = fields.Boolean('Can Upvote', compute='_get_post_karma_rights')
    can_downvote = fields.Boolean('Can Downvote',
                                  compute='_get_post_karma_rights')
    can_comment = fields.Boolean('Can Comment',
                                 compute='_get_post_karma_rights')
    can_comment_convert = fields.Boolean('Can Convert to Comment',
                                         compute='_get_post_karma_rights')

    @api.one
    def _get_post_karma_rights(self):
        user = self.env.user

        self.karma_accept = self.parent_id and self.parent_id.create_uid.id == self._uid and self.forum_id.karma_answer_accept_own or self.forum_id.karma_answer_accept_all
        self.karma_edit = self.create_uid.id == self._uid and self.forum_id.karma_edit_own or self.forum_id.karma_edit_all
        self.karma_close = self.create_uid.id == self._uid and self.forum_id.karma_close_own or self.forum_id.karma_close_all
        self.karma_unlink = self.create_uid.id == self._uid and self.forum_id.karma_unlink_own or self.forum_id.karma_unlink_all
        self.karma_comment = self.create_uid.id == self._uid and self.forum_id.karma_comment_own or self.forum_id.karma_comment_all
        self.karma_comment_convert = self.create_uid.id == self._uid and self.forum_id.karma_comment_convert_own or self.forum_id.karma_comment_convert_all

        self.can_ask = user.karma >= self.forum_id.karma_ask
        self.can_answer = user.karma >= self.forum_id.karma_answer
        self.can_accept = user.karma >= self.karma_accept
        self.can_edit = user.karma >= self.karma_edit
        self.can_close = user.karma >= self.karma_close
        self.can_unlink = user.karma >= self.karma_unlink
        self.can_upvote = user.karma >= self.forum_id.karma_upvote
        self.can_downvote = user.karma >= self.forum_id.karma_downvote
        self.can_comment = user.karma >= self.karma_comment
        self.can_comment_convert = user.karma >= self.karma_comment_convert

    @api.model
    def create(self, vals):
        post = super(Post,
                     self.with_context(mail_create_nolog=True)).create(vals)
        # deleted or closed questions
        if post.parent_id and (post.parent_id.state == 'close'
                               or post.parent_id.active is False):
            raise Warning(
                _('Posting answer on a [Deleted] or [Closed] question is not possible'
                  ))
        # karma-based access
        if not post.parent_id and not post.can_ask:
            raise KarmaError('Not enough karma to create a new question')
        elif post.parent_id and not post.can_answer:
            raise KarmaError('Not enough karma to answer to a question')
        # messaging and chatter
        base_url = self.env['ir.config_parameter'].get_param('web.base.url')
        if post.parent_id:
            body = _(
                '<p>A new answer for <i>%s</i> has been posted. <a href="%s/forum/%s/question/%s">Click here to access the post.</a></p>'
                % (post.parent_id.name, base_url, slug(
                    post.parent_id.forum_id), slug(post.parent_id)))
            post.parent_id.message_post(subject=_('Re: %s') %
                                        post.parent_id.name,
                                        body=body,
                                        subtype='website_forum.mt_answer_new')
        else:
            body = _(
                '<p>A new question <i>%s</i> has been asked on %s. <a href="%s/forum/%s/question/%s">Click here to access the question.</a></p>'
                % (post.name, post.forum_id.name, base_url, slug(
                    post.forum_id), slug(post)))
            post.message_post(subject=post.name,
                              body=body,
                              subtype='website_forum.mt_question_new')
            self.env.user.sudo().add_karma(
                post.forum_id.karma_gen_question_new)
        return post

    @api.multi
    def write(self, vals):
        if 'state' in vals:
            if vals['state'] in ['active', 'close'] and any(not post.can_close
                                                            for post in self):
                raise KarmaError('Not enough karma to close or reopen a post.')
        if 'active' in vals:
            if any(not post.can_unlink for post in self):
                raise KarmaError(
                    'Not enough karma to delete or reactivate a post')
        if 'is_correct' in vals:
            if any(not post.can_accept for post in self):
                raise KarmaError(
                    'Not enough karma to accept or refuse an answer')
            # update karma except for self-acceptance
            mult = 1 if vals['is_correct'] else -1
            for post in self:
                if vals['is_correct'] != post.is_correct and post.create_uid.id != self._uid:
                    post.create_uid.sudo().add_karma(
                        post.forum_id.karma_gen_answer_accepted * mult)
                    self.env.user.sudo().add_karma(
                        post.forum_id.karma_gen_answer_accept * mult)
        if any(key not in [
                'state', 'active', 'is_correct', 'closed_uid', 'closed_date',
                'closed_reason_id'
        ] for key in vals.keys()) and any(not post.can_edit for post in self):
            raise KarmaError('Not enough karma to edit a post.')

        res = super(Post, self).write(vals)
        # if post content modify, notify followers
        if 'content' in vals or 'name' in vals:
            for post in self:
                if post.parent_id:
                    body, subtype = _(
                        'Answer Edited'), 'website_forum.mt_answer_edit'
                    obj_id = post.parent_id
                else:
                    body, subtype = _(
                        'Question Edited'), 'website_forum.mt_question_edit'
                    obj_id = post
                obj_id.message_post(body=body, subtype=subtype)
        return res

    @api.multi
    def reopen(self):
        if any(post.parent_id or post.state != 'close' for post in self):
            return False

        reason_offensive = self.env.ref('website_forum.reason_7')
        reason_spam = self.env.ref('website_forum.reason_8')
        for post in self:
            if post.closed_reason_id in (reason_offensive, reason_spam):
                _logger.info(
                    'Upvoting user <%s>, reopening spam/offensive question',
                    post.create_uid)
                # TODO: in master, consider making this a tunable karma parameter
                post.create_uid.sudo().add_karma(
                    post.forum_id.karma_gen_question_downvote * -5)

        self.sudo().write({'state': 'active'})

    @api.multi
    def close(self, reason_id):
        if any(post.parent_id for post in self):
            return False

        reason_offensive = self.env.ref('website_forum.reason_7').id
        reason_spam = self.env.ref('website_forum.reason_8').id
        if reason_id in (reason_offensive, reason_spam):
            for post in self:
                _logger.info(
                    'Downvoting user <%s> for posting spam/offensive contents',
                    post.create_uid)
                # TODO: in master, consider making this a tunable karma parameter
                post.create_uid.sudo().add_karma(
                    post.forum_id.karma_gen_question_downvote * 5)

        self.write({
            'state':
            'close',
            'closed_uid':
            self._uid,
            'closed_date':
            datetime.today().strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT),
            'closed_reason_id':
            reason_id,
        })
        return True

    @api.multi
    def unlink(self):
        if any(not post.can_unlink for post in self):
            raise KarmaError('Not enough karma to unlink a post')
        # if unlinking an answer with accepted answer: remove provided karma
        for post in self:
            if post.is_correct:
                post.create_uid.sudo().add_karma(
                    post.forum_id.karma_gen_answer_accepted * -1)
                self.env.user.sudo().add_karma(
                    post.forum_id.karma_gen_answer_accepted * -1)
        return super(Post, self).unlink()

    @api.multi
    def vote(self, upvote=True):
        Vote = self.env['forum.post.vote']
        vote_ids = Vote.search([('post_id', 'in', self._ids),
                                ('user_id', '=', self._uid)])
        new_vote = '1' if upvote else '-1'
        voted_forum_ids = set()
        if vote_ids:
            for vote in vote_ids:
                if upvote:
                    new_vote = '0' if vote.vote == '-1' else '1'
                else:
                    new_vote = '0' if vote.vote == '1' else '-1'
                vote.vote = new_vote
                voted_forum_ids.add(vote.post_id.id)
        for post_id in set(self._ids) - voted_forum_ids:
            for post_id in self._ids:
                Vote.create({'post_id': post_id, 'vote': new_vote})
        return {'vote_count': self.vote_count, 'user_vote': new_vote}

    @api.one
    def convert_answer_to_comment(self):
        """ Tools to convert an answer (forum.post) to a comment (mail.message).
        The original post is unlinked and a new comment is posted on the question
        using the post create_uid as the comment's author. """
        if not self.parent_id:
            return False

        # karma-based action check: use the post field that computed own/all value
        if not self.can_comment_convert:
            raise KarmaError(
                'Not enough karma to convert an answer to a comment')

        # post the message
        question = self.parent_id
        values = {
            'author_id': self.create_uid.partner_id.id,
            'body': tools.html2plaintext(self.content),
            'type': 'comment',
            'subtype': 'mail.mt_comment',
            'date': self.create_date,
        }
        new_message = self.browse(question.id).with_context(
            mail_create_nosubcribe=True).message_post(**values)

        # unlink the original answer, using SUPERUSER_ID to avoid karma issues
        self.sudo().unlink()

        return new_message

    @api.model
    def convert_comment_to_answer(self, message_id, default=None):
        """ Tool to convert a comment (mail.message) into an answer (forum.post).
        The original comment is unlinked and a new answer from the comment's author
        is created. Nothing is done if the comment's author already answered the
        question. """
        comment = self.env['mail.message'].sudo().browse(message_id)
        post = self.browse(comment.res_id)
        if not comment.author_id or not comment.author_id.user_ids:  # only comment posted by users can be converted
            return False

        # karma-based action check: must check the message's author to know if own / all
        karma_convert = comment.author_id.id == self.env.user.partner_id.id and post.forum_id.karma_comment_convert_own or post.forum_id.karma_comment_convert_all
        can_convert = self.env.user.karma >= karma_convert
        if not can_convert:
            raise KarmaError(
                'Not enough karma to convert a comment to an answer')

        # check the message's author has not already an answer
        question = post.parent_id if post.parent_id else post
        post_create_uid = comment.author_id.user_ids[0]
        if any(answer.create_uid.id == post_create_uid.id
               for answer in question.child_ids):
            return False

        # create the new post
        post_values = {
            'forum_id': question.forum_id.id,
            'content': comment.body,
            'parent_id': question.id,
        }
        # done with the author user to have create_uid correctly set
        new_post = self.sudo(post_create_uid.id).create(post_values)

        # delete comment
        comment.unlink()

        return new_post

    @api.one
    def unlink_comment(self, message_id):
        user = self.env.user
        comment = self.env['mail.message'].sudo().browse(message_id)
        if not comment.model == 'forum.post' or not comment.res_id == self.id:
            return False
        # karma-based action check: must check the message's author to know if own or all
        karma_unlink = comment.author_id.id == user.partner_id.id and self.forum_id.karma_comment_unlink_own or self.forum_id.karma_comment_unlink_all
        can_unlink = user.karma >= karma_unlink
        if not can_unlink:
            raise KarmaError('Not enough karma to unlink a comment')
        return comment.unlink()

    @api.multi
    def set_viewed(self):
        self._cr.execute(
            """UPDATE forum_post SET views = views+1 WHERE id IN %s""",
            (self._ids, ))
        return True

    @api.model
    def _get_access_link(self, mail, partner):
        post = self.browse(mail.res_id)
        res_id = post.parent_id and "%s#answer-%s" % (post.parent_id.id,
                                                      post.id) or post.id
        return "/forum/%s/question/%s" % (post.forum_id.id, res_id)

    @api.cr_uid_ids_context
    def message_post(self,
                     cr,
                     uid,
                     thread_id,
                     type='notification',
                     subtype=None,
                     context=None,
                     **kwargs):
        if thread_id and type == 'comment':  # user comments have a restriction on karma
            if isinstance(thread_id, (list, tuple)):
                post_id = thread_id[0]
            else:
                post_id = thread_id
            post = self.browse(cr, uid, post_id, context=context)
            # TDE FIXME: trigger browse because otherwise the function field is not compted - check with RCO
            tmp1, tmp2 = post.karma_comment, post.can_comment
            user = self.pool['res.users'].browse(cr, uid, uid)
            tmp3 = user.karma
            # TDE END FIXME
            if not post.can_comment:
                raise KarmaError('Not enough karma to comment')
        return super(Post, self).message_post(cr,
                                              uid,
                                              thread_id,
                                              type=type,
                                              subtype=subtype,
                                              context=context,
                                              **kwargs)
Ejemplo n.º 10
0
class res_partner(models.Model):
    _inherit = 'res.partner'
    # START TO DELETE #
    @api.model
    def _purchase_invoicing_trigger_get(self):
        return [
            ('picking', _('To the delivery')),
            ('manual', _('On demand')),
            ('postpaid', _('On the order')),
        ]

    @api.model
    def _purchase_invoiced_on_get(self):
        return [
            ('order', _('Ordered quantities')),
            ('delivery', _('Delivered quantities')),
        ]

    # END TO DELETE #
    @api.model
    def _purchase_communication_method_get(self):
        return [
            ('fax', _('Fax')),
            ('email', _('Email')),
        ]

    @api.model
    def _purchase_invoice_postage_get(self):
        return [
            ('never', _('Never')),
            ('always', _('Always')),
            ('threshold', _('< Threshold')),
        ]

    @api.one
    @api.depends('email', 'fax', 'purchase_contact_ids',
                 'purchase_communication_method')
    def _compute_purchase_communication_value(self):
        self.purchase_communication_value = self.env[
            'res.partner'].calcul_value_com(self.purchase_contact_ids, self,
                                            self.purchase_communication_method)

    @api.one
    def _compute_purchase_type_inv_ids(self):
        partner_type_rcs = self.env['res.partner.purchase.type'].search([
            ('partner_type_id', '=', self.id)
        ])
        partner_rcs = self.env['res.partner']
        if partner_type_rcs:
            for partner_type in partner_type_rcs:
                partner_rcs |= partner_type.partner_id

        self.purchase_type_inv_ids = partner_rcs.ids

    #===========================================================================
    # COLUMNS
    #===========================================================================
    # Onglet achat
    is_seller = fields.Boolean(string='Is seller', default=False)
    invoiced_by = fields.Boolean(string='Invoiced by', default=False)
    delivered_by = fields.Boolean(string='Delivered by', default=False)
    paid_to = fields.Boolean(string='Paid to', default=False)
    purchase_type_ids = fields.One2many('res.partner.purchase.type',
                                        'partner_id',
                                        string='Partners types')
    purchase_type_inv_ids = fields.Many2many(
        'res.partner',
        'purchase_res_partner_type_partner_rel',
        'partner_id',
        'partner_type_inv_id',
        string='Partners types inverse',
        compute='_compute_purchase_type_inv_ids')
    partial_purchase_delivery = fields.Boolean(string='Partial receipt',
                                               default=False)
    generate_purchase_rest = fields.Boolean(string='Generate rest',
                                            default=False)
    purchase_receipt_demand = fields.Boolean(string='Request RD',
                                             default=False)
    supplier_reminder = fields.Boolean(string='Supplier reminder',
                                       default=False)
    reminder_delay = fields.Integer(string='Reminder delay',
                                    default=0,
                                    required=False,
                                    help="In days")
    purchase_contact_ids = fields.Many2many('res.partner',
                                            'purchase_contact_id_partner_rel',
                                            'partner_id',
                                            'contact_id',
                                            string='Contacts')
    #Champ conservé pour la reprise des données, à supprimer plus tard
    purchase_contact_id = fields.Many2one('res.partner',
                                          string='Contact',
                                          required=False,
                                          ondelete='restrict')
    purchase_communication_method = fields.Selection(
        '_purchase_communication_method_get', string='Communication method')
    purchaser_id = fields.Many2one('res.users',
                                   string='Purchaser',
                                   required=False,
                                   ondelete='restrict')
    purchase_stat_family_id = fields.Many2one('partner.stat.family',
                                              string='Statistics family',
                                              required=False,
                                              ondelete='restrict')
    gei_code = fields.Char(string='GEI code', size=16, required=False)
    num_with_supplier = fields.Char(string='Our number with the supplier',
                                    size=128,
                                    required=False)
    note_purchase_header = fields.Html(
        string='Note on the purchase order header')
    purchase_payment_method_id = fields.Many2one('payment.method',
                                                 string='Payment method',
                                                 required=False,
                                                 ondelete='restrict')
    # START TO DELETE #
    purchase_invoicing_trigger = fields.Selection(
        '_purchase_invoicing_trigger_get', string='Invoicing trigger method')
    purchase_invoiced_on = fields.Selection('_purchase_invoiced_on_get',
                                            string='Invoiced on')
    # END TO DELETE #
    purchase_discount_management = fields.Boolean(string='Discount management',
                                                  default=False)
    purchase_max_delay = fields.Integer(string='Maximum delay for application',
                                        default=0,
                                        required=False,
                                        help="In days")
    purchase_discount_value = fields.Float(string='Discount value',
                                           default=0.0,
                                           required=False)
    supplier_account_position_id = fields.Many2one(
        'account.fiscal.position',
        string='Fiscal position',
        required=False,
        ondelete='restrict',
        help="The fiscal position will determine taxes"
        " and accounts used for the partner.")
    purchase_communication_value = fields.Char(
        string='Communication value',
        compute='_compute_purchase_communication_value',
        help='Select '
        'the mode of communication. It will be proposed by default when sending the order to the '
        'supplier or used to boost the supplier who did not confirm the order within 48 hours'
    )
    #Champ transport
    purchase_incoterm_id = fields.Many2one(
        'stock.incoterms',
        string='Incoterm',
        required=False,
        ondelete='restrict',
        help='Incoterm which '
        'stands for \'International Commercial terms\' implies its a series of sales terms which are used '
        'in the commercial transaction.')
    purchase_invoice_postage = fields.Selection(
        '_purchase_invoice_postage_get', string='Invoice postage type')
    purchase_threshold = fields.Float(string='Threshold',
                                      default=0.0,
                                      required=False)
    purchase_forwarding_agent_id = fields.Many2one('res.partner',
                                                   string='Forwarding Agent',
                                                   required=False,
                                                   ondelete='restrict')
    purchase_delivery_delay = fields.Integer(string='Delivery delay',
                                             default=0,
                                             required=False)
    note_receipt_order = fields.Html(string='Note on receipt order')
    purchase_invoicing_method_id = fields.Many2one('account.invoicing.method',
                                                   string='Invoicing method',
                                                   required=False,
                                                   ondelete='restrict')

    _sql_constraints = [(
        'check_purchase_partner_qualified',
        """CHECK((is_supplier=true AND (state='qualified' AND ((invoiced_by=true AND purchase_invoicing_method_id IS NOT NULL) OR (invoiced_by=false)) AND ((delivered_by=true AND supplier_account_position_id IS NOT NULL) OR (delivered_by=false)) AND ((paid_to=true AND property_supplier_payment_term_id IS NOT NULL AND purchase_invoicing_method_id IS NOT NULL AND purchase_payment_method_id IS NOT NULL) OR (paid_to=false))) OR (state!='qualified')) OR (is_supplier=false))""",
        """Some required fields are not filled, please check the form view of the partner:
                 - if the partner is a delivery, you must fill the incoterm and the fiscal position,
                 - if the partner can invoice, you must fill the invoicing method,
                 - if the partner can be payed, you must fill the payment term and the payment method.
          """)]

    @api.one
    @api.constrains('is_seller', 'invoiced_by', 'paid_to', 'delivered_by')
    def _check_purchase_partner_type(self):
        """
            Si le partenaire est un fournisseur qui est une société et qu'il est autorisé à vendre,
            on doit avoir au moins un factureur, un livreur et un payé à.
        """
        if self.is_company and self.is_supplier:
            if self.is_seller:
                invoiced_by = False
                delivered_by = False
                paid_to = False
                if self.invoiced_by:
                    invoiced_by = True

                if self.paid_to:
                    paid_to = True

                if self.delivered_by:
                    delivered_by = True

                for partner in self.purchase_type_ids:
                    if partner.partner_type == 'invoiced_by':
                        invoiced_by = True
                    elif partner.partner_type == 'paid_to':
                        paid_to = True
                    elif partner.partner_type == 'delivered_by':
                        delivered_by = True

                if not invoiced_by or not paid_to or not delivered_by:
                    raise Warning(
                        _('Error ! If the partner can sell, it must be or have a "paid to" partner, '
                          'and an "invoiced by" partner.'))

        return True

    @api.onchange('partial_purchase_delivery')
    def _onchange_partial_purchase_delivery(self):
        """
            Si 'Livraison incomplète' est décochée, on décoche 'Générer un reliquat'
        """
        if not self.partial_purchase_delivery:
            self.generate_purchase_rest = False

#     @api.onchange('purchase_invoiced_on', 'property_supplier_payment_term_id')
#     def _onchange_purchase_invoiced_on(self):
#         """
#             Si on passe la facturation sur les quantités livrées, on sélectionne le mode de déclenchement à la livraison
#         """
#         if self.purchase_invoiced_on == 'delivery':
#             self.purchase_invoicing_trigger = 'picking'
#         elif self.purchase_invoiced_on == 'order' and self.property_supplier_payment_term_id.payment_type != 'after_invoicing' and self.purchase_invoicing_trigger != 'manual':
#             self.purchase_invoicing_trigger = 'postpaid'
#
#
#     @api.onchange('purchase_invoicing_trigger')
#     def _onchange_purchase_invoicing_trigger(self):
#         """
#             Si on passe le mode de déclenchement en manuel ou à la commande, on sélectionne la facturation sur
#             quantités livrées
#         """
#         if self.purchase_invoicing_trigger in ['manual','postpaid']:
#             self.purchase_invoiced_on = 'order'
#         elif self.purchase_invoicing_trigger == 'picking' and self.property_supplier_payment_term_id.payment_type != 'after_invoicing':
#             self.purchase_invoiced_on = 'delivery'

#     @api.one
#     @api.constrains('property_supplier_payment_term_id', 'purchase_invoicing_trigger', 'purchase_invoiced_on')
#     def _check_purchase_invoiced_on(self):
#         """
#             Verifie :
#               -que le mode de déclenchement soit 'A la livraison' si la facturation est sur les quantités livrées
#               -que la facturation est sur les quantités commandées si la condition de paiement est "Avant livraison"
#               -que la méthode de déclenchement est manuelle si la condition de paiement est "Paiement immédiat" ou
#                   si elle a la case "Bloquer la commande avant validation" de cochée
#               -que la méthode de déclenchement est manuelle ou à la commande si la condition de paiement a la case
#                   "Bloquer la commande avant livraison" de cochée
#         """
#         if self.is_company:
#             if self.purchase_invoiced_on == 'delivery' and self.purchase_invoicing_trigger != 'picking':
#                 raise Warning(_('Error ! You cannot have an invoice in the delivery if the purchase trigger is not in'
#                                 ' the picking (purchase tab)'))
#
#             elif self.purchase_invoiced_on == 'order':
#                 if self.property_supplier_payment_term_id.payment_type in ['before_validation', 'before_delivery'] and self.purchase_invoicing_trigger not in ['postpaid', 'manual']:
#                     raise Warning(_('Error ! You cannot have an invoice in the ordered quantities if the invoice trigger is not in the order or manual'))

    @api.one
    @api.constrains('partial_purchase_delivery', 'generate_purchase_rest')
    def _check_partial_purchase_delivery(self):
        """
            Verifie que le booléen de génération de reliquat soit bien décoché si celui
            de livraison incomplète l'es aussi 
        """
        if self.is_company and not self.partial_purchase_delivery and self.generate_purchase_rest:
            raise Warning(
                _('Error ! You cannot generate rest if you don\'t accept partial delivery (in the purchase tab)'
                  ))

        return True

    def get_partner_address(self, infos={}, return_id=False):
        """
            Fonction qui ramène toutes les adresses et les fournisseurs
            pour la livraison, la facturation, la commande et le paiement
            On passe un dictionnaire dans lequel on entre les informations souhaitées. Exemple:
            Si on ne souhaite que l'adresse de livraison, on entrera uniquement 'delivery' dans le dictionnaire
            :type self: res.partner
            :param infos: Dictionnaire contenant les informations souhaitées
            :type infos: dict
            :param return_id: True si on veut des ids, False pour des recordset
            :type return_id: boolean
            :return: Le dictionnaire contenant les informations demandées
            :rtype: dict
        """
        res = super(res_partner, self).get_partner_address(infos=infos,
                                                           return_id=return_id)
        #On vérifie avant tout que le partenaire soit bien un fournisseur
        partner = self
        if partner:
            principal_address = res.get('order_address') or (
                return_id and partner.address_id.id or partner.address_id)
            principal_customer = return_id and partner.id or partner
            if partner.is_company and partner.is_supplier:
                #On commence par récupérer l'adresse principale du partenaire
                #On regarde ensuite si le partenaire peut être livreur. Si c'est le cas,
                #on renvoie son adresse principale et l'id du partner
                if infos.get('purchase_delivery'):
                    if partner.delivered_by:
                        res['purchase_delivery_address'] = principal_address
                        res['purchase_delivery_partner'] = principal_customer
                    #Sinon, on recherche et récupère le partner livreur, ainsi que
                    #son adresse principale.
                    else:
                        for line in partner.purchase_type_ids:
                            if line.partner_type == 'delivered_by':
                                res['purchase_delivery_partner'] = return_id and line.partner_type_id.id or line.partner_type_id
                                res['purchase_delivery_address'] = return_id and line.partner_type_id.address_id.id or line.partner_type_id.address_id
                                break

                #On regarde ensuite si le partenaire peut facturer. Si c'est le cas,
                #on renvoie son adresse principale et l'id du partner
                if infos.get('purchase_invoiced'):
                    if partner.invoiced_by:
                        res['purchase_invoiced_address'] = principal_address
                        res['purchase_invoiced_partner'] = principal_customer
                    #Sinon, on recherche et récupère le partenaire factureur, ainsi que
                    #son adresse principale.
                    else:
                        for line in partner.purchase_type_ids:
                            if line.partner_type == 'invoiced_by':
                                res['purchase_invoiced_partner'] = return_id and line.partner_type_id.id or line.partner_type_id
                                res['purchase_invoiced_address'] = return_id and line.partner_type_id.address_id.id or line.partner_type_id.address_id
                                break

                #On regarde enfin si le partenaire peut être payeé. Si c'est le cas,
                #on renvoie son id. Sinon, on recherche et récupère le partenaire payé
                if infos.get('purchase_paid'):
                    if partner.paid_to:
                        res['purchase_pay_partner'] = principal_customer
                    else:
                        for line in partner.purchase_type_ids:
                            if line.partner_type == 'paid_to':
                                res['purchase_pay_partner'] = return_id and line.partner_type_id.id or line.partner_type_id
                                break

        return res

    @api.multi
    def show_partner_purchase(self):
        """
            Fonction qui cherche et retourne les achats du partenaire
        """
        action_struc = {}
        action_dict = get_form_view(self,
                                    'purchase.action_see_all_purchase_order')
        if action_dict and action_dict.get('id') and action_dict.get('type'):
            action = self.env[action_dict['type']].browse(action_dict['id'])
            action_struc = action.read()
            action_struc[0]['context'] = {'partner_id': self.id}
            action_struc = action_struc[0]

        return action_struc

    def get_purchase_transport_fields(self, return_id=False):
        if self:
            res = {
                'purchase_incoterm_id':
                return_id and self.purchase_incoterm_id.id
                or self.purchase_incoterm_id,
                'purchase_invoice_postage':
                self.purchase_invoice_postage,
                'purchase_threshold':
                self.purchase_threshold,
                'purchase_forwarding_agent_id':
                return_id and self.purchase_forwarding_agent_id.id
                or self.purchase_forwarding_agent_id,
            }
        else:
            res = {
                'purchase_incoterm_id': False,
                'purchase_invoice_postage': False,
                'purchase_threshold': False,
                'purchase_forwarding_agent_id': False,
            }

        return res

    def compute_domain_args_purchase(self, args):
        """
            Fonction appelée par le search afin de n'afficher que les contacts inscrits dans la liste de contact,
            et pour récupérer uniquement les payé à et les facturé par d'un fournisseur
        """
        args2 = []
        for arg in args:
            match = False
            if isinstance(arg, str) or (isinstance(arg, list)
                                        and arg[0] in ('!', '|', '&')):
                args2.append(arg)
                continue

            if arg[0] == 'contact_in_partner_purchase':
                arg[0] = 'id'
                arg[-1] = [x[1] for x in arg[-1] if x[0] != 2]
            elif arg[0] == 'partner_contact_purchase_order':
                arg[0] = 'id'
                arg[1] = 'in'
                partner_id = arg[-1]
                arg[-1] = []
                for contact in self.browse(partner_id).contact_ids:
                    arg[-1].append(contact.id)

            elif arg[0] == 'purchase_order_domain':
                if isinstance(arg[-1], list) and arg[-1][0]:
                    arg = ('id', '=', arg[-1][1])
                else:
                    match = True
                    args2 += [('is_supplier', '=', True)]

            #On affiche les partenaires payeurs qui sont dans la liste du partenaire passé dans le domaine
            elif arg[0] in [
                    'invoiced_supplier_in_order_list',
                    'paid_supplier_in_order_list',
                    'delivered_supplier_in_order_list'
            ]:
                dict_value = {
                    'invoiced_supplier_in_order_list': 'invoiced_by',
                    'paid_supplier_in_order_list': 'paid_to',
                    'delivered_supplier_in_order_list': 'delivered_by'
                }
                search_args = [('partner_type', '=',
                                dict_value.get(arg[0], False)),
                               ('partner_id', '=', arg[-1])]
                arg[0] = 'id'
                arg[1] = 'in'
                arg[-1] = [
                    x.partner_type_id.id for x in
                    self.env['res.partner.purchase.type'].search(search_args)
                    if x.partner_type_id
                ]

            elif arg[0] == 'order_supplier_in_invoiced_list':
                arg[0] = 'id'
                arg[1] = 'in'
                arg[-1] = [
                    x.partner_id.id
                    for x in self.env['res.partner.purchase.type'].search([(
                        'partner_type', '=',
                        'invoiced_by'), ('partner_type_id', '=', arg[-1])])
                    if x.partner_id
                ]

            if not match:
                args2.append(arg)

        return args2

    @api.model
    def search(self, args=None, offset=0, limit=None, order=None, count=None):
        """
            Modification du search afin de n'afficher que les contacts inscrits dans la liste de contact
            et récupérer uniquement les 'payé à' et les 'facturé par' d'un fournisseur
        """
        args = args or []
        args_modified = self.compute_domain_args_purchase(args)
        return super(res_partner, self).search(args=args_modified,
                                               offset=offset,
                                               limit=limit,
                                               order=order,
                                               count=count)

    @api.model
    def read_group(self,
                   domain,
                   fields,
                   groupby,
                   offset=0,
                   limit=None,
                   orderby=False,
                   lazy=True):
        domain = self.compute_domain_args_purchase(domain)
        return super(res_partner, self).read_group(domain=domain,
                                                   fields=fields,
                                                   groupby=groupby,
                                                   offset=offset,
                                                   limit=limit,
                                                   orderby=orderby,
                                                   lazy=lazy)

    def modification_gergonne(self, delivered_vals):
        """
            Fonction pour gergonne, sachant que la position fiscale d'achat est un related de celle de la vente, 
            on remplit le champ position fiscale de la vente par celle dû  fournisseur qui peut livré 
        """
        return delivered_vals

    def purchase_modif_value_partner_type(self, modif_field):
        """"
            Mise à jour des champs dans les partenaires, selon les informations rentrées dans res.partner.type
            :param modif_field: Liste pour savoir quoi modifier: invoiced_by, delivered_by, paid_to, purchase_type_ids
            :type modif_field: char           
        """
        res = super(
            res_partner,
            self).purchase_modif_value_partner_type(modif_field=modif_field)
        if modif_field == 'paid_to' and self.paid_to:
            request = """Select
                            partner_type_val.partner_id
                        From
                            (Select
                        
                                partner_id,
                                id
                            From
                                res_partner_purchase_type
                            Where
                                partner_type_id =  %s and
                                partner_type = 'paid_to') partner_type_val,
                            res_partner
                        Where
                            (res_partner.paid_to is null or res_partner.paid_to = false) and
                            partner_type_val.partner_id = res_partner.id and
                            partner_type_val.id = (Select id 
                                                   From res_partner_purchase_type 
                                                   Where partner_type = 'paid_to' and 
                                                         partner_id = partner_type_val.partner_id 
                                                   order by sequence asc limit 1) """ % (
                self.id)
            self.env.cr.execute(request)
            res_sql = self.env.cr.fetchall()
            if res_sql:
                partner_ids = [x[0] for x in res_sql]
                paid_vals = {}
                for x in self.purchase_paid_fields_partner():
                    if type(self[x]) is not int and type(
                            self[x]
                    ) is not str and type(self[x]) is not float and type(
                            self[x]) is not bool and type(
                                self[x]) is not unicode and self[x] != None:
                        if isinstance(
                            (self._fields[x]), fields.Many2many) or isinstance(
                                (self._fields[x]), fields.One2many):
                            paid_vals[x] = []
                            for i in self[x].ids:
                                paid_vals[x].append((4, i))
                        else:
                            paid_vals[x] = self[x].id
                    else:
                        paid_vals[x] = self[x]

                self.browse(partner_ids).write(paid_vals)

        elif modif_field == 'delivered_by' and self.delivered_by:
            request = """Select
                            partner_type_val.partner_id
                        From
                            (Select
                        
                                partner_id,
                                id
                            From
                                res_partner_purchase_type
                            Where
                                partner_type_id =  %s and
                                partner_type = 'delivered_by') partner_type_val,
                            res_partner
                        Where
                            (res_partner.delivered_by is null or res_partner.delivered_by = false) and
                            partner_type_val.partner_id = res_partner.id and
                            partner_type_val.id = (Select id 
                                                   From res_partner_purchase_type 
                                                   Where partner_type = 'delivered_by' and 
                                                         partner_id = partner_type_val.partner_id 
                                                   order by sequence asc limit 1) """ % (
                self.id)
            self.env.cr.execute(request)
            res_sql = self.env.cr.fetchall()
            if res_sql:
                partner_ids = [x[0] for x in res_sql]
                delivered_vals = {}
                for x in self.purchase_delivered_fields_partner():
                    if type(self[x]) is not int and type(
                            self[x]
                    ) is not str and type(self[x]) is not float and type(
                            self[x]) is not bool and type(
                                self[x]) is not unicode and self[x] != None:
                        if isinstance(
                            (self._fields[x]), fields.Many2many) or isinstance(
                                (self._fields[x]), fields.One2many):
                            delivered_vals[x] = []
                            for i in self[x].ids:
                                delivered_vals[x].append((4, i))
                        else:
                            delivered_vals[x] = self[x].id
                    else:
                        delivered_vals[x] = self[x]

                delivered_vals = self.modification_gergonne(delivered_vals)
                self.browse(partner_ids).write(delivered_vals)

        elif modif_field == 'invoiced_by' and self.invoiced_by:
            request = """Select
                            partner_type_val.partner_id
                        From
                            (Select
                        
                                partner_id,
                                id
                            From
                                res_partner_purchase_type
                            Where
                                partner_type_id =  %s and
                                partner_type = 'invoiced_by') partner_type_val,
                            res_partner
                        Where
                            (res_partner.invoiced_by is null or res_partner.invoiced_by = false) and
                            partner_type_val.partner_id = res_partner.id and
                            partner_type_val.id = (Select id 
                                                   From res_partner_purchase_type 
                                                   Where partner_type = 'invoiced_by' and 
                                                         partner_id = partner_type_val.partner_id 
                                                   order by sequence asc limit 1) """ % (
                self.id)
            self.env.cr.execute(request)
            res_sql = self.env.cr.fetchall()
            if res_sql:
                partner_ids = [x[0] for x in res_sql]
                invoiced_vals = {}
                for x in self.purchase_invoiced_fields_partner():
                    if type(self[x]) is not int and type(
                            self[x]
                    ) is not str and type(self[x]) is not float and type(
                            self[x]) is not bool and type(
                                self[x]) is not unicode and self[x] != None:
                        if isinstance(
                            (self._fields[x]), fields.Many2many) or isinstance(
                                (self._fields[x]), fields.One2many):
                            invoiced_vals[x] = []
                            for i in self[x].ids:
                                invoiced_vals[x].append((4, i))
                        else:
                            invoiced_vals[x] = self[x].id
                    else:
                        invoiced_vals[x] = self[x]

                self.browse(partner_ids).write(invoiced_vals)

        elif modif_field == 'purchase_type_ids':
            partner_type_obj = self.env['res.partner.purchase.type']
            if not self.paid_to:
                partner_type_rcs = partner_type_obj.search(
                    [('partner_type', '=', 'paid_to'),
                     ('partner_id', '=', self.id)],
                    order='sequence asc',
                    limit=1)
                if partner_type_rcs:
                    paid_vals = {}
                    for x in self.purchase_paid_fields_partner():
                        if type(
                                partner_type_rcs.partner_type_id[x]
                        ) is not int and type(
                                partner_type_rcs.partner_type_id[x]
                        ) is not str and type(
                                partner_type_rcs.partner_type_id[x]
                        ) is not float and type(
                                partner_type_rcs.partner_type_id[x]
                        ) is not bool and type(
                                partner_type_rcs.partner_type_id[x]
                        ) is not unicode and partner_type_rcs.partner_type_id[
                                x] != None:
                            if isinstance(
                                (self._fields[x]),
                                    fields.Many2many) or isinstance(
                                        (self._fields[x]), fields.One2many):
                                paid_vals[x] = []
                                for i in partner_type_rcs.partner_type_id[
                                        x].ids:
                                    paid_vals[x].append((4, i))
                            else:
                                paid_vals[
                                    x] = partner_type_rcs.partner_type_id[x].id
                        else:
                            paid_vals[x] = partner_type_rcs.partner_type_id[x]

                    self.write(paid_vals)

            if not self.delivered_by:
                partner_type_rcs = partner_type_obj.search(
                    [('partner_type', '=', 'delivered_by'),
                     ('partner_id', '=', self.id)],
                    order='sequence asc',
                    limit=1)
                if partner_type_rcs:
                    delivered_vals = {}
                    for x in self.purchase_delivered_fields_partner():
                        if type(
                                partner_type_rcs.partner_type_id[x]
                        ) is not int and type(
                                partner_type_rcs.partner_type_id[x]
                        ) is not str and type(
                                partner_type_rcs.partner_type_id[x]
                        ) is not float and type(
                                partner_type_rcs.partner_type_id[x]
                        ) is not bool and type(
                                partner_type_rcs.partner_type_id[x]
                        ) is not unicode and partner_type_rcs.partner_type_id[
                                x] != None:
                            if isinstance(
                                (self._fields[x]),
                                    fields.Many2many) or isinstance(
                                        (self._fields[x]), fields.One2many):
                                delivered_vals[x] = []
                                for i in partner_type_rcs.partner_type_id[
                                        x].ids:
                                    delivered_vals[x].append((4, i))
                            else:
                                delivered_vals[
                                    x] = partner_type_rcs.partner_type_id[x].id
                        else:
                            delivered_vals[
                                x] = partner_type_rcs.partner_type_id[x]

                    delivered_vals = partner_type_rcs.partner_type_id.modification_gergonne(
                        delivered_vals)
                    self.write(delivered_vals)

            if not self.invoiced_by:
                partner_type_rcs = partner_type_obj.search(
                    [('partner_type', '=', 'invoiced_by'),
                     ('partner_id', '=', self.id)],
                    order='sequence asc',
                    limit=1)
                if partner_type_rcs:
                    invoiced_vals = {}
                    for x in self.purchase_invoiced_fields_partner():
                        if type(
                                partner_type_rcs.partner_type_id[x]
                        ) is not int and type(
                                partner_type_rcs.partner_type_id[x]
                        ) is not str and type(
                                partner_type_rcs.partner_type_id[x]
                        ) is not float and type(
                                partner_type_rcs.partner_type_id[x]
                        ) is not bool and type(
                                partner_type_rcs.partner_type_id[x]
                        ) is not unicode and partner_type_rcs.partner_type_id[
                                x] != None:
                            if isinstance(
                                (self._fields[x]),
                                    fields.Many2many) or isinstance(
                                        (self._fields[x]), fields.One2many):
                                invoiced_vals[x] = []
                                for i in partner_type_rcs.partner_type_id[
                                        x].ids:
                                    invoiced_vals[x].append((4, i))
                            else:
                                invoiced_vals[
                                    x] = partner_type_rcs.partner_type_id[x].id
                        else:
                            invoiced_vals[
                                x] = partner_type_rcs.partner_type_id[x]

                    self.write(invoiced_vals)

        return res
Ejemplo n.º 11
0
class lubon_qlan_assets(models.Model):
    _name = "lubon_qlan.assets"
    _description = 'zzEquipment'
    _rec_name = "asset_name"
    _inherit = ['mail.thread', 'ir.needaction_mixin']
    _order = 'asset_name'
    parent_id = fields.Many2one('lubon_qlan.assets', string="Part of")

    child_ids = fields.One2many('lubon_qlan.assets', 'parent_id')
    is_container = fields.Boolean(string="Container",
                                  help="Can contain other devices")
    show_in_site = fields.Boolean(string="Show",
                                  help="Show in sites",
                                  default=True)
    quant_id = fields.Many2one('stock.quant')
    product_id = fields.Many2one('product.product')
    site_id = fields.Many2one(
        'lubon_qlan.sites',
        required=True,
        help="Readonly if part of another eqpt or has parts.")
    tenant_id = fields.Many2one('lubon_qlan.tenants', string="Tenant")
    asset_name = fields.Char(required=True, string="Eqpt. name")
    asset_type = fields.Selection([
        ('switch', 'Switch'),
        ('server', 'Physical server'),
        ('firewall', 'Firewall'),
        ('vm', 'Virtual machine'),
        ('vc', 'Vcenter'),
    ])
    asset_remarks = fields.Html(string="Remarks")

    glacier_vault_archive_ids = fields.One2many('aws.glacier_vault_archives',
                                                'asset_id')
    contract_line_id = fields.Many2one(
        'account.analytic.invoice.line',
        domain="[('analytic_account_id','in', validcontract_ids[0][2])]")
    validcontract_ids = fields.Many2many('account.analytic.account',
                                         related='tenant_id.contract_ids')
    lot = fields.Char(string="Serial", help="Serial Number")
    part = fields.Char(string="Part nr", help="Manufacturer part number")
    warranty_end_date = fields.Date(string="End date warranty")
    sequence = fields.Integer()
    notes = fields.Html()
    location = fields.Char(help="Where is the asset located")
    ips = fields.One2many('lubon_qlan.ip', 'asset_id')
    interfaces_ids = fields.One2many('lubon_qlan.interfaces', 'asset_id')
    credentials_ids = fields.One2many('lubon_credentials.credentials',
                                      'asset_id')

    assigned_events_ids = fields.One2many(
        'lubon_qlan.events',
        'related_id',
        domain=lambda self: [('model', '=', self._name)],
        auto_join=True,
        string='Assignedevents',
        help="Events assigned to this asset")
    asset_event_last_check = fields.Datetime(help="Time of the latest event")

    licenses_id = fields.Many2one('lubon_qlan.licenses')

    vm_memory = fields.Char(track_visibility='onchange')
    vm_cpu = fields.Integer(
        track_visibility='onchange',
        string="Virtual CPU",
        help="Number of virtual cpus. (Socket * number of cores per socket")
    vm_guestos = fields.Char(track_visibility='onchange')
    vm_cores_per_socket = fields.Integer(track_visibility='onchange',
                                         string="Cores/Socket",
                                         help='Number of cores per socket')
    vm_sockets = fields.Integer(track_visibility='onchange',
                                string="Sockets",
                                help='Number of sockets')
    vm_uuid_instance = fields.Char(track_visibility='onchange')
    vm_uuid_bios = fields.Char(track_visibility='onchange')
    vm_path_name = fields.Char(track_visibility='onchange')
    vm_power_state = fields.Char(track_visibility='onchange')
    vm_check_backup = fields.Boolean(track_visibility='onchange', default=True)
    vm_restorepoints_ids = fields.One2many('lubon_qlan.restorepoints',
                                           "asset_id")
    vm_latest_restore_point = fields.Datetime()
    vm_glacier_block = fields.Boolean(
        help='Block glacier settings from tenant')
    vm_glacier_cleanup = fields.Boolean(help='Cleanup glacier automatically')
    vm_glacier_month_retention_age = fields.Integer(
        default=180,
        string='Retention days month',
        help="Retention time in days for the monthly backups")
    vm_glacier_month_retention_num = fields.Integer(
        default=6,
        string='Monthly minimum #',
        help="Minimum number of monthly backups to keep")
    vm_glacier_week_retention_age = fields.Integer(
        default=90,
        string='Retention days week',
        help="Retention time in days for the weekly backups")
    vm_glacier_week_retention_num = fields.Integer(
        default=13,
        string='Weekly minimum #',
        help="Minimum number of weekly backups to keep")

    vm_restorepoints_instances_ids = fields.One2many(
        'lubon_qlan.restorepoints_instances', "asset_id")
    vm_snapshots_ids = fields.One2many("lubon_qlan.snapshots", "asset_id")
    vm_drives_ids = fields.One2many("lubon_qlan.drives", "asset_id")
    vm_update_type = fields.Selection([('kaseya', 'Kaseya'), ('mecm', 'MECM'),
                                       ('manual', 'Manual'), ('no', 'No')])

    vm_snapshots_count = fields.Integer()
    vm_date_last = fields.Datetime(help="Date last inventoried")
    vm_backup_req_restorepoints = fields.Integer(
        help="Min restorepoints required", string="Min req restore points")
    vm_backup_req_veeam_replicas = fields.Integer(
        help="Min veeam replicas required", string="Min req veeam replicas")
    vm_backup_req_vsphere_replicas = fields.Integer(
        help="Min vsphere replicas required",
        string="Min req vsphere replicas")
    vm_backup_req_mo = fields.Boolean(string="Monday", default=True)
    vm_backup_req_tu = fields.Boolean(string="Tuesday", default=True)
    vm_backup_req_we = fields.Boolean(string="Wednesday", default=True)
    vm_backup_req_th = fields.Boolean(string="Thursday", default=True)
    vm_backup_req_fr = fields.Boolean(string="Friday", default=True)
    vm_backup_req_sa = fields.Boolean(string="Saturday", default=True)
    vm_backup_req_su = fields.Boolean(string="Sunday", default=True)

    #vcenter fields
    vc_dns = fields.Char(string="vcenter dns")
    vc_port = fields.Integer(string="vcenter tcp port", default=443)
    vc_password_id = fields.Many2one('lubon_credentials.credentials')
    vc_check = fields.Boolean(string="Include in schedule?")
    vc_portgroups_ids = fields.One2many("lubon_qlan.portgroups", "asset_id")
    vc_datastores_ids = fields.One2many("lubon_qlan.datastores", "asset_id")
    vc_events_ids = fields.One2many("lubon_qlan.events", "asset_id")
    vc_event_retentiontime = fields.Integer(
        help="Days events should be retained", default=180)

    @api.multi
    def new_asset(self, site_id, quant_id):
        asset = self.create({
            'site_id':
            site_id.id,
            'quant_id':
            quant_id.id,
            'asset_name':
            quant_id.product_id.name + '-' + quant_id.name,
            'lot':
            quant_id.lot_id.name,
            'product_id':
            quant_id.product_id.id,
        })
        #pdb.set_trace()
    @api.one
    @api.depends('ips')
    def _calculate_ip_display(self):
        self.ip_display = ""
        for ip in self.ips:
            if ip.name:
                if self.ip_display:
                    self.ip_display += ","
                self.ip_display += ip.name

    ip_display = fields.Char(string="IP",
                             compute="_calculate_ip_display",
                             store=True)

    @api.one
    @api.onchange('site_id')
    def manage_site_id(self):
        #pdb.set_trace()
        #		if self.child_ids:
        #			return {'title': 'Fout', 'message': "Heeft childs"}
        for interface in self.interfaces_ids:
            interface.site_id = self.site_id
        for credential in self.credentials_ids:
            credential.site_id = self.site_id
            #pdb.set_trace()
        for ip in self.ips:
            ip.site_id = self.site_id

    # @api.one
    # def write(self,vals):
    # 	pdb.set_trace()
    # 	super(lubon_qlan_assets,self).write(vals)
    @api.one
    def _vc_login(self):
        try:
            context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
            context.verify_mode = ssl.CERT_NONE
            service_instance = connect.SmartConnect(
                host=self.vc_dns,
                user=self.vc_password_id.user,
                pwd=self.vc_password_id.decrypt()[0],
                port=self.vc_port,
                sslContext=context)

            #			atexit.register(connect.Disconnect, service_instance)
            #			pdb.set_trace()
            #			raise Warning ("Login OK")
            return service_instance
        except vmodl.MethodFault as error:
            raise Warning("Caught vmodl fault :", error.msg)

    @api.one
    def vc_test_login(self):
        session = self._vc_login()

    @api.multi
    def vc_inventory(self, dummy=None):
        for vc in self.search([('vc_check', '=', True)]):
            logger.info("Run vc_inventory: %s" % vc.asset_name)
            vc.vc_get_all()

    @api.one
    def _vc_get_containerview(self, viewType):
        context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
        context.verify_mode = ssl.CERT_NONE
        global containerView
        global content
        service_instance = connect.SmartConnect(
            host=self.vc_dns,
            user=self.vc_password_id.user,
            pwd=self.vc_password_id.decrypt()[0],
            port=self.vc_port,
            sslContext=context)
        atexit.register(connect.Disconnect, service_instance)
        content = service_instance.RetrieveContent()

        container = content.rootFolder  # starting point to look into
        #		viewType = [vim.VirtualMachine]  # object types to look for
        viewType = viewType  # object types to look for
        recursive = True  # whether we should look into it recursively
        containerView = content.viewManager.CreateContainerView(
            container, viewType, recursive)

    @api.one
    def vc_get_all(self):
        logger.info("Start vc_get_all %s" % self.asset_name)
        self.vc_get_networks()
        self.vc_get_datastores()
        self.vc_get_vms()
        self.vc_get_events()
        #self.vc_cleanup_events()

    @api.one
    def vc_get_vms(self):
        logger.info("Start vc_get_vms %s" % self.asset_name)
        self._vc_get_containerview([vim.VirtualMachine])
        for child in containerView.view:
            if '_replica_' not in child.summary.config.name:
                self.vc_check_vm(child)

    @api.one
    def vc_get_networks(self):
        logger.info("Start vc_get_networks %s" % self.asset_name)
        self._vc_get_containerview([vim.DistributedVirtualPortgroup])
        for child in containerView.view:
            #logger.info("Portgroup: %s " %  child.config.name)
            logger.info("Portgroup: %s " % child.key)
            portgroup = self.vc_portgroups_ids.search([
                ('uuid', '=', child.key), ('asset_id', '=', self.id)
            ])
            if not portgroup:
                self.vc_portgroups_ids.create({
                    'uuid': child.key,
                    'name': child.config.name,
                    'asset_id': self.id,
                })
            #pdb.set_trace()
    @api.one
    def vc_get_datastores(self):
        logger.info("Start vc_get_datastores %s" % self.asset_name)

        self._vc_get_containerview([vim.Datastore])
        for child in containerView.view:
            #logger.info("Portgroup: %s " %  child.config.name)
            logger.info("Datastore: %s " % child.info.name)
            datastore = self.vc_datastores_ids.search([
                ('url', '=', child.info.url), ('asset_id', '=', self.id)
            ])
            if not datastore:
                datastore = self.vc_datastores_ids.create({
                    'url': child.info.url,
                    'name': child.info.name,
                    'asset_id': self.id,
                })
            datastore.free = child.info.freeSpace / (1024 * 1024 * 1024)
            datastore.capacity = child.summary.capacity / (1024 * 1024 * 1024)
            datastore.rate_free = int(
                10000 * datastore.free / datastore.capacity) / 100
            #pdb.set_trace()

    @api.multi
    def vc_cleanup_events(self):
        logger.info("Start vc_cleanup_events %s" % self.asset_name)
        cleandate = fields.Date.to_string(
            fields.Date.from_string(fields.Date.context_today(self)) -
            datetime.timedelta(days=self.vc_event_retentiontime))
        events = self.vc_events_ids.search([('asset_id', '=', self.id),
                                            ('createtime', '<', cleandate)])
        logger.info("Cleaning %s %d events" % (self.asset_name, len(events)))
        events.unlink()

    @api.multi
    def vc_get_events(self,
                      context=None,
                      eventTypeId='hbr.primary.DeltaCompletedEvent'):
        logger.info("Start vc_get_events %s" % self.asset_name)
        self._vc_get_containerview([vim.HostSystem])
        eMgrRef = content.eventManager
        filter_spec = vim.event.EventFilterSpec()
        filter_spec.eventTypeId = eventTypeId
        oldevents = self.env['lubon_qlan.events'].search([
            ('asset_id', '=', self.id), ('event_type', '=', eventTypeId)
        ])
        oldevents.sorted(key=lambda r: r.createtime, reverse=True)
        if oldevents:
            newest = fields.Datetime.from_string(oldevents[0].createtime)
            time_filter = vim.event.EventFilterSpec.ByTime(
                beginTime=newest + datetime.timedelta(seconds=1))
            filter_spec.time = time_filter
        #	logger.info("vc_get_events time filter: %s" % fields.Datetime.to_string(newest))

        while True:
            event_res = eMgrRef.QueryEvents(filter_spec)
            if len(event_res) == 1:
                break
            logger.info("vc_get_events: Number of events: %d" % len(event_res))
            for e in event_res:
                #pdb.set_trace()
                if not self.env['lubon_qlan.events'].search(
                    [('asset_id', '=', self.id), ('external_id', "=", e.key)]):
                    vm = self.env['lubon_qlan.assets'].search([
                        ('asset_name', '=', e.vm.name),
                        ('parent_id', '=', self.id)
                    ])
                    evt = self.env['lubon_qlan.events'].create({
                        'asset_id':
                        self.id,
                        'external_id':
                        e.key,
                        'event_type':
                        e.eventTypeId,
                        'event_source_type':
                        'vc',
                        'event_full':
                        e.fullFormattedMessage,
                        'createtime':
                        fields.Datetime.to_string(e.createdTime),
                        'model':
                        'lubon_qlan.assets',
                        'related_id':
                        vm.id,
                    })
            time_filter = vim.event.EventFilterSpec.ByTime(
                beginTime=e.createdTime)
            filter_spec.time = time_filter
            #pdb.set_trace()

    @api.one
    def vc_check_vm(self, child):
        asset = self.env['lubon_qlan.assets'].search([('asset_name', '=',
                                                       child.name)])
        virtual_machine = child
        if len(asset) > 1:
            raise Warning("Duplicate VM found :", asset[0].asset_name)
        if not asset:
            asset = self.env['lubon_qlan.assets'].create({
                'asset_name':
                virtual_machine.summary.config.name,
                'vm_uuid_instance':
                virtual_machine.summary.config.instanceUuid,
                'vm_uuid_bios':
                virtual_machine.summary.config.uuid,
                'parent_id':
                self.id,
                'site_id':
                self.site_id.id,
                'asset_type':
                'vm',
            })
        else:
            if not asset.asset_type:
                asset.asset_type = 'vm'
            if not asset.vm_uuid_bios:
                asset.vm_uuid_bios = virtual_machine.summary.config.uuid
            if not asset.vm_uuid_instance:
                asset.vm_uuid_instance = virtual_machine.summary.config.instanceUuid
            if not asset.parent_id:
                asset.parent_id = self.id
        asset.vm_memory = virtual_machine.summary.config.memorySizeMB
        asset.vm_cpu = virtual_machine.summary.config.numCpu
        asset.vm_guestos = virtual_machine.summary.config.guestFullName
        #pdb.set_trace()
        asset.vm_cores_per_socket = virtual_machine.config.hardware.numCoresPerSocket
        asset.vm_sockets = asset.vm_cpu / asset.vm_cores_per_socket
        asset.vm_path_name = virtual_machine.summary.config.vmPathName
        asset.vm_power_state = virtual_machine.runtime.powerState
        asset.vm_date_last = fields.Datetime.now()
        for snapshot in asset.vm_snapshots_ids:
            snapshot.unlink()
        asset.vm_snapshots_count = 0
        if virtual_machine.snapshot:
            for snapshot in virtual_machine.snapshot.rootSnapshotList:
                self.env["lubon_qlan.snapshots"].create({
                    'asset_id':
                    asset.id,
                    'name':
                    snapshot.name,
                    'createTime':
                    snapshot.createTime,
                })
                logger.info("Name: %s " % snapshot.name)
                asset.vm_snapshots_count += 1
        #if virtual_machine.tag:
        #	pdb.set_trace()
        #for network in virtual_machine.network:
        #pdb.set_trace()
    @api.multi
    def get_vm_drives(self):
        self.env['lubon_qlan.drives'].read_drives()

    @api.multi
    def get_restorepoints(self, instance_id=None, querytype=None):
        if instance_id:
            target_date = instance_id.stats_id.date

        result = get_restorepoints(self.asset_name, target_date, querytype)
        #		pdb.set_trace()
        points = result['res']
        newest = ""
        for point in points:
            rec = self.env['lubon_qlan.restorepoints'].search([('uid', 'like',
                                                                point['uid'])])
            if not rec:
                newrec = {
                    'uid': point['uid'],
                    'asset_id': self.id,
                    'creationtimeutc': point['creationtimeutc'],
                    'BackupServerReference': point['BackupServerReference'],
                    'algorithm': point['algorithm'],
                    'pointtype': point['pointtype'],
                    'veeamtype': querytype,
                    #					'hierarchyobjref':point['hierarchyobjref'],
                }
                #pdb.set_trace()
                rec = self.env['lubon_qlan.restorepoints'].create(newrec)
            if point['creationtimeutc'] > newest:
                newest = point['creationtimeutc']
            if instance_id:
                rec.restorepoints_instances_id = instance_id
        instance_id.result_href = result['href']
        instance_id.result_code = result['response'].status_code
        instance_id.result_response = result['response'].content
        #pdb.set_trace()
        datetime_begin = fields.Datetime.from_string(instance_id.stats_id.date)
        datetime_end = fields.Datetime.from_string(
            instance_id.stats_id.date) + datetime.timedelta(hours=+24)
        datetime_begin = fields.Datetime.to_string(datetime_begin)
        datetime_end = fields.Datetime.to_string(datetime_end)
        #pdb.set_trace()
        instance_id.number_vsphere_replica = self.env[
            'lubon_qlan.events'].search_count([
                ('related_id', '=', self.id),
                ('model', '=', 'lubon_qlan.assets'),
                ('createtime', '>=', datetime_begin),
                ('createtime', '<', datetime_end),
                ('event_type', '=', 'hbr.primary.DeltaCompletedEvent'),
            ])
        #pdb.set_trace()

        if len(newest) > 0 and (newest.replace('T', ' ') >
                                self.vm_latest_restore_point):
            self.vm_latest_restore_point = newest.replace('T', ' ')

    @api.multi
    def get_all_restorepoints(self, fake=None):
        vms = self.search([("vm_check_backup", "=", True),
                           ("asset_type", "=", 'vm')])
        #pdb.set_trace()
        for vm in vms:
            vm.get_restorepoints()

    @api.multi
    def glacier_set_backup_type(self):
        monthlys = self.glacier_vault_archive_ids.search([
            ('asset_id', '=', self.id), ('backup_type', '=', 'M')
        ]).sorted(key=lambda r: r.backup_date, reverse=True)
        unknowns = self.glacier_vault_archive_ids.search([
            ('asset_id', '=', self.id), ('backup_type', '=', 'U')
        ]).sorted(key=lambda r: r.backup_date, reverse=False)
        if monthlys:
            latest = fields.Datetime.from_string(monthlys[0].backup_date)
            if latest:
                for a in unknowns:
                    thisdate = fields.Datetime.from_string(a.backup_date)
                    if (thisdate.month > latest.month and thisdate.year
                            == latest.year) or thisdate.year > latest.year:
                        a.backup_type = 'M'
                        latest = thisdate
                    else:
                        a.backup_type = 'W'

    @api.multi
    def glacier_mark_obsoletes(self):
        for asset in self:
            weeklys = asset.glacier_vault_archive_ids.search([
                ('asset_id', '=', asset.id), ('backup_type', '=', 'W'),
                ('marked_for_delete', '=', False)
            ]).sorted(key=lambda r: r.backup_date, reverse=True)
            monthlys = asset.glacier_vault_archive_ids.search([
                ('asset_id', '=', asset.id), ('backup_type', '=', 'M'),
                ('marked_for_delete', '=', False)
            ]).sorted(key=lambda r: r.backup_date, reverse=True)
            w_obsolete = weeklys - weeklys[0:asset.
                                           vm_glacier_week_retention_num]
            m_obsolete = monthlys - monthlys[0:asset.
                                             vm_glacier_month_retention_num]

            for a in w_obsolete:
                if (fields.Datetime.from_string(fields.Datetime.now()) -
                        fields.Datetime.from_string(a.backup_date)
                    ).days > asset.vm_glacier_week_retention_age:
                    a.marked_for_delete = True

            for a in m_obsolete:
                if (fields.Datetime.from_string(fields.Datetime.now()) -
                        fields.Datetime.from_string(a.backup_date)
                    ).days > asset.vm_glacier_month_retention_age:
                    a.marked_for_delete = True

    @api.multi
    def glacier_reset_obsoletes(self):
        archives = self.glacier_vault_archive_ids.search([
            ('asset_id', '=', self.id), ('marked_for_delete', '=', True),
            ('delete_initiated', '=', False)
        ])
        for a in archives:
            a.marked_for_delete = False

    @api.multi
    def glacier_process_obsoletes(self, dummy=None):
        vms = self.env['lubon_qlan.assets'].search([
            ('asset_type', '=', 'vm'),
            ('glacier_vault_archive_ids', '!=', False)
        ])
        for vm in vms:
            logging.info("Processing set backup type VM: %s" % (vm.asset_name))
            vm.glacier_set_backup_type()
        vms = self.env['lubon_qlan.assets'].search([('asset_type', '=', 'vm'),
                                                    ('vm_glacier_cleanup', '=',
                                                     True)])
        for vm in vms:
            logging.info("Processing mark obsoletes VM: %s" % (vm.asset_name))
            vm.glacier_mark_obsoletes()
        #initiate delete of all obsolete archives on glacier


# next 3 lines actually delete the archives.
#		obsoletes=self.glacier_vault_archive_ids.search([('marked_for_delete','=',True),('delete_initiated','=',False)])
#		for archive in obsoletes:
#			archive.delete_archive()
Ejemplo n.º 12
0
class website_sale_category_description(models.Model):
    _inherit = 'product.public.category'

    description = fields.Html()
#    name = fields.Char()
Ejemplo n.º 13
0
class LoanDetails(models.Model):
    _name = "wc.loan.detail"
    _description = "Loan Detail"
    _inherit = "wc.loan.amortization"
    _order = "date_due"

    loan_id = fields.Many2one('wc.loan', 'Loan', ondelete='cascade')

    #penalty_base = fields.Float("Penalty",digits=(12,2))
    penalty = fields.Float("Penalty", digits=(12, 2))
    adjustment = fields.Float("Adjustment",
                              digits=(12, 2),
                              compute="_compute_adjustment",
                              store=True)
    penalty_adjusted = fields.Float("Adjusted Penalty",
                                    digits=(12, 2),
                                    compute="_compute_total_paid",
                                    store=False)
    principal_paid = fields.Float("Principal Paid",
                                  digits=(12, 2),
                                  compute="_compute_total_paid",
                                  store=True)
    interest_paid = fields.Float("Interest Paid",
                                 digits=(12, 2),
                                 compute="_compute_total_paid",
                                 store=True)
    penalty_paid = fields.Float("Penalty Paid",
                                digits=(12, 2),
                                compute="_compute_total_paid",
                                store=True)
    others_paid = fields.Float("Others Paid",
                               digits=(12, 2),
                               compute="_compute_total_paid",
                               store=True)
    others_paid_dict = fields.Char("Others Detail",
                                   digits=(12, 2),
                                   compute="_compute_total_paid",
                                   store=True)
    others_html = fields.Html("Others Breakdown",
                              compute="_compute_others_html")
    penalty_base = fields.Float("Penalty Base",
                                digits=(12, 2),
                                compute="_compute_total_paid",
                                store=True)
    total_due = fields.Float("Total Due",
                             digits=(12, 2),
                             compute="_compute_total_paid",
                             store=True)
    days = fields.Integer(string="PD",
                          help="Past due in days")  #,compute="_compute_days")

    state = fields.Selection([
        ('next_due', 'Next Due'),
        ('due', 'Due'),
        ('paid', 'Paid'),
        ('reversed', 'Reversed'),
        ('del', 'To be deleted'),
    ],
                             string='State',
                             default=lambda self: 'next_due',
                             readonly=False)

    payment_date = fields.Date("Payment Date", compute="compute_payment")
    payment_amount = fields.Float("Payment Amount",
                                  digits=(12, 2),
                                  compute="compute_payment")

    advance_payment_id = fields.Many2one('wc.loan.payment', 'Advance Payment')
    #is_principal_first = fields.Boolean("Principal First", readonly=True)

    distributions = fields.One2many('wc.loan.payment.distribution',
                                    'detail_id', 'Payments Distribution')
    adjustments = fields.One2many('wc.loan.adjustment', 'detail_id',
                                  'Adjustment')
    #deduction_ids = fields.One2many('wc.loan.detail.deduction', 'detail_id', 'Recurring Payments')

    @api.depends('distributions', 'distributions.payment_id',
                 'distributions.payment_id.date')
    def compute_payment(self):
        for r in self:
            is_date_set = False
            tamt = 0.0
            for p in r.distributions:
                if p.payment_id and p.payment_id.date and not is_date_set:
                    r.payment_date = p.payment_id.date
                    is_date_set = True
                tamt += p.amount
            r.payment_amount = tamt

    @api.model
    def get_others_paid_dict(self, det):
        try:
            others_paid_dict = eval(det.others_paid_dict)
        except:
            _logger.debug("*get_others_paid_dict error: %s",
                          det.others_paid_dict)
            others_paid_dict = {}
        return others_paid_dict

    @api.depends(
        'others_paid_dict',
        'others_paid',
        'others_due',
    )
    def _compute_others_html(self):
        for det in self:
            others_paid_dict = det.get_others_paid_dict(det)
            others_html = ""
            if not det.no_others_due:
                for ded in det.loan_id.deduction_ids:
                    if ded.recurring:
                        paid = others_paid_dict.get(ded.code, 0.0)
                        if ded.amount or paid:
                            others_html += "<tr>"
                            others_html += "<td>%s</td>" % ded.code
                            others_html += "<td style='text-align:right;'>%s</td>" % "{:,.2f}".format(
                                ded.amount)
                            others_html += "<td style='text-align:right;'>%s</td>" % "{:,.2f}".format(
                                paid)
                            others_html += "</tr>"
                if others_html:
                    others_html2 = "<table class='ded_details'>"
                    others_html2 += "<tr>"
                    others_html2 += "<th>Code</th>"
                    others_html2 += "<th style='text-align:right;'>Due</th>"
                    others_html2 += "<th style='text-align:right;'>Paid</th>"
                    others_html2 += "</tr>" + others_html
                    others_html2 += "</table>"
                    det.others_html = others_html2

    @api.depends(
        'principal_due',
        'interest_due',
        'penalty',
        'adjustment',
        'distributions',
        'distributions.amount',
        'distributions.payment_type',
        'distributions.payment_id',
        'distributions.payment_id.state',
        'distributions.code',
    )
    def _compute_total_paid(self):
        for det in self:
            total = {
                'penalty': 0.0,
                'interest': 0.0,
                'principal': 0.0,
                'others': 0.0,
            }
            others_paid_dict = {}
            for p in det.distributions:
                if p.payment_id.state not in ['cancelled', 'draft']:
                    total[p.payment_type] += p.amount
                    others_paid_dict[p.code] = others_paid_dict.get(
                        p.code, 0.0) + p.amount

            _logger.debug("Paid Total: %s", total)
            #pbase = det.principal_due + det.interest_due - total['interest'] - total['principal']
            pbase = det.principal_due - total['principal']
            det.penalty_paid = total['penalty']
            det.interest_paid = total['interest']
            det.principal_paid = total['principal']
            det.others_paid = total['others']
            det.others_paid_dict = "%s" % others_paid_dict
            det.penalty_base = pbase
            tdue = (max(det.principal_due - total['principal'], 0.0) +
                    max(det.interest_due - total['interest'], 0.0) +
                    max(det.penalty + det.adjustment - total['penalty'], 0.0) +
                    max(det.others_due - total['others'], 0.0))
            det.penalty_adjusted = det.penalty + det.adjustment
            if tdue > 0.0:
                det.total_due = tdue
            else:
                det.total_due = 0

    @api.depends('adjustments', 'adjustments.amount', 'adjustments.detail_id')
    def _compute_adjustment(self):
        for det in self:
            adjustment = 0.0
            for adj in det.adjustments:
                adjustment += adj.amount
            det.adjustment = adjustment

    @api.multi
    def add_adjustment(self):
        #self.ensure_one()
        if self.state == 'paid':
            return {}
        else:
            view_id = self.env.ref('wc_loan.add_adjustment_form').id
            context = self._context.copy()
            context.update({
                'default_loan_id': self.loan_id.id,
                'default_detail_id': self.id,
            })
            return {
                'name': 'Add Adjustment',
                'view_type': 'form',
                'view_mode': 'form',
                'views': [(view_id, 'form')],
                'res_model': 'wc.loan.adjustment.wizard',
                'view_id': view_id,
                'type': 'ir.actions.act_window',
                'target': 'new',
                'context': context,
            }
Ejemplo n.º 14
0
class Question(models.Model):
    _name = 'bestja.question'
    _inherit = [
        'protected_fields.mixin',
        'ir.needaction_mixin',
        'message_template.mixin',
    ]
    _protected_fields = ['answear', 'is_faq']
    _permitted_groups = ['bestja_base.instance_admin']

    name = fields.Char(required=True, string=u"Pytanie")
    category = fields.Many2one('bestja.question.category',
                               required=True,
                               string=u"Kategoria")
    question = fields.Text(string=u"Szczegóły pytania")
    screenshot = fields.Binary(string="Zrzut ekranu")
    answear = fields.Html(string=u"Odpowiedź")
    is_faq = fields.Boolean(string="Najczęściej zadawane pytanie",
                            default=False)
    is_answeared = fields.Boolean(compute='_compute_is_answeared',
                                  store=True,
                                  string=u"Odpowiedziano")
    user_is_asker = fields.Boolean(compute='_compute_user_is_asker')
    user_is_admin = fields.Boolean(compute='_compute_user_is_admin')

    @api.one
    @api.depends('answear')
    def _compute_is_answeared(self):
        self.is_answeared = bool(self.answear)

    @api.one
    @api.depends('create_uid')
    def _compute_user_is_asker(self):
        self.user_is_asker = (self.create_uid.id == self.env.uid)

    @api.one
    def _compute_user_is_admin(self):
        self.user_is_admin = self.user_has_groups('bestja_base.instance_admin')

    @api.model
    def create(self, vals):
        record = super(Question, self).create(vals)
        record.send_group(
            template='bestja_helpdesk.msg_question_new',
            group='bestja_base.instance_admin',
        )
        return record

    @api.multi
    def write(self, vals):
        old_is_answeared = self.is_answeared
        success = super(Question, self).write(vals)
        if not old_is_answeared and self.is_answeared:
            self.send(
                template='bestja_helpdesk.msg_question_answeared',
                recipients=self.create_uid,
                sender=self.env.user,
            )
        return success

    @api.model
    def _needaction_domain_get(self):
        """
        Show unansweared count in menu - only for admins.
        """
        if not self.user_has_groups('bestja_base.instance_admin'):
            return False
        return [('is_answeared', '=', False)]
Ejemplo n.º 15
0
class Audit(models.Model):
    """
    Auditoría
    """
    _name = 'gpsi.staff.audit'
    _description = 'Audit'
    _inherit = ['mail.thread']

    chk_id = fields.Many2one('gpsi.staff.audit.chk',
                             'Checklist',
                             domain=[('is_template', '=', True)],
                             help='Checklist')
    asst_id = fields.Many2one('gpsi.staff.audit.chk',
                              'Assessment',
                              domain=[('is_template', '=', False)],
                              help='Assessment')
    active = fields.Boolean('Active', default=True)
    execution_date = fields.Date('Date')
    auditee_id = fields.Many2one('res.partner', 'Auditee')
    auditor_id = fields.Many2one('res.partner',
                                 compute='_compute_auditor_id',
                                 help='Auditor lead')
    member_ids = fields.One2many('gpsi.staff.audit.member', 'audit_id',
                                 'Audit Team')
    car_ids = fields.One2many('gpsi.staff.audit.car', 'audit_id',
                              'Action Requests')
    car_count = fields.Integer('Car Count', compute='_compute_car_count')
    notes = fields.Html('Notes')
    plan_ln_ids = fields.One2many('gpsi.staff.audit.plan.line', 'audit_id',
                                  'Plan')
    company_id = fields.Many2one('res.company',
                                 string='Company',
                                 readonly=True,
                                 default=lambda self: self.env.user.company_id)
    ex_rpt_id = fields.Many2one(related='asst_id.ex_rpt_id')
    gn_rpt_id = fields.Many2one(related='asst_id.gn_rpt_id')

    @api.model
    def create(self, vals):
        res = super(Audit, self).create(vals)

        if res.chk_id and not res.asst_id:
            asst = res.chk_id.copy({'is_template': False})
            res.write({'asst_id': asst.id})

        return res

    @api.multi
    def name_get(self):
        return [(r.id, 'AU-{0:03d}'.format(r.id)) for r in self]

    @api.multi
    def _compute_car_count(self):
        """
        Calcula la cantidad de acciones correctivas
        """
        for audit in self:
            audit.car_count = len(audit.car_ids)

    @api.multi
    def _compute_auditor_id(self):
        for audit in self:
            lead = audit.member_ids.find_lead()
            audit.auditor_id = lead and lead.id or False

    @api.multi
    def action_open_cars(self):
        self.ensure_one()
        return {
            "type": 'ir.actions.act_window',
            "res_model": 'gpsi.staff.audit.car',
            "views": [[False, 'tree'], [False, 'form']],
            "domain": [('audit_id', '=', self.id)],
            "context": {
                'default_audit_id': self.id
            },
            "name": "CAR'S",
        }

    @api.multi
    def action_open_asst_editor(self):
        """Abre una nueva ventana para editar la evaluación usando un sitio web personalizado
        """
        self.ensure_one()
        return {
            'type': 'ir.actions.act_url',
            'url': '/gpsi/staff/audits/{0}/assessment'.format(self.id),
            'target': 'new'
        }

    @api.multi
    def print_audit_executive_rpt(self):
        """Imprime el reporte executivo
        """
        self.ensure_one()
        return self.env['report'].get_action(self, self.ex_rpt_id.report_name)

    @api.multi
    def print_audit_general_rpt(self):
        """Imprime el reporte general
        """
        self.ensure_one()
        return self.env['report'].get_action(self, self.gn_rpt_id.report_name)
Ejemplo n.º 16
0
class Forum(models.Model):
    _name = 'forum.forum'
    _description = 'Forum'
    _inherit = ['mail.thread', 'website.seo.metadata']

    def init(self, cr):
        """ Add forum uuid for user email validation.

        TDE TODO: move me somewhere else, auto_init ? """
        forum_uuids = self.pool['ir.config_parameter'].search(
            cr, SUPERUSER_ID, [('key', '=', 'website_forum.uuid')])
        if not forum_uuids:
            self.pool['ir.config_parameter'].set_param(cr, SUPERUSER_ID,
                                                       'website_forum.uuid',
                                                       str(uuid.uuid4()),
                                                       ['base.group_system'])

    @api.model
    def _get_default_faq(self):
        fname = modules.get_module_resource('website_forum', 'data',
                                            'forum_default_faq.html')
        with open(fname, 'r') as f:
            return f.read()
        return False

    # description and use
    name = fields.Char('Forum Name', required=True, translate=True)
    faq = fields.Html('Guidelines', default=_get_default_faq, translate=True)
    description = fields.Html(
        'Description',
        default=
        '<p> This community is for professionals and enthusiasts of our products and services.'
        'Share and discuss the best content and new marketing ideas,'
        'build your professional profile and become a better marketer together.</p>'
    )
    default_order = fields.Selection([('create_date desc', 'Newest'),
                                      ('write_date desc', 'Last Updated'),
                                      ('vote_count desc', 'Most Voted'),
                                      ('relevancy desc', 'Relevancy'),
                                      ('child_count desc', 'Answered')],
                                     string='Default Order',
                                     required=True,
                                     default='write_date desc')
    relevancy_post_vote = fields.Float('First Relevancy Parameter',
                                       default=0.8)
    relevancy_time_decay = fields.Float('Second Relevancy Parameter',
                                        default=1.8)
    default_post_type = fields.Selection([('question', 'Question'),
                                          ('discussion', 'Discussion'),
                                          ('link', 'Link')],
                                         string='Default Post',
                                         required=True,
                                         default='question')
    allow_question = fields.Boolean(
        'Questions',
        help=
        "Users can answer only once per question. Contributors can edit answers and mark the right ones.",
        default=True)
    allow_discussion = fields.Boolean('Discussions', default=True)
    allow_link = fields.Boolean(
        'Links',
        help="When clicking on the post, it redirects to an external link",
        default=True)
    # karma generation
    karma_gen_question_new = fields.Integer(string='Asking a question',
                                            default=2)
    karma_gen_question_upvote = fields.Integer(string='Question upvoted',
                                               default=5)
    karma_gen_question_downvote = fields.Integer(string='Question downvoted',
                                                 default=-2)
    karma_gen_answer_upvote = fields.Integer(string='Answer upvoted',
                                             default=10)
    karma_gen_answer_downvote = fields.Integer(string='Answer downvoted',
                                               default=-2)
    karma_gen_answer_accept = fields.Integer(string='Accepting an answer',
                                             default=2)
    karma_gen_answer_accepted = fields.Integer(string='Answer accepted',
                                               default=15)
    karma_gen_answer_flagged = fields.Integer(string='Answer flagged',
                                              default=-100)
    # karma-based actions
    karma_ask = fields.Integer(string='Ask a new question', default=3)
    karma_answer = fields.Integer(string='Answer a question', default=3)
    karma_edit_own = fields.Integer(string='Edit its own posts', default=1)
    karma_edit_all = fields.Integer(string='Edit all posts', default=300)
    karma_close_own = fields.Integer(string='Close its own posts', default=100)
    karma_close_all = fields.Integer(string='Close all posts', default=500)
    karma_unlink_own = fields.Integer(string='Delete its own posts',
                                      default=500)
    karma_unlink_all = fields.Integer(string='Delete all posts', default=1000)
    karma_upvote = fields.Integer(string='Upvote', default=5)
    karma_downvote = fields.Integer(string='Downvote', default=50)
    karma_answer_accept_own = fields.Integer(
        string='Accept an answer on its own questions', default=20)
    karma_answer_accept_all = fields.Integer(
        string='Accept an answers to all questions', default=500)
    karma_editor_link_files = fields.Integer(string='Linking files (Editor)',
                                             default=20)
    karma_editor_clickable_link = fields.Integer(
        string='Add clickable links (Editor)', default=20)
    karma_comment_own = fields.Integer(string='Comment its own posts',
                                       default=1)
    karma_comment_all = fields.Integer(string='Comment all posts', default=1)
    karma_comment_convert_own = fields.Integer(
        string='Convert its own answers to comments and vice versa',
        default=50)
    karma_comment_convert_all = fields.Integer(
        string='Convert all answers to answers and vice versa', default=500)
    karma_comment_unlink_own = fields.Integer(string='Unlink its own comments',
                                              default=50)
    karma_comment_unlink_all = fields.Integer(string='Unlinnk all comments',
                                              default=500)
    karma_retag = fields.Integer(string='Change question tags', default=75)
    karma_flag = fields.Integer(string='Flag a post as offensive', default=500)
    karma_dofollow = fields.Integer(
        string='Disabled links',
        help=
        'If the author has not enough karma, a nofollow attribute is added to links',
        default=500)

    @api.model
    def create(self, values):
        return super(Forum,
                     self.with_context(mail_create_nolog=True)).create(values)

    @api.model
    def _tag_to_write_vals(self, tags=''):
        User = self.env['res.users']
        Tag = self.env['forum.tag']
        post_tags = []
        existing_keep = []
        for tag in filter(None, tags.split(',')):
            if tag.startswith('_'):  # it's a new tag
                # check that not arleady created meanwhile or maybe excluded by the limit on the search
                tag_ids = Tag.search([('name', '=', tag[1:])])
                if tag_ids:
                    existing_keep.append(int(tag_ids[0]))
                else:
                    # check if user have Karma needed to create need tag
                    user = User.sudo().browse(self._uid)
                    if user.exists() and user.karma >= self.karma_retag:
                        post_tags.append((0, 0, {
                            'name': tag[1:],
                            'forum_id': self.id
                        }))
            else:
                existing_keep.append(int(tag))
        post_tags.insert(0, [6, 0, existing_keep])
        return post_tags
Ejemplo n.º 17
0
class event_event(models.Model):
    """Event"""
    _name = 'event.event'
    _description = 'Event'
    _inherit = ['mail.thread', 'ir.needaction_mixin']
    _order = 'date_begin'

    name = fields.Char(string='Name',
                       translate=True,
                       required=True,
                       readonly=False,
                       states={'done': [('readonly', True)]})
    user_id = fields.Many2one('res.users',
                              string='Responsible',
                              default=lambda self: self.env.user,
                              readonly=False,
                              states={'done': [('readonly', True)]})
    company_id = fields.Many2one('res.company',
                                 string='Company',
                                 change_default=True,
                                 default=lambda self: self.env['res.company'].
                                 _company_default_get('event.event'),
                                 required=False,
                                 readonly=False,
                                 states={'done': [('readonly', True)]})
    organizer_id = fields.Many2one(
        'res.partner',
        string='Organizer',
        default=lambda self: self.env.user.company_id.partner_id)
    type = fields.Many2one('event.type',
                           string='Category',
                           readonly=False,
                           states={'done': [('readonly', True)]})
    color = fields.Integer('Kanban Color Index')
    event_mail_ids = fields.One2many(
        'event.mail',
        'event_id',
        string='Mail Schedule',
        default=lambda self: self._default_event_mail_ids())

    @api.model
    def _default_event_mail_ids(self):
        return [(0, 0, {
            'interval_unit': 'now',
            'interval_type': 'after_sub',
            'template_id': self.env.ref('event.event_subscription')
        })]

    # Seats and computation
    seats_max = fields.Integer(
        string='Maximum Available Seats',
        oldname='register_max',
        readonly=True,
        states={
            'draft': [('readonly', False)],
            'confirm': [('readonly', False)]
        },
        help=
        "You can for each event define a maximum registration level. If you have too much registrations you are not able to confirm your event. (put 0 to ignore this rule )"
    )
    seats_availability = fields.Selection([('limited', 'Limited'),
                                           ('unlimited', 'Unlimited')],
                                          'Available Seat',
                                          required=True,
                                          default='unlimited')
    seats_min = fields.Integer(
        string='Minimum Reserved Seats',
        oldname='register_min',
        help=
        "You can for each event define a minimum registration level. If you do not enough registrations you are not able to confirm your event. (put 0 to ignore this rule )"
    )
    seats_reserved = fields.Integer(oldname='register_current',
                                    string='Reserved Seats',
                                    store=True,
                                    readonly=True,
                                    compute='_compute_seats')
    seats_available = fields.Integer(oldname='register_avail',
                                     string='Available Seats',
                                     store=True,
                                     readonly=True,
                                     compute='_compute_seats')
    seats_unconfirmed = fields.Integer(oldname='register_prospect',
                                       string='Unconfirmed Seat Reservations',
                                       store=True,
                                       readonly=True,
                                       compute='_compute_seats')
    seats_used = fields.Integer(oldname='register_attended',
                                string='Number of Participants',
                                store=True,
                                readonly=True,
                                compute='_compute_seats')
    seats_expected = fields.Integer(string='Number of Expected Attendees',
                                    readonly=True,
                                    compute='_compute_seats')

    @api.multi
    @api.depends('seats_max', 'registration_ids.state')
    def _compute_seats(self):
        """ Determine reserved, available, reserved but unconfirmed and used seats. """
        # initialize fields to 0
        for event in self:
            event.seats_unconfirmed = event.seats_reserved = event.seats_used = event.seats_available = 0
        # aggregate registrations by event and by state
        if self.ids:
            state_field = {
                'draft': 'seats_unconfirmed',
                'open': 'seats_reserved',
                'done': 'seats_used',
            }
            query = """ SELECT event_id, state, count(event_id)
                        FROM event_registration
                        WHERE event_id IN %s AND state IN ('draft', 'open', 'done')
                        GROUP BY event_id, state
                    """
            self._cr.execute(query, (tuple(self.ids), ))
            for event_id, state, num in self._cr.fetchall():
                event = self.browse(event_id)
                event[state_field[state]] += num
        # compute seats_available
        for event in self:
            if event.seats_max > 0:
                event.seats_available = event.seats_max - (
                    event.seats_reserved + event.seats_used)
            event.seats_expected = event.seats_unconfirmed + event.seats_reserved + event.seats_used

    # Registration fields
    registration_ids = fields.One2many('event.registration',
                                       'event_id',
                                       string='Attendees',
                                       readonly=False,
                                       states={'done': [('readonly', True)]})
    # Date fields
    date_tz = fields.Selection('_tz_get',
                               string='Timezone',
                               default=lambda self: self.env.user.tz)
    date_begin = fields.Datetime(string='Start Date',
                                 required=True,
                                 readonly=True,
                                 states={'draft': [('readonly', False)]})
    date_end = fields.Datetime(string='End Date',
                               required=True,
                               readonly=True,
                               states={'draft': [('readonly', False)]})
    date_begin_located = fields.Datetime(string='Start Date Located',
                                         compute='_compute_date_begin_tz')
    date_end_located = fields.Datetime(string='End Date Located',
                                       compute='_compute_date_end_tz')

    @api.model
    def _tz_get(self):
        return [(x, x) for x in pytz.all_timezones]

    @api.one
    @api.depends('date_tz', 'date_begin')
    def _compute_date_begin_tz(self):
        if self.date_begin:
            self_in_tz = self.with_context(tz=(self.date_tz or 'UTC'))
            date_begin = fields.Datetime.from_string(self.date_begin)
            self.date_begin_located = fields.Datetime.to_string(
                fields.Datetime.context_timestamp(self_in_tz, date_begin))
        else:
            self.date_begin_located = False

    @api.one
    @api.depends('date_tz', 'date_end')
    def _compute_date_end_tz(self):
        if self.date_end:
            self_in_tz = self.with_context(tz=(self.date_tz or 'UTC'))
            date_end = fields.Datetime.from_string(self.date_end)
            self.date_end_located = fields.Datetime.to_string(
                fields.Datetime.context_timestamp(self_in_tz, date_end))
        else:
            self.date_end_located = False

    state = fields.Selection(
        [('draft', 'Unconfirmed'), ('cancel', 'Cancelled'),
         ('confirm', 'Confirmed'), ('done', 'Done')],
        string='Status',
        default='draft',
        readonly=True,
        required=True,
        copy=False,
        help=
        "If event is created, the status is 'Draft'. If event is confirmed for the particular dates the status is set to 'Confirmed'. If the event is over, the status is set to 'Done'. If event is cancelled the status is set to 'Cancelled'."
    )
    auto_confirm = fields.Boolean(string='Auto Confirmation Activated',
                                  compute='_compute_auto_confirm')

    @api.one
    def _compute_auto_confirm(self):
        self.auto_confirm = self.env['ir.values'].get_default(
            'marketing.config.settings', 'auto_confirmation')

    reply_to = fields.Char(
        'Reply-To Email',
        readonly=False,
        states={'done': [('readonly', True)]},
        help=
        "The email address of the organizer is likely to be put here, with the effect to be in the 'Reply-To' of the mails sent automatically at event or registrations confirmation. You can also put the email address of your mail gateway if you use one."
    )
    address_id = fields.Many2one(
        'res.partner',
        string='Location',
        default=lambda self: self.env.user.company_id.partner_id,
        readonly=False,
        states={'done': [('readonly', True)]})
    country_id = fields.Many2one('res.country',
                                 'Country',
                                 related='address_id.country_id',
                                 store=True)
    description = fields.Html(string='Description',
                              oldname='note',
                              translate=True,
                              readonly=False,
                              states={'done': [('readonly', True)]})
    # badge fields
    badge_front = fields.Html(string='Badge Front')
    badge_back = fields.Html(string='Badge Back')
    badge_innerleft = fields.Html(string='Badge Innner Left')
    badge_innerright = fields.Html(string='Badge Inner Right')
    event_logo = fields.Html(string='Event Logo')

    @api.multi
    @api.depends('name', 'date_begin', 'date_end')
    def name_get(self):
        result = []
        for event in self:
            dates = [
                dt.split(' ')[0] for dt in [event.date_begin, event.date_end]
                if dt
            ]
            dates = sorted(set(dates))
            result.append(
                (event.id, '%s (%s)' % (event.name, ' - '.join(dates))))
        return result

    @api.one
    @api.constrains('seats_max', 'seats_available')
    def _check_seats_limit(self):
        if self.seats_availability == 'limited' and self.seats_max and self.seats_available < 0:
            raise UserError(_('No more available seats.'))

    @api.one
    @api.constrains('date_begin', 'date_end')
    def _check_closing_date(self):
        if self.date_end < self.date_begin:
            raise UserError(
                _('Closing Date cannot be set before Beginning Date.'))

    @api.model
    def create(self, vals):
        res = super(event_event, self).create(vals)
        if res.organizer_id:
            res.message_subscribe([res.organizer_id.id])
        if res.auto_confirm:
            res.button_confirm()
        return res

    @api.multi
    def write(self, vals):
        res = super(event_event, self).write(vals)
        if vals.get('organizer_id'):
            self.message_subscribe([vals['organizer_id']])
        return res

    @api.one
    def button_draft(self):
        self.state = 'draft'

    @api.one
    def button_cancel(self):
        for event_reg in self.registration_ids:
            if event_reg.state == 'done':
                raise UserError(
                    _("You have already set a registration for this event as 'Attended'. Please reset it to draft if you want to cancel this event."
                      ))
        self.registration_ids.write({'state': 'cancel'})
        self.state = 'cancel'

    @api.one
    def button_done(self):
        self.state = 'done'

    @api.one
    def button_confirm(self):
        self.state = 'confirm'

    @api.onchange('type')
    def _onchange_type(self):
        if self.type:
            self.seats_min = self.type.default_registration_min
            self.seats_max = self.type.default_registration_max
            self.reply_to = self.type.default_reply_to

    @api.multi
    def action_event_registration_report(self):
        res = self.env['ir.actions.act_window'].for_xml_id(
            'event', 'action_report_event_registration')
        res['context'] = {
            "search_default_event_id": self.id,
            "group_by": ['create_date:day'],
        }
        return res

    @api.one
    def mail_attendees(self,
                       template_id,
                       force_send=False,
                       filter_func=lambda self: True):
        for attendee in self.registration_ids.filtered(filter_func):
            self.env['mail.template'].browse(template_id).send_mail(
                attendee.id, force_send=force_send)
Ejemplo n.º 18
0
class Message(models.Model):
    """ Messages model: system notification (replacing res.log notifications),
        comments (OpenChatter discussion) and incoming emails. """
    _name = 'mail.message'
    _description = 'Message'
    _inherit = ['ir.needaction_mixin']
    _order = 'id desc'
    _rec_name = 'record_name'

    _message_read_limit = 30

    @api.model
    def _get_default_from(self):
        if self.env.user.alias_name and self.env.user.alias_domain:
            return formataddr(
                (self.env.user.name, '%s@%s' %
                 (self.env.user.alias_name, self.env.user.alias_domain)))
        elif self.env.user.email:
            return formataddr((self.env.user.name, self.env.user.email))
        raise UserError(
            _("Unable to send email, please configure the sender's email address or alias."
              ))

    @api.model
    def _get_default_author(self):
        return self.env.user.partner_id

    # content
    subject = fields.Char('Subject')
    date = fields.Datetime('Date', default=fields.Datetime.now)
    body = fields.Html('Contents',
                       default='',
                       help='Automatically sanitized HTML contents')
    attachment_ids = fields.Many2many(
        'ir.attachment',
        'message_attachment_rel',
        'message_id',
        'attachment_id',
        string='Attachments',
        help=
        'Attachments are linked to a document through model / res_id and to the message'
        'through this field.')
    parent_id = fields.Many2one('mail.message',
                                'Parent Message',
                                select=True,
                                ondelete='set null',
                                help="Initial thread message.")
    child_ids = fields.One2many('mail.message', 'parent_id', 'Child Messages')
    # related document
    model = fields.Char('Related Document Model', select=1)
    res_id = fields.Integer('Related Document ID', select=1)
    record_name = fields.Char('Message Record Name',
                              help="Name get of the related document.")
    # characteristics
    message_type = fields.Selection(
        [('email', 'Email'), ('comment', 'Comment'),
         ('notification', 'System notification')],
        'Type',
        required=True,
        default='email',
        help="Message type: email for email message, notification for system "
        "message, comment for other messages such as user replies",
        oldname='type')
    subtype_id = fields.Many2one('mail.message.subtype',
                                 'Subtype',
                                 ondelete='set null',
                                 select=1)
    # origin
    email_from = fields.Char(
        'From',
        default=_get_default_from,
        help=
        "Email address of the sender. This field is set when no matching partner is found and replaces the author_id field in the chatter."
    )
    author_id = fields.Many2one(
        'res.partner',
        'Author',
        select=1,
        ondelete='set null',
        default=_get_default_author,
        help=
        "Author of the message. If not set, email_from may hold an email address that did not match any partner."
    )
    author_avatar = fields.Binary("Author's avatar",
                                  related='author_id.image_small')
    # recipients
    partner_ids = fields.Many2many('res.partner', string='Recipients')
    needaction_partner_ids = fields.Many2many(
        'res.partner',
        'mail_message_res_partner_needaction_rel',
        string='Need Action')
    needaction = fields.Boolean('Need Action',
                                compute='_get_needaction',
                                search='_search_needaction',
                                help='Need Action')
    channel_ids = fields.Many2many('mail.channel',
                                   'mail_message_mail_channel_rel',
                                   string='Channels')
    # user interface
    starred_partner_ids = fields.Many2many(
        'res.partner',
        'mail_message_res_partner_starred_rel',
        string='Favorited By')
    starred = fields.Boolean(
        'Starred',
        compute='_get_starred',
        search='_search_starred',
        help='Current user has a starred notification linked to this message')
    # tracking
    tracking_value_ids = fields.One2many(
        'mail.tracking.value',
        'mail_message_id',
        string='Tracking values',
        help=
        'Tracked values are stored in a separate model. This field allow to reconstruct'
        'the tracking and to generate statistics on the model.')
    # mail gateway
    no_auto_thread = fields.Boolean(
        'No threading for answers',
        help=
        'Answers do not go in the original document discussion thread. This has an impact on the generated message-id.'
    )
    message_id = fields.Char('Message-Id',
                             help='Message unique identifier',
                             select=1,
                             readonly=1,
                             copy=False)
    reply_to = fields.Char(
        'Reply-To',
        help=
        'Reply email address. Setting the reply_to bypasses the automatic thread creation.'
    )
    mail_server_id = fields.Many2one('ir.mail_server',
                                     'Outgoing mail server',
                                     readonly=1)

    @api.multi
    def _get_needaction(self):
        """ Need action on a mail.message = notified on my channel """
        my_messages = self.sudo().filtered(
            lambda msg: self.env.user.partner_id in msg.needaction_partner_ids)
        for message in self:
            message.needaction = message in my_messages

    @api.model
    def _search_needaction(self, operator, operand):
        if operator == '=' and operand:
            return [('needaction_partner_ids', 'in',
                     self.env.user.partner_id.id)]
        return [('needaction_partner_ids', 'not in',
                 self.env.user.partner_id.id)]

    @api.depends('starred_partner_ids')
    def _get_starred(self):
        """ Compute if the message is starred by the current user. """
        # TDE FIXME: use SQL
        starred = self.sudo().filtered(
            lambda msg: self.env.user.partner_id in msg.starred_partner_ids)
        for message in self:
            message.starred = message in starred

    @api.model
    def _search_starred(self, operator, operand):
        if operator == '=' and operand:
            return [('starred_partner_ids', 'in',
                     [self.env.user.partner_id.id])]
        return [('starred_partner_ids', 'not in',
                 [self.env.user.partner_id.id])]

    @api.model
    def _needaction_domain_get(self):
        return [('needaction', '=', True)]

    #------------------------------------------------------
    # Notification API
    #------------------------------------------------------

    @api.multi
    def set_message_needaction(self, partner_ids=None):
        if not partner_ids:
            partner_ids = [self.env.user.partner_id.id]
        return self.write(
            {'needaction_partner_ids': [(4, pid) for pid in partner_ids]})

    @api.multi
    def set_message_done(self, partner_ids=None):
        if not partner_ids:
            partner_ids = [self.env.user.partner_id.id]
        return self.write(
            {'needaction_partner_ids': [(3, pid) for pid in partner_ids]})

    @api.multi
    def set_message_starred(self, starred):
        """ Set messages as (un)starred. Technically, the notifications related
            to uid are set to (un)starred.

            :param bool starred: set notification as (un)starred
            :param bool create_missing: create notifications for missing entries
                (i.e. when acting on displayed messages not notified)
        """
        if starred:
            self.write(
                {'starred_partner_ids': [(4, self.env.user.partner_id.id)]})
        else:
            self.write(
                {'starred_partner_ids': [(3, self.env.user.partner_id.id)]})
        return starred

    #------------------------------------------------------
    # Message loading for web interface
    #------------------------------------------------------

    @api.model
    def _message_read_dict_postprocess(self, messages, message_tree):
        """ Post-processing on values given by message_read. This method will
            handle partners in batch to avoid doing numerous queries.

            :param list messages: list of message, as get_dict result
            :param dict message_tree: {[msg.id]: msg browse record}
        """
        pid = self.env.user.partner_id.id

        # 1. Aggregate partners (author_id and partner_ids), attachments and tracking values
        partners = self.env['res.partner']
        attachments = self.env['ir.attachment']
        trackings = self.env['mail.tracking.value']
        for key, message in message_tree.iteritems():
            if message.author_id:
                partners |= message.author_id
            if message.subtype_id and message.partner_ids:  # take notified people of message with a subtype
                partners |= message.partner_ids
            elif not message.subtype_id and message.partner_ids:  # take specified people of message without a subtype (log)
                partners |= message.partner_ids
            if message.attachment_ids:
                attachments |= message.attachment_ids
            if message.tracking_value_ids:
                trackings |= message.tracking_value_ids
        # Read partners as SUPERUSER -> display the names like classic m2o even if no access
        partners_names = partners.sudo().name_get()
        partner_tree = dict(
            (partner[0], partner) for partner in partners_names)

        # 2. Attachments as SUPERUSER, because could receive msg and attachments for doc uid cannot see
        attachments_data = attachments.sudo().read(
            ['id', 'datas_fname', 'name', 'mimetype'])
        attachments_tree = dict((attachment['id'], {
            'id': attachment['id'],
            'filename': attachment['datas_fname'],
            'name': attachment['name'],
            'mimetype': attachment['mimetype'],
        }) for attachment in attachments_data)

        # 3. Tracking values
        tracking_tree = dict((tracking.id, {
            'id': tracking.id,
            'changed_field': tracking.field_desc,
            'old_value': tracking.get_old_display_value()[0],
            'new_value': tracking.get_new_display_value()[0],
        }) for tracking in trackings)

        # 4. Update message dictionaries
        for message_dict in messages:
            message_id = message_dict.get('id')
            message = message_tree[message_id]
            if message.author_id:
                author = partner_tree[message.author_id.id]
            else:
                author = (0, message.email_from)
            partner_ids = []
            if message.subtype_id:
                partner_ids = [
                    partner_tree[partner.id] for partner in message.partner_ids
                    if partner.id in partner_tree
                ]
            else:
                partner_ids = [
                    partner_tree[partner.id] for partner in message.partner_ids
                    if partner.id in partner_tree
                ]
            attachment_ids = []
            for attachment in message.attachment_ids:
                if attachment.id in attachments_tree:
                    attachment_ids.append(attachments_tree[attachment.id])
            tracking_value_ids = []
            for tracking_value in message.tracking_value_ids:
                if tracking_value.id in tracking_tree:
                    tracking_value_ids.append(tracking_tree[tracking_value.id])

            message_dict.update({
                'is_author': pid == author[0],
                'author_id': author,
                'partner_ids': partner_ids,
                'attachment_ids': attachment_ids,
                'tracking_value_ids': tracking_value_ids,
                'user_pid': pid
            })

        return True

    @api.multi
    def _message_read_dict(self, parent_id=False):
        """ Return a dict representation of the message. This representation is
            used in the JS client code, to display the messages. Partners and
            attachments related stuff will be done in post-processing in batch.

            :param dict message: mail.message browse record
        """
        self.ensure_one()
        # private message: no model, no res_id
        is_private = False
        if not self.model or not self.res_id:
            is_private = True

        return {
            'id': self.id,
            'message_type': self.message_type,
            'subtype': self.subtype_id.name if self.subtype_id else False,
            'body': self.body,
            'model': self.model,
            'res_id': self.res_id,
            'record_name': self.record_name,
            'subject': self.subject,
            'date': self.date,
            'needaction': self.needaction,
            'parent_id': parent_id,
            'is_private': is_private,
            'author_id': False,
            'author_avatar': self.author_avatar,
            'is_author': False,
            'partner_ids': [],
            'is_favorite': self.starred,
            'attachment_ids': [],
            'tracking_value_ids': [],
        }

    @api.cr_uid_context
    def message_read_wrapper(self,
                             cr,
                             uid,
                             ids=None,
                             domain=None,
                             context=None,
                             thread_level=0,
                             parent_id=False,
                             limit=None,
                             child_limit=None):
        return self.message_read(cr,
                                 uid,
                                 ids,
                                 domain=domain,
                                 thread_level=thread_level,
                                 context=context,
                                 parent_id=parent_id,
                                 limit=limit,
                                 child_limit=child_limit)

    @api.multi
    def message_read(self,
                     domain=None,
                     thread_level=0,
                     context=None,
                     parent_id=False,
                     limit=None,
                     child_limit=None):
        """ Read messages from mail.message, and get back a list of structured
            messages to be displayed as discussion threads. If IDs is set,
            fetch these records. Otherwise use the domain to fetch messages.
            After having fetch messages, their ancestors will be added to obtain
            well formed threads, if uid has access to them.

            After reading the messages, expandable messages are added in the
            message list. It consists in messages holding the 'read more' data: 
            number of messages to read, domain to apply.

            :param list ids: optional IDs to fetch
            :param list domain: optional domain for searching ids if ids not set
            :param int parent_id: context of parent_id
                - if parent_id reached when adding ancestors, stop going further
                  in the ancestor search
                - if set in flat mode, ancestor_id is set to parent_id
            :param int limit: number of messages to fetch, before adding the
                ancestors and expandables
            :param int child_limit: number of child messages to fetch
            :return dict: 
                - int: number of messages read (status 'unread' to 'read')
                - list: list of threads [[messages_of_thread1], [messages_of_thread2]]
        """
        assert thread_level in [
            0, 1
        ], 'message_read() thread_level should be 0 (flat) or 1 (1 level of thread); given %s.' % thread_level

        domain = domain if domain is not None else []
        limit = limit or self._message_read_limit
        child_limit = child_limit or self._message_read_limit

        message_tree = {}
        parent_tree = {}
        child_ids = []
        parent_ids = []
        exp_domain = []

        # no specific IDS given: fetch messages according to the domain, add their parents if uid has access to
        if not self.ids and domain:
            self = self.search(domain, limit=limit)

        # fetch parent if threaded, sort messages
        for message in self:
            message_id = message.id
            if message_id in message_tree:
                continue
            message_tree[message_id] = message

            # find parent_id
            if thread_level == 0:
                tree_parent_id = parent_id
            else:
                tree_parent_id = message_id
                parent = message
                while parent.parent_id and parent.parent_id.id != parent_id:
                    parent = parent.parent_id
                    tree_parent_id = parent.id
                if parent.id not in message_tree:
                    message_tree[parent.id] = parent
            # newest messages first
            parent_tree.setdefault(tree_parent_id, [])

        # build thread structure
        # for each parent_id: get child messages, add message expandable and parent message if needed [child1, child2, expandable, parent_message]
        # add thread expandable if it remains some uncaught parent_id
        if self.ids and len(self.ids) > 0:
            for parent in parent_tree:
                parent_ids.append(parent)

                if not thread_level:
                    child_ids = self.ids
                    exp_domain = domain + [('id', '<', min(child_ids))]
                else:
                    child_ids = [
                        msg.id for msg in self.browse(parent).child_ids
                    ][0:child_limit]
                    exp_domain = [('parent_id', '=', parent),
                                  ('id', '>', parent)]
                    if len(child_ids):
                        exp_domain += [('id', '<', min(child_ids))]

                for cid in child_ids:
                    if cid not in message_tree:
                        message_tree[cid] = self.browse(cid)
                    parent_tree[parent].append(
                        message_tree[cid]._message_read_dict(parent_id=parent))

                if parent and thread_level:
                    parent_tree[parent].sort(key=lambda item: item['id'])
                    parent_tree[parent].reverse()
                    parent_tree[parent].append(
                        message_tree[parent]._message_read_dict())

                self._message_read_dict_postprocess(parent_tree[parent],
                                                    message_tree)

                # add 'message' expandable (inside a thread)
                more_count = self.search_count(exp_domain)
                if more_count:
                    exp = {
                        'message_type': 'expandable',
                        'domain': exp_domain,
                        'nb_messages': more_count,
                        'parent_id': parent
                    }

                    if parent and thread_level:
                        #insert expandable before parent message
                        parent_tree[parent].insert(
                            len(parent_tree[parent]) - 1, exp)
                    else:
                        #insert expandable at the end of the message list
                        parent_tree[parent].append(exp)

            # create final ordered parent_list based on parent_tree
            parent_list = parent_tree.values()
            parent_list = sorted(
                parent_list,
                key=lambda item: max([msg.get('id') for msg in item]),
                reverse=True)

            #add 'thread' expandable
            if thread_level:
                exp_domain = domain + [('id', '<', min(self.ids)),
                                       ('id', 'not in', parent_ids),
                                       ('parent_id', 'not in', parent_ids)]
                more_count = self.search_count(exp_domain)
                if more_count:
                    parent_list.append([{
                        'message_type': 'expandable',
                        'domain': exp_domain,
                        'nb_messages': more_count,
                        'parent_id': parent_id
                    }])

            nb_read = 0
            if context and 'mail_read_set_read' in context and context[
                    'mail_read_set_read']:
                nb_read = self.set_message_read(True, create_missing=False)

        else:
            nb_read = 0
            parent_list = []

        return {'nb_read': nb_read, 'threads': parent_list}

    #------------------------------------------------------
    # mail_message internals
    #------------------------------------------------------

    def init(self, cr):
        cr.execute(
            """SELECT indexname FROM pg_indexes WHERE indexname = 'mail_message_model_res_id_idx'"""
        )
        if not cr.fetchone():
            cr.execute(
                """CREATE INDEX mail_message_model_res_id_idx ON mail_message (model, res_id)"""
            )

    @api.model
    def _find_allowed_model_wise(self, doc_model, doc_dict):
        doc_ids = doc_dict.keys()
        allowed_doc_ids = self.env[doc_model].with_context(
            active_test=False).search([('id', 'in', doc_ids)]).ids
        return set([
            message_id for allowed_doc_id in allowed_doc_ids
            for message_id in doc_dict[allowed_doc_id]
        ])

    @api.model
    def _find_allowed_doc_ids(self, model_ids):
        IrModelAccess = self.env['ir.model.access']
        allowed_ids = set()
        for doc_model, doc_dict in model_ids.iteritems():
            if not IrModelAccess.check(doc_model, 'read', False):
                continue
            allowed_ids |= self._find_allowed_model_wise(doc_model, doc_dict)
        return allowed_ids

    @api.model
    def _search(self,
                args,
                offset=0,
                limit=None,
                order=None,
                count=False,
                access_rights_uid=None):
        """ Override that adds specific access rights of mail.message, to remove
        ids uid could not see according to our custom rules. Please refer to
        check_access_rule for more details about those rules.

        Non employees users see only message with subtype (aka do not see
        internal logs).

        After having received ids of a classic search, keep only:
        - if author_id == pid, uid is the author, OR
        - uid belongs to a notified channel, OR
        - uid is in the specified recipients, OR
        - uid have read access to the related document is model, res_id
        - otherwise: remove the id
        """
        # Rules do not apply to administrator
        if self._uid == SUPERUSER_ID:
            return super(Message,
                         self)._search(args,
                                       offset=offset,
                                       limit=limit,
                                       order=order,
                                       count=count,
                                       access_rights_uid=access_rights_uid)
        # Non-employee see only messages with a subtype (aka, no internal logs)
        if not self.env['res.users'].has_group('base.group_user'):
            args = [
                '&', '&', ('subtype_id', '!=', False),
                ('subtype_id.internal', '=', False)
            ] + list(args)
        # Perform a super with count as False, to have the ids, not a counter
        ids = super(Message, self)._search(args,
                                           offset=offset,
                                           limit=limit,
                                           order=order,
                                           count=False,
                                           access_rights_uid=access_rights_uid)
        if not ids and count:
            return 0
        elif not ids:
            return ids

        pid = self.env.user.partner_id.id
        author_ids, partner_ids, channel_ids, allowed_ids = set([]), set(
            []), set([]), set([])
        model_ids = {}

        # check read access rights before checking the actual rules on the given ids
        super(Message, self.sudo(access_rights_uid
                                 or self._uid)).check_access_rights('read')

        self._cr.execute(
            """SELECT DISTINCT m.id, m.model, m.res_id, m.author_id, partner_rel.res_partner_id, channel_partner.channel_id as channel_id
            FROM "%s" m
            LEFT JOIN "mail_message_res_partner_rel" partner_rel
            ON partner_rel.mail_message_id = m.id AND partner_rel.res_partner_id = (%%s)
            LEFT JOIN "mail_message_mail_channel_rel" channel_rel
            ON channel_rel.mail_message_id = m.id
            LEFT JOIN "mail_channel" channel
            ON channel.id = channel_rel.mail_channel_id
            LEFT JOIN "mail_channel_partner" channel_partner
            ON channel_partner.channel_id = channel.id AND channel_partner.partner_id = (%%s)
            WHERE m.id = ANY (%%s)""" % self._table, (
                pid,
                pid,
                ids,
            ))
        for id, rmod, rid, author_id, partner_id, channel_id in self._cr.fetchall(
        ):
            if author_id == pid:
                author_ids.add(id)
            elif partner_id == pid:
                partner_ids.add(id)
            elif channel_id:
                channel_ids.add(id)
            elif rmod and rid:
                model_ids.setdefault(rmod, {}).setdefault(rid, set()).add(id)

        allowed_ids = self._find_allowed_doc_ids(model_ids)

        final_ids = author_ids | partner_ids | channel_ids | allowed_ids

        if count:
            return len(final_ids)
        else:
            # re-construct a list based on ids, because set did not keep the original order
            id_list = [id for id in ids if id in final_ids]
            return id_list

    @api.multi
    def check_access_rule(self, operation):
        """ Access rules of mail.message:
            - read: if
                - author_id == pid, uid is the author OR
                - uid is in the recipients (partner_ids) OR
                - uid is member of a listern channel (channel_ids.partner_ids) OR
                - uid have read access to the related document if model, res_id
                - otherwise: raise
            - create: if
                - no model, no res_id (private message) OR
                - pid in message_follower_ids if model, res_id OR
                - uid can read the parent OR
                - uid have write or create access on the related document if model, res_id, OR
                - otherwise: raise
            - write: if
                - author_id == pid, uid is the author, OR
                - uid has write or create access on the related document if model, res_id
                - otherwise: raise
            - unlink: if
                - uid has write or create access on the related document if model, res_id
                - otherwise: raise

        Specific case: non employee users see only messages with subtype (aka do
        not see internal logs).
        """
        def _generate_model_record_ids(msg_val, msg_ids):
            """ :param model_record_ids: {'model': {'res_id': (msg_id, msg_id)}, ... }
                :param message_values: {'msg_id': {'model': .., 'res_id': .., 'author_id': ..}}
            """
            model_record_ids = {}
            for id in msg_ids:
                vals = msg_val.get(id, {})
                if vals.get('model') and vals.get('res_id'):
                    model_record_ids.setdefault(vals['model'],
                                                set()).add(vals['res_id'])
            return model_record_ids

        if self._uid == SUPERUSER_ID:
            return
        # Non employees see only messages with a subtype (aka, not internal logs)
        if not self.env['res.users'].has_group('base.group_user'):
            self._cr.execute(
                '''SELECT DISTINCT message.id, message.subtype_id, subtype.internal
                                FROM "%s" AS message
                                LEFT JOIN "mail_message_subtype" as subtype
                                ON message.subtype_id = subtype.id
                                WHERE message.message_type = %%s AND (message.subtype_id IS NULL OR subtype.internal IS TRUE) AND message.id = ANY (%%s)'''
                % (self._table), (
                    'comment',
                    self.ids,
                ))
            if self._cr.fetchall():
                raise AccessError(
                    _('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)'
                      ) % (self._description, operation))

        # Read mail_message.ids to have their values
        message_values = dict((res_id, {}) for res_id in self.ids)

        if operation == 'read':
            self._cr.execute(
                """SELECT DISTINCT m.id, m.model, m.res_id, m.author_id, m.parent_id, partner_rel.res_partner_id, channel_partner.channel_id as channel_id
                FROM "%s" m
                LEFT JOIN "mail_message_res_partner_rel" partner_rel
                ON partner_rel.mail_message_id = m.id AND partner_rel.res_partner_id = (%%s)
                LEFT JOIN "mail_message_mail_channel_rel" channel_rel
                ON channel_rel.mail_message_id = m.id
                LEFT JOIN "mail_channel" channel
                ON channel.id = channel_rel.mail_channel_id
                LEFT JOIN "mail_channel_partner" channel_partner
                ON channel_partner.channel_id = channel.id AND channel_partner.partner_id = (%%s)
                WHERE m.id = ANY (%%s)""" % self._table, (
                    self.env.user.partner_id.id,
                    self.env.user.partner_id.id,
                    self.ids,
                ))
            for mid, rmod, rid, author_id, parent_id, partner_id, channel_id in self._cr.fetchall(
            ):
                message_values[mid] = {
                    'model': rmod,
                    'res_id': rid,
                    'author_id': author_id,
                    'parent_id': parent_id,
                    'partner_id': partner_id,
                    'channel_id': channel_id
                }
        else:
            self._cr.execute(
                """SELECT DISTINCT id, model, res_id, author_id, parent_id FROM "%s" WHERE id = ANY (%%s)"""
                % self._table, (self.ids, ))
            for mid, rmod, rid, author_id, parent_id in self._cr.fetchall():
                message_values[mid] = {
                    'model': rmod,
                    'res_id': rid,
                    'author_id': author_id,
                    'parent_id': parent_id
                }

        # Author condition (READ, WRITE, CREATE (private))
        author_ids = []
        if operation == 'read' or operation == 'write':
            author_ids = [
                mid for mid, message in message_values.iteritems()
                if message.get('author_id')
                and message.get('author_id') == self.env.user.partner_id.id
            ]
        elif operation == 'create':
            author_ids = [
                mid for mid, message in message_values.iteritems()
                if not message.get('model') and not message.get('res_id')
            ]

        # Parent condition, for create (check for received notifications for the created message parent)
        notified_ids = []
        if operation == 'create':
            # TDE: probably clean me
            parent_ids = [
                message.get('parent_id')
                for mid, message in message_values.iteritems()
                if message.get('parent_id')
            ]
            self._cr.execute(
                """SELECT DISTINCT m.id FROM "%s" m
                LEFT JOIN "mail_message_res_partner_rel" partner_rel
                ON partner_rel.mail_message_id = m.id AND partner_rel.res_partner_id = (%%s)
                LEFT JOIN "mail_message_mail_channel_rel" channel_rel
                ON channel_rel.mail_message_id = m.id
                LEFT JOIN "mail_channel" channel
                ON channel.id = channel_rel.mail_channel_id
                LEFT JOIN "mail_channel_partner" channel_partner
                ON channel_partner.channel_id = channel.id AND channel_partner.partner_id = (%%s)
                WHERE m.id = ANY (%%s)""" % self._table, (
                    self.env.user.partner_id.id,
                    self.env.user.partner_id.id,
                    parent_ids,
                ))
            not_parent_ids = [mid[0] for mid in self._cr.fetchall()]
            notified_ids += [
                mid for mid, message in message_values.iteritems()
                if message.get('parent_id') in not_parent_ids
            ]

        # Notification condition, for read (check for received notifications and create (in message_follower_ids)) -> could become an ir.rule, but not till we do not have a many2one variable field
        other_ids = set(self.ids).difference(set(author_ids),
                                             set(notified_ids))
        model_record_ids = _generate_model_record_ids(message_values,
                                                      other_ids)
        if operation == 'read':
            notified_ids = [
                mid for mid, message in message_values.iteritems()
                if message.get('partner_id') or message.get('channel_id')
            ]
        elif operation == 'create':
            for doc_model, doc_ids in model_record_ids.items():
                followers = self.env['mail.followers'].sudo().search([
                    ('res_model', '=', doc_model),
                    ('res_id', 'in', list(doc_ids)),
                    ('partner_id', '=', self.env.user.partner_id.id),
                ])
                fol_mids = [follower.res_id for follower in followers]
                notified_ids += [
                    mid for mid, message in message_values.iteritems()
                    if message.get('model') == doc_model
                    and message.get('res_id') in fol_mids
                ]

        # CRUD: Access rights related to the document
        other_ids = other_ids.difference(set(notified_ids))
        model_record_ids = _generate_model_record_ids(message_values,
                                                      other_ids)
        document_related_ids = []
        for model, doc_ids in model_record_ids.items():
            DocumentModel = self.env[model]
            mids = DocumentModel.browse(doc_ids).exists()
            if hasattr(DocumentModel, 'check_mail_message_access'):
                DocumentModel.check_mail_message_access(mids.ids,
                                                        operation)  # ?? mids ?
            else:
                self.env['mail.thread'].check_mail_message_access(
                    mids.ids, operation, model_name=model)
            document_related_ids += [
                mid for mid, message in message_values.iteritems()
                if message.get('model') == model
                and message.get('res_id') in mids.ids
            ]

        # Calculate remaining ids: if not void, raise an error
        other_ids = other_ids.difference(set(document_related_ids))
        if not other_ids:
            return
        raise AccessError(
            _('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)'
              ) % (self._description, operation))

    @api.model
    def _get_record_name(self, values):
        """ Return the related document name, using name_get. It is done using
            SUPERUSER_ID, to be sure to have the record name correctly stored. """
        model = values.get('model', self.env.context.get('default_model'))
        res_id = values.get('res_id', self.env.context.get('default_res_id'))
        if not model or not res_id or model not in self.pool:
            return False
        return self.env[model].sudo().browse(res_id).name_get()[0][1]

    @api.model
    def _get_reply_to(self, values):
        """ Return a specific reply_to: alias of the document through
        message_get_reply_to or take the email_from """
        model, res_id, email_from = values.get(
            'model', self._context.get('default_model')), values.get(
                'res_id', self._context.get('default_res_id')), values.get(
                    'email_from')  # ctx values / defualt_get res ?
        if model:
            # return self.env[model].browse(res_id).message_get_reply_to([res_id], default=email_from)[res_id]
            return self.env[model].message_get_reply_to(
                [res_id], default=email_from)[res_id]
        else:
            # return self.env['mail.thread'].message_get_reply_to(default=email_from)[None]
            return self.env['mail.thread'].message_get_reply_to(
                [None], default=email_from)[None]

    @api.model
    def _get_message_id(self, values):
        if values.get('no_auto_thread', False) is True:
            message_id = tools.generate_tracking_message_id('reply_to')
        elif values.get('res_id') and values.get('model'):
            message_id = tools.generate_tracking_message_id(
                '%(res_id)s-%(model)s' % values)
        else:
            message_id = tools.generate_tracking_message_id('private')
        return message_id

    @api.model
    def create(self, values):
        # coming from mail.js that does not have pid in its values
        if self.env.context.get('default_starred'):
            self = self.with_context({
                'default_starred_partner_ids':
                [(4, self.env.user.partner_id.id)]
            })

        if 'email_from' not in values:  # needed to compute reply_to
            values['email_from'] = self._get_default_from()
        if not values.get('message_id'):
            values['message_id'] = self._get_message_id(values)
        if 'reply_to' not in values:
            values['reply_to'] = self._get_reply_to(values)
        if 'record_name' not in values and 'default_record_name' not in self.env.context:
            values['record_name'] = self._get_record_name(values)

        message = super(Message, self).create(values)

        message._notify(
            force_send=self.env.context.get('mail_notify_force_send', True),
            user_signature=self.env.context.get('mail_notify_user_signature',
                                                True))
        return message

    @api.multi
    def read(self, fields=None, load='_classic_read'):
        """ Override to explicitely call check_access_rule, that is not called
            by the ORM. It instead directly fetches ir.rules and apply them. """
        self.check_access_rule('read')
        return super(Message, self).read(fields=fields, load=load)

    @api.multi
    def unlink(self):
        # cascade-delete attachments that are directly attached to the message (should only happen
        # for mail.messages that act as parent for a standalone mail.mail record).
        self.check_access_rule('unlink')
        self.mapped('attachment_ids').filtered(
            lambda attach: attach.res_model == self._name and
            (attach.res_id in self.ids or attach.res_id == 0)).unlink()
        return super(Message, self).unlink()

    #------------------------------------------------------
    # Messaging API
    #------------------------------------------------------

    @api.multi
    def _notify(self, force_send=False, user_signature=True):
        """ Add the related record followers to the destination partner_ids if is not a private message.
            Call mail_notification.notify to manage the email sending
        """
        # TDE CHECK: add partners / channels as arguments to be able to notify a message with / without computation ??
        self.ensure_one()  # tde: not sure, just for testing, will see

        # all followers of the mail.message document have to be added as partners and notified
        # and filter to employees only if the subtype is internal
        if self.subtype_id and self.model and self.res_id:
            followers = self.env['mail.followers'].sudo().search([
                ('res_model', '=', self.model), ('res_id', '=', self.res_id)
            ]).filtered(lambda fol: self.subtype_id in fol.subtype_ids)
            # if self.subtype_id.internal:
            #     followers.filtered(lambda fol: fol.partner_id.user_ids and fol.partner_id.user_ids[0].has_group('base.group_user'))
            channels = self.channel_ids | followers.mapped('channel_id')
            partners = self.partner_ids | followers.mapped('partner_id')
        else:
            channels = self.channel_ids
            partners = self.partner_ids

        # remove me from notified partners
        if self.author_id:
            partners = partners - self.author_id

        # notify partners
        # TDE TODO: model-dependant ? (like customer -> always email ?)
        email_channels = channels.filtered(lambda channel: channel.email_send)
        self.env['res.partner'].sudo().search([
            '|', ('id', 'in', partners.ids),
            ('channel_ids', 'in', email_channels.ids),
            ('notify_email', '!=', 'none')
        ])._notify(
            self, force_send=force_send,
            user_signature=user_signature)  # TDE: clean those parameters
        # notify partners and channels
        channels._notify(self)

        # An error appear when a user receive a notification without notifying
        # the parent message -> add a read notification for the parent
        if self.parent_id:
            self.parent_id.invalidate_cache(
            )  # avoid access rights issues, as notifications are used for access

        # update message
        return self.write({
            'channel_ids': [(6, 0, channels.ids)],
            'needaction_partner_ids': [(6, 0, partners.ids)]
        })
Ejemplo n.º 19
0
class environment(models.Model):
    """"""
    # TODO agregar bloqueo de volver a estado cancel. Solo se debe poder
    # volver si no existe el path ni el source path y si no existen ambienets
    # activos

    _name = 'infrastructure.environment'
    _description = 'environment'
    _inherit = ['ir.needaction_mixin', 'mail.thread']

    _states_ = [
        # State machine: untitle
        ('draft', 'Draft'),
        ('active', 'Active'),
        ('cancel', 'Cancel'),
    ]

    number = fields.Integer(
        string='Number',
        required=True,
        readonly=True,
        states={'draft': [('readonly', False)]},
    )

    name = fields.Char(
        string='Name',
        readonly=True,
        required=True,
        size=16,
        states={'draft': [('readonly', False)]},
    )

    type = fields.Selection([(u'virtualenv', u'Virtualenv'),
                             (u'oerpenv', u'Oerpenv')],
                            string='Type',
                            readonly=True,
                            required=True,
                            states={'draft': [('readonly', False)]},
                            default='virtualenv')

    description = fields.Char(string='Description')

    partner_id = fields.Many2one(
        'res.partner',
        string='Partner',
        required=True,
        readonly=True,
        states={'draft': [('readonly', False)]},
    )

    environment_version_id = fields.Many2one(
        'infrastructure.environment_version',
        string='Version',
        required=True,
        readonly=True,
        states={'draft': [('readonly', False)]},
    )

    note = fields.Html(string='Note')

    color = fields.Integer(string='Color Index')

    install_server_command = fields.Char(string='Install Server Command',
                                         required=True,
                                         default='python setup.py install')

    state = fields.Selection(
        _states_,
        string="State",
        default='draft',
    )

    environment_repository_ids = fields.One2many(
        'infrastructure.environment_repository',
        'environment_id',
        string='Repositories',
    )

    server_id = fields.Many2one(
        'infrastructure.server',
        string='Server',
        ondelete='cascade',
        required=True,
        readonly=True,
        states={'draft': [('readonly', False)]},
    )

    instance_ids = fields.One2many('infrastructure.instance',
                                   'environment_id',
                                   string='Instances',
                                   context={'from_environment': True})

    sources_path = fields.Char(
        string='Sources Path',
        # compute='_get_env_paths',
        # store=True,
        readonly=True,
        required=True,
        states={'draft': [('readonly', False)]})

    backups_path = fields.Char(
        string='Backups Path',
        # compute='_get_env_paths',
        # store=True,
        readonly=True,
        required=True,
        states={'draft': [('readonly', False)]})

    path = fields.Char(
        string='Path',
        # compute='_get_path',
        # store=True,
        readonly=True,
        required=True,
        states={'draft': [('readonly', False)]})

    instance_count = fields.Integer(string='# Instances',
                                    compute='_get_instances')

    database_ids = fields.One2many('infrastructure.database',
                                   'environment_id',
                                   string='Databases')

    database_count = fields.Integer(string='# Databases',
                                    compute='_get_databases')

    sever_copied = fields.Boolean(string='Server Copied?',
                                  compute='_get_sever_copied')

    _track = {
        'state': {
            'infrastructure.environment_draft':
            lambda self, cr, uid, obj, ctx=None: obj['state'] == 'draft',
            'infrastructure.environment_active':
            lambda self, cr, uid, obj, ctx=None: obj['state'] == 'active',
            'infrastructure.environment_cancel':
            lambda self, cr, uid, obj, ctx=None: obj['state'] == 'cancel',
        },
    }

    @api.one
    @api.depends(
        'environment_repository_ids',
        'environment_repository_ids.path',
        'environment_repository_ids.server_repository_id.repository_id.is_server',
    )
    def _get_sever_copied(self):
        sever_copied = False
        servers = [
            x for x in self.environment_repository_ids
            if x.server_repository_id.repository_id.is_server and x.path
        ]
        if servers:
            sever_copied = True
        self.sever_copied = sever_copied

    @api.one
    @api.depends('database_ids')
    def _get_databases(self):
        self.database_count = len(self.database_ids)

    @api.one
    @api.depends('instance_ids')
    def _get_instances(self):
        self.instance_count = len(self.instance_ids)

    @api.one
    @api.constrains('number')
    def _check_number(self):
        if not self.number or self.number < 10 or self.number > 99:
            raise Warning(_('Number should be between 10 and 99'))

    @api.one
    def unlink(self):
        if self.state not in ('draft', 'cancel'):
            raise Warning(
                _('You cannot delete a environment which is not \
                    draft or cancelled.'))
        return super(environment, self).unlink()

    @api.one
    @api.onchange('server_id')
    def _get_number(self):
        environments = self.search(
            [('server_id', '=', self.server_id.id)],
            order='number desc',
        )
        self.number = environments and environments[0].number + 1 or 10

    # No funciona como debe
    # @api.one
    # @api.onchange('server_id', 'environment_version_id')
    # def _get_repositories(self):
    #     if not self.server_id or not self.environment_version_id:
    #         return False
    #     server_default_repositories = [
    #         x for x in self.server_id.server_repository_ids if x.repository_id.default_in_new_env]
    #     server_actual_repositories = [
    #         x for x in self.environment_repository_ids]
    #     server_new_repositories = list(
    #         set(server_default_repositories) - set(server_actual_repositories))
    #     server_rep_vals = []

    #     default_branch_id = self.environment_version_id.default_branch_id.id
    #     for server_repository in server_new_repositories:
    #         branch_ids = server_repository.repository_id.branch_ids
    #         repo_branch_ids = [
    #             x.id for x in server_repository.repository_id.branch_ids]
    #         branch_id = branch_ids and branch_ids.ids[0] or False
    #         if default_branch_id and default_branch_id in repo_branch_ids:
    #             branch_id = default_branch_id
    #         vals = {
    #             'server_repository_id': server_repository.id,
    #             'branch_id': branch_id,
    #             'environment_id': self.id,
    #         }
    #         server_rep_vals.append([0, False, vals])
    #     self.environment_repository_ids = server_rep_vals

    @api.one
    @api.onchange('name', 'server_id')
    def _get_path(self):
        path = False
        if self.server_id.base_path and self.name:
            path = os.path.join(self.server_id.base_path, self.name)
        self.path = path

    @api.one
    @api.onchange('path')
    def _get_env_paths(self):
        sources_path = False
        backups_path = False
        if self.path:
            sources_path = os.path.join(self.path, 'sources')
            backups_path = os.path.join(self.path, 'backups')
        self.sources_path = sources_path
        self.backups_path = backups_path

    @api.one
    def make_environment(self):
        if self.type == 'virtualenv':
            self.server_id.get_env()
            if exists(self.path, use_sudo=True):
                raise Warning(
                    _("It seams that the environment already exists \
                        because there is a folder '%s'") % (self.path))
            sudo('virtualenv ' + self.path)
        else:
            raise Warning(_("Type '%s' not implemented yet.") % (self.type))

    @api.one
    def make_env_paths(self):
        self.server_id.get_env()
        if exists(self.sources_path, use_sudo=True):
            raise Warning(
                _("Folder '%s' already exists") % (self.sources_path))
        sudo('mkdir -p ' + self.sources_path)
        if exists(self.backups_path, use_sudo=True):
            raise Warning(
                _("Folder '%s' already exists") % (self.backups_path))
        sudo('mkdir -p ' + self.backups_path)

    @api.one
    @api.returns('infrastructure.environment_repository')
    def check_repositories(self):
        self.server_id.get_env()
        environment_repository = False
        for repository in self.environment_repository_ids:
            if repository.server_repository_id.repository_id.is_server:
                environment_repository = repository
        if not environment_repository:
            raise Warning(
                _("No Server Repository Found on actual environment"))
        if not exists(environment_repository.path, use_sudo=True):
            raise Warning(
                _("Server Path '%s' does not exist, check \
                server repository path. It is probable that repositories \
                have not been copied to this environment yet.") %
                (environment_repository.path))
        return environment_repository

    @api.one
    def install_odoo(self):
        # TODO agregar que si ya existe openerp tal vez haya que borrar y
        # volver a crearlo
        if self.type == 'virtualenv':
            self.server_id.get_env()
            environment_repository = self.check_repositories()
            with cd(environment_repository.path):
                sudo(
                    'source ' + os.path.join(
                        self.path, 'bin/activate') + ' && ' + \
                    environment_repository.server_repository_id.repository_id.install_server_command)
            self.change_path_group_and_perm()
        else:
            raise Warning(_("Type '%s' not implemented yet.") % (self.type))

    @api.one
    def change_path_group_and_perm(self):
        self.server_id.get_env()
        try:
            sudo('chown -R :%s %s' %
                 (self.server_id.instance_user_group, self.path))
            sudo('chmod -R g+rw %s' % (self.path))
        except:
            raise Warning(
                _("Error changing group '%s' to path '%s'.\
             Please verifify that group and path exists") %
                (self.server_id.instance_user_group, self.path))

    @api.multi
    def create_environment(self):
        self.make_environment()
        self.make_env_paths()
        self.signal_workflow('sgn_to_active')

    @api.multi
    def delete(self):
        if self.instance_ids:
            raise Warning(
                _('You can not delete an environment that has instances'))
        self.server_id.get_env()
        paths = [self.sources_path, self.backups_path, self.path]
        for path in paths:
            sudo('rm -f -r ' + path)
        self.signal_workflow('sgn_cancel')

    def action_wfk_set_draft(self, cr, uid, ids, *args):
        self.write(cr, uid, ids, {'state': 'draft'})
        wf_service = netsvc.LocalService("workflow")
        for obj_id in ids:
            wf_service.trg_delete(uid, 'infrastructure.environment', obj_id,
                                  cr)
            wf_service.trg_create(uid, 'infrastructure.environment', obj_id,
                                  cr)
        return True

    _sql_constraints = [
        ('name_uniq', 'unique(name, server_id)',
         'Name must be unique per server!'),
        ('path_uniq', 'unique(path, server_id)',
         'Path must be unique per server!'),
        ('sources_path_uniq', 'unique(path, server_id)',
         'Sources Path must be unique per server!'),
        ('sources_number', 'unique(number, server_id)',
         'Number must be unique per server!'),
    ]

    @api.multi
    def action_view_instances(self):
        '''
        This function returns an action that display a form or tree view
        '''
        instances = self.instance_ids.search([('environment_id', 'in',
                                               self.ids)])
        action = self.env['ir.model.data'].xmlid_to_object(
            'infrastructure.action_infrastructure_instance_instances')

        if not action:
            return False
        res = action.read()[0]
        res['domain'] = [('id', 'in', instances.ids)]
        if len(self) == 1:
            res['context'] = {'default_environment_id': self.id}
        if not len(instances.ids) > 1:
            form_view_id = self.env['ir.model.data'].xmlid_to_res_id(
                'infrastructure.view_infrastructure_instance_form')
            res['views'] = [(form_view_id, 'form')]
            # if 1 then we send res_id, if 0 open a new form view
            res['res_id'] = instances and instances.ids[0] or False
        return res

    @api.multi
    def action_view_databases(self):
        '''
        This function returns an action that display a form or tree view
        '''
        databases = self.database_ids.search([('environment_id', 'in',
                                               self.ids)])
        action = self.env['ir.model.data'].xmlid_to_object(
            'infrastructure.action_infrastructure_database_databases')

        if not action:
            return False
        res = action.read()[0]
        res['domain'] = [('id', 'in', databases.ids)]
        if len(self) == 1:
            res['context'] = {'default_server_id': self.id}
        if not len(databases.ids) > 1:
            form_view_id = self.env['ir.model.data'].xmlid_to_res_id(
                'infrastructure.view_infrastructure_database_form')
            res['views'] = [(form_view_id, 'form')]
            # if 1 then we send res_id, if 0 open a new form view
            res['res_id'] = databases and databases.ids[0] or False
        return res
Ejemplo n.º 20
0
class CompetitionCompetition(models.Model):
    """Competition"""
    _name = 'competition.competition'
    _description = 'Competicion'
    _inherit = ['mail.thread', 'ir.needaction_mixin']
    _order = 'name'

    name = fields.Char(string='Nombre',
                       translate=True,
                       required=True,
                       readonly=False,
                       states={'closed': [('readonly', True)]})
    type = fields.Many2one('competition.type',
                           string='Tipo de competición',
                           readonly=False,
                           states={'closed': [('readonly', True)]})
    description = fields.Html(string='Descripción',
                              translate=True,
                              readonly=False,
                              states={'closed': [('readonly', True)]})
    organizer_id = fields.Many2one(
        'res.partner',
        string='Organizador',
        default=lambda self: self.env.user.company_id.partner_id)
    default_reply_to = fields.Char(
        string='Responder a por defecto',
        help=
        "Dirección de correo del organizador que se asigna al campo 'responder a' de todos los mensajes enviados automáticamente al confirmar la competición o el registro."
    )
    default_email_competition = fields.Many2one(
        'email.template', string='Mensaje de confirmación de competición')
    default_email_registration = fields.Many2one(
        'email.template', string='Mensaje de confirmación de registro')
    default_seats_min = fields.Integer(string='Núm. plazas mínimo por defecto',
                                       default=0)
    default_seats_max = fields.Integer(string='Núm. plazas máximo por defecto',
                                       default=0)
    default_registration_type = fields.Selection(
        selection=[
            ('individual', 'Individual'),
            ('team', 'Team'),
        ],
        string='Tipo de registro por defecto',
        default='team',
        required=True,
        readonly=True,
        states={'draft': [('readonly', False)]})
    state = fields.Selection(
        [('draft', 'Borrador'), ('open', 'Abierta'), ('close', 'Cerrada'),
         ('cancel', 'Cancelada')],
        string='Estado',
        default='draft',
        readonly=True,
        required=True,
        copy=False,
        help=
        "Cuando se crea la competición el estado es 'Borrador'. Si la competición está activa el estado es 'Abierta', se pueden crear ediciones. Si la competición está cerrada, el estado es 'Cerrada' y no pueden crearse nuevas ediciones. Si la competición se cancela, se establece el estado a 'Cancelada', no se visualizará en la web."
    )
    edition_ids = fields.One2many('competition.edition',
                                  'competition_id',
                                  string='Ediciones',
                                  readonly=False,
                                  states={
                                      'close': [('readonly', True)],
                                      'cancel': [('readonly', True)]
                                  })
    company_id = fields.Many2one(
        'res.company',
        string='Empresa',
        change_default=True,
        default=lambda self: self.env['res.company']._company_default_get(
            'competition.competition'),
        required=False,
        readonly=True,
        states={'draft': [('readonly', False)]})
    active = fields.Boolean('Activo')

    _defaults = {
        'active': True,
    }

    @api.one
    def button_draft(self):
        self.state = 'draft'

    @api.one
    def button_cancel(self):
        self.edition_ids.write({'state': 'cancel'})
        self.state = 'cancel'

    @api.one
    def button_close(self):
        self.state = 'close'

    @api.one
    def button_open(self):
        self.state = 'open'
Ejemplo n.º 21
0
class ResUsers(models.Model):
    _inherit = 'res.users'

    signature_id = fields.Many2one('res.users.signature', string='Signature template', help='Keep empty to edit signature manually')

        'signature': old_fields.Html('Signature', sanitize=False)
Ejemplo n.º 22
0
class event_track(models.Model):
    _name = "event.track"
    _description = 'Event Track'
    _order = 'priority, date'
    _inherit = [
        'mail.thread', 'ir.needaction_mixin', 'website.seo.metadata',
        'website.published.mixin'
    ]

    name = fields.Char('Title', required=True, translate=True)
    user_id = fields.Many2one('res.users',
                              'Responsible',
                              track_visibility='onchange',
                              default=lambda self: self.env.user)
    partner_id = fields.Many2one('res.partner', 'Proposed by')
    partner_name = fields.Char('Partner Name')
    partner_email = fields.Char('Partner Email')
    partner_phone = fields.Char('Partner Phone')
    partner_biography = fields.Html('Partner Biography')
    speaker_ids = fields.Many2many('res.partner', string='Speakers')
    tag_ids = fields.Many2many('event.track.tag', string='Tags')
    state = fields.Selection([('draft', 'Proposal'),
                              ('confirmed', 'Confirmed'),
                              ('announced', 'Announced'),
                              ('published', 'Published'),
                              ('refused', 'Refused'), ('cancel', 'Cancelled')],
                             'Status',
                             default='draft',
                             required=True,
                             copy=False,
                             track_visibility='onchange')
    description = fields.Html('Track Description', translate=True)
    date = fields.Datetime('Track Date')
    duration = fields.Float('Duration', digits=(16, 2), default=1.5)
    location_id = fields.Many2one('event.track.location', 'Room')
    event_id = fields.Many2one('event.event', 'Event', required=True)
    color = fields.Integer('Color Index')
    priority = fields.Selection([('0', 'Low'), ('1', 'Medium'), ('2', 'High'),
                                 ('3', 'Highest')],
                                'Priority',
                                required=True,
                                default='1')
    image = fields.Binary('Image',
                          compute='_compute_image',
                          readonly=True,
                          store=True)

    @api.one
    @api.depends('speaker_ids.image')
    def _compute_image(self):
        if self.speaker_ids:
            self.image = self.speaker_ids[0].image
        else:
            self.image = False

    @api.model
    def create(self, vals):
        res = super(event_track, self).create(vals)
        res.message_subscribe(res.speaker_ids.ids)
        res.event_id.message_post(body="""<h3>%(header)s</h3>
<ul>
    <li>%(proposed_by)s</li>
    <li>%(mail)s</li>
    <li>%(phone)s</li>
    <li>%(title)s</li>
    <li>%(speakers)s</li>
    <li>%(introduction)s</li>
</ul>""" % {
            'header':
            _('New Track Proposal'),
            'proposed_by':
            '<b>%s</b>: %s' %
            (_('Proposed By'),
             (res.partner_id.name or res.partner_name or res.partner_email)),
            'mail':
            '<b>%s</b>: %s' % (_('Mail'), '<a href="mailto:%s">%s</a>' %
                               (res.partner_email, res.partner_email)),
            'phone':
            '<b>%s</b>: %s' % (_('Phone'), res.partner_phone),
            'title':
            '<b>%s</b>: %s' % (_('Title'), res.name),
            'speakers':
            '<b>%s</b>: %s' % (_('Speakers Biography'), res.partner_biography),
            'introduction':
            '<b>%s</b>: %s' % (_('Talk Introduction'), res.description),
        },
                                  subtype='event.mt_event_track')
        return res

    @api.multi
    def write(self, vals):
        if vals.get('state') == 'published':
            vals.update({'website_published': True})
        res = super(event_track, self).write(vals)
        if vals.get('speaker_ids'):
            self.message_subscribe([
                speaker['id'] for speaker in self.resolve_2many_commands(
                    'speaker_ids', vals['speaker_ids'], ['id'])
            ])
        return res

    @api.multi
    @api.depends('name')
    def _website_url(self, field_name, arg):
        res = super(event_track, self)._website_url(field_name, arg)
        res.update({
            (track.id,
             '/event/%s/track/%s' % (slug(track.event_id), slug(track)))
            for track in self
        })
        return res

    def read_group(self,
                   cr,
                   uid,
                   domain,
                   fields,
                   groupby,
                   offset=0,
                   limit=None,
                   context=None,
                   orderby=False,
                   lazy=True):
        """ Override read_group to always display all states. """
        if groupby and groupby[0] == "state":
            # Default result structure
            # states = self._get_state_list(cr, uid, context=context)
            states = [('draft', 'Proposal'), ('confirmed', 'Confirmed'),
                      ('announced', 'Announced'), ('published', 'Published'),
                      ('cancel', 'Cancelled')]
            read_group_all_states = [{
                '__context': {
                    'group_by': groupby[1:]
                },
                '__domain':
                domain + [('state', '=', state_value)],
                'state':
                state_value,
                'state_count':
                0,
            } for state_value, state_name in states]
            # Get standard results
            read_group_res = super(event_track,
                                   self).read_group(cr,
                                                    uid,
                                                    domain,
                                                    fields,
                                                    groupby,
                                                    offset=offset,
                                                    limit=limit,
                                                    context=context,
                                                    orderby=orderby)
            # Update standard results with default results
            result = []
            for state_value, state_name in states:
                res = filter(lambda x: x['state'] == state_value,
                             read_group_res)
                if not res:
                    res = filter(lambda x: x['state'] == state_value,
                                 read_group_all_states)
                if state_value == 'cancel':
                    res[0]['__fold'] = True
                res[0]['state'] = [state_value, state_name]
                result.append(res[0])
            return result
        else:
            return super(event_track, self).read_group(cr,
                                                       uid,
                                                       domain,
                                                       fields,
                                                       groupby,
                                                       offset=offset,
                                                       limit=limit,
                                                       context=context,
                                                       orderby=orderby)

    def open_track_speakers_list(self, cr, uid, track_id, context=None):
        track_id = self.browse(cr, uid, track_id, context=context)
        return {
            'name':
            _('Speakers'),
            'domain':
            [('id', 'in', [partner.id for partner in track_id.speaker_ids])],
            'view_type':
            'form',
            'view_mode':
            'kanban,form',
            'res_model':
            'res.partner',
            'view_id':
            False,
            'type':
            'ir.actions.act_window',
        }
Ejemplo n.º 23
0
class Forum(models.Model):
    _name = 'forum.forum'
    _description = 'Forum'
    _inherit = ['mail.thread', 'website.seo.metadata']

    def init(self, cr):
        """ Add forum uuid for user email validation.

        TDE TODO: move me somewhere else, auto_init ? """
        forum_uuids = self.pool['ir.config_parameter'].search(
            cr, SUPERUSER_ID, [('key', '=', 'website_forum.uuid')])
        if not forum_uuids:
            self.pool['ir.config_parameter'].set_param(cr, SUPERUSER_ID,
                                                       'website_forum.uuid',
                                                       str(uuid.uuid4()),
                                                       ['base.group_system'])

    @api.model
    def _get_default_faq(self):
        fname = modules.get_module_resource('website_forum', 'data',
                                            'forum_default_faq.html')
        with open(fname, 'r') as f:
            return f.read()
        return False

    # description and use
    name = fields.Char('Forum Name', required=True, translate=True)
    faq = fields.Html('Guidelines', default=_get_default_faq, translate=True)
    description = fields.Text(
        'Description',
        translate=True,
        default=
        'This community is for professionals and enthusiasts of our products and services.'
        'Share and discuss the best content and new marketing ideas,'
        'build your professional profile and become a better marketer together.'
    )
    welcome_message = fields.Html(
        'Welcome Message',
        default=
        """<section class="bg-info" style="height: 168px;"><div class="container">
                        <div class="row">
                            <div class="col-md-12">
                                <h1 class="text-center" style="text-align: left;">Welcome!</h1>
                                <p class="text-muted text-center" style="text-align: left;">This community is for professionals and enthusiasts of our products and services. Share and discuss the best content and new marketing ideas, build your professional profile and become a better marketer together.</p>
                            </div>
                            <div class="col-md-12">
                                <a href="#" class="js_close_intro">Hide Intro</a>    <a class="btn btn-primary forum_register_url" href="/web/login">Register</a> </div>
                            </div>
                        </div>
                    </section>""")
    default_order = fields.Selection([('create_date desc', 'Newest'),
                                      ('write_date desc', 'Last Updated'),
                                      ('vote_count desc', 'Most Voted'),
                                      ('relevancy desc', 'Relevance'),
                                      ('child_count desc', 'Answered')],
                                     string='Default Order',
                                     required=True,
                                     default='write_date desc')
    relevancy_post_vote = fields.Float(
        'First Relevance Parameter',
        default=0.8,
        help=
        "This formula is used in order to sort by relevance. The variable 'votes' represents number of votes for a post, and 'days' is number of days since the post creation"
    )
    relevancy_time_decay = fields.Float('Second Relevance Parameter',
                                        default=1.8)
    default_post_type = fields.Selection([('question', 'Question'),
                                          ('discussion', 'Discussion'),
                                          ('link', 'Link')],
                                         string='Default Post',
                                         required=True,
                                         default='question')
    allow_question = fields.Boolean(
        'Questions',
        help=
        "Users can answer only once per question. Contributors can edit answers and mark the right ones.",
        default=True)
    allow_discussion = fields.Boolean('Discussions', default=True)
    allow_link = fields.Boolean(
        'Links',
        help="When clicking on the post, it redirects to an external link",
        default=True)
    allow_bump = fields.Boolean(
        'Allow Bump',
        default=True,
        help='Posts without answers older than 10 days will display a popup'
        'when displayed on the website to propose users to share them '
        'on social networks, bumping the question at top of the thread.')
    allow_share = fields.Boolean(
        'Sharing Options',
        default=True,
        help='After posting the user will be proposed to share its question'
        'or answer on social networks, enabling social network propagation'
        'of the forum content.')
    # karma generation
    karma_gen_question_new = fields.Integer(string='Asking a question',
                                            default=2)
    karma_gen_question_upvote = fields.Integer(string='Question upvoted',
                                               default=5)
    karma_gen_question_downvote = fields.Integer(string='Question downvoted',
                                                 default=-2)
    karma_gen_answer_upvote = fields.Integer(string='Answer upvoted',
                                             default=10)
    karma_gen_answer_downvote = fields.Integer(string='Answer downvoted',
                                               default=-2)
    karma_gen_answer_accept = fields.Integer(string='Accepting an answer',
                                             default=2)
    karma_gen_answer_accepted = fields.Integer(string='Answer accepted',
                                               default=15)
    karma_gen_answer_flagged = fields.Integer(string='Answer flagged',
                                              default=-100)
    # karma-based actions
    karma_ask = fields.Integer(string='Ask questions', default=3)
    karma_answer = fields.Integer(string='Answer questions', default=3)
    karma_edit_own = fields.Integer(string='Edit own posts', default=1)
    karma_edit_all = fields.Integer(string='Edit all posts', default=300)
    karma_close_own = fields.Integer(string='Close own posts', default=100)
    karma_close_all = fields.Integer(string='Close all posts', default=500)
    karma_unlink_own = fields.Integer(string='Delete own posts', default=500)
    karma_unlink_all = fields.Integer(string='Delete all posts', default=1000)
    karma_upvote = fields.Integer(string='Upvote', default=5)
    karma_downvote = fields.Integer(string='Downvote', default=50)
    karma_answer_accept_own = fields.Integer(
        string='Accept an answer on own questions', default=20)
    karma_answer_accept_all = fields.Integer(
        string='Accept an answer to all questions', default=500)
    karma_comment_own = fields.Integer(string='Comment own posts', default=1)
    karma_comment_all = fields.Integer(string='Comment all posts', default=1)
    karma_comment_convert_own = fields.Integer(
        string='Convert own answers to comments and vice versa', default=50)
    karma_comment_convert_all = fields.Integer(
        string='Convert all answers to answers and vice versa', default=500)
    karma_comment_unlink_own = fields.Integer(string='Unlink own comments',
                                              default=50)
    karma_comment_unlink_all = fields.Integer(string='Unlink all comments',
                                              default=500)
    karma_retag = fields.Integer(string='Change question tags', default=75)
    karma_flag = fields.Integer(string='Flag a post as offensive', default=500)
    karma_dofollow = fields.Integer(
        string='Nofollow links',
        help=
        'If the author has not enough karma, a nofollow attribute is added to links',
        default=500)
    karma_editor = fields.Integer(string='Editor Features: image and links',
                                  default=30,
                                  oldname='karma_editor_link_files')
    karma_user_bio = fields.Integer(string='Display detailed user biography',
                                    default=750)

    @api.one
    @api.constrains('allow_question', 'allow_discussion', 'allow_link',
                    'default_post_type')
    def _check_default_post_type(self):
        if (self.default_post_type == 'question' and not self.allow_question) \
                or (self.default_post_type == 'discussion' and not self.allow_discussion) \
                or (self.default_post_type == 'link' and not self.allow_link):
            raise UserError(
                _('You cannot choose %s as default post since the forum does not allow it.'
                  % self.default_post_type))

    @api.one
    @api.constrains('allow_link', 'allow_question', 'allow_discussion',
                    'default_post_type')
    def _check_default_post_type(self):
        if self.default_post_type == 'link' and not self.allow_link or self.default_post_type == 'question' and not self.allow_question or self.default_post_type == 'discussion' and not self.allow_discussion:
            raise Warning(_('Post type in "Default post" must be activated'))

    @api.model
    def create(self, values):
        return super(Forum,
                     self.with_context(mail_create_nolog=True)).create(values)

    @api.model
    def _tag_to_write_vals(self, tags=''):
        User = self.env['res.users']
        Tag = self.env['forum.tag']
        post_tags = []
        existing_keep = []
        for tag in filter(None, tags.split(',')):
            if tag.startswith('_'):  # it's a new tag
                # check that not arleady created meanwhile or maybe excluded by the limit on the search
                tag_ids = Tag.search([('name', '=', tag[1:])])
                if tag_ids:
                    existing_keep.append(int(tag_ids[0]))
                else:
                    # check if user have Karma needed to create need tag
                    user = User.sudo().browse(self._uid)
                    if user.exists() and user.karma >= self.karma_retag:
                        post_tags.append((0, 0, {
                            'name': tag[1:],
                            'forum_id': self.id
                        }))
            else:
                existing_keep.append(int(tag))
        post_tags.insert(0, [6, 0, existing_keep])
        return post_tags

    def get_tags_first_char(self):
        """ get set of first letter of forum tags """
        tags = self.env['forum.tag'].search([('forum_id', '=', self.id),
                                             ('posts_count', '>', 0)])
        return sorted(set([tag.name[0].upper() for tag in tags]))
Ejemplo n.º 24
0
class ProjectTask(models.Model):
    _inherit = "project.task"

    corrective_action = fields.Html(string='Corrective Action')
Ejemplo n.º 25
0
class SaasPortalPlan(models.Model):
    _name = 'saas_portal.plan'

    name = fields.Char('Plan', required=True)
    summary = fields.Char('Summary')
    template_id = fields.Many2one('saas_portal.database',
                                  'Template',
                                  ondelete='restrict')
    demo = fields.Boolean('Install Demo Data')
    maximum_allowed_db_per_partner = fields.Integer(
        help='maximum allowed databases per customer', default=0)

    max_users = fields.Char('Initial Max users', default='0')
    total_storage_limit = fields.Integer('Total storage limit (MB)')
    block_on_expiration = fields.Boolean('Block clients on expiration',
                                         default=False)
    block_on_storage_exceed = fields.Boolean('Block clients on storage exceed',
                                             default=False)

    def _get_default_lang(self):
        return self.env.lang

    def _default_tz(self):
        return self.env.user.tz

    lang = fields.Selection(scan_languages(),
                            'Language',
                            default=_get_default_lang)
    tz = fields.Selection(_tz_get, 'TimeZone', default=_default_tz)
    sequence = fields.Integer('Sequence')
    state = fields.Selection([('draft', 'Draft'), ('confirmed', 'Confirmed')],
                             'State',
                             compute='_get_state',
                             store=True)
    expiration = fields.Integer('Expiration (hours)',
                                help='time to delete database. Use for demo')
    _order = 'sequence'

    dbname_template = fields.Char(
        'DB Names',
        help=
        'Used for generating client database domain name. Use %i for numbering. Ignore if you use manually created db names',
        placeholder='crm-%i.odoo.com')
    server_id = fields.Many2one('saas_portal.server',
                                string='SaaS Server',
                                ondelete='restrict',
                                help='User this saas server or choose random')

    website_description = fields.Html('Website description')
    logo = fields.Binary('Logo')

    @api.one
    @api.depends('template_id.state')
    def _get_state(self):
        if self.template_id.state == 'template':
            self.state = 'confirmed'
        else:
            self.state = 'draft'

    @api.one
    def _new_database_vals(self, vals):
        vals['max_users'] = self.max_users
        vals['total_storage_limit'] = self.total_storage_limit
        vals['block_on_expiration'] = self.block_on_expiration
        vals['block_on_storage_exceed'] = self.block_on_storage_exceed
        return vals

    @api.multi
    def create_new_database(self,
                            dbname=None,
                            client_id=None,
                            partner_id=None,
                            user_id=None,
                            notify_user=False,
                            trial=False,
                            support_team_id=None):
        self.ensure_one()
        db_count = self.env['saas_portal.client'].search_count([
            ('partner_id', '=', partner_id), ('state', '=', 'open'),
            ('plan_id', '=', self.id)
        ])
        if self.maximum_allowed_db_per_partner != 0 and db_count >= self.maximum_allowed_db_per_partner:
            raise MaximumDBException

        server = self.server_id
        if not server:
            server = self.env['saas_portal.server'].get_saas_server()

        server.action_sync_server()

        vals = {
            'name': dbname or self.generate_dbname()[0],
            'server_id': server.id,
            'plan_id': self.id,
            'partner_id': partner_id,
            'trial': trial,
            'support_team_id': support_team_id,
        }
        client = None
        if client_id:
            vals['client_id'] = client_id
            client = self.env['saas_portal.client'].search([('client_id', '=',
                                                             client_id)])

        vals = self._new_database_vals(vals)[0]

        if client:
            client.write(vals)
        else:
            client = self.env['saas_portal.client'].create(vals)
        client_id = client.client_id

        scheme = server.request_scheme
        port = server.request_port
        if user_id:
            owner_user = self.env['res.users'].browse(user_id)
        else:
            owner_user = self.env.user
        owner_user_data = {
            'user_id': owner_user.id,
            'login': owner_user.login,
            'name': owner_user.name,
            'email': owner_user.email,
        }
        trial_expiration_datetime = datetime.strptime(
            client.create_date, DEFAULT_SERVER_DATETIME_FORMAT) + timedelta(
                hours=self.expiration)  # for trial
        state = {
            'd': client.name,
            'e': trial and trial_expiration_datetime or client.create_date,
            'r': '%s://%s:%s/web' % (scheme, client.name, port),
            'owner_user': owner_user_data,
            't': client.trial,
        }
        if self.template_id:
            state.update({'db_template': self.template_id.name})
        scope = ['userinfo', 'force_login', 'trial', 'skiptheuse']
        url = server._request_server(
            path='/saas_server/new_database',
            scheme=scheme,
            port=port,
            state=state,
            client_id=client_id,
            scope=scope,
        )[0]
        res = requests.get(url,
                           verify=(self.server_id.request_scheme == 'https'
                                   and self.server_id.verify_ssl))
        if res.status_code != 200:
            # TODO /saas_server/new_database show more details here
            raise exceptions.Warning('Error %s' % res.status_code)
        data = simplejson.loads(res.text)
        params = {
            'state':
            data.get('state'),
            'access_token':
            client.oauth_application_id._get_access_token(user_id,
                                                          create=True),
        }
        url = '{url}?{params}'.format(url=data.get('url'),
                                      params=werkzeug.url_encode(params))

        # send email
        if notify_user:
            template = self.env.ref('saas_portal.email_template_create_saas')
            client.message_post_with_template(template.id,
                                              composition_mode='comment')

        if trial:
            client.expiration_datetime = trial_expiration_datetime
        client.send_params_to_client_db()
        client.server_id.action_sync_server()

        return {'url': url, 'id': client.id, 'client_id': client_id}

    @api.one
    def generate_dbname(self, raise_error=True):
        if not self.dbname_template:
            if raise_error:
                raise exceptions.Warning(
                    _('Template for db name is not configured'))
            return ''
        sequence = self.env['ir.sequence'].get('saas_portal.plan')
        return self.dbname_template.replace('%i', sequence)

    @api.multi
    def create_template(self):
        assert len(self) == 1, 'This method is applied only for single record'
        # TODO use create_new_database function
        plan = self[0]
        state = {
            'd': plan.template_id.name,
            'demo': plan.demo and 1 or 0,
            'addons': [],
            'lang': plan.lang,
            'tz': plan.tz,
            'is_template_db': 1,
        }
        client_id = plan.template_id.client_id
        plan.template_id.server_id = plan.server_id
        params = plan.server_id._request_params(
            path='/saas_server/new_database', state=state,
            client_id=client_id)[0]

        access_token = plan.template_id.oauth_application_id.sudo(
        )._get_access_token(create=True)
        params.update({
            'token_type': 'Bearer',
            'access_token': access_token,
            'expires_in': 3600,
        })
        url = '{scheme}://{saas_server}:{port}{path}?{params}'.format(
            scheme=plan.server_id.request_scheme,
            saas_server=plan.server_id.name,
            port=plan.server_id.request_port,
            path='/saas_server/new_database',
            params=werkzeug.url_encode(params))
        res = requests.get(url,
                           verify=(plan.server_id.request_scheme == 'https'
                                   and plan.server_id.verify_ssl))
        if res.ok != True:
            msg = """Status Code - %s
Reason - %s
URL - %s
            """ % (res.status_code, res.reason, res.url)
            raise Warning(msg)
        return self.action_sync_server()

    @api.multi
    def action_sync_server(self):
        for r in self:
            r.server_id.action_sync_server()
        return True

    @api.multi
    def edit_template(self):
        return self[0].template_id.edit_database()

    @api.multi
    def upgrade_template(self):
        return self[0].template_id.upgrade_database()

    @api.multi
    def delete_template(self):
        res = self[0].template_id.delete_database()
        return res
Ejemplo n.º 26
0
class is_utilisateur(models.Model):
    _name = "is.utilisateur"
    _description = "Utilisateurs"
    _order = 'site_id,name'
    _sql_constraints = [
        ('name_uniq', 'UNIQUE(name)', 'Ce nom existe déjà'),
        ('login_uniq', 'UNIQUE(login)', 'Ce login existe déjà'),
    ]

    site_id = fields.Many2one('is.site', 'Site', required=True)
    name = fields.Char('Prénom Nom', required=True)
    login = fields.Char('Login', required=True)
    mail = fields.Char('Mail')
    service_id = fields.Many2one('is.service', 'Service')
    fonction = fields.Char('Fonction')
    telephone = fields.Char('Téléphone')
    portable = fields.Char('Portable')
    fax = fields.Char('Fax')
    autre = fields.Char('Autre')
    commentaire = fields.Text('Commentaire')
    action_ids = fields.One2many('is.action',
                                 'utilisateur_id',
                                 u'Actions',
                                 readonly=True)
    signature_mail = fields.Html(u'Signature mail', sanitize=False)
    active = fields.Boolean('Actif', default=True)

    @api.multi
    def get_tr(self, val):
        if val and val != '':
            val = u"""
                <tr>
                    <td>
                        <font size="2" color="#939393">""" + val + u"""</font>
                    </td>
                </tr>
            """
        else:
            val = ''
        return val

    @api.multi
    def generer_signature_mail(self):
        for obj in self:
            if not obj.site_id.signature_mail:
                raise Warning(u"Signature mail non renseignée pour le site " +
                              obj.site_id.name)
            html = obj.site_id.signature_mail
            telephone = obj.telephone or ''
            portable = obj.portable or ''
            fax = obj.fax or ''
            if telephone != '':
                telephone = u'Tél : ' + telephone
            if portable != '':
                portable = u'Mobile : ' + portable
            if fax != '':
                fax = u'Fax : ' + fax

            fonction = self.get_tr(obj.fonction)
            telephone = self.get_tr(telephone)
            portable = self.get_tr(portable)
            fax = self.get_tr(fax)
            autre = self.get_tr(obj.autre)

            html = html.replace('${name}', (obj.name or ''))
            html = html.replace('${mail}', (obj.mail or ''))
            html = html.replace('<tr><td>${fonction}</td></tr>', fonction)
            html = html.replace('<tr><td>${telephone}</td></tr>', telephone)
            html = html.replace('<tr><td>${portable}</td></tr>', portable)
            html = html.replace('<tr><td>${fax}</td></tr>', fax)
            html = html.replace('<tr><td>${autre}</td></tr>', autre)
            if html:
                obj.signature_mail = html
            obj.generer_piece_jointe()

    @api.multi
    def generer_piece_jointe(self):
        model = self._name
        for obj in self:
            if not obj.signature_mail:
                raise Warning(u"Signature mail non générée pour " + obj.name)
            name = 'signature-mail.html'
            path = '/tmp/' + name
            f = open(path, 'wb')
            f.write(obj.signature_mail.encode('utf-8'))
            f.close()
            datas = open(path, 'rb').read().encode('base64')

            # ** Recherche si une pièce jointe est déja associèe ***************
            attachment_obj = self.env['ir.attachment']
            model = self._name
            #name='commandes.pdf'
            attachments = attachment_obj.search([('res_model', '=', model),
                                                 ('res_id', '=', obj.id),
                                                 ('name', '=', name)])
            # ******************************************************************

            # ** Creation ou modification de la pièce jointe *******************
            vals = {
                'name': name,
                'datas_fname': name,
                'type': 'binary',
                'res_model': model,
                'res_id': obj.id,
                'datas': datas,
            }
            if attachments:
                for attachment in attachments:
                    attachment.write(vals)
            else:
                attachment = attachment_obj.create(vals)
            # ******************************************************************

    @api.multi
    def envoyer_signature_mail(self):
        model = self._name
        for obj in self:
            self.generer_signature_mail()
            subject = u'[' + obj.name + u'] Nouvelle signature de mail'
            email_to = obj.mail
            user = self.env['res.users'].browse(self._uid)
            email_from = user.email
            email_to = obj.mail
            nom = user.name
            # body_html=u"""
            #     <p>Bonjour,</p>
            #     <p>Voici la signature mail nouveau format, avec les liens vers les réseaux sociaux et le site de Plastigray.</p>
            #     <p>Vous trouverez ci-joint le fichier signature HTML à télécharger, ainsi que la procédure en PDF pour configurer le logiciel de messagerie.</p>
            #     <p>"""+nom+u"""</p>
            # """
            body_html = obj.site_id.contenu_mail
            attachment_ids = []
            attachment_obj = self.env['ir.attachment']
            attachments = attachment_obj.search([('res_model', '=', model),
                                                 ('res_id', '=', obj.id)])
            for attachment in attachments:
                attachment_ids.append(attachment.id)

            # ** Ajout des pieces jointes associèes au site ********************
            attachments = attachment_obj.search([('res_model', '=', 'is.site'),
                                                 ('res_id', '=',
                                                  obj.site_id.id)])
            for attachment in attachments:
                attachment_ids.append(attachment.id)
            # ******************************************************************

            vals = {
                'email_from': email_from,
                'email_to': email_to,
                'email_cc': email_from,
                'subject': subject,
                'body_html': body_html,
                'attachment_ids': [(6, 0, attachment_ids)]
            }
            email = self.env['mail.mail'].create(vals)
            if email:
                self.env['mail.mail'].send(email)
Ejemplo n.º 27
0
class PABIPartnerDunningLetter(models.Model):
    _name = 'pabi.partner.dunning.letter'
    _rec_name = 'number'

    number = fields.Char(
        string='Number',
        size=500,
    )
    create_uid = fields.Many2one(
        'res.users',
        string='Creator',
        readonly=True,
    )
    date_letter = fields.Date(string='Letter Date')
    letter_type = fields.Selection(
        [('l1', 'Overdue 7 Days'), ('l2', 'Overdue 14 Days'),
         ('l3', 'Overdue 19 Days')],
        string='Type',
        required=True,
    )
    currency_id = fields.Many2one(
        'res.currency',
        string='Currency',
        compute='_compute_letter_text',
        readonly=True,
    )
    date_run = fields.Date(
        string='Run Date',
        required=True,
    )
    to_whom_title = fields.Char(
        string='To Whom',
        size=500,
        default='ผศจ. ผ่าน ผอ.ฝ่ายบริหารธุรกิจอุทยานวิทยาศาสตร์ประเทศไทย')
    partner_id = fields.Many2one(
        'res.partner',
        string='Partner',
        required=True,
    )
    subject = fields.Char(
        string='Subject',
        compute='_compute_letter_text',
        readonly=True,
    )
    amount_total = fields.Float(
        string='Total',
        compute='_compute_amount_total',
    )
    amount_total_text_en = fields.Char(
        string='Total Amount Text',
        compute='_compute_amount_total',
    )
    line_ids = fields.One2many('pabi.partner.dunning.letter.line',
                               'letter_id',
                               string='Dunning Lines')
    # Printing Text
    letter_header = fields.Html(
        string='Header',
        compute='_compute_letter_text',
        readonly=True,
    )
    letter_footer = fields.Html(
        string='Header',
        compute='_compute_letter_text',
        readonly=True,
    )
    letter_signature = fields.Html(
        string='Header',
        compute='_compute_letter_text',
        readonly=True,
    )

    @api.multi
    def _compute_amount_total(self):
        for letter in self:
            self._cr.execute(
                """
                select sum(amount_residual)
                from pabi_partner_dunning_letter_line
                where letter_id = %s
            """, (letter.id, ))
            letter.amount_total = self._cr.fetchone()[0]
            letter.amount_total_text_en = amount_to_text(
                letter.amount_total, 'en',
                'Baht').replace('and Zero Cent', 'Only').replace(
                    'Cent', 'Satang').replace('Cents', 'Satang')

    @api.model
    def _eval_text(self, text, obj):
        template = self.env['email.template']
        return template.render_template(text, obj._name, obj.id)

    @api.multi
    def _compute_letter_text(self):
        company = self.env['res.company'].search([])[0]
        for letter in self:
            move_line = letter.line_ids and \
                letter.line_ids[0].move_line_id or False
            letter.currency_id = move_line and move_line.currency_id or \
                self.env.user.company_id.currency_id
            if letter.letter_type == 'l1':
                letter.subject = company.letter1_subject
                letter.letter_header = \
                    self._eval_text(company.letter1_header, letter)
                letter.letter_footer = \
                    self._eval_text(company.letter1_footer, letter)
                letter.letter_signature = \
                    self._eval_text(company.letter1_signature, letter)
            if letter.letter_type == 'l2':
                letter.subject = company.letter2_subject
                letter.letter_header = \
                    self._eval_text(company.letter2_header, letter)
                letter.letter_footer = \
                    self._eval_text(company.letter2_footer, letter)
                letter.letter_signature = \
                    self._eval_text(company.letter2_signature, letter)
            if letter.letter_type == 'l3':
                letter.subject = company.letter3_subject
                letter.letter_header = \
                    self._eval_text(company.letter3_header, letter)
                letter.letter_footer = \
                    self._eval_text(company.letter3_footer, letter)
                letter.letter_signature = \
                    self._eval_text(company.letter3_signature, letter)

    @api.multi
    def get_dunning_letter_line(self, lines):
        if not lines:
            return []
        where = ''
        if len(lines) == 1:
            where += ' dl.id = %s' % (str(lines.ids[0]), )
        else:
            where += ' dl.id in %s' % (str(tuple(lines.ids)), )
        self._cr.execute("""
            select ml.ref, dl.date_invoice, dl.date_due,
                   sum(dl.amount_residual)
            from pabi_partner_dunning_letter_line dl
            join account_move_line ml on dl.move_line_id = ml.id
            where %s
            group by ml.ref, dl.date_invoice, dl.date_due
            order by ml.ref, dl.date_invoice, dl.date_due
            """ % (where, ))
        return self._cr.fetchall()
class SendgridTemplate(models.Model):
    """ Reference to a template available on the SendGrid user account. """
    _name = 'sendgrid.template'

    ##########################################################################
    #                                 FIELDS                                 #
    ##########################################################################
    name = fields.Char()
    remote_id = fields.Char(readonly=True)
    html_content = fields.Html(readonly=True)
    plain_content = fields.Text(readonly=True)
    detected_keywords = fields.Char(compute='_compute_keywords')

    def _compute_keywords(self):
        for template in self:
            if template.html_content:
                keywords = template.get_keywords()
                self.detected_keywords = ';'.join(keywords)

    @api.one
    def update(self):
        api_key = config.get('sendgrid_api_key')
        if not api_key:
            raise exceptions.Warning(
                'ConfigError',
                _('Missing sendgrid_api_key in conf file'))

        sg = sendgrid.SendGridAPIClient(apikey=api_key)
        template_client = sg.client.templates
        msg = template_client.get().body
        result = json.loads(msg)

        for template in result.get("templates", list()):
            id = template["id"]
            msg = template_client._(id).get().body
            template_versions = json.loads(msg)['versions']
            for version in template_versions:
                if version['active']:
                    template_vals = version
                    break
            else:
                continue

            vals = {
                "remote_id": id,
                "name": template["name"],
                "html_content": template_vals["html_content"],
                "plain_content": template_vals["plain_content"],
            }
            record = self.search([('remote_id', '=', id)])
            if record:
                record.write(vals)
            else:
                self.create(vals)
        return True

    def get_keywords(self):
        """ Search in the Sendgrid template for keywords included with the
        following syntax: {keyword_name} and returns the list of keywords.
        keyword_name shouldn't be longer than 20 characters and only contain
        alphanumeric characters (underscore is allowed).
        You can replace the substitution prefix and suffix by adding values
        in the system parameters
            - mail_sendgrid.substitution_prefix
            - mail_sendgrid.substitution_suffix
        """
        self.ensure_one()
        params = self.env['ir.config_parameter']
        prefix = params.search([
            ('key', '=', 'mail_sendgrid.substitution_prefix')
        ]).value or '{'
        suffix = params.search([
            ('key', '=', 'mail_sendgrid.substitution_suffix')
        ]) or '}'
        pattern = prefix + '\w{0,20}' + suffix
        return list(set(re.findall(pattern, self.html_content)))
Ejemplo n.º 29
0
class Partner(models.Model):
    '''Partner'''
    _inherit = 'res.partner'
    
    student = fields.Boolean("Student",default=False)
    teacher = fields.Boolean("Teacher",default=False)
    employee = fields.Boolean("Employee",default=False)
    
    initials = fields.Char('Initials')
    
    @api.one
    @api.constrains('initials')
    def _check_initials(self):
        _logger.info('Chech here !!')
        if self.initials and not re.match('([A-Z]\.,)*([A-Z]\.)?',self.initials):
            raise UserError(_("Please encode initials as eg X.,V.,T."))
    
    birthplace = fields.Char('Birthplace')
    phone2 = fields.Char('Phone2')
    title = fields.Selection([('Mr', 'Monsieur'),('Mme', 'Madame'),('Mlle', 'Mademoiselle')])
    marial_status = fields.Selection([('M', 'Maried'),('S', 'Single')])
    registration_date = fields.Date('Registration Date')
    email_personnel = fields.Char('Email personnel')
    reg_number = fields.Char('Registration Number')
    mat_number = fields.Char('Matricule Number')
    
    student_bloc_ids = fields.One2many('school.individual_bloc', 'student_id', string='Programs')

    # Secondary adress

    secondary_street = fields.Char('Street')
    secondary_street2 = fields.Char('Street2')
    secondary_zip = fields.Char('Zip', size=24, change_default=True)
    secondary_city = fields.Char('City')
    secondary_state_id = fields.Many2one("res.country.state", 'State', ondelete='restrict')
    secondary_country_id = fields.Many2one('res.country', 'Country', ondelete='restrict')

    year_sequence = fields.Selection([
        ('current','Current'),
        ('previous','Previous'),
        ('next','Next'),
        ('never','Never'),
        ], string="Year Sequence", compute="_compute_year_sequence", search="_search_year_sequence")

    def _compute_year_sequence(self):
        for item in self:
            current_year_id = self.env.user.current_year_id
            year_ids = item.student_bloc_ids.mapped('year_id.id')
            if current_year_id.id in year_ids:
                item.year_sequence = 'current'
                return
            if current_year_id.previous.id in year_ids:
                item.year_sequence = 'previous'
                return
            if current_year_id.next.id in year_ids:
                item.year_sequence = 'next'
                return
            item.year_sequence = 'never'
    
    def _search_year_sequence(self, operator, value):
        current_year_id = self.env.user.current_year_id
        year_ids = []
        if 'never' in value:
            return [('student_bloc_ids','=',False)]
        if 'current' in value:
            year_ids.append(current_year_id.id)
        if 'previous' in value:
            year_ids.append(current_year_id.previous.id)
        if 'next' in value:
            year_ids.append(current_year_id.next.id)
        return [('student_bloc_ids.year_id','in',year_ids)]
        
    student_current_course_ids = fields.One2many('school.individual_course', compute='_get_student_current_individual_course_ids', string='Courses')
    student_course_ids = fields.One2many('school.individual_course', 'student_id', string='Courses', domain="[('year_id', '=', self.env.user.current_year_id.id)]")
    
    teacher_current_course_ids = fields.One2many('school.individual_course_proxy', compute='_get_teacher_current_individual_course_ids', string="Current Courses")
    teacher_course_ids = fields.One2many('school.individual_course', 'teacher_id', string='Courses', domain="[('year_id', '=', self.env.user.current_year_id.id)]")
    
    teacher_curriculum_vitae = fields.Html('Curriculum Vitae')
    
    @api.one
    def _get_teacher_current_individual_course_ids(self):
        self.teacher_current_course_ids = self.env['school.individual_course_proxy'].search([['year_id', '=', self.env.user.current_year_id.id], ['teacher_id', '=', self.id]])

    @api.one
    def _get_student_current_individual_course_ids(self):
        self.teacher_current_course_ids = self.env['school.individual_course_proxy'].search([['year_id', '=', self.env.user.current_year_id.id], ['student_id', '=', self.id]])

    @api.one
    def _get_teacher_current_course_session_ids(self):
        res = self.env['school.course_session'].search([['year_id', '=', self.env.user.current_year_id.id], ['teacher_id', '=', self.id]])
        self.teacher_current_assigment_ids = res
    
    # TODO : This is not working but don't know why
    @api.model
    def _get_default_image(self, is_company, colorize=False):
        if getattr(threading.currentThread(), 'testing', False) or self.env.context.get('install_mode'):
            return False

        if self.env.context.get('partner_type') == 'invoice':
            img_path = openerp.modules.get_module_resource('school_management', 'static/src/img', 'home-icon.png')
            with open(img_path, 'rb') as f:
                image = f.read()
            return tools.image_resize_image_big(image.encode('base64'))
        else:
            return super(Partner, self)._get_default_image(is_company, colorize)
Ejemplo n.º 30
0
class ShiftTemplate(models.Model):
    _name = 'shift.template'
    _description = 'Shift Template'
    _order = 'shift_type_id, start_datetime'

    # Columns section
    name = fields.Char(
        string='Template Name', compute="_compute_template_name", store=True)
    active = fields.Boolean(default=True, track_visibility="onchange")
    shift_ids = fields.One2many(
        'shift.shift', 'shift_template_id', string='Shifts', readonly=True)
    shift_qty = fields.Integer(
        string='Number of shifts', compute='_compute_shift_qty', store=True)
    user_id = fields.Many2one(
        'res.partner', string='Shift Leader')
    user_ids = fields.Many2many(
        'res.partner', 'res_partner_shift_template_rel', 'shift_template_id',
        'partner_id', string='Shift Leaders', required=True)
    company_id = fields.Many2one(
        'res.company', string='Company', change_default=True,
        default=lambda self: self.env['res.company']._company_default_get(
            'shift.shift'))
    shift_type_id = fields.Many2one(
        'shift.type', string='Category', required=True,
        default=lambda self: self._default_shift_type())
    week_number = fields.Selection(
        WEEK_NUMBERS, string='Week', compute="_compute_week_number",
        store=True)
    color = fields.Integer('Kanban Color Index')
    shift_mail_ids = fields.One2many(
        'shift.template.mail', 'shift_template_id', string='Mail Schedule',
        default=lambda self: self._default_shift_mail_ids())
    seats_max = fields.Integer(
        string='Maximum Attendees Number',
        help="""For each shift you can define a maximum registration of
        seats(number of attendees), above this numbers the registrations
        are not accepted.""")
    seats_availability = fields.Selection(
        [('limited', 'Limited'), ('unlimited', 'Unlimited')],
        'Maximum Attendees', required=True, default='unlimited')
    seats_min = fields.Integer(
        string='Minimum Attendees', oldname='register_min',
        help="""For each shift you can define a minimum reserved seats (number
        of attendees), if it does not reach the mentioned registrations the
        shift can not be confirmed (keep 0 to ignore this rule)""")
    seats_reserved = fields.Integer(
        string='Reserved Seats',
        store=True, readonly=True, compute='_compute_seats_template')
    seats_available = fields.Integer(
        string='Maximum Attendees',
        store=True, readonly=True, compute='_compute_seats_template')
    seats_unconfirmed = fields.Integer(
        string='Unconfirmed Seat Reservations',
        store=True, readonly=True, compute='_compute_seats_template')
    seats_used = fields.Integer(
        string='Number of Participants',
        store=True, readonly=True, compute='_compute_seats_template')
    seats_expected = fields.Integer(
        string='Number of Expected Attendees',
        readonly=True, compute='_compute_seats')
    registration_ids = fields.One2many(
        'shift.template.registration', 'shift_template_id', string='Attendees')
    registration_qty = fields.Integer(
        string='Number of Attendees', compute='_compute_registration_qty',
        store=True)
    shift_ticket_ids = fields.One2many(
        'shift.template.ticket', 'shift_template_id', string='Shift Ticket',
        default=lambda rec: rec._default_tickets(), copy=True)
    reply_to = fields.Char(
        'Reply-To Email',
        help="""The email address of the organizer is likely to be put here,
        with the effect to be in the 'Reply-To' of the mails sent automatically
        at shift or registrations confirmation. You can also put the email
        address of your mail gateway if you use one.""")
    address_id = fields.Many2one(
        'res.partner', string='Location',
        default=lambda self: self._default_location_for_shift())
    country_id = fields.Many2one(
        'res.country', 'Country', related='address_id.country_id', store=True)
    description = fields.Html(
        string='Description', oldname='note', translate=True,
        readonly=False,)
    start_datetime = fields.Datetime(
        string='Start Date Time', required=True, help="First date this shift"
        " will be scheduled")
    start_datetime_tz = fields.Datetime(
        string='Start Date Time', compute="_compute_start_datetime_tz",
        store=True)
    end_datetime = fields.Datetime(
        string='End Date Time', required=True, help="End date of the first"
        "  shift")
    end_datetime_tz = fields.Datetime(
        string='End Date Time', compute="_compute_end_datetime_tz")
    start_date = fields.Date(
        string='Obsolete Start Date', compute='_compute_start_date',
        help="Technical Field. First date this shift will be scheduled",
        store=True)

    start_time = fields.Float(
        string='Obsolete Start Time', compute='_compute_start_time',
        store=True)

    end_time = fields.Float(
        string='Obsolete End Time', compute='_compute_end_time',
        store=True)

    duration = fields.Float(
        string='Duration (hours)', compute='_compute_duration', store=True)

    updated_fields = fields.Char('Updated Fields')
    last_shift_date = fields.Date(
        "Last Scheduled Shift", compute="_compute_last_shift_date")

    # RECURRENCE FIELD
    rrule_type = fields.Selection([
        ('daily', 'Day(s)'), ('weekly', 'Week(s)'), ('monthly', 'Month(s)'),
        ('yearly', 'Year(s)')], 'Recurrency', default='weekly',
        help="Let the shift automatically repeat at that interval")
    recurrency = fields.Boolean(
        'Recurrent', help="Recurrent Meeting", default=True)
    recurrent_id = fields.Integer('Recurrent ID')
    recurrent_id_date = fields.Datetime('Recurrent ID date')
    end_type = fields.Selection([
        ('count', 'Number of repetitions'), ('end_date', 'End date'),
        ('no_end', 'No end')], string='Recurrence Termination',
        default='no_end',)
    interval = fields.Integer(
        'Repeat Every', help="Repeat every (Days/Week/Month/Year)", default=4)
    count = fields.Integer('Repeat', help="Repeat x times")
    mo = fields.Boolean('Mon', compute="_compute_week_day", store=True)
    tu = fields.Boolean('Tue', compute="_compute_week_day", store=True)
    we = fields.Boolean('Wed', compute="_compute_week_day", store=True)
    th = fields.Boolean('Thu', compute="_compute_week_day", store=True)
    fr = fields.Boolean('Fri', compute="_compute_week_day", store=True)
    sa = fields.Boolean('Sat', compute="_compute_week_day", store=True)
    su = fields.Boolean('Sun', compute="_compute_week_day", store=True)
    month_by = fields.Selection([
        ('date', 'Date of month'), ('day', 'Day of month')], 'Option',)
    day = fields.Integer('Date of month')
    week_list = fields.Selection([
        ('MO', 'Monday'), ('TU', 'Tuesday'), ('WE', 'Wednesday'),
        ('TH', 'Thursday'), ('FR', 'Friday'), ('SA', 'Saturday'),
        ('SU', 'Sunday')], 'Weekday')
    byday = fields.Selection([
        ('1', 'First'), ('2', 'Second'), ('3', 'Third'), ('4', 'Fourth'),
        ('5', 'Fifth'), ('-1', 'Last')], 'By day')
    final_date = fields.Date('Repeat Until')  # The last shift of a recurrence
    rrule = fields.Char(
        compute="_compute_rulestring", store=True, string='Recurrent Rule',)

    @api.model
    def _default_tickets(self):
        try:
            product = self.env.ref('coop_shift.product_product_shift_standard')
            product2 = self.env.ref('coop_shift.product_product_shift_ftop')
            return [
                {
                    'name': _('Standard'),
                    'product_id': product.id,
                    'price': 0,
                },
                {
                    'name': _('FTOP'),
                    'product_id': product2.id,
                    'price': 0,
                }]
        except ValueError:
            return self.env['shift.template.ticket']

    @api.model
    def _default_location_for_shift(self):
        comp_id = self.env['res.company']._company_default_get('shift.shift')
        if comp_id:
            for child in comp_id.partner_id.child_ids:
                if child.type == 'other' and child.default_addess_for_shifts:
                    return child
            return comp_id.partner_id
    # Compute Section
    @api.multi
    @api.depends('shift_ids')
    def _compute_shift_qty(self):
        for template in self:
            template.shift_qty = len(template.shift_ids)

    @api.multi
    @api.depends('registration_ids')
    def _compute_registration_qty(self):
        for template in self:
            template.registration_qty = len(template.registration_ids)

    @api.multi
    @api.depends('seats_max', 'registration_ids')
    def _compute_seats_template(self):
        """ Determine reserved, available, reserved but unconfirmed and used
        seats. """
        # initialize fields to 0
        for template in self:
            template.seats_unconfirmed = template.seats_reserved =\
                template.seats_used = template.seats_available = 0
        # aggregate registrations by template and by state
        if self.ids:
            state_field = {
                'draft': 'seats_reserved',
                'open': 'seats_reserved',
                'done': 'seats_used',
            }

        # compute seats_available
        for template in self:
            for reg in template.registration_ids.filtered(
                lambda r, states=state_field.keys():
                    r.is_current and r.state in states):
                template[state_field[reg.state]] += 1
            if template.seats_max > 0:
                template.seats_available = template.seats_max - (
                    template.seats_reserved + template.seats_used)
            template.seats_expected = template.seats_unconfirmed +\
                template.seats_reserved + template.seats_used

    @api.multi
    @api.depends(
        'shift_type_id', 'week_number', 'mo', 'tu', 'we', 'th', 'fr', 'sa',
        'su', 'start_time', 'address_id', 'address_id.name')
    def _compute_template_name(self):
        for template in self:
            if template.shift_type_id == template.env.ref(
                    'coop_shift.shift_type'):
                name = ""
            else:
                name = template.shift_type_id.name\
                    and template.shift_type_id.name + ' - ' or ''
            name += template.week_number and (
                WEEK_NUMBERS[template.week_number - 1][1]) or ""
            name += _("Mo") if template.mo else ""
            name += _("Tu") if template.tu else ""
            name += _("We") if template.we else ""
            name += _("Th") if template.th else ""
            name += _("Fr") if template.fr else ""
            name += _("Sa") if template.sa else ""
            name += _("Su") if template.su else ""
            name += " - %02d:%02d" % (
                int(template.start_time),
                int(round((template.start_time - int(template.start_time)) *
                    60)))
            # add 4 letters from the beginning as a shortcode for place.
            if template.company_id and template.address_id:
                if template.address_id.name and \
                    template.address_id.name != template.company_id.name:
                    addr_name = template.address_id.name
                    isa_characters = \
                        "".join([c for c in addr_name if c.isalnum()])
                    str_place = udd.normalize("NFKD",
                        isa_characters[0:5]).encode("ascii", "ignore")
                    if str_place:
                        name += " - %s" % (str_place)
            template.name = name

    @api.multi
    @api.depends(
        'byday', 'recurrency', 'final_date', 'rrule_type', 'month_by',
        'interval', 'count', 'end_type', 'mo', 'tu', 'we', 'th', 'fr',
        'sa', 'su', 'day', 'week_list')
    def _compute_rulestring(self):
        """
        Gets Recurrence rule string according to value type RECUR of iCalendar
        from the values given.
        @return: dictionary of rrule value.
        """

        for templ in self:
            # read these fields as SUPERUSER because if the record is private a
            # normal search could raise an error
            recurrent_fields = templ._get_recurrent_fields()
            fields = templ.sudo().read(recurrent_fields)[0]
            if fields['end_type'] == 'no_end':
                fields['end_type'] = 'count'
                fields['count'] = 999
            if fields['recurrency']:
                templ.rrule = templ.compute_rule_string(fields)
            else:
                templ.rrule = ''

    @api.multi
    @api.depends('start_datetime_tz')
    def _compute_start_date(self):
        for template in self:
            if template.start_datetime_tz:
                template.start_date = datetime.strptime(
                    template.start_datetime_tz, '%Y-%m-%d %H:%M:%S').strftime(
                    '%Y-%m-%d')

    @api.multi
    @api.depends('start_datetime', 'end_datetime')
    def _compute_duration(self):
        for template in self:
            if template.start_datetime and template.end_datetime:
                start_date_object = datetime.strptime(
                    template.start_datetime, '%Y-%m-%d %H:%M:%S')
                end_date_object = datetime.strptime(
                    template.end_datetime, '%Y-%m-%d %H:%M:%S')
                template.duration = \
                    (end_date_object - start_date_object).seconds / 3600.0

    @api.multi
    @api.depends('start_datetime')
    def _compute_start_datetime_tz(self):
        tz_name = self._context.get('tz') or self.env.user.tz
        if not tz_name:
            raise UserError(_(
                "You can not create Shift Template if your timezone is not"
                " defined."))
        for template in self:
            if template.start_datetime:
                start_date_object = datetime.strptime(
                    template.start_datetime, '%Y-%m-%d %H:%M:%S')
                utc_timestamp = pytz.utc.localize(
                    start_date_object, is_dst=False)
                context_tz = pytz.timezone(tz_name)
                start_date_object_tz = utc_timestamp.astimezone(context_tz)
                template.start_datetime_tz = "%s-%02d-%02d %02d:%02d:%02d" % (
                    start_date_object_tz.year,
                    start_date_object_tz.month,
                    start_date_object_tz.day,
                    start_date_object_tz.hour,
                    start_date_object_tz.minute,
                    start_date_object_tz.second,
                )

    @api.multi
    @api.depends('end_datetime')
    def _compute_end_datetime_tz(self):
        tz_name = self._context.get('tz') or self.env.user.tz
        if not tz_name:
            raise UserError(_(
                "You can not create Shift Template if your timezone is not"
                " defined."))
        for template in self:
            if template.end_datetime:
                end_date_object = datetime.strptime(
                    template.end_datetime, '%Y-%m-%d %H:%M:%S')
                utc_timestamp = pytz.utc.localize(
                    end_date_object, is_dst=False)
                context_tz = pytz.timezone(tz_name)
                end_date_object_tz = utc_timestamp.astimezone(context_tz)
                template.end_datetime_tz = "%s-%02d-%02d %02d:%02d:%02d" % (
                    end_date_object_tz.year,
                    end_date_object_tz.month,
                    end_date_object_tz.day,
                    end_date_object_tz.hour,
                    end_date_object_tz.minute,
                    end_date_object_tz.second,
                )

    @api.multi
    @api.depends('start_datetime_tz')
    def _compute_start_time(self):
        for template in self:
            if template.start_datetime_tz:
                start_date_object = datetime.strptime(
                    template.start_datetime_tz, '%Y-%m-%d %H:%M:%S')
                template.start_time = (
                    start_date_object.hour +
                    (start_date_object.minute / 60.0))

    @api.multi
    @api.depends('end_datetime_tz')
    def _compute_end_time(self):
        for template in self:
            if template.end_datetime_tz:
                end_date_object = datetime.strptime(
                    template.end_datetime_tz, '%Y-%m-%d %H:%M:%S')
                template.end_time = (
                    end_date_object.hour +
                    (end_date_object.minute / 60.0))

    @api.multi
    @api.depends('start_date')
    def _compute_week_number(self):
        for template in self:
            if not template.start_date:
                template.week_number = False
            else:
                weekA_date = fields.Date.from_string(
                    self.env.ref('coop_shift.config_parameter_weekA').value)
                start_date = fields.Date.from_string(template.start_date)
                template.week_number =\
                    1 + (((start_date - weekA_date).days // 7) % 4)

    @api.multi
    @api.depends('shift_ids')
    def _compute_last_shift_date(self):
        for template in self:
            if template.shift_ids:
                template.last_shift_date = max(
                    shift.date_begin for shift in template.shift_ids)
            else:
                template.last_shift_date = False

    # Constraint Section
    @api.multi
    @api.constrains('start_datetime', 'end_datetime')
    def _check_date(self):
        for template in self:
            if template.start_datetime >= template.end_datetime:
                raise UserError(_(
                    "End datetime should greater than Start Datetime"))

    @api.multi
    @api.constrains('seats_max', 'seats_available')
    def _check_seats_limit(self):
        for templ in self:
            if templ.seats_availability == 'limited' and templ.seats_max and\
                    templ.seats_available < 0:
                raise UserError(_('No more available seats.'))

    # Default Section
    @api.model
    def _default_shift_mail_ids(self):
        return [(0, 0, {
            'interval_unit': 'now',
            'interval_type': 'after_sub',
            'template_id': self.env.ref('coop_shift.shift_subscription')
        })]

    @api.model
    def _default_shift_type(self):
        return self.env.ref('coop_shift.shift_type')

    # On change Section
    @api.depends('start_datetime')
    @api.multi
    def _compute_week_day(self):
        for template in self:
            if template.start_datetime_tz:
                start_date_object_tz = datetime.strptime(
                    template.start_datetime_tz, '%Y-%m-%d %H:%M:%S')
                wd = start_date_object_tz.weekday()
                template.mo = 0
                template.tu = 0
                template.we = 0
                template.th = 0
                template.fr = 0
                template.sa = 0
                template.su = 0
                if wd == 0:
                    template.mo = True
                    template.week_list = "MO"
                elif wd == 1:
                    template.tu = True
                    template.week_list = "TU"
                elif wd == 2:
                    template.we = True
                    template.week_list = "WE"
                elif wd == 3:
                    template.th = True
                    template.week_list = "TH"
                elif wd == 4:
                    template.fr = True
                    template.week_list = "FR"
                elif wd == 5:
                    template.sa = True
                    template.week_list = "SA"
                elif wd == 6:
                    template.su = True
                    template.week_list = "SU"
                template.day = start_date_object_tz.day
                template.byday = "%s" % (
                    (start_date_object_tz.day - 1) // 7 + 1)

    # Overload Section
    @api.multi
    def write(self, vals):
        if 'updated_fields' not in vals.keys() and len(self.shift_ids):
            vals['updated_fields'] = str(vals)
        return super(ShiftTemplate, self).write(vals)

    # Custom Public Section
    @api.multi
    def discard_changes(self):
        return self.write({'updated_fields': ''})

    @api.multi
    def act_template_shift_from_template(self):
        action = self.env.ref('coop_shift.action_shift_view')
        result = action.read()[0]
        shift_ids = sum([template.shift_ids.ids for template in self], [])
        # choose the view_mode accordingly
        if len(shift_ids) > 1:
            result['domain'] = "[('id','in',[" + ','.join(
                map(str, shift_ids)) + "])]"
        elif len(shift_ids) == 1:
            res = self.env.ref('coop_shift.view_shift_form', False)
            result['views'] = [(res and res.id or False, 'form')]
            result['res_id'] = shift_ids and shift_ids[0] or False
        result['context'] = unicode({'search_default_upcoming': 1})
        return result

    @api.multi
    def create_shifts_from_template(self, after=False, before=False):
        if not before:
            before = fields.Datetime.to_string(
                datetime.today() + timedelta(days=SHIFT_CREATION_DAYS))
        for template in self:
            after = template.last_shift_date
            rec_dates = template.get_recurrent_dates(
                after=after, before=before)
            for rec_date in rec_dates:
                start_date_object_tz = datetime.strptime(
                    template.start_datetime_tz, '%Y-%m-%d %H:%M:%S')
                date_begin = datetime.strftime(
                    rec_date + timedelta(hours=(start_date_object_tz.hour)) +
                    timedelta(minutes=(start_date_object_tz.minute)),
                    "%Y-%m-%d %H:%M:%S")
                if date_begin.split(" ")[0] <= template.last_shift_date:
                    continue
                end_date_object_tz = datetime.strptime(
                    template.end_datetime_tz, '%Y-%m-%d %H:%M:%S')
                diff_day = end_date_object_tz.day - start_date_object_tz.day
                diff_month = end_date_object_tz.month -\
                    start_date_object_tz.month
                diff_year = end_date_object_tz.year - start_date_object_tz.year
                date_end = datetime.strftime(
                    rec_date + timedelta(hours=(end_date_object_tz.hour)) +
                    timedelta(minutes=(end_date_object_tz.minute)) +
                    relativedelta(days=diff_day) +
                    relativedelta(months=diff_month) +
                    relativedelta(years=diff_year),
                    "%Y-%m-%d %H:%M:%S")
                rec_date = datetime.strftime(rec_date, "%Y-%m-%d")
                vals = {
                    'shift_template_id': template.id,
                    'name': template.name,
                    'user_ids': [(6, 0, template.user_ids.ids)],
                    'company_id': template.company_id.id,
                    'seats_max': template.seats_max,
                    'seats_availability': template.seats_availability,
                    'seats_min': template.seats_min,
                    'date_begin_tz': date_begin,
                    'date_end_tz': date_end,
                    'state': 'draft',
                    'reply_to': template.reply_to,
                    'address_id': template.address_id.id,
                    'description': template.description,
                    'shift_type_id': template.shift_type_id.id,
                    'week_number': template.week_number,
                    'week_list': template.week_list,
                    'shift_ticket_ids': None,
                }
                shift_id = self.env['shift.shift'].create(vals)
                for ticket in template.shift_ticket_ids:
                    vals = {
                        'name': ticket.name,
                        'shift_id': shift_id.id,
                        'product_id': ticket.product_id.id,
                        'price': ticket.price,
                        'deadline': ticket.deadline,
                        'seats_availability': ticket.seats_availability,
                        'seats_max': ticket.seats_max,
                    }
                    if ticket.product_id.shift_type_id.is_ftop:
                        vals['seats_availability'] = 'limited'
                        vals['seats_max'] = 0
                    ticket_id = self.env['shift.ticket'].create(vals)

                    for attendee in ticket.registration_ids:
                        state, strl_id = attendee._get_state(rec_date)
                        if state:
                            vals = {
                                'partner_id': attendee.partner_id.id,
                                'user_ids': [(6, 0, template.user_ids.ids)],
                                'state': state,
                                'email': attendee.email,
                                'phone': attendee.phone,
                                'name': attendee.name,
                                'shift_id': shift_id.id,
                                'shift_ticket_id': ticket_id.id,
                                'tmpl_reg_line_id': strl_id,
                                'template_created': True,
                            }
                            self.env['shift.registration'].create(vals)

    def compute_rule_string(self, data):
        """
        Compute rule string according to value type RECUR of iCalendar from
        the values given.
        @param self: the object pointer
        @param data: dictionary of freq and interval value
        @return: string containing recurring rule (empty if no rule)
        """
        if data['interval'] and data['interval'] < 0:
            raise UserError(_('interval cannot be negative.'))
        if data['count'] and data['count'] <= 0:
            raise UserError(_('Event recurrence interval cannot be negative.'))

        def get_week_string(freq, data):
            weekdays = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su']
            if freq == 'weekly':
                byday = map(
                    lambda x: x.upper(), filter(
                        lambda x: data.get(x) and x in weekdays, data))
                if byday:
                    return ';BYDAY=' + ','.join(byday)
            return ''

        def get_month_string(freq, data):
            if freq == 'monthly':
                if data.get('month_by') == 'date' and (
                        data.get('day') < 1 or data.get('day') > 31):
                    raise UserError(_(
                        "Please select a proper day of the month."))

                if data.get('month_by') == 'day':  # Eg : 2nd Monday of month
                    return ';BYDAY=' + data.get('byday') + data.get(
                        'week_list')
                elif data.get('month_by') == 'date':  # Eg : 16th of the month
                    return ';BYMONTHDAY=' + str(data.get('day'))
            return ''

        def get_end_date(data):
            if data.get('final_date'):
                data['end_date_new'] = ''.join((re.compile(r'\d')).findall(
                    data.get('final_date'))) + 'T235959Z'
            return (
                data.get('end_type') == 'count' and
                (';COUNT=' + str(data.get('count'))) or ''
            ) +\
                ((
                    data.get('end_date_new') and
                    data.get('end_type') == 'end_date' and
                    (';UNTIL=' + data.get('end_date_new'))) or '')

        freq = data.get('rrule_type', False)  # day/week/month/year
        res = ''
        if freq:
            interval_srting = data.get('interval') and\
                (';INTERVAL=' + str(data.get('interval'))) or ''
            res = 'FREQ=' + freq.upper() + get_week_string(freq, data) +\
                interval_srting + get_end_date(data) +\
                get_month_string(freq, data)
        return res

    @api.model
    def run_shift_creation(self):
        # This method is called by the cron task
        templates = self.env['shift.template'].search([])
        templates.create_shifts_from_template(
            before=fields.Datetime.to_string(
                datetime.today() + timedelta(days=SHIFT_CREATION_DAYS)))

    # Custom Private Section
    @api.model
    def _get_week_number(self, test_date):
        if not test_date:
            return False
        weekA_date = fields.Datetime.from_string(
            self.env.ref('coop_shift.config_parameter_weekA').value)
        week_number = 1 + (((test_date - weekA_date).days // 7) % 4)
        return week_number

    @api.multi
    def get_recurrent_dates(self, after=None, before=None):
        for template in self:
            start = fields.Datetime.from_string(after or template.start_date)
            stop = fields.Datetime.from_string(before or template.final_date)
            delta = (template.week_number - self._get_week_number(start)) % 4
            start += timedelta(weeks=delta)
            return rrule.rrulestr(str(template.rrule), dtstart=start).between(
                after=start, before=stop, inc=True)

    def _get_empty_rrule_data(self):
        return {
            'byday': False,
            'recurrency': False,
            'final_date': False,
            'rrule_type': False,
            'month_by': False,
            'interval': 0,
            'count': False,
            'end_type': False,
            'mo': False,
            'tu': False,
            'we': False,
            'th': False,
            'fr': False,
            'sa': False,
            'su': False,
            'day': False,
            'week_list': False
        }

    def _get_recurrent_fields(self):
        return ['byday', 'recurrency', 'final_date', 'rrule_type', 'month_by',
                'interval', 'count', 'end_type', 'mo', 'tu', 'we', 'th', 'fr',
                'sa', 'su', 'day', 'week_list']

    @api.model
    def _migrate_user_ids(self):
        for template in self.search([]):
            if template.user_id and template.user_id not in template.user_ids:
                template.user_ids += template.user_id
                template.discard_changes
        for shift in self.env['shift.shift'].search([('state', '=', 'draft')]):
            if shift.user_id and shift.user_id not in shift.user_ids:
                shift.user_ids += shift.user_id
            for ticket in shift.shift_ticket_ids:
                if ticket.user_id and ticket.user_id not in ticket.user_ids:
                    ticket.user_ids += ticket.user_id