🎉 Initialize module repository

This commit is contained in:
Marc Wempe
2026-04-03 23:08:57 +02:00
commit bf9156b973
190 changed files with 4090 additions and 0 deletions

6
models/__init__.py Normal file
View File

@@ -0,0 +1,6 @@
from . import mvd_tcg_game
from . import mvd_tcg_card
from . import mvd_tcg_mtg_card_face
from . import mvd_tcg_mtg_card_legality
from . import mvd_tcg_mtg_set
from . import mvd_tcg_mtg_taxonomy

499
models/mvd_tcg_card.py Normal file
View File

@@ -0,0 +1,499 @@
"""Magic: The Gathering card extensions for the TCG suite."""
import re
from odoo import api, fields, models
COLLECTOR_NUMBER_PATTERN = re.compile(r"^(\d+)(.*)$")
class MvdTcgCard(models.Model):
"""Extend neutral card references with MTG-specific metadata."""
_inherit = "mvd.tcg.card"
is_mtg_game = fields.Boolean(compute="_compute_is_mtg_game")
mtg_set_id = fields.Many2one(
"mvd.tcg.mtg.set",
string="Set",
index=True,
ondelete="restrict",
)
mtg_set_code = fields.Char(
related="mtg_set_id.code",
string="Set Code",
store=True,
index=True,
)
mtg_rarity_id = fields.Many2one(
"mvd.tcg.mtg.rarity",
string="Rarity",
index=True,
ondelete="restrict",
)
mtg_rarity_code = fields.Char(
related="mtg_rarity_id.code",
string="Rarity Code",
store=True,
index=True,
)
mtg_collector_number = fields.Char(string="Collector Number", index=True)
mtg_collector_sort_key = fields.Char(
string="Collector Sort Key",
compute="_compute_mtg_collector_sort_key",
store=True,
index=True,
)
mtg_oracle_id = fields.Char(string="Oracle ID", index=True)
mtg_layout = fields.Char(string="Layout", index=True)
mtg_mana_cost = fields.Char(string="Mana Cost")
mtg_mana_value = fields.Float(string="Mana Value")
mtg_type_line = fields.Char(string="Type Line", translate=True)
mtg_oracle_text = fields.Text(string="Oracle Text", translate=True)
mtg_flavor_text = fields.Text(string="Flavor Text", translate=True)
mtg_color_ids = fields.Many2many(
"mvd.tcg.mtg.color",
"mvd_tcg_card_mtg_color_rel",
"card_id",
"color_id",
string="Colors",
)
mtg_color_identity_ids = fields.Many2many(
"mvd.tcg.mtg.color",
"mvd_tcg_card_mtg_color_identity_rel",
"card_id",
"color_id",
string="Color Identity",
)
mtg_color_identity_signature = fields.Char(
string="Color Identity Signature",
compute="_compute_mtg_color_identity_signature",
store=True,
index=True,
help="Ordered MTG color identity signature, for example W, UB or WUBRG.",
)
mtg_card_type_ids = fields.Many2many(
"mvd.tcg.mtg.card.type",
"mvd_tcg_card_mtg_card_type_rel",
"card_id",
"type_id",
string="Card Types",
)
mtg_keyword_ids = fields.Many2many(
"mvd.tcg.mtg.keyword",
"mvd_tcg_card_mtg_keyword_rel",
"card_id",
"keyword_id",
string="Keywords",
)
mtg_game_platform_ids = fields.Many2many(
"mvd.tcg.mtg.platform",
"mvd_tcg_card_mtg_platform_rel",
"card_id",
"platform_id",
string="Games",
)
mtg_finish_ids = fields.Many2many(
"mvd.tcg.mtg.finish",
"mvd_tcg_card_mtg_finish_rel",
"card_id",
"finish_id",
string="Finishes",
)
mtg_power = fields.Char(string="Power")
mtg_toughness = fields.Char(string="Toughness")
mtg_loyalty = fields.Char(string="Loyalty")
mtg_artist = fields.Char(string="Artist", index="trigram")
mtg_color_signature = fields.Char(
string="Color Signature",
compute="_compute_mtg_color_signature",
store=True,
index=True,
help="Ordered MTG color signature, for example W, UB or WUBRG.",
)
mtg_face_ids = fields.One2many("mvd.tcg.mtg.card.face", "card_id", string="Faces")
mtg_face_count = fields.Integer(
string="Face Count",
compute="_compute_mtg_face_count",
store=True,
)
mtg_legality_ids = fields.One2many(
"mvd.tcg.mtg.card.legality",
"card_id",
string="Legalities",
)
mtg_is_token = fields.Boolean(string="Token")
mtg_is_reprint = fields.Boolean(string="Reprint")
mtg_is_promo = fields.Boolean(string="Promo")
mtg_is_digital = fields.Boolean(string="Digital")
mtg_is_full_art = fields.Boolean(string="Full Art")
mtg_is_textless = fields.Boolean(string="Textless")
mtg_set_released_on = fields.Date(
related="mtg_set_id.released_on",
string="Released On",
store=True,
index=True,
)
mtg_set_type = fields.Char(
related="mtg_set_id.set_type",
string="Set Type",
store=True,
index=True,
)
_mtg_set_collector_unique = models.Constraint(
"UNIQUE (mtg_set_id, mtg_collector_number)",
"The collector number must be unique inside an MTG set.",
)
@api.depends("game_id.code")
def _compute_is_mtg_game(self):
"""Flag whether the current card belongs to the MTG adapter."""
for card in self:
card.is_mtg_game = card.game_id.code == "mtg"
@api.depends("mtg_collector_number")
def _compute_mtg_collector_sort_key(self):
"""Build a stable natural-sort key for MTG collector numbers.
Returns:
None: The compute updates records in place.
"""
for card in self:
collector_number = (card.mtg_collector_number or "").strip().lower()
if not collector_number:
card.mtg_collector_sort_key = "99999999:"
continue
match = COLLECTOR_NUMBER_PATTERN.match(collector_number)
if match:
numeric_part, suffix = match.groups()
card.mtg_collector_sort_key = (
f"{int(numeric_part):08d}:{suffix.strip()}"
)
continue
card.mtg_collector_sort_key = f"99999999:{collector_number}"
@api.depends("mtg_color_ids", "mtg_color_ids.sequence", "mtg_color_ids.code")
def _compute_mtg_color_signature(self):
"""Build the canonical MTG color signature from selected colors.
Returns:
None: The compute updates records in place.
"""
for card in self:
colors = card.mtg_color_ids.sorted(
key=lambda color: (color.sequence, color.code or "", color.id)
)
card.mtg_color_signature = "".join(
(color.code or "").strip().upper() for color in colors
) or False
@api.depends(
"mtg_color_identity_ids",
"mtg_color_identity_ids.sequence",
"mtg_color_identity_ids.code",
)
def _compute_mtg_color_identity_signature(self):
"""Build the canonical MTG color identity signature.
Returns:
None: The compute updates records in place.
"""
for card in self:
colors = card.mtg_color_identity_ids.sorted(
key=lambda color: (color.sequence, color.code or "", color.id)
)
card.mtg_color_identity_signature = "".join(
(color.code or "").strip().upper() for color in colors
) or False
@api.depends("mtg_face_ids")
def _compute_mtg_face_count(self):
"""Compute how many explicit faces are linked to each card.
Returns:
None: The compute updates records in place.
"""
face_data = self.env["mvd.tcg.mtg.card.face"]._read_group(
[("card_id", "in", self.ids)],
["card_id"],
["__count"],
)
counts_by_card = {record.id: count for record, count in face_data}
for card in self:
card.mtg_face_count = counts_by_card.get(card.id, 0)
def action_open_mtg_set(self):
"""Open the linked MTG set in form view.
Returns:
dict: Window action for the linked MTG set.
"""
self.ensure_one()
if not self.mtg_set_id:
return False
return {
"type": "ir.actions.act_window",
"name": self.mtg_set_id.display_name,
"res_model": "mvd.tcg.mtg.set",
"view_mode": "form",
"res_id": self.mtg_set_id.id,
"target": "current",
}
def mtg_get_rules_sections(self):
"""Return one face-aware MTG rules structure for the current card.
Returns:
list[dict[str, object]]: Ordered rules sections. Multi-face cards
return one section per face, while single-face cards return one
fallback section from the card header fields.
"""
self.ensure_one()
face_sections = []
for face in self.mtg_face_ids.sorted(lambda current_face: (current_face.sequence, current_face.id)):
section = {
"sequence": face.sequence,
"name": face.name or False,
"mana_cost": face.mana_cost or False,
"type_line": face.type_line or False,
"oracle_text": face.oracle_text or False,
"flavor_text": face.flavor_text or False,
"power": face.power or False,
"toughness": face.toughness or False,
"loyalty": face.loyalty or False,
}
if any(section.values()):
face_sections.append(section)
if face_sections:
return face_sections
return [
{
"sequence": 10,
"name": self.name or False,
"mana_cost": self.mtg_mana_cost or False,
"type_line": self.mtg_type_line or False,
"oracle_text": self.mtg_oracle_text or False,
"flavor_text": self.mtg_flavor_text or False,
"power": self.mtg_power or False,
"toughness": self.mtg_toughness or False,
"loyalty": self.mtg_loyalty or False,
}
]
def mtg_get_rules_summary(self):
"""Return one analysis-friendly MTG rules summary.
Returns:
str | bool: Single-face cards return their oracle text directly.
Multi-face cards return a combined face-aware rules summary.
"""
self.ensure_one()
rule_sections = self.mtg_get_rules_sections()
if len(rule_sections) <= 1:
return rule_sections and rule_sections[0]["oracle_text"] or False
rendered_sections = []
for section in rule_sections:
section_lines = []
heading_parts = [section["name"]]
if section["mana_cost"]:
heading_parts.append(section["mana_cost"])
heading = " ".join(part for part in heading_parts if part)
if heading:
section_lines.append(heading)
if section["type_line"]:
section_lines.append(section["type_line"])
if section["oracle_text"]:
section_lines.append(section["oracle_text"])
if section_lines:
rendered_sections.append("\n".join(section_lines))
return "\n\n//\n\n".join(rendered_sections) or False
def mtg_get_singleton_key_aliases(self):
"""Return stable MTG identity aliases across printings and styles.
Returns:
tuple[str, ...]: Ordered identity aliases. Cards with a missing
Oracle ID still expose rules-based fallbacks that can match
styled variants of the same card.
"""
self.ensure_one()
english_card = self.with_context(lang="en_US")
aliases = []
oracle_id = (english_card.mtg_oracle_id or "").strip()
if oracle_id:
aliases.append(f"oracle:{oracle_id}")
rules_summary = re.sub(
r"\s+",
" ",
(english_card.mtg_get_rules_summary() or "").strip(),
).strip()
if rules_summary:
aliases.append(f"rules:{rules_summary}")
deduplicated_oracle_chunks = []
for section in english_card.mtg_get_rules_sections():
oracle_text = re.sub(
r"\s+",
" ",
(section["oracle_text"] or "").strip(),
).strip()
if oracle_text and oracle_text not in deduplicated_oracle_chunks:
deduplicated_oracle_chunks.append(oracle_text)
if deduplicated_oracle_chunks:
aliases.append(f"oracletext:{' // '.join(deduplicated_oracle_chunks)}")
face_names = " // ".join(
section["name"]
for section in english_card.mtg_get_rules_sections()
if section["name"]
)
fallback_name = face_names or (english_card.name or "").strip()
fallback_type = (english_card.mtg_type_line or "").strip()
fallback_cost = (english_card.mtg_mana_cost or "").strip()
if any((fallback_name, fallback_type, fallback_cost)):
aliases.append(f"fallback:{fallback_name}|{fallback_type}|{fallback_cost}")
deduplicated_aliases = []
for alias in aliases:
if alias and alias not in deduplicated_aliases:
deduplicated_aliases.append(alias)
return tuple(deduplicated_aliases)
def mtg_get_singleton_key(self):
"""Return the primary MTG singleton key for the current card.
Returns:
str | bool: First stable singleton alias, if any.
"""
self.ensure_one()
aliases = self.mtg_get_singleton_key_aliases()
return aliases[0] if aliases else False
def mtg_allows_unlimited_copies(self):
"""Return whether one MTG card bypasses singleton restrictions.
Returns:
bool: ``True`` for basic lands and cards with explicit unlimited-
copy rules text.
"""
self.ensure_one()
english_card = self.with_context(lang="en_US")
combined_type_line = " ".join(
section["type_line"]
for section in english_card.mtg_get_rules_sections()
if section["type_line"]
).lower()
rules_summary = (english_card.mtg_get_rules_summary() or "").lower()
is_basic_land = "land" in combined_type_line and "basic" in combined_type_line
allows_unlimited_copies = (
"a deck can have any number of cards named" in rules_summary
)
return is_basic_land or allows_unlimited_copies
@api.model
def _mtg_exact_color_signature_from_ids(self, color_ids):
"""Build the canonical color signature for selected color records.
Args:
color_ids: MTG color record ids from a filter domain.
Returns:
str | bool: Canonical signature like ``WB`` or ``False`` when invalid.
"""
normalized_ids = [int(color_id) for color_id in color_ids or [] if color_id]
if not normalized_ids:
return False
colors = self.env["mvd.tcg.mtg.color"].browse(normalized_ids).exists().sorted(
key=lambda color: (color.sequence, color.code or "", color.id)
)
if len(colors) != len(set(normalized_ids)):
return False
return "".join((color.code or "").strip().upper() for color in colors)
@api.model
def _mtg_transform_exact_color_domain(self, domain):
"""Rewrite MTG color filters into exact color-signature filters.
Args:
domain: Original ORM domain.
Returns:
list: Domain with exact MTG color matching where applicable.
"""
domain_object = fields.Domain(domain or [])
selected_color_ids = []
for condition in domain_object.iter_conditions():
if condition.field_expr != "mtg_color_ids":
continue
if condition.operator == "=" and condition.value:
color_id = int(condition.value)
if color_id not in selected_color_ids:
selected_color_ids.append(color_id)
continue
if condition.operator == "in" and isinstance(condition.value, (list, tuple)):
for color_id in condition.value:
normalized_id = int(color_id)
if normalized_id not in selected_color_ids:
selected_color_ids.append(normalized_id)
if not selected_color_ids:
return domain
exact_signature = self._mtg_exact_color_signature_from_ids(selected_color_ids)
if not exact_signature:
return list(fields.Domain.FALSE)
remaining_domain = domain_object.map_conditions(
lambda condition: (
fields.Domain.TRUE
if condition.field_expr == "mtg_color_ids"
else fields.Domain(condition)
)
).optimize(self)
exact_domain = fields.Domain(
[("mtg_color_signature", "=", exact_signature)]
)
return list(fields.Domain.AND([remaining_domain, exact_domain]).optimize(self))
@api.model
def _search(
self,
domain,
offset=0,
limit=None,
order=None,
*,
active_test=True,
bypass_access=False,
):
"""Apply exact MTG color matching for the Magic card action.
Args:
domain: ORM domain for the current search.
offset: Search offset.
limit: Optional maximal number of records.
order: SQL order clause.
active_test: Whether active records should be filtered implicitly.
bypass_access: Whether access rules should be bypassed.
Returns:
Query: Matching search query.
"""
if self.env.context.get("mvd_mtg_exact_color_filter"):
domain = self._mtg_transform_exact_color_domain(domain)
return super()._search(
domain,
offset=offset,
limit=limit,
order=order,
active_test=active_test,
bypass_access=bypass_access,
)

14
models/mvd_tcg_game.py Normal file
View File

@@ -0,0 +1,14 @@
"""MTG-specific helpers on the neutral game model."""
from odoo import api, models
class MvdTcgGame(models.Model):
"""Provide stable access to the seeded MTG game record."""
_inherit = "mvd.tcg.game"
@api.model
def _mvd_tcg_get_mtg_game(self):
"""Return the seeded MTG game record."""
return self.env.ref("mvd_tcg_mtg.mvd_tcg_game_mtg")

View File

@@ -0,0 +1,33 @@
"""Magic: The Gathering face-level reference models."""
from odoo import fields, models
class MvdTcgMtgCardFace(models.Model):
"""Represent one ordered printed face of an MTG card reference."""
_name = "mvd.tcg.mtg.card.face"
_description = "MTG Card Face"
_order = "card_id, sequence, id"
card_id = fields.Many2one(
"mvd.tcg.card",
required=True,
index=True,
ondelete="cascade",
)
sequence = fields.Integer(default=10, index=True)
name = fields.Char(required=True, translate=True, index="trigram")
mana_cost = fields.Char()
type_line = fields.Char(translate=True)
oracle_text = fields.Text(translate=True)
flavor_text = fields.Text(translate=True)
power = fields.Char()
toughness = fields.Char()
loyalty = fields.Char()
artist = fields.Char(index="trigram")
_card_sequence_unique = models.Constraint(
"UNIQUE (card_id, sequence)",
"The MTG face sequence must be unique per card.",
)

View File

@@ -0,0 +1,52 @@
"""Magic: The Gathering legality models."""
from odoo import fields, models
LEGALITY_SELECTION = [
("legal", "Legal"),
("not_legal", "Not Legal"),
("restricted", "Restricted"),
("banned", "Banned"),
]
class MvdTcgMtgCardLegality(models.Model):
"""Store one legality status per MTG card and constructed format."""
_name = "mvd.tcg.mtg.card.legality"
_description = "MTG Card Legality"
_order = "format_sequence, id"
card_id = fields.Many2one(
"mvd.tcg.card",
required=True,
index=True,
ondelete="cascade",
)
format_id = fields.Many2one(
"mvd.tcg.mtg.format",
required=True,
index=True,
ondelete="restrict",
)
format_code = fields.Char(
related="format_id.code",
store=True,
index=True,
)
format_sequence = fields.Integer(
related="format_id.sequence",
store=True,
index=True,
)
status = fields.Selection(
selection=LEGALITY_SELECTION,
required=True,
default="not_legal",
index=True,
)
_card_format_unique = models.Constraint(
"UNIQUE (card_id, format_id)",
"The MTG legality format must be unique per card.",
)

72
models/mvd_tcg_mtg_set.py Normal file
View File

@@ -0,0 +1,72 @@
"""Magic: The Gathering set models for the TCG suite."""
from odoo import api, fields, models
class MvdTcgMtgSet(models.Model):
"""Represent a Magic: The Gathering set within the MTG adapter."""
_name = "mvd.tcg.mtg.set"
_description = "MTG Set"
_order = "released_on desc, code, 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)
game_id = fields.Many2one(
"mvd.tcg.game",
required=True,
index=True,
default=lambda self: self._default_game_id(),
ondelete="restrict",
)
released_on = fields.Date(index=True)
set_type = fields.Char(index=True)
official_card_count = fields.Integer()
icon_svg_uri = fields.Char()
note = fields.Text(translate=True)
mtg_card_ids = fields.One2many(
"mvd.tcg.card",
"mtg_set_id",
string="Cards",
)
mtg_card_count = fields.Integer(
string="Card Count",
compute="_compute_mtg_card_count",
)
_code_unique = models.Constraint(
"UNIQUE (game_id, code)",
"The MTG set code must be unique per game.",
)
@api.model
def _default_game_id(self):
"""Return the seeded MTG game record."""
return self.env["mvd.tcg.game"]._mvd_tcg_get_mtg_game().id
@api.depends("mtg_card_ids")
def _compute_mtg_card_count(self):
"""Compute how many MTG cards are currently linked to each set."""
card_data = self.env["mvd.tcg.card"]._read_group(
[("mtg_set_id", "in", self.ids)],
["mtg_set_id"],
["__count"],
)
counts_by_set = {record.id: count for record, count in card_data}
for mtg_set in self:
mtg_set.mtg_card_count = counts_by_set.get(mtg_set.id, 0)
def action_open_cards(self):
"""Open the MTG cards catalog filtered on the selected set."""
self.ensure_one()
action = self.env["ir.actions.actions"]._for_xml_id(
"mvd_tcg_mtg.mvd_tcg_mtg_card_action"
)
action["domain"] = [("mtg_set_id", "=", self.id)]
action["context"] = {
"default_game_id": self.game_id.id,
"default_mtg_set_id": self.id,
}
return action

View File

@@ -0,0 +1,230 @@
"""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.",
)