141 lines
4.9 KiB
Python
141 lines
4.9 KiB
Python
"""Game-neutral models for supported TCG systems."""
|
|
|
|
import re
|
|
|
|
from odoo import _, api, fields, models
|
|
from odoo.exceptions import UserError
|
|
|
|
|
|
class MvdTcgGame(models.Model):
|
|
"""Represent a supported trading card game within the suite."""
|
|
|
|
_name = "mvd.tcg.game"
|
|
_description = "TCG Game"
|
|
_order = "sequence, name, id"
|
|
|
|
name = fields.Char(required=True, translate=True, index="trigram")
|
|
code = fields.Char(required=True, index=True)
|
|
active = fields.Boolean(default=True)
|
|
sequence = fields.Integer(default=10)
|
|
description = fields.Text(translate=True)
|
|
card_ids = fields.One2many("mvd.tcg.card", "game_id")
|
|
card_count = fields.Integer(compute="_compute_card_count")
|
|
|
|
_code_unique = models.Constraint(
|
|
"UNIQUE (code)",
|
|
"The game code must be unique.",
|
|
)
|
|
|
|
@api.model
|
|
def _mvd_tcg_generate_game_code(self, name):
|
|
"""Return a slug-like technical code for one game name."""
|
|
return re.sub(r"[^a-z0-9]+", "_", (name or "").strip().lower()).strip("_") or "game"
|
|
|
|
@api.model
|
|
def _mvd_tcg_get_unique_game_code(self, name):
|
|
"""Return a globally unique technical code for one game name."""
|
|
base_code = self._mvd_tcg_generate_game_code(name)
|
|
existing_codes = set(self.search([]).mapped("code"))
|
|
if base_code not in existing_codes:
|
|
return base_code
|
|
suffix = 2
|
|
while f"{base_code}_{suffix}" in existing_codes:
|
|
suffix += 1
|
|
return f"{base_code}_{suffix}"
|
|
|
|
def _mvd_tcg_can_edit_game_code(self):
|
|
"""Return whether the current user may edit technical game codes."""
|
|
return self.env.is_superuser() or any(
|
|
self.env.user.has_group(xmlid)
|
|
for xmlid in (
|
|
"mvd_tcg_base.mvd_tcg_base_group_administrator",
|
|
"base.group_system",
|
|
)
|
|
)
|
|
|
|
@api.model_create_multi
|
|
def create(self, vals_list):
|
|
"""Generate missing technical game codes on manual creates."""
|
|
prepared_vals_list = []
|
|
for vals in vals_list:
|
|
prepared_vals = dict(vals)
|
|
if (
|
|
prepared_vals.get("code")
|
|
and not self.env.context.get("mvd_tcg_bypass_game_code_write")
|
|
and not self._mvd_tcg_can_edit_game_code()
|
|
):
|
|
raise UserError(
|
|
_(
|
|
"Game codes are technical identifiers and can only be set "
|
|
"by TCG administrators."
|
|
)
|
|
)
|
|
if not prepared_vals.get("code"):
|
|
prepared_vals["code"] = self._mvd_tcg_get_unique_game_code(
|
|
prepared_vals.get("name")
|
|
)
|
|
prepared_vals_list.append(prepared_vals)
|
|
return super().create(prepared_vals_list)
|
|
|
|
def write(self, vals):
|
|
"""Protect technical game codes from normal business edits."""
|
|
if "code" in vals and not self.env.context.get("mvd_tcg_bypass_game_code_write"):
|
|
if not self._mvd_tcg_can_edit_game_code():
|
|
raise UserError(
|
|
_(
|
|
"Game codes are technical identifiers and can only be changed "
|
|
"by TCG administrators."
|
|
)
|
|
)
|
|
return super().write(vals)
|
|
|
|
@api.depends("card_ids")
|
|
def _compute_card_count(self):
|
|
"""Compute the number of base cards linked to each game."""
|
|
card_data = self.env["mvd.tcg.card"]._read_group(
|
|
[("game_id", "in", self.ids)],
|
|
["game_id"],
|
|
["__count"],
|
|
)
|
|
counts_by_game = {game.id: count for game, count in card_data}
|
|
for game in self:
|
|
game.card_count = counts_by_game.get(game.id, 0)
|
|
|
|
def action_open_cards(self):
|
|
"""Open the cards catalog filtered on the selected game."""
|
|
self.ensure_one()
|
|
action = self.env["ir.actions.actions"]._for_xml_id(
|
|
"mvd_tcg_base.mvd_tcg_card_action"
|
|
)
|
|
action["domain"] = [("game_id", "=", self.id)]
|
|
action["context"] = {"default_game_id": self.id}
|
|
return action
|
|
|
|
def _mvd_tcg_get_default_deck_board_templates(self):
|
|
"""Return default board definitions for new decks of this game.
|
|
|
|
Returns:
|
|
list[dict[str, object]]: Ordered board configuration dictionaries.
|
|
"""
|
|
self.ensure_one()
|
|
return [
|
|
{
|
|
"name": _("Mainboard"),
|
|
"code": "mainboard",
|
|
"sequence": 10,
|
|
"include_in_total": True,
|
|
},
|
|
{
|
|
"name": _("Sideboard"),
|
|
"code": "sideboard",
|
|
"sequence": 20,
|
|
"include_in_total": True,
|
|
},
|
|
{
|
|
"name": _("Maybeboard"),
|
|
"code": "maybeboard",
|
|
"sequence": 30,
|
|
"include_in_total": False,
|
|
},
|
|
]
|