mirror of
https://github.com/odoo/runbot.git
synced 2025-03-15 15:35:46 +07:00

Broken (can't run odoo at all): - In Odoo 17.0, the `pre_init_hook` takes an env, not a cursor, update `_check_citext`. - Odoo 17.0 rejects `@attrs` and doesn't say where they are or how to update them, fun, hunt down `attrs={'invisible': ...` and try to fix them. - Odoo 17.0 warns on non-multi creates, update them, most were very reasonable, one very wasn't. Test failures: - Odoo 17.0 deprecates `name_get` and doesn't use it as a *source* anymore, replace overrides by overrides to `_compute_display_name`. - Multiple tracking changes: - `_track_set_author` takes a `Partner` not an id. - `_message_compute_author` still requires overriding in order to handle record creation, which in standard doesn't support author overriding. - `mail.tracking.value.field_type` has been removed, the field type now needs to be retrieved from the `field_id`. - Some tracking ordering have changed and require adjusting a few tests. Also added a few flushes before SQL queries which are not (obviously at least) at the start of a cron or controller, no test failure observed but better safe than sorry (probably).
225 lines
8.9 KiB
Python
225 lines
8.9 KiB
Python
import random
|
|
from email.utils import parseaddr
|
|
|
|
from markupsafe import Markup, escape
|
|
|
|
import odoo.tools
|
|
from odoo import fields, models, tools, api, Command
|
|
|
|
from .. import github
|
|
|
|
class CIText(fields.Char):
|
|
type = 'char'
|
|
column_type = ('citext', 'citext')
|
|
column_cast_from = ('varchar', 'text')
|
|
|
|
class Partner(models.Model):
|
|
_name = 'res.partner'
|
|
_inherit = ['res.partner', 'mail.thread']
|
|
|
|
email = fields.Char(index=True)
|
|
github_login = CIText()
|
|
delegate_reviewer = fields.Many2many('runbot_merge.pull_requests')
|
|
formatted_email = fields.Char(string="commit email", compute='_rfc5322_formatted')
|
|
review_rights = fields.One2many('res.partner.review', 'partner_id')
|
|
override_rights = fields.Many2many('res.partner.override')
|
|
override_sensitive = fields.Boolean(compute="_compute_sensitive_overrides")
|
|
|
|
def _auto_init(self):
|
|
res = super(Partner, self)._auto_init()
|
|
tools.create_unique_index(
|
|
self._cr, 'runbot_merge_unique_gh_login', self._table, ['github_login'])
|
|
return res
|
|
|
|
@api.depends('name', 'email', 'github_login')
|
|
def _rfc5322_formatted(self):
|
|
for partner in self:
|
|
if partner.email:
|
|
email = parseaddr(partner.email)[1]
|
|
elif partner.github_login:
|
|
email = '%s@users.noreply.github.com' % partner.github_login
|
|
else:
|
|
email = ''
|
|
partner.formatted_email = '%s <%s>' % (partner.name, email)
|
|
|
|
def fetch_github_email(self):
|
|
# this requires a token in order to fetch the email field, otherwise
|
|
# it's just not returned, select a random project to fetch
|
|
gh = github.GH(random.choice(self.env['runbot_merge.project'].search([])).github_token, None)
|
|
for p in self.filtered(lambda p: p.github_login and p.email is False):
|
|
p.email = gh.user(p.github_login)['email'] or False
|
|
return False
|
|
|
|
@api.depends("override_rights.context")
|
|
def _compute_sensitive_overrides(self):
|
|
for p in self:
|
|
p.override_sensitive = any(o.context == 'ci/security' for o in p.override_rights)
|
|
|
|
def write(self, vals):
|
|
created = []
|
|
updated = {}
|
|
deleted = set()
|
|
for cmd, id, values in vals.get('review_rights', []):
|
|
if cmd == Command.DELETE:
|
|
deleted.add(id)
|
|
elif cmd == Command.CREATE:
|
|
# 'repository_id': 3, 'review': True, 'self_review': False
|
|
created.append(values)
|
|
elif cmd == Command.UPDATE:
|
|
updated[id] = values
|
|
# could also be LINK for records which are not touched but we don't care
|
|
|
|
new_rights = None
|
|
if r := vals.get('override_rights'):
|
|
# only handle reset (for now?) even though technically e.g. 0 works
|
|
# the web client doesn't seem to use it (?)
|
|
if r[0][0] == 6:
|
|
new_rights = self.env['res.partner.override'].browse(r[0][2])
|
|
|
|
Repo = self.env['runbot_merge.repository'].browse
|
|
for p in self:
|
|
msgs = []
|
|
if ds := p.review_rights.filtered(lambda r: r.id in deleted):
|
|
msgs.append("removed review rights on {}\n".format(
|
|
', '.join(ds.mapped('repository_id.name'))
|
|
))
|
|
if us := p.review_rights.filtered(lambda r: r.id in updated):
|
|
msgs.extend(
|
|
"updated review rights on {}: {}\n".format(
|
|
u.repository_id.name,
|
|
', '.join(
|
|
f'allowed {f}' if v else f'forbid {f}'
|
|
for f in ['review', 'self_review']
|
|
if (v := updated[u.id].get(f)) is not None
|
|
)
|
|
)
|
|
for u in us
|
|
)
|
|
msgs.extend(
|
|
'added review rights on {}: {}\n'.format(
|
|
Repo(c['repository_id']).name,
|
|
', '.join(filter(c.get, ['review', 'self_review'])),
|
|
)
|
|
for c in created
|
|
)
|
|
if new_rights is not None:
|
|
for r in p.override_rights - new_rights:
|
|
msgs.append(f"removed override rights for {r.context!r} on {r.repository_id.name}")
|
|
for r in new_rights - p.override_rights:
|
|
msgs.append(f"added override rights for {r.context!r} on {r.repository_id.name}")
|
|
if msgs:
|
|
p._message_log(body=Markup('<ul>{}</ul>').format(Markup().join(
|
|
map(Markup('<li>{}</li>').format, reversed(msgs))
|
|
)))
|
|
|
|
return super().write(vals)
|
|
|
|
|
|
class PartnerMerge(models.TransientModel):
|
|
_inherit = 'base.partner.merge.automatic.wizard'
|
|
|
|
@api.model
|
|
def _update_values(self, src_partners, dst_partner):
|
|
# sift down through src partners, removing all github_login and keeping
|
|
# the last one
|
|
new_login = None
|
|
for p in src_partners:
|
|
new_login = p.github_login or new_login
|
|
if new_login:
|
|
src_partners.write({'github_login': False})
|
|
if new_login and not dst_partner.github_login:
|
|
dst_partner.github_login = new_login
|
|
super()._update_values(src_partners, dst_partner)
|
|
|
|
class ReviewRights(models.Model):
|
|
_name = 'res.partner.review'
|
|
_description = "mapping of review rights between partners and repos"
|
|
|
|
partner_id = fields.Many2one('res.partner', required=True, ondelete='cascade')
|
|
repository_id = fields.Many2one('runbot_merge.repository', required=True)
|
|
review = fields.Boolean(default=False)
|
|
self_review = fields.Boolean(default=False)
|
|
|
|
def _auto_init(self):
|
|
res = super()._auto_init()
|
|
tools.create_unique_index(self._cr, 'runbot_merge_review_m2m', self._table, ['partner_id', 'repository_id'])
|
|
return res
|
|
|
|
@api.depends('repository_id.name', 'review', 'self_review')
|
|
def _compute_display_name(self):
|
|
for r in self:
|
|
r.display_name = '%s: %s' % (r.repository_id.name, ', '.join(filter(None, [
|
|
r.review and "reviewer",
|
|
r.self_review and "self-reviewer"
|
|
])))
|
|
|
|
@api.model
|
|
def name_search(self, name='', args=None, operator='ilike', limit=100):
|
|
return self.search((args or []) + [('repository_id.name', operator, name)], limit=limit).name_get()
|
|
|
|
class OverrideRights(models.Model):
|
|
_name = 'res.partner.override'
|
|
_description = 'lints which the partner can override'
|
|
|
|
partner_ids = fields.Many2many('res.partner')
|
|
repository_id = fields.Many2one('runbot_merge.repository')
|
|
context = fields.Char(required=True)
|
|
|
|
def init(self):
|
|
super().init()
|
|
tools.create_unique_index(
|
|
self.env.cr, 'res_partner_override_unique', self._table,
|
|
['context', 'coalesce(repository_id, 0)']
|
|
)
|
|
|
|
@api.model_create_multi
|
|
def create(self, vals_list):
|
|
for partner, contexts in odoo.tools.groupby((
|
|
(partner_id, vals['context'], vals['repository_id'])
|
|
for vals in vals_list
|
|
# partner_ids is of the form [Command.set(ids)
|
|
for partner_id in vals.get('partner_ids', [(None, None, [])])[0][2]
|
|
), lambda p: p[0]):
|
|
partner = self.env['res.partner'].browse(partner)
|
|
for _, context, repository in contexts:
|
|
repository = self.env['runbot_merge.repository'].browse(repository)
|
|
partner._message_log(body=f"added override rights for {context!r} on {repository.name}")
|
|
|
|
return super().create(vals_list)
|
|
|
|
def write(self, vals):
|
|
new = None
|
|
if pids := vals.get('partner_ids'):
|
|
new = self.env['res.partner'].browse(pids[0][2])
|
|
if new is not None:
|
|
for o in self:
|
|
added = new - o.partner_ids
|
|
removed = o.partner_ids - new
|
|
for p in added:
|
|
p._message_log(body=f"added override rights for {o.context!r} on {o.repository_id.name}")
|
|
for r in removed:
|
|
r._message_log(body=f"removed override rights for {o.context!r} on {o.repository_id.name}")
|
|
|
|
return super().write(vals)
|
|
|
|
def unlink(self):
|
|
for o in self:
|
|
for p in o.partner_ids:
|
|
p._message_log(body=f"removed override rights for {o.context!r} on {o.repository_id.name}")
|
|
return super().unlink()
|
|
|
|
@api.model
|
|
def name_search(self, name='', args=None, operator='ilike', limit=100):
|
|
return self.search((args or []) + [
|
|
'|', ('context', operator, name),
|
|
('repository_id.name', operator, name)
|
|
], limit=limit).name_get()
|
|
|
|
@api.depends('repository_id.name', 'context')
|
|
def _compute_display_name(self):
|
|
for r in self:
|
|
if r.repository_id:
|
|
r.display_name = f'{r.repository_id.name}: {r.context}'
|
|
else:
|
|
r.display_name = r.context
|