Enable more ruff rules

This commit is contained in:
Gabriel Augendre 2023-02-28 12:34:45 +01:00
parent bcabc15054
commit 16d7ff5d20
38 changed files with 394 additions and 215 deletions

View file

@ -36,50 +36,12 @@ python_files = [
[tool.ruff]
src = ["src"]
target-version = "py311"
select = [
"F", # pyflakes
"E", "W", # pycodestyle
"C90", # mccabe
"I", # isort
"N", # pep8-naming
"D", # pydocstyle
"S", # flake8-bandit
"FBT", # flake8-boolean-trap
"B", # flake8-bugbear
"A", # flake8-builtins
"C4", # flake8-comprehensions
"DTZ", # flake8-datetimez
"T10", # flake8-debugger
"EXE", # flake8-executable
"ISC", # flake8-implicit-str-concat
"ICN", # flake8-import-conventions
"G", # flake8-logging-format
"INP", # flake8-no-pep420
"PIE", # flake8-pie
"T20", # flake8-print
"PT", # flake8-pytest-style
"RET", # flake8-return
"SIM", # flake8-simplify
"TID", # flake8-tidy-imports
"ARG", # flake8-unused-arguments
"PTH", # flake8-use-pathlib
"ERA", # eradicate
"PD", # pandas-vet
"PGH", # pygrep-hooks
"PL", # pylint
"TRY", # tryceratops
"RUF", # ruff-specific rules
]
select = ["ALL"]
unfixable = ["T20", "RUF001", "RUF002", "RUF003"]
ignore = [
"UP", # pyupgrade
"YTT", # flake8-2020
"ANN", # flake8-annotations
"BLE", # flake8-blind-except
"COM", # flake8-commas
"EM", # flake8-errmsg
"Q", # flake8-quotes
"TCH", # flake8-type-checking / TODO: revisit later ?
"E501", # long lines

View file

@ -92,12 +92,14 @@ class RaceAdmin(admin.ModelAdmin):
class CharacterAdminForm(ModelForm):
class Meta:
model = models.Character
exclude = ()
exclude = () # noqa: DJ006
def __init__(self, *args, **kwargs):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.fields["capabilities"].queryset = models.Capability.objects.select_related(
"path", "path__race", "path__profile"
"path",
"path__race",
"path__profile",
)
self.fields[
"racial_capability"
@ -127,7 +129,7 @@ class CharacterAdmin(admin.ModelAdmin):
"level",
"race",
"private",
]
],
},
),
("Apparence", {"fields": ["gender", "age", "height", "weight"]}),
@ -141,7 +143,7 @@ class CharacterAdmin(admin.ModelAdmin):
("value_intelligence", "modifier_intelligence"),
("value_wisdom", "modifier_wisdom"),
("value_charisma", "modifier_charisma"),
]
],
},
),
(
@ -153,7 +155,7 @@ class CharacterAdmin(admin.ModelAdmin):
"attack_range",
"attack_magic",
"states",
]
],
},
),
("Vitalité", {"fields": [("health_max", "health_remaining")]}),
@ -165,7 +167,7 @@ class CharacterAdmin(admin.ModelAdmin):
"weapons",
"equipment",
("money_pp", "money_po", "money_pa", "money_pc"),
]
],
},
),
("Race", {"fields": ["racial_capability"]}),

View file

@ -18,10 +18,12 @@ class AddPathForm(forms.Form):
empty_label="----- Voies liées au personnage -----",
)
other_path = forms.ModelChoiceField(
Path.objects.none(), required=False, empty_label="----- Autres voies -----"
Path.objects.none(),
required=False,
empty_label="----- Autres voies -----",
)
def __init__(self, character: Character, *args, **kwargs):
def __init__(self, character: Character, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
paths = {cap.path_id for cap in character.capabilities.all()}
paths = (
@ -30,12 +32,12 @@ class AddPathForm(forms.Form):
.select_related("profile", "race")
)
character_paths = paths.filter(
Q(profile=character.profile) | Q(race=character.race)
Q(profile=character.profile) | Q(race=character.race),
)
self.fields["character_path"].queryset = character_paths
self.fields["character_path"].widget.attrs["class"] = "form-select"
self.fields["other_path"].queryset = paths.exclude(
pk__in={path.pk for path in character_paths}
pk__in={path.pk for path in character_paths},
)
self.fields["other_path"].widget.attrs["class"] = "form-select"
@ -43,12 +45,13 @@ class AddPathForm(forms.Form):
cleaned_data = super().clean()
values = [cleaned_data.get("character_path"), cleaned_data.get("other_path")]
if len(list(filter(None, values))) != 1:
raise ValidationError("Vous devez sélectionner une seule valeur.")
msg = "Vous devez sélectionner une seule valeur."
raise ValidationError(msg)
return cleaned_data
class CharacterCreateForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.fields[
"racial_capability"

View file

@ -17,7 +17,8 @@ class Command(BaseCommand):
while len(cards) < expected_capability_count:
self.selenium.find_element(By.TAG_NAME, "body").send_keys(Keys.END)
cards = self.selenium.find_elements(
By.CSS_SELECTOR, ".col-md-4.col-sm-6.col-12.mb-4.views-row"
By.CSS_SELECTOR,
".col-md-4.col-sm-6.col-12.mb-4.views-row",
)
for card in cards:
try:
@ -63,11 +64,11 @@ class Command(BaseCommand):
},
)
self.stdout.write(
self.style.SUCCESS(f"Created/updated cap {capability}")
self.style.SUCCESS(f"Created/updated cap {capability}"),
)
except Exception as e:
self.stdout.write(
self.style.ERROR(f"Couldn't create/update cap {name}: {e}")
self.style.ERROR(f"Couldn't create/update cap {name}: {e}"),
)
def get_paths(self, card: WebElement, name: str) -> list[Path]:
@ -78,7 +79,7 @@ class Command(BaseCommand):
paths.append(Path.objects.get(name__iexact=path_name))
except Exception:
self.stdout.write(
self.style.WARNING(f"Couldn't find path in card for cap '{name}'.")
self.style.WARNING(f"Couldn't find path in card for cap '{name}'."),
)
return []
return paths

View file

@ -22,10 +22,12 @@ class Command(BaseCommand):
def import_row(self, url: str, state_row: WebElement) -> None:
name = state_row.find_element(By.CLASS_NAME, "views-field-name").text.strip()
description = state_row.find_element(
By.CLASS_NAME, "views-field-description__value"
By.CLASS_NAME,
"views-field-description__value",
).text.strip()
icon_url = state_row.find_element(
By.CSS_SELECTOR, ".views-field-field-svg-icon img"
By.CSS_SELECTOR,
".views-field-field-svg-icon img",
).get_attribute("src")
state, _ = HarmfulState.objects.update_or_create(
name=name,

View file

@ -16,7 +16,8 @@ class Command(BaseCommand):
while len(anchors) < expected_path_count:
self.selenium.find_element(By.TAG_NAME, "body").send_keys(Keys.END)
anchors = self.selenium.find_elements(
By.CSS_SELECTOR, ".card-body .card-title a"
By.CSS_SELECTOR,
".card-body .card-title a",
)
urls = [anchor.get_attribute("href") for anchor in anchors]
for url in urls:
@ -57,7 +58,8 @@ class Command(BaseCommand):
try:
category = (
self.selenium.find_element(
By.CSS_SELECTOR, ".field--name-type .field__item"
By.CSS_SELECTOR,
".field--name-type .field__item",
)
.text.lower()
.strip()
@ -65,8 +67,8 @@ class Command(BaseCommand):
except Exception:
self.stdout.write(
self.style.WARNING(
f"Couldn't find category for {name}. Defaulting to profile."
)
f"Couldn't find category for {name}. Defaulting to profile.",
),
)
return Path.Category.PROFILE
@ -79,7 +81,8 @@ class Command(BaseCommand):
def get_profile(self, name: str) -> Profile | None:
try:
profile_name = self.selenium.find_element(
By.CSS_SELECTOR, ".field--name-type + strong + a"
By.CSS_SELECTOR,
".field--name-type + strong + a",
).text
except Exception:
self.stdout.write(self.style.WARNING(f"Couldn't find profile for {name}"))
@ -99,7 +102,8 @@ class Command(BaseCommand):
def get_notes(self) -> str:
try:
return self.selenium.find_element(
By.CSS_SELECTOR, ".mt-3 > .field--name-description"
By.CSS_SELECTOR,
".mt-3 > .field--name-description",
).text.strip()
except Exception:
return ""

View file

@ -41,19 +41,20 @@ class Command(BaseCommand):
def get_dice(self, name: str) -> Dice:
dice = self.selenium.find_element(By.CSS_SELECTOR, ".dice + div").text.split(
"D"
"D",
)
number_of_dice, dice_value = int(dice[0]), int(dice[1])
if number_of_dice != 1:
self.stdout.write(
self.style.WARNING(f"Multiple dice for {name}: {number_of_dice}")
self.style.WARNING(f"Multiple dice for {name}: {number_of_dice}"),
)
return Dice(dice_value)
def get_magical_strength(self) -> Profile.MagicalStrength:
try:
magical_strength = self.selenium.find_element(
By.CSS_SELECTOR, ".field--name-magic-attack-modifier .field__item"
By.CSS_SELECTOR,
".field--name-magic-attack-modifier .field__item",
).text
magical_strength = Profile.MagicalStrength(magical_strength)
except Exception:

View file

@ -27,7 +27,8 @@ class Command(BaseCommand):
self.stdout.write(self.style.SUCCESS(f"Created/updated race {race}"))
racial_cap = self.selenium.find_element(
By.CSS_SELECTOR, ".field--name-abilities"
By.CSS_SELECTOR,
".field--name-abilities",
)
racial_name = (
racial_cap.find_element(By.TAG_NAME, "strong")

View file

@ -32,13 +32,15 @@ class Migration(migrations.Migration):
(
"created",
django_extensions.db.fields.CreationDateTimeField(
auto_now_add=True, verbose_name="created"
auto_now_add=True,
verbose_name="created",
),
),
(
"modified",
django_extensions.db.fields.ModificationDateTimeField(
auto_now=True, verbose_name="modified"
auto_now=True,
verbose_name="modified",
),
),
(
@ -47,7 +49,7 @@ class Migration(migrations.Migration):
validators=[
django.core.validators.MinValueValidator(1),
django.core.validators.MaxValueValidator(5),
]
],
),
),
("limited", models.BooleanField(blank=True)),
@ -74,13 +76,15 @@ class Migration(migrations.Migration):
(
"created",
django_extensions.db.fields.CreationDateTimeField(
auto_now_add=True, verbose_name="created"
auto_now_add=True,
verbose_name="created",
),
),
(
"modified",
django_extensions.db.fields.ModificationDateTimeField(
auto_now=True, verbose_name="modified"
auto_now=True,
verbose_name="modified",
),
),
(
@ -106,7 +110,7 @@ class Migration(migrations.Migration):
(10, "D10"),
(12, "D12"),
(20, "D20"),
]
],
),
),
],
@ -130,13 +134,15 @@ class Migration(migrations.Migration):
(
"created",
django_extensions.db.fields.CreationDateTimeField(
auto_now_add=True, verbose_name="created"
auto_now_add=True,
verbose_name="created",
),
),
(
"modified",
django_extensions.db.fields.ModificationDateTimeField(
auto_now=True, verbose_name="modified"
auto_now=True,
verbose_name="modified",
),
),
],
@ -160,13 +166,15 @@ class Migration(migrations.Migration):
(
"created",
django_extensions.db.fields.CreationDateTimeField(
auto_now_add=True, verbose_name="created"
auto_now_add=True,
verbose_name="created",
),
),
(
"modified",
django_extensions.db.fields.ModificationDateTimeField(
auto_now=True, verbose_name="modified"
auto_now=True,
verbose_name="modified",
),
),
("damage", models.CharField(blank=True, max_length=50)),
@ -192,20 +200,23 @@ class Migration(migrations.Migration):
(
"created",
django_extensions.db.fields.CreationDateTimeField(
auto_now_add=True, verbose_name="created"
auto_now_add=True,
verbose_name="created",
),
),
(
"modified",
django_extensions.db.fields.ModificationDateTimeField(
auto_now=True, verbose_name="modified"
auto_now=True,
verbose_name="modified",
),
),
("description", models.TextField()),
(
"race",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="character.race"
on_delete=django.db.models.deletion.CASCADE,
to="character.race",
),
),
],
@ -229,13 +240,15 @@ class Migration(migrations.Migration):
(
"created",
django_extensions.db.fields.CreationDateTimeField(
auto_now_add=True, verbose_name="created"
auto_now_add=True,
verbose_name="created",
),
),
(
"modified",
django_extensions.db.fields.ModificationDateTimeField(
auto_now=True, verbose_name="modified"
auto_now=True,
verbose_name="modified",
),
),
(
@ -355,7 +368,8 @@ class Migration(migrations.Migration):
model_name="capability",
name="path",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="character.path"
on_delete=django.db.models.deletion.CASCADE,
to="character.path",
),
),
migrations.AddConstraint(
@ -369,7 +383,9 @@ class Migration(migrations.Migration):
migrations.AddConstraint(
model_name="capability",
constraint=models.UniqueConstraint(
models.F("path"), models.F("rank"), name="unique_path_rank"
models.F("path"),
models.F("rank"),
name="unique_path_rank",
),
),
]

View file

@ -17,7 +17,9 @@ class Migration(migrations.Migration):
migrations.AddConstraint(
model_name="racialcapability",
constraint=models.UniqueConstraint(
models.F("name"), models.F("race"), name="unique_name_race"
models.F("name"),
models.F("race"),
name="unique_name_race",
),
),
]

View file

@ -56,7 +56,9 @@ class Migration(migrations.Migration):
model_name="capability",
name="limited",
field=models.BooleanField(
blank=True, default=False, verbose_name="limitée"
blank=True,
default=False,
verbose_name="limitée",
),
),
migrations.AlterField(
@ -108,7 +110,9 @@ class Migration(migrations.Migration):
model_name="character",
name="capabilities",
field=models.ManyToManyField(
blank=True, to="character.capability", verbose_name="capacités"
blank=True,
to="character.capability",
verbose_name="capacités",
),
),
migrations.AlterField(
@ -140,7 +144,7 @@ class Migration(migrations.Migration):
model_name="character",
name="health_remaining",
field=models.PositiveSmallIntegerField(
verbose_name="points de vie restants"
verbose_name="points de vie restants",
),
),
migrations.AlterField(
@ -162,14 +166,15 @@ class Migration(migrations.Migration):
model_name="character",
name="luck_points_remaining",
field=models.PositiveSmallIntegerField(
verbose_name="points de chance restants"
verbose_name="points de chance restants",
),
),
migrations.AlterField(
model_name="character",
name="mana_consumed",
field=models.PositiveSmallIntegerField(
default=0, verbose_name="mana utilisé"
default=0,
verbose_name="mana utilisé",
),
),
migrations.AlterField(
@ -261,7 +266,9 @@ class Migration(migrations.Migration):
model_name="character",
name="weapons",
field=models.ManyToManyField(
blank=True, to="character.weapon", verbose_name="armes"
blank=True,
to="character.weapon",
verbose_name="armes",
),
),
migrations.AlterField(

View file

@ -17,7 +17,8 @@ class Migration(migrations.Migration):
model_name="character",
name="mana_remaining",
field=models.PositiveSmallIntegerField(
default=0, verbose_name="mana restant"
default=0,
verbose_name="mana restant",
),
),
]

View file

@ -13,7 +13,8 @@ class Migration(migrations.Migration):
model_name="character",
name="recovery_points_remaining",
field=models.PositiveSmallIntegerField(
default=5, verbose_name="points de récupération restants"
default=5,
verbose_name="points de récupération restants",
),
),
]

View file

@ -30,13 +30,15 @@ class Migration(migrations.Migration):
(
"created",
django_extensions.db.fields.CreationDateTimeField(
auto_now_add=True, verbose_name="created"
auto_now_add=True,
verbose_name="created",
),
),
(
"modified",
django_extensions.db.fields.ModificationDateTimeField(
auto_now=True, verbose_name="modified"
auto_now=True,
verbose_name="modified",
),
),
("description", models.TextField()),

View file

@ -13,7 +13,8 @@ class Migration(migrations.Migration):
model_name="character",
name="states",
field=models.ManyToManyField(
related_name="characters", to="character.harmfulstate"
related_name="characters",
to="character.harmfulstate",
),
),
]

View file

@ -13,7 +13,9 @@ class Migration(migrations.Migration):
model_name="character",
name="states",
field=models.ManyToManyField(
blank=True, related_name="characters", to="character.harmfulstate"
blank=True,
related_name="characters",
to="character.harmfulstate",
),
),
]

View file

@ -32,13 +32,15 @@ class Migration(migrations.Migration):
(
"created",
django_extensions.db.fields.CreationDateTimeField(
auto_now_add=True, verbose_name="created"
auto_now_add=True,
verbose_name="created",
),
),
(
"modified",
django_extensions.db.fields.ModificationDateTimeField(
auto_now=True, verbose_name="modified"
auto_now=True,
verbose_name="modified",
),
),
(

View file

@ -25,7 +25,7 @@ class Migration(migrations.Migration):
character.models.character.validate_image,
*(),
**{"megabytes_limit": 2},
)
),
],
verbose_name="image de profil",
),

View file

@ -32,7 +32,9 @@ class Path(DocumentedModel, UniquelyNamedModel, TimeStampedModel, models.Model):
CREATURE = "creature", "Créature"
category = models.CharField(
max_length=20, choices=Category.choices, verbose_name="catégorie"
max_length=20,
choices=Category.choices,
verbose_name="catégorie",
)
notes = models.TextField(blank=True, verbose_name="notes")
@ -93,13 +95,20 @@ class Capability(DocumentedModel, TimeStampedModel, models.Model):
related_name="capabilities",
)
rank = models.PositiveSmallIntegerField(
validators=[MinValueValidator(1), MaxValueValidator(5)], verbose_name="rang"
validators=[MinValueValidator(1), MaxValueValidator(5)],
verbose_name="rang",
)
limited = models.BooleanField(
blank=True, null=False, default=False, verbose_name="limitée"
blank=True,
null=False,
default=False,
verbose_name="limitée",
)
spell = models.BooleanField(
blank=True, null=False, default=False, verbose_name="sort"
blank=True,
null=False,
default=False,
verbose_name="sort",
)
description = models.TextField(verbose_name="description")
@ -134,7 +143,9 @@ class RacialCapabilityManager(models.Manager):
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"
"character.Race",
on_delete=models.CASCADE,
verbose_name="race",
)
description = models.TextField(verbose_name="description")

View file

@ -17,7 +17,12 @@ from character.models.equipment import Weapon
from common.models import DocumentedModel, UniquelyNamedModel
class Profile(DocumentedModel, UniquelyNamedModel, TimeStampedModel, models.Model):
class Profile( # noqa: DJ008
DocumentedModel,
UniquelyNamedModel,
TimeStampedModel,
models.Model,
):
class MagicalStrength(models.TextChoices):
NONE = "NON", "Aucun"
INTELLIGENCE = "INT", "Intelligence"
@ -36,10 +41,13 @@ class Profile(DocumentedModel, UniquelyNamedModel, TimeStampedModel, models.Mode
verbose_name="force magique",
)
life_dice = models.PositiveSmallIntegerField(
choices=Dice.choices, verbose_name="dé de vie"
choices=Dice.choices,
verbose_name="dé de vie",
)
mana_max_compute = models.PositiveSmallIntegerField(
choices=ManaMax.choices, verbose_name="calcul mana max", default=ManaMax.NO_MANA
choices=ManaMax.choices,
verbose_name="calcul mana max",
default=ManaMax.NO_MANA,
)
notes = models.TextField(blank=True, verbose_name="notes")
@ -48,13 +56,23 @@ class Profile(DocumentedModel, UniquelyNamedModel, TimeStampedModel, models.Mode
verbose_name_plural = "Profils"
class Race(DocumentedModel, UniquelyNamedModel, TimeStampedModel, models.Model):
class Race( # noqa: DJ008
DocumentedModel,
UniquelyNamedModel,
TimeStampedModel,
models.Model,
):
class Meta(UniquelyNamedModel.Meta, TimeStampedModel.Meta):
verbose_name = "Race"
verbose_name_plural = "Races"
class HarmfulState(DocumentedModel, UniquelyNamedModel, TimeStampedModel, models.Model):
class HarmfulState( # noqa: DJ008
DocumentedModel,
UniquelyNamedModel,
TimeStampedModel,
models.Model,
):
description = models.TextField()
icon_url = models.URLField()
@ -88,7 +106,7 @@ class CharacterQuerySet(models.QuerySet):
from party.models import Party
return self.filter(
Q(player=user) | Q(parties__in=Party.objects.managed_by(user))
Q(player=user) | Q(parties__in=Party.objects.managed_by(user)),
)
def mastered_by(self, user):
@ -113,7 +131,7 @@ class CharacterQuerySet(models.QuerySet):
return self.filter(
Q(player=user)
| Q(parties__in=Party.objects.related_to(user))
| Q(invites__in=Party.objects.related_to(user))
| Q(invites__in=Party.objects.related_to(user)),
).distinct()
@ -184,7 +202,10 @@ class Character(models.Model):
level = models.PositiveSmallIntegerField(verbose_name="niveau", default=1)
gender = models.CharField(
max_length=1, choices=Gender.choices, default=Gender.OTHER, verbose_name="genre"
max_length=1,
choices=Gender.choices,
default=Gender.OTHER,
verbose_name="genre",
)
age = models.PositiveSmallIntegerField(verbose_name="âge")
height = models.PositiveSmallIntegerField(verbose_name="taille")
@ -193,17 +214,17 @@ class Character(models.Model):
value_strength = models.PositiveSmallIntegerField(verbose_name="valeur force")
value_dexterity = models.PositiveSmallIntegerField(verbose_name="valeur dextérité")
value_constitution = models.PositiveSmallIntegerField(
verbose_name="valeur constitution"
verbose_name="valeur constitution",
)
value_intelligence = models.PositiveSmallIntegerField(
verbose_name="valeur intelligence"
verbose_name="valeur intelligence",
)
value_wisdom = models.PositiveSmallIntegerField(verbose_name="valeur sagesse")
value_charisma = models.PositiveSmallIntegerField(verbose_name="valeur charisme")
health_max = models.PositiveSmallIntegerField(verbose_name="points de vie max")
health_remaining = models.PositiveSmallIntegerField(
verbose_name="points de vie restants"
verbose_name="points de vie restants",
)
racial_capability = models.ForeignKey(
@ -214,7 +235,9 @@ class Character(models.Model):
)
weapons = models.ManyToManyField(
"character.Weapon", blank=True, verbose_name="armes"
"character.Weapon",
blank=True,
verbose_name="armes",
)
armor = models.PositiveSmallIntegerField(verbose_name="armure", default=0)
@ -222,20 +245,24 @@ class Character(models.Model):
defense_misc = models.SmallIntegerField(verbose_name="divers défense", default=0)
initiative_misc = models.SmallIntegerField(
verbose_name="divers initiative", default=0
verbose_name="divers initiative",
default=0,
)
capabilities = models.ManyToManyField(
"character.Capability", blank=True, verbose_name="capacités"
"character.Capability",
blank=True,
verbose_name="capacités",
)
equipment = models.TextField(blank=True, verbose_name="équipement")
luck_points_remaining = models.PositiveSmallIntegerField(
verbose_name="points de chance restants"
verbose_name="points de chance restants",
)
mana_remaining = models.PositiveSmallIntegerField(
default=0, verbose_name="mana restant"
default=0,
verbose_name="mana restant",
)
money_pp = models.PositiveSmallIntegerField(default=0, verbose_name="PP")
@ -244,7 +271,8 @@ class Character(models.Model):
money_pc = models.PositiveSmallIntegerField(default=0, verbose_name="PC")
recovery_points_remaining = models.PositiveSmallIntegerField(
default=5, verbose_name="points de récupération restants"
default=5,
verbose_name="points de récupération restants",
)
notes = models.TextField(blank=True, verbose_name="notes", default=DEFAULT_NOTES)
@ -267,8 +295,10 @@ class Character(models.Model):
verbose_name_plural = "Personnages"
constraints = [
models.UniqueConstraint(
Lower("name"), "player", name="unique_character_player"
)
Lower("name"),
"player",
name="unique_character_player",
),
]
def __str__(self):
@ -329,7 +359,8 @@ class Character(models.Model):
Profile.MagicalStrength.NONE: 0,
}
return modifier_map.get(
Profile.MagicalStrength(self.profile.magical_strength), 0
Profile.MagicalStrength(self.profile.magical_strength),
0,
)
@property
@ -403,8 +434,9 @@ class Character(models.Model):
for capability in path.capabilities.all():
capabilities_by_path[capability.path].append(
CharacterCapability(
capability, known=capability in character_capabilities
)
capability,
known=capability in character_capabilities,
),
)
return dict(
@ -414,7 +446,7 @@ class Character(models.Model):
for path, capabilities in capabilities_by_path.items()
),
key=lambda x: x[0].name,
)
),
)
def get_formatted_notes(self) -> str:
@ -427,7 +459,7 @@ class Character(models.Model):
def get_missing_states(self) -> Iterable[HarmfulState]:
return HarmfulState.objects.exclude(
pk__in=self.states.all().values_list("pk", flat=True)
pk__in=self.states.all().values_list("pk", flat=True),
)
def managed_by(self, user):

View file

@ -4,7 +4,12 @@ from django_extensions.db.models import TimeStampedModel
from common.models import DocumentedModel, UniquelyNamedModel
class Weapon(UniquelyNamedModel, DocumentedModel, TimeStampedModel, models.Model):
class Weapon( # noqa: DJ008
UniquelyNamedModel,
DocumentedModel,
TimeStampedModel,
models.Model,
):
class Category(models.TextChoices):
MELEE = "MEL", "corps à corps"
RANGE = "RAN", "à distance"
@ -13,7 +18,9 @@ class Weapon(UniquelyNamedModel, DocumentedModel, TimeStampedModel, models.Model
damage = models.CharField(max_length=50, blank=True, verbose_name="dégâts")
special = models.TextField(blank=True, verbose_name="spécial")
category = models.CharField(
max_length=3, choices=Category.choices, default=Category.NONE
max_length=3,
choices=Category.choices,
default=Category.NONE,
)
class Meta(UniquelyNamedModel.Meta, TimeStampedModel.Meta):

View file

@ -44,7 +44,10 @@ def test_can_access_character_in_party(client):
notes = "Some notes"
gm_notes = "Some GM notes"
friend_character = baker.make(
Character, player=friend, notes=notes, gm_notes=gm_notes
Character,
player=friend,
notes=notes,
gm_notes=gm_notes,
)
party = baker.make(Party)
party.characters.add(character)

View file

@ -68,7 +68,10 @@ def test_attack_range(level, dexterity):
@given(armor=integers(), shield=integers(), dexterity=ability_values(), misc=integers())
def test_defense(armor, shield, dexterity, misc):
char = Character(
armor=armor, shield=shield, value_dexterity=dexterity, defense_misc=misc
armor=armor,
shield=shield,
value_dexterity=dexterity,
defense_misc=misc,
)
assert char.defense == 10 + armor + shield + modifier_test(dexterity) + misc

View file

@ -118,7 +118,8 @@ def test_list_characters(selenium: WebDriver, live_server: LiveServer):
names = {
name.text
for name in selenium.find_elements(
By.CSS_SELECTOR, ".character.card .card-title"
By.CSS_SELECTOR,
".character.card .card-title",
)
}
expected_names = {character.name for character in characters}
@ -137,7 +138,8 @@ def test_delete_character(selenium: WebDriver, live_server: LiveServer):
assert Character.objects.count() == 2
selenium.find_element(
By.CSS_SELECTOR, f".character.card[data-id='{characters[0].pk}'] .delete"
By.CSS_SELECTOR,
f".character.card[data-id='{characters[0].pk}'] .delete",
).click()
selenium.find_element(By.CSS_SELECTOR, "[type=submit]").click()
@ -148,7 +150,9 @@ def test_delete_character(selenium: WebDriver, live_server: LiveServer):
@pytest.mark.django_db()
def test_reset_stats_view(
selenium: WebDriver, live_server: LiveServer, initial_data: None
selenium: WebDriver,
live_server: LiveServer,
initial_data: None,
):
username, password = "user", "some_password"
player = User.objects.create_user(username, password=password)
@ -186,7 +190,10 @@ def create_hurt_character(player, profile):
def login(
selenium: WebDriver, live_server: LiveServer, username: str, password: str
selenium: WebDriver,
live_server: LiveServer,
username: str,
password: str,
) -> None:
selenium.get(live_server.url)
selenium.find_element(By.ID, "login").click()

View file

@ -10,7 +10,9 @@ urlpatterns = [
path("<int:pk>/change/", views.character_change, name="change"),
path("<int:pk>/delete/", views.character_delete, name="delete"),
path(
"<int:pk>/health_change/", views.character_health_change, name="health_change"
"<int:pk>/health_change/",
views.character_health_change,
name="health_change",
),
path("<int:pk>/mana_change/", views.character_mana_change, name="mana_change"),
path(
@ -87,7 +89,9 @@ urlpatterns = [
),
path("<int:pk>/add_path/", views.add_path, name="add_path"),
path(
"<int:pk>/remove_state/<int:state_pk>/", views.remove_state, name="remove_state"
"<int:pk>/remove_state/<int:state_pk>/",
views.remove_state,
name="remove_state",
),
path("<int:pk>/add_state/<int:state_pk>/", views.add_state, name="add_state"),
path("<int:pk>/reset_stats/", views.reset_stats, name="reset_stats"),

View file

@ -14,7 +14,8 @@ from party.models import Party
def characters_list(request):
context = {
"characters": Character.objects.owned_by(request.user).select_related(
"race", "profile"
"race",
"profile",
),
"all_states": HarmfulState.objects.all(),
}
@ -99,7 +100,7 @@ def add_path(request, pk: int):
context = {"character": character}
if form.is_valid():
path: Path = form.cleaned_data.get("character_path") or form.cleaned_data.get(
"other_path"
"other_path",
)
cap = path.get_next_capability(character)
character.capabilities.add(cap)
@ -117,7 +118,8 @@ def add_path(request, pk: int):
def character_health_change(request, pk: int):
character = get_object_or_404(
Character.objects.managed_by(request.user).only(
"health_max", "health_remaining"
"health_max",
"health_remaining",
),
pk=pk,
)
@ -150,7 +152,9 @@ def character_recovery_points_change(request, pk: int):
pk=pk,
)
value = get_updated_value(
request, character.recovery_points_remaining, character.recovery_points_max
request,
character.recovery_points_remaining,
character.recovery_points_max,
)
character.recovery_points_remaining = value
character.save(update_fields=["recovery_points_remaining"])
@ -160,7 +164,8 @@ def character_recovery_points_change(request, pk: int):
@login_required
def character_defense_misc_change(request, pk: int):
character = get_object_or_404(
Character.objects.managed_by(request.user).only("defense_misc"), pk=pk
Character.objects.managed_by(request.user).only("defense_misc"),
pk=pk,
)
value = get_updated_value(request, character.defense_misc, float("inf"))
character.defense_misc = value
@ -172,7 +177,8 @@ def character_defense_misc_change(request, pk: int):
@login_required
def character_shield_change(request, pk: int):
character = get_object_or_404(
Character.objects.managed_by(request.user).only("shield"), pk=pk
Character.objects.managed_by(request.user).only("shield"),
pk=pk,
)
value = get_updated_value(request, character.shield, float("inf"))
character.shield = value
@ -184,7 +190,8 @@ def character_shield_change(request, pk: int):
@login_required
def character_armor_change(request, pk: int):
character = get_object_or_404(
Character.objects.managed_by(request.user).only("armor"), pk=pk
Character.objects.managed_by(request.user).only("armor"),
pk=pk,
)
value = get_updated_value(request, character.armor, float("inf"))
character.armor = value
@ -196,7 +203,8 @@ def character_armor_change(request, pk: int):
@login_required
def character_initiative_misc_change(request, pk: int):
character = get_object_or_404(
Character.objects.managed_by(request.user).only("initiative_misc"), pk=pk
Character.objects.managed_by(request.user).only("initiative_misc"),
pk=pk,
)
value = get_updated_value(request, character.initiative_misc, float("inf"))
character.initiative_misc = value
@ -209,12 +217,15 @@ def character_initiative_misc_change(request, pk: int):
def character_luck_points_change(request, pk: int):
character = get_object_or_404(
Character.objects.managed_by(request.user).only(
"luck_points_remaining", "value_charisma"
"luck_points_remaining",
"value_charisma",
),
pk=pk,
)
value = get_updated_value(
request, character.luck_points_remaining, character.luck_points_max
request,
character.luck_points_remaining,
character.luck_points_max,
)
character.luck_points_remaining = value
character.save(update_fields=["luck_points_remaining"])
@ -222,7 +233,9 @@ def character_luck_points_change(request, pk: int):
def get_updated_value(
request, remaining_value: int | float, max_value: int | float
request,
remaining_value: int | float,
max_value: int | float,
) -> int:
form_value = request.GET.get("value")
if form_value == "ko":
@ -241,7 +254,10 @@ def get_updated_value(
def character_get_defense(request, pk: int):
character = get_object_or_404(
Character.objects.managed_by(request.user).only(
"defense_misc", "armor", "shield", "value_dexterity"
"defense_misc",
"armor",
"shield",
"value_dexterity",
),
pk=pk,
)
@ -252,13 +268,16 @@ def character_get_defense(request, pk: int):
def character_get_health_bar(request, pk: int):
character = get_object_or_404(
Character.objects.managed_by(request.user).only(
"health_max", "health_remaining"
"health_max",
"health_remaining",
),
pk=pk,
)
context = {"character": character}
return render(
request, "character/snippets/character_details/health_bar.html", context
request,
"character/snippets/character_details/health_bar.html",
context,
)
@ -270,7 +289,9 @@ def character_get_mana_bar(request, pk: int):
)
context = {"character": character}
return render(
request, "character/snippets/character_details/mana_bar.html", context
request,
"character/snippets/character_details/mana_bar.html",
context,
)
@ -278,7 +299,8 @@ def character_get_mana_bar(request, pk: int):
def character_get_initiative(request, pk: int):
character = get_object_or_404(
Character.objects.managed_by(request.user).only(
"initiative_misc", "value_dexterity"
"initiative_misc",
"value_dexterity",
),
pk=pk,
)
@ -299,7 +321,8 @@ def character_gm_notes_change(request, pk: int):
def character_equipment_change(request, pk: int):
field = "equipment"
character = get_object_or_404(
Character.objects.managed_by(request.user).only(field), pk=pk
Character.objects.managed_by(request.user).only(field),
pk=pk,
)
context = {"character": character}
if request.method == "GET":
@ -331,7 +354,8 @@ def character_damage_reduction_change(request, pk: int):
def update_text_field(request, pk, field):
character = get_object_or_404(
Character.objects.managed_by(request.user).only(field), pk=pk
Character.objects.managed_by(request.user).only(field),
pk=pk,
)
context = {"character": character}
if request.method == "GET":
@ -343,14 +367,17 @@ def update_text_field(request, pk, field):
setattr(character, field, request.POST.get(field))
character.save(update_fields=[field])
return render(
request, f"character/snippets/character_details/{field}_display.html", context
request,
f"character/snippets/character_details/{field}_display.html",
context,
)
@login_required
def add_next_in_path(request, character_pk: int, path_pk: int):
character = get_object_or_404(
Character.objects.managed_by(request.user), pk=character_pk
Character.objects.managed_by(request.user),
pk=character_pk,
)
path = get_object_or_404(Path, pk=path_pk)
capability = path.get_next_capability(character)
@ -369,10 +396,11 @@ def add_next_in_path(request, character_pk: int, path_pk: int):
@login_required
def remove_last_in_path(request, character_pk: int, path_pk: int):
character = get_object_or_404(
Character.objects.managed_by(request.user), pk=character_pk
Character.objects.managed_by(request.user),
pk=character_pk,
)
last_rank = max(
character.capabilities.filter(path_id=path_pk).values_list("rank", flat=True)
character.capabilities.filter(path_id=path_pk).values_list("rank", flat=True),
)
cap = Capability.objects.get(path_id=path_pk, rank=last_rank)
character.capabilities.remove(cap)
@ -390,13 +418,16 @@ def remove_last_in_path(request, character_pk: int, path_pk: int):
@login_required
def remove_state(request, pk: int, state_pk: int):
character: Character = get_object_or_404(
Character.objects.managed_by(request.user), pk=pk
Character.objects.managed_by(request.user),
pk=pk,
)
state = get_object_or_404(HarmfulState, pk=state_pk)
character.states.remove(state)
context = {"character": character, "all_states": HarmfulState.objects.all()}
response = render(
request, "character/snippets/character_details/states.html", context
request,
"character/snippets/character_details/states.html",
context,
)
return trigger_client_event(response, "refresh_tooltips", after="swap")
@ -404,13 +435,16 @@ def remove_state(request, pk: int, state_pk: int):
@login_required
def add_state(request, pk: int, state_pk: int):
character: Character = get_object_or_404(
Character.objects.managed_by(request.user), pk=pk
Character.objects.managed_by(request.user),
pk=pk,
)
state = get_object_or_404(HarmfulState, pk=state_pk)
character.states.add(state)
context = {"character": character, "all_states": HarmfulState.objects.all()}
response = render(
request, "character/snippets/character_details/states.html", context
request,
"character/snippets/character_details/states.html",
context,
)
return trigger_client_event(response, "refresh_tooltips", after="swap")
@ -418,7 +452,8 @@ def add_state(request, pk: int, state_pk: int):
@login_required
def reset_stats(request, pk: int):
character: Character = get_object_or_404(
Character.objects.managed_by(request.user), pk=pk
Character.objects.managed_by(request.user),
pk=pk,
)
context = {"character": character}
if request.method == "POST":

View file

@ -128,7 +128,7 @@ DATABASES = {"default": env.db()}
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.db.DatabaseCache",
}
},
}
SOLO_CACHE = "default"
@ -244,7 +244,7 @@ APP = {
"date": "latest-date",
"commit": "latest-commit",
"describe": "latest-describe",
}
},
}
try:
with Path("/app/git/build-date").open() as f:

View file

@ -28,7 +28,9 @@ class Migration(migrations.Migration):
(
"last_login",
models.DateTimeField(
blank=True, null=True, verbose_name="last login"
blank=True,
null=True,
verbose_name="last login",
),
),
(
@ -43,13 +45,13 @@ class Migration(migrations.Migration):
"username",
models.CharField(
error_messages={
"unique": "A user with that username already exists."
"unique": "A user with that username already exists.",
},
help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
max_length=150,
unique=True,
validators=[
django.contrib.auth.validators.UnicodeUsernameValidator()
django.contrib.auth.validators.UnicodeUsernameValidator(),
],
verbose_name="username",
),
@ -57,19 +59,25 @@ class Migration(migrations.Migration):
(
"first_name",
models.CharField(
blank=True, max_length=150, verbose_name="first name"
blank=True,
max_length=150,
verbose_name="first name",
),
),
(
"last_name",
models.CharField(
blank=True, max_length=150, verbose_name="last name"
blank=True,
max_length=150,
verbose_name="last name",
),
),
(
"email",
models.EmailField(
blank=True, max_length=254, verbose_name="email address"
blank=True,
max_length=254,
verbose_name="email address",
),
),
(
@ -91,7 +99,8 @@ class Migration(migrations.Migration):
(
"date_joined",
models.DateTimeField(
default=django.utils.timezone.now, verbose_name="date joined"
default=django.utils.timezone.now,
verbose_name="date joined",
),
),
(

View file

@ -13,7 +13,11 @@ class UniquelyNamedModelManager(models.Manager):
class UniquelyNamedModel(models.Model):
name = models.CharField(
max_length=100, blank=False, null=False, unique=True, verbose_name="nom"
max_length=100,
blank=False,
null=False,
unique=True,
verbose_name="nom",
)
objects = UniquelyNamedModelManager()

View file

@ -9,11 +9,12 @@ def main():
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
msg = (
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
)
raise ImportError(msg) from exc
execute_from_command_line(sys.argv)

View file

@ -7,7 +7,7 @@ from party.models import BattleEffect, Party
class PartyForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
def __init__(self, *args, **kwargs) -> None:
self.original_instance = kwargs.get("instance")
super().__init__(*args, **kwargs)
qs = Character.objects.all()
@ -16,9 +16,10 @@ class PartyForm(forms.ModelForm):
Q(private=False)
| Q(
pk__in=self.original_instance.invited_characters.all().values_list(
"pk", flat=True
)
)
"pk",
flat=True,
),
),
)
self.fields["invited_characters"].queryset = qs

View file

@ -34,13 +34,15 @@ class Migration(migrations.Migration):
(
"created",
django_extensions.db.fields.CreationDateTimeField(
auto_now_add=True, verbose_name="created"
auto_now_add=True,
verbose_name="created",
),
),
(
"modified",
django_extensions.db.fields.ModificationDateTimeField(
auto_now=True, verbose_name="modified"
auto_now=True,
verbose_name="modified",
),
),
(

View file

@ -28,13 +28,15 @@ class Migration(migrations.Migration):
(
"created",
django_extensions.db.fields.CreationDateTimeField(
auto_now_add=True, verbose_name="created"
auto_now_add=True,
verbose_name="created",
),
),
(
"modified",
django_extensions.db.fields.ModificationDateTimeField(
auto_now=True, verbose_name="modified"
auto_now=True,
verbose_name="modified",
),
),
("name", models.CharField(max_length=100, verbose_name="nom")),

View file

@ -17,14 +17,14 @@ class PartyQuerySet(models.QuerySet):
def played_or_mastered_by(self, user):
return self.filter(
Q(game_master=user)
| Q(characters__in=Character.objects.filter(player=user))
| Q(characters__in=Character.objects.filter(player=user)),
).distinct()
def related_to(self, user):
return self.filter(
Q(game_master=user)
| Q(characters__in=Character.objects.filter(player=user))
| Q(invited_characters__in=Character.objects.filter(player=user))
| Q(invited_characters__in=Character.objects.filter(player=user)),
).distinct()
def invited_to(self, user):
@ -35,7 +35,7 @@ class PartyManager(UniquelyNamedModelManager):
pass
class Party(UniquelyNamedModel, TimeStampedModel, models.Model):
class Party(UniquelyNamedModel, TimeStampedModel, models.Model): # noqa: DJ008
game_master = models.ForeignKey(
"common.User",
on_delete=models.PROTECT,
@ -96,7 +96,10 @@ class BattleEffectManager(models.Manager):
class BattleEffect(TimeStampedModel, models.Model):
name = models.CharField(max_length=100, blank=False, null=False, verbose_name="nom")
target = models.CharField(
max_length=100, blank=False, null=False, verbose_name="cible"
max_length=100,
blank=False,
null=False,
verbose_name="cible",
)
description = models.TextField(blank=True, null=False, verbose_name="description")
remaining_rounds = models.SmallIntegerField(
@ -126,3 +129,6 @@ class BattleEffect(TimeStampedModel, models.Model):
if self.remaining_rounds >= max_display_percent or self.remaining_rounds < 0:
return 100
return self.remaining_rounds / max_display_percent * 100
def __str__(self):
return self.name

View file

@ -28,7 +28,8 @@ def test_add_character_to_existing_group(selenium: WebDriver, live_server: LiveS
selenium.get(live_server.url + reverse("party:list"))
selenium.find_element(
By.CSS_SELECTOR, f".party[data-id='{party.pk}'] .edit"
By.CSS_SELECTOR,
f".party[data-id='{party.pk}'] .edit",
).click()
invited = Select(selenium.find_element(By.ID, "id_invited_characters"))
invited.select_by_index(0)
@ -41,7 +42,8 @@ def test_add_character_to_existing_group(selenium: WebDriver, live_server: LiveS
@pytest.mark.django_db()
def test_gm_observe_invited_character_in_group(
selenium: WebDriver, live_server: LiveServer
selenium: WebDriver,
live_server: LiveServer,
):
username, password = "gm", "password"
gm = User.objects.create_user(username, password=password)
@ -54,10 +56,12 @@ def test_gm_observe_invited_character_in_group(
selenium.get(live_server.url + reverse("party:list"))
selenium.find_element(
By.CSS_SELECTOR, f".party[data-id='{party.pk}'] .access"
By.CSS_SELECTOR,
f".party[data-id='{party.pk}'] .access",
).click()
selenium.find_element(
By.CSS_SELECTOR, f".character[data-id='{character.pk}'] .observe"
By.CSS_SELECTOR,
f".character[data-id='{character.pk}'] .observe",
).click()
title = selenium.find_element(By.TAG_NAME, "h1").text.strip()
assert title == character.name
@ -65,7 +69,8 @@ def test_gm_observe_invited_character_in_group(
@pytest.mark.django_db()
def test_gm_observe_invited_character_in_two_groups(
selenium: WebDriver, live_server: LiveServer
selenium: WebDriver,
live_server: LiveServer,
):
username, password = "gm", "password"
gm = User.objects.create_user(username, password=password)
@ -80,10 +85,12 @@ def test_gm_observe_invited_character_in_two_groups(
selenium.get(live_server.url + reverse("party:list"))
selenium.find_element(
By.CSS_SELECTOR, f".party[data-id='{party.pk}'] .access"
By.CSS_SELECTOR,
f".party[data-id='{party.pk}'] .access",
).click()
selenium.find_element(
By.CSS_SELECTOR, f".character[data-id='{character.pk}'] .observe"
By.CSS_SELECTOR,
f".character[data-id='{character.pk}'] .observe",
).click()
title = selenium.find_element(By.TAG_NAME, "h1").text.strip()
assert title == character.name
@ -91,7 +98,9 @@ def test_gm_observe_invited_character_in_two_groups(
@pytest.mark.django_db()
def test_reset_stats_view(
selenium: WebDriver, live_server: LiveServer, initial_data: None
selenium: WebDriver,
live_server: LiveServer,
initial_data: None,
):
user, password = "gm", "password"
gm = User.objects.create_user(user, password=password)
@ -189,13 +198,22 @@ def test_gm_can_change_remaining_rounds(selenium: WebDriver, live_server: LiveSe
party=party,
)
active_nearly_terminated = baker.make(
BattleEffect, _quantity=3, remaining_rounds=1, party=party
BattleEffect,
_quantity=3,
remaining_rounds=1,
party=party,
)
terminated = baker.make( # noqa: F841
BattleEffect, _quantity=5, remaining_rounds=0, party=party
BattleEffect,
_quantity=5,
remaining_rounds=0,
party=party,
)
permanent = baker.make( # noqa: F841
BattleEffect, _quantity=2, remaining_rounds=-1, party=party
BattleEffect,
_quantity=2,
remaining_rounds=-1,
party=party,
)
not_party = baker.make(BattleEffect, _quantity=4, remaining_rounds=55) # noqa: F841
beacon = active_nearly_terminated[0]
@ -246,7 +264,8 @@ def test_gm_can_change_remaining_rounds(selenium: WebDriver, live_server: LiveSe
@pytest.mark.django_db()
def test_gm_can_delete_any_existing_effect(
selenium: WebDriver, live_server: LiveServer
selenium: WebDriver,
live_server: LiveServer,
):
"""The GM of a group can delete any existing effect, running or terminated."""
user, password = "gm", "password"
@ -258,7 +277,8 @@ def test_gm_can_delete_any_existing_effect(
go_to_party(selenium, live_server, party, user, password)
selenium.find_element(
By.CSS_SELECTOR, f'.effect[data-id="{effects[0].pk}"] .delete'
By.CSS_SELECTOR,
f'.effect[data-id="{effects[0].pk}"] .delete',
).click()
assert BattleEffect.objects.count() == 1
@ -267,7 +287,8 @@ def test_gm_can_delete_any_existing_effect(
@pytest.mark.django_db()
def test_player_cant_change_existing_running_effect(
selenium: WebDriver, live_server: LiveServer
selenium: WebDriver,
live_server: LiveServer,
):
"""Members of the group can only view existing running effects, no update."""
user, password = "player", "password"
@ -280,7 +301,8 @@ def test_player_cant_change_existing_running_effect(
go_to_party(selenium, live_server, party, user, password)
effect = effects[0]
effect_element = selenium.find_element(
By.CSS_SELECTOR, f'.effect[data-id="{effect.pk}"]'
By.CSS_SELECTOR,
f'.effect[data-id="{effect.pk}"]',
)
assert effect.name in effect_element.text
assert effect.target in effect_element.text
@ -295,7 +317,11 @@ def test_player_cant_change_existing_running_effect(
def fill_effect(
selenium: WebDriver, name: str, description: str, target: str, remaining_rounds: str
selenium: WebDriver,
name: str,
description: str,
target: str,
remaining_rounds: str,
) -> None:
selenium.find_element(By.ID, "add-effect").click()
selenium.find_element(By.ID, "id_name").send_keys(name)
@ -308,7 +334,10 @@ def fill_effect(
def assert_effect_is_created(
name: str, description: str, target: str, remaining_rounds: str
name: str,
description: str,
target: str,
remaining_rounds: str,
) -> BattleEffect:
assert BattleEffect.objects.count() == 1
effect = BattleEffect.objects.first()
@ -320,7 +349,11 @@ def assert_effect_is_created(
def go_to_party(
selenium: WebDriver, live_server: LiveServer, party: Party, user: str, password: str
selenium: WebDriver,
live_server: LiveServer,
party: Party,
user: str,
password: str,
) -> None:
login(selenium, live_server, user, password)
url = reverse("party:details", kwargs={"pk": party.pk})

View file

@ -17,10 +17,14 @@ urlpatterns = [
name="delete_effect",
),
path(
"<int:pk>/increase_rounds/", views.party_increase_rounds, name="increase_rounds"
"<int:pk>/increase_rounds/",
views.party_increase_rounds,
name="increase_rounds",
),
path(
"<int:pk>/decrease_rounds/", views.party_decrease_rounds, name="decrease_rounds"
"<int:pk>/decrease_rounds/",
views.party_decrease_rounds,
name="decrease_rounds",
),
path("<int:pk>/leave/<int:character_pk>/", views.party_leave, name="leave"),
path("<int:pk>/join/<int:character_pk>/", views.party_join, name="join"),

View file

@ -139,7 +139,8 @@ def party_change(request, pk):
def party_leave(request, pk, character_pk):
party = get_object_or_404(Party.objects.played_by(request.user).distinct(), pk=pk)
character = get_object_or_404(
Character.objects.owned_by(request.user), pk=character_pk
Character.objects.owned_by(request.user),
pk=character_pk,
)
context = {"party": party, "character": character}
if request.method == "POST":
@ -154,7 +155,8 @@ def party_leave(request, pk, character_pk):
def party_join(request, pk, character_pk):
party = get_object_or_404(Party.objects.invited_to(request.user).distinct(), pk=pk)
character = get_object_or_404(
Character.objects.owned_by(request.user), pk=character_pk
Character.objects.owned_by(request.user),
pk=character_pk,
)
party.characters.add(character)
party.invited_characters.remove(character)
@ -167,7 +169,8 @@ def party_join(request, pk, character_pk):
def party_refuse(request, pk, character_pk):
party = get_object_or_404(Party.objects.invited_to(request.user).distinct(), pk=pk)
character = get_object_or_404(
Character.objects.owned_by(request.user), pk=character_pk
Character.objects.owned_by(request.user),
pk=character_pk,
)
party.invited_characters.remove(character)
messages.success(request, f"{character} a refusé l'invitation au groupe {party}.")

View file

@ -84,7 +84,9 @@ def download_db(ctx: Context):
)
ctx.run("rm -rf src/media/", pty=True, echo=True)
ctx.run(
"scp -r ubuntu:/mnt/data/charasheet/media/ ./src/media", pty=True, echo=True
"scp -r ubuntu:/mnt/data/charasheet/media/ ./src/media",
pty=True,
echo=True,
)
with ctx.cd(SRC_DIR):
ctx.run("./manage.py changepassword gaugendre", pty=True, echo=True)