diff --git a/addons/crm/models/crm_lead.py b/addons/crm/models/crm_lead.py index 59540383fc7..f791d4be431 100644 --- a/addons/crm/models/crm_lead.py +++ b/addons/crm/models/crm_lead.py @@ -486,8 +486,17 @@ class Lead(models.Model): # ------------------------------------------------------------ def toggle_active(self): + """ When archiving: mark probability as 0. When re-activating + update probability again, for leads and opportunities. """ res = super(Lead, self).toggle_active() - self.filtered(lambda lead: lead.active)._update_probability() + activated = self.filtered(lambda lead: lead.active) + archived = self.filtered(lambda lead: not lead.active) + if activated: + activated.write({'lost_reason': False}) + activated._update_probability() + if archived: + archived.write({'probability': 0, 'automated_probability': 0}) + archived._rebuild_pls_frequency_table_threshold() return res def _rebuild_pls_frequency_table_threshold(self): @@ -506,12 +515,14 @@ class Lead(models.Model): def action_set_lost(self, **additional_values): """ Lost semantic: probability = 0 or active = False """ - result = self.write({'active': False, 'probability': 0, 'automated_probability': 0, **additional_values}) - self._rebuild_pls_frequency_table_threshold() - return result + res = self.action_archive() + if additional_values: + self.write(dict(additional_values)) + return res def action_set_won(self): """ Won semantic: probability = 100 (active untouched) """ + self.action_unarchive() for lead in self: stage_id = lead._stage_find(domain=[('is_won', '=', True)]) lead.write({'stage_id': stage_id.id, 'probability': 100}) @@ -1039,12 +1050,14 @@ class Lead(models.Model): self.ensure_one() if 'stage_id' in init_values and self.probability == 100 and self.stage_id: return self.env.ref('crm.mt_lead_won') - elif 'lost_reason' in init_values: + elif 'lost_reason' in init_values and self.lost_reason: return self.env.ref('crm.mt_lead_lost') elif 'stage_id' in init_values: return self.env.ref('crm.mt_lead_stage') elif 'active' in init_values and self.active: return self.env.ref('crm.mt_lead_restored') + elif 'active' in init_values and not self.active: + return self.env.ref('crm.mt_lead_lost') return super(Lead, self)._track_subtype(init_values) def _notify_get_groups(self): diff --git a/addons/crm/wizard/crm_lead_to_opportunity.py b/addons/crm/wizard/crm_lead_to_opportunity.py index e56dd0583d0..a00f76f12da 100644 --- a/addons/crm/wizard/crm_lead_to_opportunity.py +++ b/addons/crm/wizard/crm_lead_to_opportunity.py @@ -125,8 +125,7 @@ class Lead2OpportunityPartner(models.TransientModel): def _action_merge(self): to_merge = self.duplicated_lead_ids result_opportunity = to_merge.merge_opportunity(auto_unlink=False) - if not result_opportunity.active: - result_opportunity.write({'active': True, 'lost_reason': False}) + result_opportunity.action_unarchive() if result_opportunity.type == "lead": self._convert_and_allocate(result_opportunity, [self.user_id.id], team_id=self.team_id.id) diff --git a/addons/mail/models/mail_blacklist.py b/addons/mail/models/mail_blacklist.py index b2d27238a2c..4c686c2a045 100644 --- a/addons/mail/models/mail_blacklist.py +++ b/addons/mail/models/mail_blacklist.py @@ -73,7 +73,7 @@ class MailBlackList(models.Model): normalized = tools.email_normalize(email) record = self.env["mail.blacklist"].with_context(active_test=False).search([('email', '=', normalized)]) if len(record) > 0: - record.write({'active': True}) + record.action_unarchive() else: record = self.create({'email': email}) return record @@ -89,7 +89,7 @@ class MailBlackList(models.Model): normalized = tools.email_normalize(email) record = self.env["mail.blacklist"].with_context(active_test=False).search([('email', '=', normalized)]) if len(record) > 0: - record.write({'active': False}) + record.action_archive() else: record = record.create({'email': email, 'active': False}) return record diff --git a/addons/mass_mailing/models/mailing_list.py b/addons/mass_mailing/models/mailing_list.py index b10d7cab06e..57173bdbdfb 100644 --- a/addons/mass_mailing/models/mailing_list.py +++ b/addons/mass_mailing/models/mailing_list.py @@ -129,7 +129,7 @@ class MassMailingList(models.Model): self.flush() self.invalidate_cache() if archive: - (src_lists - self).write({'active': False}) + (src_lists - self).action_archive() def close_dialog(self): return {'type': 'ir.actions.act_window_close'} diff --git a/addons/phone_validation/models/phone_blacklist.py b/addons/phone_validation/models/phone_blacklist.py index 3fd1c4b4bcc..17f59b7fc38 100644 --- a/addons/phone_validation/models/phone_blacklist.py +++ b/addons/phone_validation/models/phone_blacklist.py @@ -89,7 +89,7 @@ class PhoneBlackList(models.Model): records = self.env["phone.blacklist"].with_context(active_test=False).search([('number', 'in', numbers)]) todo = [n for n in numbers if n not in records.mapped('number')] if records: - records.write({'active': True}) + records.action_unarchive() if todo: records += self.create([{'number': n} for n in todo]) return records @@ -112,7 +112,7 @@ class PhoneBlackList(models.Model): records = self.env["phone.blacklist"].with_context(active_test=False).search([('number', 'in', numbers)]) todo = [n for n in numbers if n not in records.mapped('number')] if records: - records.write({'active': False}) + records.action_archive() if todo: records += self.create([{'number': n, 'active': False} for n in todo]) return records diff --git a/addons/survey/models/survey_survey.py b/addons/survey/models/survey_survey.py index 60558a94924..5d8a297363e 100644 --- a/addons/survey/models/survey_survey.py +++ b/addons/survey/models/survey_survey.py @@ -328,6 +328,12 @@ class Survey(models.Model): default = dict(default or {}, title=title) return super(Survey, self).copy_data(default) + def toggle_active(self): + super(Survey, self).toggle_active() + activated = self.filtered(lambda survey: survey.active) + activated.mapped('certification_badge_id').action_unarchive() + (self - activated).mapped('certification_badge_id').action_archive() + # ------------------------------------------------------------ # ANSWER MANAGEMENT # ------------------------------------------------------------ @@ -946,6 +952,7 @@ class Survey(models.Model): # ------------------------------------------------------------ # GAMIFICATION / BADGES # ------------------------------------------------------------ + def _prepare_challenge_category(self): return 'certification' @@ -983,9 +990,8 @@ class Survey(models.Model): def _handle_certification_badges(self, vals): if vals.get('certification_give_badge'): # If badge already set on records, reactivate the ones that are not active. - surveys_with_badge = self.filtered(lambda survey: survey.certification_badge_id - and not survey.certification_badge_id.active) - surveys_with_badge.mapped('certification_badge_id').write({'active': True}) + surveys_with_badge = self.filtered(lambda survey: survey.certification_badge_id and not survey.certification_badge_id.active) + surveys_with_badge.mapped('certification_badge_id').action_unarchive() # (re-)create challenge and goal for survey in self: survey._create_certification_badge_trigger() @@ -994,7 +1000,7 @@ class Survey(models.Model): badges = self.mapped('certification_badge_id') challenges_to_delete = self.env['gamification.challenge'].search([('reward_id', 'in', badges.ids)]) goals_to_delete = challenges_to_delete.mapped('line_ids').mapped('definition_id') - badges.write({'active': False}) + badges.action_archive() # delete all challenges and goals because not needed anymore (challenge lines are deleted in cascade) challenges_to_delete.unlink() goals_to_delete.unlink() diff --git a/addons/survey/tests/test_certification_badge.py b/addons/survey/tests/test_certification_badge.py index 2b7348be427..df31c5e68d9 100644 --- a/addons/survey/tests/test_certification_badge.py +++ b/addons/survey/tests/test_certification_badge.py @@ -8,6 +8,7 @@ from odoo.tools import mute_logger class TestCertificationBadge(common.TestSurveyCommon): + def setUp(self): super(TestCertificationBadge, self).setUp() self.certification_survey = self.env['survey.survey'].with_user(self.survey_manager).create({ @@ -49,6 +50,21 @@ class TestCertificationBadge(common.TestSurveyCommon): 'level': None, }) + def test_archive(self): + """ Archive status of survey is propagated to its badges. """ + self.certification_survey.write({ + 'certification_give_badge': True, + 'certification_badge_id': self.certification_badge.id + }) + + self.certification_survey.action_archive() + self.assertFalse(self.certification_survey.active) + self.assertFalse(self.certification_badge.active) + + self.certification_survey.action_unarchive() + self.assertTrue(self.certification_survey.active) + self.assertTrue(self.certification_badge.active) + def test_give_badge_without_badge(self): with mute_logger('odoo.sql_db'): with self.assertRaises(IntegrityError): diff --git a/addons/website/controllers/main.py b/addons/website/controllers/main.py index 9cc42507c92..52d5f8e711f 100644 --- a/addons/website/controllers/main.py +++ b/addons/website/controllers/main.py @@ -301,7 +301,7 @@ class Website(Home): @http.route('/website/toggle_switchable_view', type='json', auth='user', website=True) def toggle_switchable_view(self, view_key): - request.website.viewref(view_key).toggle() + request.website.viewref(view_key).toggle_active() @http.route('/website/reset_template', type='http', auth='user', methods=['POST'], website=True, csrf=False) def reset_template(self, view_id, mode='soft', redirect='/', **kwargs): diff --git a/addons/website_slides/models/slide_channel.py b/addons/website_slides/models/slide_channel.py index 43a496513ad..8a2bac24b5d 100644 --- a/addons/website_slides/models/slide_channel.py +++ b/addons/website_slides/models/slide_channel.py @@ -87,7 +87,7 @@ class Channel(models.Model): # description name = fields.Char('Name', translate=True, required=True) - active = fields.Boolean(default=True) + active = fields.Boolean(default=True, tracking=100) description = fields.Text('Description', translate=True, help="The description that is displayed on top of the course page, just below the title") description_short = fields.Text('Short Description', translate=True, help="The description that is displayed on the course card") description_html = fields.Html('Detailed Description', translate=tools.html_translate, sanitize_attributes=False, sanitize_form=False) @@ -391,14 +391,22 @@ class Channel(models.Model): if vals.get('user_id'): self._action_add_members(self.env['res.users'].sudo().browse(vals['user_id']).partner_id) - if 'active' in vals: - # archiving/unarchiving a channel does it on its slides, too - self.with_context(active_test=False).mapped('slide_ids').write({'active': vals['active']}) if 'enroll_group_ids' in vals: self._add_groups_members() return res + def toggle_active(self): + # archiving/unarchiving a channel does it on its slides, too + to_archive = self.filtered(lambda channel: channel.active) + to_activate = self.filtered(lambda channel: not channel.active) + res = super(Channel, self).toggle_active() + if to_archive: + to_archive.mapped('slide_ids').action_archive() + if to_activate: + to_activate.with_context(active_test=False).mapped('slide_ids').action_unarchive() + return res + @api.returns('mail.message', lambda value: value.id) def message_post(self, *, parent_id=False, subtype_id=False, **kwargs): """ Temporary workaround to avoid spam. If someone replies on a channel diff --git a/addons/website_slides/models/slide_slide.py b/addons/website_slides/models/slide_slide.py index b99a3723963..062cb8a4d13 100644 --- a/addons/website_slides/models/slide_slide.py +++ b/addons/website_slides/models/slide_slide.py @@ -127,7 +127,7 @@ class Slide(models.Model): # description name = fields.Char('Title', required=True, translate=True) - active = fields.Boolean(default=True) + active = fields.Boolean(default=True, tracking=100) sequence = fields.Integer('Sequence', default=0) user_id = fields.Many2one('res.users', string='Uploaded by', default=lambda self: self.env.uid) description = fields.Text('Description', translate=True) @@ -175,7 +175,7 @@ class Slide(models.Model): html_content = fields.Html("HTML Content", help="Custom HTML content for slides of type 'Web Page'.", translate=True, sanitize_form=False) # website website_id = fields.Many2one(related='channel_id.website_id', readonly=True) - date_published = fields.Datetime('Publish Date', readonly=True, tracking=True) + date_published = fields.Datetime('Publish Date', readonly=True, tracking=1) likes = fields.Integer('Likes', compute='_compute_user_info', store=True, compute_sudo=False) dislikes = fields.Integer('Dislikes', compute='_compute_user_info', store=True, compute_sudo=False) user_vote = fields.Integer('User vote', compute='_compute_user_info', compute_sudo=False) diff --git a/odoo/addons/base/models/ir_ui_view.py b/odoo/addons/base/models/ir_ui_view.py index 3ed1603efb2..15d98396d04 100644 --- a/odoo/addons/base/models/ir_ui_view.py +++ b/odoo/addons/base/models/ir_ui_view.py @@ -511,12 +511,6 @@ actual arch. default = dict(default or {}, key=new_key) return super(View, self).copy(default) - def toggle(self): - """ Switches between enabled and disabled statuses - """ - for view in self: - view.write({'active': not view.active}) - # default view selection @api.model def default_view(self, model, view_type): diff --git a/odoo/addons/base/tests/test_views.py b/odoo/addons/base/tests/test_views.py index c3afcc7d39f..10370071f4d 100644 --- a/odoo/addons/base/tests/test_views.py +++ b/odoo/addons/base/tests/test_views.py @@ -2634,7 +2634,7 @@ class TestOptionalViews(ViewCase): """ Change active states of v2 and v3, check that the results are as expected """ - self.v2.toggle() + self.v2.toggle_active() context = {'check_view_ids': self.View.search([]).ids} arch = self.v0.with_context(context).read_combined(['arch'])['arch'] self.assertEqual( @@ -2645,7 +2645,7 @@ class TestOptionalViews(ViewCase): ) ) - self.v3.toggle() + self.v3.toggle_active() context = {'check_view_ids': self.View.search([]).ids} arch = self.v0.with_context(context).read_combined(['arch'])['arch'] self.assertEqual( @@ -2657,7 +2657,7 @@ class TestOptionalViews(ViewCase): ) ) - self.v2.toggle() + self.v2.toggle_active() context = {'check_view_ids': self.View.search([]).ids} arch = self.v0.with_context(context).read_combined(['arch'])['arch'] self.assertEqual(