583 lines
21 KiB
Python
583 lines
21 KiB
Python
# -*- coding: utf-8 -*-
|
|
import base64
|
|
import io
|
|
import re
|
|
from datetime import datetime
|
|
|
|
from odoo import _, api, fields, models
|
|
from odoo.exceptions import UserError
|
|
from odoo.tools.misc import xlsxwriter
|
|
|
|
|
|
class UserAccessRightsWizard(models.TransientModel):
|
|
_name = "user.access.rights.wizard"
|
|
_description = "User Access Rights Export Wizard"
|
|
|
|
excel_file = fields.Binary(string="Excel File", readonly=True)
|
|
filename = fields.Char(string="File Name", readonly=True)
|
|
|
|
def action_generate_report(self):
|
|
self.ensure_one()
|
|
if not xlsxwriter:
|
|
raise UserError(_("The python library xlsxwriter is required to export Excel files."))
|
|
|
|
users = self._get_users()
|
|
user_data = []
|
|
user_summary_rows = []
|
|
|
|
for user in users:
|
|
group_names = ", ".join(user.groups_id.mapped("display_name")) or _("No Groups")
|
|
model_access = self._collect_model_access(user)
|
|
record_rules = self._collect_record_rules(user)
|
|
|
|
user_summary_rows.append({
|
|
"name": user.display_name,
|
|
"login": user.login or "",
|
|
"groups": group_names,
|
|
"active": self._bool_to_str(user.active),
|
|
"model_count": len(model_access),
|
|
"rule_count": len(record_rules),
|
|
})
|
|
|
|
user_data.append({
|
|
"user": user,
|
|
"groups": group_names,
|
|
"model_access": model_access,
|
|
"record_rules": record_rules,
|
|
})
|
|
|
|
groups = self._get_groups()
|
|
group_data = []
|
|
group_summary_rows = []
|
|
|
|
for group in groups:
|
|
model_access = self._collect_group_model_access(group)
|
|
record_rules = self._collect_group_record_rules(group)
|
|
users_in_group = group.users
|
|
|
|
group_summary_rows.append({
|
|
"name": group.display_name,
|
|
"technical_name": group.full_name,
|
|
"category": group.category_id.display_name or _("Uncategorized"),
|
|
"user_count": len(users_in_group),
|
|
"model_count": len(model_access),
|
|
"rule_count": len(record_rules),
|
|
})
|
|
|
|
group_data.append({
|
|
"group": group,
|
|
"users": users_in_group,
|
|
"model_access": model_access,
|
|
"record_rules": record_rules,
|
|
})
|
|
|
|
output = io.BytesIO()
|
|
workbook = xlsxwriter.Workbook(output, {"in_memory": True})
|
|
formats = self._build_formats(workbook)
|
|
used_sheet_names = set()
|
|
|
|
try:
|
|
user_overview_sheet = self._make_unique_sheet_name(_("Users Overview"), used_sheet_names)
|
|
used_sheet_names.add(user_overview_sheet)
|
|
self._write_user_summary_sheet(workbook, formats, user_overview_sheet, user_summary_rows)
|
|
|
|
if group_summary_rows:
|
|
group_overview_sheet = self._make_unique_sheet_name(_("Groups Overview"), used_sheet_names)
|
|
used_sheet_names.add(group_overview_sheet)
|
|
self._write_group_summary_sheet(workbook, formats, group_overview_sheet, group_summary_rows)
|
|
|
|
for data in user_data:
|
|
sheet_name = self._make_unique_sheet_name(
|
|
data["user"].display_name or _("User"),
|
|
used_sheet_names,
|
|
)
|
|
used_sheet_names.add(sheet_name)
|
|
self._write_user_sheet(workbook, formats, sheet_name, data)
|
|
|
|
for data in group_data:
|
|
sheet_name = self._make_unique_sheet_name(
|
|
data["group"].display_name or _("Group"),
|
|
used_sheet_names,
|
|
)
|
|
used_sheet_names.add(sheet_name)
|
|
self._write_group_sheet(workbook, formats, sheet_name, data)
|
|
finally:
|
|
workbook.close()
|
|
|
|
file_content = output.getvalue()
|
|
filename = "user_access_rights_%s.xlsx" % datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
|
|
self.write({
|
|
"excel_file": base64.b64encode(file_content),
|
|
"filename": filename,
|
|
})
|
|
|
|
return {
|
|
"type": "ir.actions.act_url",
|
|
"url": "/web/content/?model=%s&id=%s&field=excel_file&filename_field=filename&download=true"
|
|
% (self._name, self.id),
|
|
"target": "self",
|
|
}
|
|
|
|
def _get_users(self):
|
|
return self.env["res.users"].sudo().with_context(active_test=False).search([], order="name")
|
|
|
|
def _get_groups(self):
|
|
return self.env["res.groups"].sudo().with_context(active_test=False).search([], order="category_id, name")
|
|
|
|
@api.model
|
|
def _collect_model_access(self, user):
|
|
user_groups = user.groups_id
|
|
acl_model = self.env["ir.model.access"].sudo().with_context(active_test=False)
|
|
acl_records = acl_model.search([], order="model_id, id")
|
|
result = []
|
|
|
|
for acl in acl_records:
|
|
applies = not acl.group_id or acl.group_id in user_groups
|
|
if not applies:
|
|
continue
|
|
|
|
result.append({
|
|
"model": acl.model_id.model,
|
|
"model_name": acl.model_id.name,
|
|
"group": acl.group_id.display_name if acl.group_id else _("All Users"),
|
|
"perm_read": self._bool_to_str(acl.perm_read),
|
|
"perm_write": self._bool_to_str(acl.perm_write),
|
|
"perm_create": self._bool_to_str(acl.perm_create),
|
|
"perm_unlink": self._bool_to_str(acl.perm_unlink),
|
|
})
|
|
|
|
return result
|
|
|
|
@api.model
|
|
def _collect_group_model_access(self, group):
|
|
acl_model = self.env["ir.model.access"].sudo().with_context(active_test=False)
|
|
acl_records = acl_model.search([("group_id", "=", group.id)], order="model_id, id")
|
|
result = []
|
|
for acl in acl_records:
|
|
result.append({
|
|
"model": acl.model_id.model,
|
|
"model_name": acl.model_id.name,
|
|
"perm_read": self._bool_to_str(acl.perm_read),
|
|
"perm_write": self._bool_to_str(acl.perm_write),
|
|
"perm_create": self._bool_to_str(acl.perm_create),
|
|
"perm_unlink": self._bool_to_str(acl.perm_unlink),
|
|
})
|
|
return result
|
|
|
|
@api.model
|
|
def _collect_record_rules(self, user):
|
|
user_groups = user.groups_id
|
|
rule_model = self.env["ir.rule"].sudo().with_context(active_test=False)
|
|
rules = rule_model.search([], order="model_id, id")
|
|
result = []
|
|
|
|
for rule in rules:
|
|
is_global = bool(getattr(rule, "global", False))
|
|
applies = is_global or (rule.groups and any(g in user_groups for g in rule.groups))
|
|
if not applies:
|
|
continue
|
|
|
|
group_names = ", ".join(rule.groups.mapped("display_name")) if rule.groups else _("All Users")
|
|
domain = rule.domain_force or "[]"
|
|
domain = re.sub(r"\s+", " ", domain).strip()
|
|
|
|
result.append({
|
|
"name": rule.name or _("Unnamed Rule"),
|
|
"model": rule.model_id.model,
|
|
"model_name": rule.model_id.name,
|
|
"domain": domain,
|
|
"group": group_names,
|
|
"global": self._bool_to_str(is_global),
|
|
"perm_read": self._bool_to_str(rule.perm_read),
|
|
"perm_write": self._bool_to_str(rule.perm_write),
|
|
"perm_create": self._bool_to_str(rule.perm_create),
|
|
"perm_unlink": self._bool_to_str(rule.perm_unlink),
|
|
})
|
|
|
|
return result
|
|
|
|
@api.model
|
|
def _collect_group_record_rules(self, group):
|
|
rule_model = self.env["ir.rule"].sudo().with_context(active_test=False)
|
|
rules = rule_model.search([], order="model_id, id")
|
|
result = []
|
|
|
|
for rule in rules:
|
|
if group not in rule.groups:
|
|
continue
|
|
|
|
domain = rule.domain_force or "[]"
|
|
domain = re.sub(r"\s+", " ", domain).strip()
|
|
|
|
result.append({
|
|
"name": rule.name or _("Unnamed Rule"),
|
|
"model": rule.model_id.model,
|
|
"model_name": rule.model_id.name,
|
|
"domain": domain,
|
|
"perm_read": self._bool_to_str(rule.perm_read),
|
|
"perm_write": self._bool_to_str(rule.perm_write),
|
|
"perm_create": self._bool_to_str(rule.perm_create),
|
|
"perm_unlink": self._bool_to_str(rule.perm_unlink),
|
|
})
|
|
|
|
return result
|
|
|
|
@api.model
|
|
def _build_formats(self, workbook):
|
|
return {
|
|
"header": workbook.add_format({
|
|
"bold": True,
|
|
"bg_color": "#F2F2F2",
|
|
"border": 1,
|
|
"text_wrap": True,
|
|
}),
|
|
"section": workbook.add_format({
|
|
"bold": True,
|
|
"font_size": 12,
|
|
"bottom": 1,
|
|
}),
|
|
"text": workbook.add_format({
|
|
"border": 1,
|
|
}),
|
|
"wrap": workbook.add_format({
|
|
"border": 1,
|
|
"text_wrap": True,
|
|
}),
|
|
"center": workbook.add_format({
|
|
"border": 1,
|
|
"align": "center",
|
|
}),
|
|
"title": workbook.add_format({
|
|
"bold": True,
|
|
"font_size": 14,
|
|
}),
|
|
}
|
|
|
|
def _write_user_summary_sheet(self, workbook, formats, sheet_name, rows):
|
|
worksheet = workbook.add_worksheet(sheet_name)
|
|
headers = [
|
|
_("User"),
|
|
_("Login"),
|
|
_("Groups"),
|
|
_("Active"),
|
|
_("Model ACLs"),
|
|
_("Record Rules"),
|
|
]
|
|
column_widths = [len(header) + 2 for header in headers]
|
|
|
|
worksheet.freeze_panes(1, 0)
|
|
|
|
for col, header in enumerate(headers):
|
|
worksheet.write(0, col, header, formats["header"])
|
|
|
|
for row_idx, data in enumerate(rows, start=1):
|
|
values = [
|
|
data["name"],
|
|
data["login"],
|
|
data["groups"],
|
|
data["active"],
|
|
data["model_count"],
|
|
data["rule_count"],
|
|
]
|
|
for col_idx, value in enumerate(values):
|
|
fmt = formats["wrap"] if col_idx == 2 else formats["text"]
|
|
worksheet.write(row_idx, col_idx, value, fmt)
|
|
column_widths[col_idx] = min(
|
|
max(column_widths[col_idx], len(str(value)) + 2),
|
|
80,
|
|
)
|
|
|
|
for col_idx, width in enumerate(column_widths):
|
|
worksheet.set_column(col_idx, col_idx, width)
|
|
|
|
def _write_group_summary_sheet(self, workbook, formats, sheet_name, rows):
|
|
worksheet = workbook.add_worksheet(sheet_name)
|
|
headers = [
|
|
_("Group"),
|
|
_("Technical Name"),
|
|
_("Category"),
|
|
_("Users"),
|
|
_("Model ACLs"),
|
|
_("Record Rules"),
|
|
]
|
|
column_widths = [len(header) + 2 for header in headers]
|
|
|
|
worksheet.freeze_panes(1, 0)
|
|
|
|
for col, header in enumerate(headers):
|
|
worksheet.write(0, col, header, formats["header"])
|
|
|
|
for row_idx, data in enumerate(rows, start=1):
|
|
values = [
|
|
data["name"],
|
|
data["technical_name"],
|
|
data["category"],
|
|
data["user_count"],
|
|
data["model_count"],
|
|
data["rule_count"],
|
|
]
|
|
for col_idx, value in enumerate(values):
|
|
fmt = formats["text"]
|
|
worksheet.write(row_idx, col_idx, value, fmt)
|
|
column_widths[col_idx] = min(
|
|
max(column_widths[col_idx], len(str(value)) + 2),
|
|
80,
|
|
)
|
|
|
|
for col_idx, width in enumerate(column_widths):
|
|
worksheet.set_column(col_idx, col_idx, width)
|
|
|
|
def _write_user_sheet(self, workbook, formats, sheet_name, data):
|
|
worksheet = workbook.add_worksheet(sheet_name)
|
|
row = 0
|
|
|
|
user = data["user"]
|
|
worksheet.write(row, 0, _("User Access Rights: %s") % (user.display_name,), formats["title"])
|
|
row += 2
|
|
|
|
info_pairs = [
|
|
(_("Name"), user.display_name or ""),
|
|
(_("Login"), user.login or ""),
|
|
(_("Email"), user.email or ""),
|
|
(_("Active"), self._bool_to_str(user.active)),
|
|
(_("Groups"), data["groups"]),
|
|
]
|
|
|
|
column_widths = [0, 0]
|
|
for label, value in info_pairs:
|
|
worksheet.write(row, 0, label, formats["header"])
|
|
worksheet.write(row, 1, value, formats["wrap"])
|
|
column_widths[0] = min(max(column_widths[0], len(label) + 2), 40)
|
|
column_widths[1] = min(max(column_widths[1], len(value) + 2), 80)
|
|
row += 1
|
|
|
|
worksheet.set_column(0, 0, column_widths[0] or 18)
|
|
worksheet.set_column(1, 1, column_widths[1] or 50)
|
|
|
|
row += 1
|
|
worksheet.write(row, 0, _("Model Access Rights"), formats["section"])
|
|
row += 1
|
|
|
|
headers = [
|
|
_("Model Technical Name"),
|
|
_("Model"),
|
|
_("Applies To Group"),
|
|
_("Read"),
|
|
_("Write"),
|
|
_("Create"),
|
|
_("Delete"),
|
|
]
|
|
for col, header in enumerate(headers):
|
|
worksheet.write(row, col, header, formats["header"])
|
|
row += 1
|
|
|
|
model_column_widths = [len(header) + 2 for header in headers]
|
|
for record in data["model_access"]:
|
|
values = [
|
|
record["model"],
|
|
record["model_name"],
|
|
record["group"],
|
|
record["perm_read"],
|
|
record["perm_write"],
|
|
record["perm_create"],
|
|
record["perm_unlink"],
|
|
]
|
|
for col_idx, value in enumerate(values):
|
|
fmt = formats["wrap"] if col_idx in (1, 2) else formats["center"] if col_idx >= 3 else formats["text"]
|
|
worksheet.write(row, col_idx, value, fmt)
|
|
model_column_widths[col_idx] = min(
|
|
max(model_column_widths[col_idx], len(str(value)) + 2),
|
|
70,
|
|
)
|
|
row += 1
|
|
|
|
for col_idx, width in enumerate(model_column_widths):
|
|
worksheet.set_column(col_idx, col_idx, width)
|
|
|
|
row += 1
|
|
worksheet.write(row, 0, _("Record Rules"), formats["section"])
|
|
row += 1
|
|
|
|
rule_headers = [
|
|
_("Rule Name"),
|
|
_("Model Technical Name"),
|
|
_("Model"),
|
|
_("Domain"),
|
|
_("Applies To Group"),
|
|
_("Global"),
|
|
_("Read"),
|
|
_("Write"),
|
|
_("Create"),
|
|
_("Delete"),
|
|
]
|
|
for col, header in enumerate(rule_headers):
|
|
worksheet.write(row, col, header, formats["header"])
|
|
row += 1
|
|
|
|
rule_column_widths = [len(header) + 2 for header in rule_headers]
|
|
for record in data["record_rules"]:
|
|
values = [
|
|
record["name"],
|
|
record["model"],
|
|
record["model_name"],
|
|
record["domain"],
|
|
record["group"],
|
|
record["global"],
|
|
record["perm_read"],
|
|
record["perm_write"],
|
|
record["perm_create"],
|
|
record["perm_unlink"],
|
|
]
|
|
for col_idx, value in enumerate(values):
|
|
if col_idx in (3, 4):
|
|
fmt = formats["wrap"]
|
|
elif col_idx >= 5:
|
|
fmt = formats["center"]
|
|
else:
|
|
fmt = formats["text"]
|
|
worksheet.write(row, col_idx, value, fmt)
|
|
rule_column_widths[col_idx] = min(
|
|
max(rule_column_widths[col_idx], len(str(value)) + 2),
|
|
90 if col_idx == 3 else 70,
|
|
)
|
|
row += 1
|
|
|
|
for col_idx, width in enumerate(rule_column_widths):
|
|
worksheet.set_column(col_idx, col_idx, width)
|
|
|
|
worksheet.freeze_panes(4 + len(data["model_access"]), 0)
|
|
|
|
def _write_group_sheet(self, workbook, formats, sheet_name, data):
|
|
worksheet = workbook.add_worksheet(sheet_name)
|
|
row = 0
|
|
|
|
group = data["group"]
|
|
worksheet.write(row, 0, _("Group Access Rights: %s") % (group.display_name,), formats["title"])
|
|
row += 2
|
|
|
|
user_names = ", ".join(data["users"].mapped("display_name")) or _("No Users")
|
|
|
|
info_pairs = [
|
|
(_("Name"), group.display_name or ""),
|
|
(_("Technical Name"), group.full_name or ""),
|
|
(_("Category"), group.category_id.display_name or _("Uncategorized")),
|
|
(_("Users"), user_names),
|
|
]
|
|
|
|
column_widths = [0, 0]
|
|
for label, value in info_pairs:
|
|
worksheet.write(row, 0, label, formats["header"])
|
|
worksheet.write(row, 1, value, formats["wrap"])
|
|
column_widths[0] = min(max(column_widths[0], len(label) + 2), 40)
|
|
column_widths[1] = min(max(column_widths[1], len(value) + 2), 80)
|
|
row += 1
|
|
|
|
worksheet.set_column(0, 0, column_widths[0] or 18)
|
|
worksheet.set_column(1, 1, column_widths[1] or 50)
|
|
|
|
row += 1
|
|
worksheet.write(row, 0, _("Model Access Rights"), formats["section"])
|
|
row += 1
|
|
|
|
headers = [
|
|
_("Model Technical Name"),
|
|
_("Model"),
|
|
_("Read"),
|
|
_("Write"),
|
|
_("Create"),
|
|
_("Delete"),
|
|
]
|
|
for col, header in enumerate(headers):
|
|
worksheet.write(row, col, header, formats["header"])
|
|
row += 1
|
|
|
|
model_column_widths = [len(header) + 2 for header in headers]
|
|
for record in data["model_access"]:
|
|
values = [
|
|
record["model"],
|
|
record["model_name"],
|
|
record["perm_read"],
|
|
record["perm_write"],
|
|
record["perm_create"],
|
|
record["perm_unlink"],
|
|
]
|
|
for col_idx, value in enumerate(values):
|
|
fmt = formats["wrap"] if col_idx == 1 else formats["center"] if col_idx >= 2 else formats["text"]
|
|
worksheet.write(row, col_idx, value, fmt)
|
|
model_column_widths[col_idx] = min(
|
|
max(model_column_widths[col_idx], len(str(value)) + 2),
|
|
70,
|
|
)
|
|
row += 1
|
|
|
|
for col_idx, width in enumerate(model_column_widths):
|
|
worksheet.set_column(col_idx, col_idx, width)
|
|
|
|
row += 1
|
|
worksheet.write(row, 0, _("Record Rules"), formats["section"])
|
|
row += 1
|
|
|
|
rule_headers = [
|
|
_("Rule Name"),
|
|
_("Model Technical Name"),
|
|
_("Model"),
|
|
_("Domain"),
|
|
_("Read"),
|
|
_("Write"),
|
|
_("Create"),
|
|
_("Delete"),
|
|
]
|
|
for col, header in enumerate(rule_headers):
|
|
worksheet.write(row, col, header, formats["header"])
|
|
row += 1
|
|
|
|
rule_column_widths = [len(header) + 2 for header in rule_headers]
|
|
for record in data["record_rules"]:
|
|
values = [
|
|
record["name"],
|
|
record["model"],
|
|
record["model_name"],
|
|
record["domain"],
|
|
record["perm_read"],
|
|
record["perm_write"],
|
|
record["perm_create"],
|
|
record["perm_unlink"],
|
|
]
|
|
for col_idx, value in enumerate(values):
|
|
if col_idx == 3:
|
|
fmt = formats["wrap"]
|
|
elif col_idx >= 4:
|
|
fmt = formats["center"]
|
|
else:
|
|
fmt = formats["text"]
|
|
worksheet.write(row, col_idx, value, fmt)
|
|
rule_column_widths[col_idx] = min(
|
|
max(rule_column_widths[col_idx], len(str(value)) + 2),
|
|
90 if col_idx == 3 else 70,
|
|
)
|
|
row += 1
|
|
|
|
for col_idx, width in enumerate(rule_column_widths):
|
|
worksheet.set_column(col_idx, col_idx, width)
|
|
|
|
worksheet.freeze_panes(4 + len(data["model_access"]), 0)
|
|
|
|
@api.model
|
|
def _make_unique_sheet_name(self, base_name, used_names):
|
|
sanitized = re.sub(r"[\[\]\*\?:\\/]", "", base_name or _("Sheet"))
|
|
sanitized = sanitized.strip() or _("Sheet")
|
|
sanitized = sanitized[:31]
|
|
candidate = sanitized
|
|
index = 2
|
|
|
|
while candidate in used_names:
|
|
suffix = f" ({index})"
|
|
candidate = (sanitized[:31 - len(suffix)] + suffix) if len(sanitized) + len(suffix) > 31 else sanitized + suffix
|
|
index += 1
|
|
|
|
return candidate
|
|
|
|
@api.model
|
|
def _bool_to_str(self, value):
|
|
return _("Yes") if value else _("No") |