"""Magic: The Gathering taxonomy models for cards and faceting.""" import re from odoo import _, api, fields, models from odoo.exceptions import UserError class MvdTcgMtgTaxonomyMixin(models.AbstractModel): """Share technical code handling across MTG taxonomy models.""" _name = "mvd.tcg.mtg.taxonomy.mixin" _description = "MTG Taxonomy Technical Code Mixin" @api.model def _mvd_tcg_generate_code(self, name): """Return a slug-like technical code for one taxonomy name.""" return re.sub(r"[^a-z0-9]+", "_", (name or "").strip().lower()).strip("_") or "item" @api.model def _mvd_tcg_get_unique_code(self, name): """Return a unique technical code for one taxonomy record name.""" base_code = self._mvd_tcg_generate_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_code(self): """Return whether the current user may edit taxonomy 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 codes for manually created taxonomy records.""" prepared_vals_list = [] for vals in vals_list: prepared_vals = dict(vals) if not prepared_vals.get("code"): prepared_vals["code"] = self._mvd_tcg_get_unique_code( prepared_vals.get("name") ) prepared_vals_list.append(prepared_vals) return super().create(prepared_vals_list) def write(self, vals): """Protect taxonomy codes from normal business edits.""" if "code" in vals and not self.env.context.get("mvd_tcg_bypass_taxonomy_code_write"): if not self._mvd_tcg_can_edit_code(): raise UserError( _( "MTG taxonomy codes are technical identifiers and can only be " "changed by TCG administrators." ) ) return super().write(vals) class MvdTcgMtgRarity(models.Model): """Store normalized MTG rarity records.""" _name = "mvd.tcg.mtg.rarity" _inherit = "mvd.tcg.mtg.taxonomy.mixin" _description = "MTG Rarity" _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) card_ids = fields.One2many("mvd.tcg.card", "mtg_rarity_id") _code_unique = models.Constraint( "UNIQUE (code)", "The MTG rarity code must be unique.", ) class MvdTcgMtgColor(models.Model): """Store normalized MTG colors for faceting and card metadata.""" _name = "mvd.tcg.mtg.color" _inherit = "mvd.tcg.mtg.taxonomy.mixin" _description = "MTG Color" _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) card_ids = fields.Many2many( "mvd.tcg.card", "mvd_tcg_card_mtg_color_rel", "color_id", "card_id", ) _code_unique = models.Constraint( "UNIQUE (code)", "The MTG color code must be unique.", ) class MvdTcgMtgCardType(models.Model): """Store normalized MTG card types for faceting and grouping.""" _name = "mvd.tcg.mtg.card.type" _inherit = "mvd.tcg.mtg.taxonomy.mixin" _description = "MTG Card Type" _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) card_ids = fields.Many2many( "mvd.tcg.card", "mvd_tcg_card_mtg_card_type_rel", "type_id", "card_id", ) _code_unique = models.Constraint( "UNIQUE (code)", "The MTG card type code must be unique.", ) class MvdTcgMtgKeyword(models.Model): """Store normalized MTG keywords imported from Scryfall.""" _name = "mvd.tcg.mtg.keyword" _inherit = "mvd.tcg.mtg.taxonomy.mixin" _description = "MTG Keyword" _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) card_ids = fields.Many2many( "mvd.tcg.card", "mvd_tcg_card_mtg_keyword_rel", "keyword_id", "card_id", ) _code_unique = models.Constraint( "UNIQUE (code)", "The MTG keyword code must be unique.", ) class MvdTcgMtgFormat(models.Model): """Store normalized MTG formats for legality information.""" _name = "mvd.tcg.mtg.format" _inherit = "mvd.tcg.mtg.taxonomy.mixin" _description = "MTG Format" _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) legality_ids = fields.One2many("mvd.tcg.mtg.card.legality", "format_id") _code_unique = models.Constraint( "UNIQUE (code)", "The MTG format code must be unique.", ) class MvdTcgMtgFinish(models.Model): """Store normalized MTG finishes such as foil and etched.""" _name = "mvd.tcg.mtg.finish" _inherit = "mvd.tcg.mtg.taxonomy.mixin" _description = "MTG Finish" _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) card_ids = fields.Many2many( "mvd.tcg.card", "mvd_tcg_card_mtg_finish_rel", "finish_id", "card_id", ) _code_unique = models.Constraint( "UNIQUE (code)", "The MTG finish code must be unique.", ) class MvdTcgMtgPlatform(models.Model): """Store normalized MTG game platforms such as paper and Arena.""" _name = "mvd.tcg.mtg.platform" _inherit = "mvd.tcg.mtg.taxonomy.mixin" _description = "MTG Platform" _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) card_ids = fields.Many2many( "mvd.tcg.card", "mvd_tcg_card_mtg_platform_rel", "platform_id", "card_id", ) _code_unique = models.Constraint( "UNIQUE (code)", "The MTG platform code must be unique.", )