user_access_rights_export/wizard/user_access_rights_wizard.py

586 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
try:
import xlsxwriter
except ImportError:
xlsxwriter = None
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")