from __future__ import annotations from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from django_extensions.db.models import TimeStampedModel from common.models import DocumentedModel, UniquelyNamedModel class Path(DocumentedModel, UniquelyNamedModel, TimeStampedModel, models.Model): profile = models.ForeignKey( "character.Profile", on_delete=models.CASCADE, related_name="paths", blank=True, null=True, verbose_name="profil", ) race = models.ForeignKey( "character.Race", on_delete=models.CASCADE, related_name="paths", blank=True, null=True, verbose_name="race", ) class Category(models.TextChoices): PROFILE = "profile", "Profil" RACE = "race", "Race" PRESTIGE = "prestige", "Prestige" CREATURE = "creature", "Créature" category = models.CharField( max_length=20, choices=Category.choices, verbose_name="catégorie", ) notes = models.TextField(blank=True, verbose_name="notes") class Meta(UniquelyNamedModel.Meta, TimeStampedModel.Meta): verbose_name = "Voie" verbose_name_plural = "Voies" def __str__(self): name = f"{self.display_name}" if self.related_to: name += f" ({self.related_to.name})" return name @property def display_name(self) -> str: to_remove = ["voie de la", "voie de l'", "voie du", "voie des", "voie de"] display_name = self.name.lower() for text in to_remove: display_name = display_name.replace(text, "") return display_name.strip().capitalize() @property def related_to(self) -> UniquelyNamedModel | None: category = Path.Category(self.category) if category == Path.Category.PROFILE: return self.profile if category == Path.Category.RACE: return self.race return None def get_next_capability(self, character) -> Capability: next_rank = self.max_rank(character) + 1 return self.capabilities.get(rank=next_rank) def has_next_capability(self, character) -> bool: try: self.get_next_capability(character) except Capability.DoesNotExist: return False else: return True def max_rank(self, character) -> int: return character.capabilities.filter(path=self).count() class CapabilityManager(models.Manager): def get_by_natural_key(self, path, rank): return self.get(path=path, rank=rank) class Capability(DocumentedModel, TimeStampedModel, models.Model): name = models.CharField(max_length=100, blank=False, null=False, verbose_name="nom") path = models.ForeignKey( "character.Path", on_delete=models.CASCADE, verbose_name="voie", related_name="capabilities", ) rank = models.PositiveSmallIntegerField( validators=[MinValueValidator(1), MaxValueValidator(5)], verbose_name="rang", ) limited = models.BooleanField( blank=True, null=False, default=False, verbose_name="limitée", ) spell = models.BooleanField( blank=True, null=False, default=False, verbose_name="sort", ) description = models.TextField(verbose_name="description") objects = CapabilityManager() class Meta(TimeStampedModel.Meta): constraints = [models.UniqueConstraint("path", "rank", name="unique_path_rank")] verbose_name = "Capacité" verbose_name_plural = "Capacités" def __str__(self): description = f"{self.name} - {self.path.display_name} rang {self.rank}" if self.path.related_to: description += f" ({self.path.related_to.name})" return description def natural_key(self): return (self.path_id, self.rank) @property def capability_points_cost(self) -> int: if self.rank in [1, 2]: return 1 return 2 class RacialCapabilityManager(models.Manager): def get_by_natural_key(self, name: str, race_id: int): return self.get(name=name, race_id=race_id) class RacialCapability(DocumentedModel, TimeStampedModel, models.Model): name = models.CharField(max_length=100, blank=False, null=False, verbose_name="nom") race = models.ForeignKey( "character.Race", on_delete=models.CASCADE, verbose_name="race", ) description = models.TextField(verbose_name="description") objects = RacialCapabilityManager() class Meta(TimeStampedModel.Meta): verbose_name = "Capacité raciale" verbose_name_plural = "Capacités raciales" constraints = [models.UniqueConstraint("name", "race", name="unique_name_race")] def __str__(self): return f"{self.name} ({self.race})" def natural_key(self): return (self.name, self.race_id)