🎉 Initialize module repository
This commit is contained in:
4
models/__init__.py
Normal file
4
models/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from . import mvd_tcg_card
|
||||
from . import mvd_tcg_game
|
||||
from . import ir_http
|
||||
from . import res_config_settings
|
||||
13
models/ir_http.py
Normal file
13
models/ir_http.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from odoo import models
|
||||
|
||||
|
||||
class IrHttp(models.AbstractModel):
|
||||
_inherit = "ir.http"
|
||||
|
||||
def session_info(self):
|
||||
"""Expose the configured max upload size to the web client."""
|
||||
info = super().session_info()
|
||||
config = self.env["ir.config_parameter"].sudo()
|
||||
upload_limit_mb = int(config.get_param("mvd_tcg_base.max_file_upload_size_mb", "256") or 256)
|
||||
info["max_file_upload_size"] = max(upload_limit_mb, 1) * 1024 * 1024
|
||||
return info
|
||||
150
models/mvd_tcg_card.py
Normal file
150
models/mvd_tcg_card.py
Normal file
@@ -0,0 +1,150 @@
|
||||
"""Game-neutral card reference models."""
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class MvdTcgCard(models.Model):
|
||||
"""Represent a game-neutral card reference entry."""
|
||||
|
||||
_name = "mvd.tcg.card"
|
||||
_description = "TCG Card"
|
||||
_inherit = "image.mixin"
|
||||
_order = "game_id, sequence, name, id"
|
||||
|
||||
active = fields.Boolean(default=True)
|
||||
sequence = fields.Integer(default=10)
|
||||
state = fields.Selection(
|
||||
selection=[
|
||||
("draft", "Draft"),
|
||||
("validated", "Validated"),
|
||||
],
|
||||
default="draft",
|
||||
required=True,
|
||||
index=True,
|
||||
copy=False,
|
||||
)
|
||||
name = fields.Char(required=True, translate=True, index="trigram")
|
||||
game_id = fields.Many2one(
|
||||
"mvd.tcg.game",
|
||||
required=True,
|
||||
index=True,
|
||||
ondelete="restrict",
|
||||
)
|
||||
external_ref = fields.Char(
|
||||
index=True,
|
||||
help="Adapter-specific identifier for this card reference.",
|
||||
)
|
||||
description = fields.Html(
|
||||
translate=True,
|
||||
)
|
||||
note = fields.Text(
|
||||
translate=True,
|
||||
help="Internal neutral note stored on the card reference.",
|
||||
)
|
||||
|
||||
_external_ref_unique = models.Constraint(
|
||||
"UNIQUE (game_id, external_ref)",
|
||||
"The external reference must be unique per game.",
|
||||
)
|
||||
|
||||
def _mvd_tcg_can_edit_external_ref(self):
|
||||
"""Return whether the current user may edit adapter reference ids.
|
||||
|
||||
Returns:
|
||||
bool: ``True`` for TCG administrators and system users.
|
||||
"""
|
||||
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):
|
||||
"""Protect technical external references during card creation.
|
||||
|
||||
Args:
|
||||
vals_list: Standard ORM payloads for new cards.
|
||||
|
||||
Returns:
|
||||
Model: Created card records.
|
||||
"""
|
||||
prepared_vals_list = []
|
||||
for vals in vals_list:
|
||||
prepared_vals = dict(vals)
|
||||
if (
|
||||
prepared_vals.get("external_ref")
|
||||
and not self.env.context.get("mvd_tcg_bypass_external_ref_write")
|
||||
and not self._mvd_tcg_can_edit_external_ref()
|
||||
):
|
||||
raise UserError(
|
||||
_(
|
||||
"Adapter reference ids can only be set by TCG administrators."
|
||||
)
|
||||
)
|
||||
prepared_vals_list.append(prepared_vals)
|
||||
return super().create(prepared_vals_list)
|
||||
|
||||
def write(self, vals):
|
||||
"""Prevent accidental edits on validated reference cards.
|
||||
|
||||
Args:
|
||||
vals: Field values to update.
|
||||
|
||||
Returns:
|
||||
bool: Result of the underlying ORM write.
|
||||
|
||||
Raises:
|
||||
UserError: If a validated card is modified outside the allowed paths.
|
||||
"""
|
||||
if (
|
||||
"external_ref" in vals
|
||||
and not self.env.context.get("mvd_tcg_bypass_external_ref_write")
|
||||
and not self._mvd_tcg_can_edit_external_ref()
|
||||
):
|
||||
raise UserError(
|
||||
_(
|
||||
"Adapter reference ids can only be changed by TCG administrators."
|
||||
)
|
||||
)
|
||||
if not self.env.context.get("mvd_tcg_bypass_validated_write"):
|
||||
validated_cards = self.filtered(lambda card: card.state == "validated")
|
||||
is_reset_to_draft = set(vals) == {"state"} and vals.get("state") == "draft"
|
||||
if validated_cards and not is_reset_to_draft:
|
||||
raise UserError(
|
||||
_(
|
||||
"Validated cards are read-only. Reset the card to Draft first "
|
||||
"if you really want to edit it."
|
||||
)
|
||||
)
|
||||
return super().write(vals)
|
||||
|
||||
def action_validate(self):
|
||||
"""Lock the current cards as validated reference data.
|
||||
|
||||
Returns:
|
||||
bool: ``True`` when the operation completed.
|
||||
"""
|
||||
self.write({"state": "validated"})
|
||||
return True
|
||||
|
||||
def action_reset_to_draft(self):
|
||||
"""Move the current cards back into editable draft state.
|
||||
|
||||
Returns:
|
||||
bool: ``True`` when the operation completed.
|
||||
"""
|
||||
self.write({"state": "draft"})
|
||||
return True
|
||||
|
||||
def _mvd_tcg_get_deck_image_binary(self):
|
||||
"""Return the preferred card image for deck previews.
|
||||
|
||||
Returns:
|
||||
str | bool: Base64 image data for deck-related previews.
|
||||
"""
|
||||
self.ensure_one()
|
||||
return self.image_512 or self.image_1920 or False
|
||||
140
models/mvd_tcg_game.py
Normal file
140
models/mvd_tcg_game.py
Normal file
@@ -0,0 +1,140 @@
|
||||
"""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,
|
||||
},
|
||||
]
|
||||
12
models/res_config_settings.py
Normal file
12
models/res_config_settings.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
_inherit = "res.config.settings"
|
||||
|
||||
mvd_tcg_max_file_upload_size_mb = fields.Integer(
|
||||
string="Maximum File Upload Size (MB)",
|
||||
config_parameter="mvd_tcg_base.max_file_upload_size_mb",
|
||||
default=256,
|
||||
help="Maximum file size exposed to the Odoo web client for uploads in TCG workflows.",
|
||||
)
|
||||
Reference in New Issue
Block a user