mirror of
https://github.com/Crocmagnon/charasheet.git
synced 2024-11-22 14:38:03 +01:00
Implement pets (#48)
This commit is contained in:
parent
738ddb7e7b
commit
f02da17f63
14 changed files with 363 additions and 12 deletions
|
@ -3,6 +3,7 @@ from django.core.exceptions import ValidationError
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
from character.models import Character, Path, RacialCapability
|
from character.models import Character, Path, RacialCapability
|
||||||
|
from character.models.pet import Pet
|
||||||
|
|
||||||
|
|
||||||
class EquipmentForm(forms.ModelForm):
|
class EquipmentForm(forms.ModelForm):
|
||||||
|
@ -50,7 +51,7 @@ class AddPathForm(forms.Form):
|
||||||
return cleaned_data
|
return cleaned_data
|
||||||
|
|
||||||
|
|
||||||
class CharacterCreateForm(forms.ModelForm):
|
class CharacterForm(forms.ModelForm):
|
||||||
def __init__(self, *args, **kwargs) -> None:
|
def __init__(self, *args, **kwargs) -> None:
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields[
|
self.fields[
|
||||||
|
@ -92,3 +93,25 @@ class CharacterCreateForm(forms.ModelForm):
|
||||||
"damage_reduction",
|
"damage_reduction",
|
||||||
"notes",
|
"notes",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class PetForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Pet
|
||||||
|
fields = [
|
||||||
|
"name",
|
||||||
|
"health_max",
|
||||||
|
"health_remaining",
|
||||||
|
"modifier_strength",
|
||||||
|
"modifier_dexterity",
|
||||||
|
"modifier_constitution",
|
||||||
|
"modifier_intelligence",
|
||||||
|
"modifier_wisdom",
|
||||||
|
"modifier_charisma",
|
||||||
|
"damage",
|
||||||
|
"initiative",
|
||||||
|
"defense",
|
||||||
|
"attack",
|
||||||
|
"recovery",
|
||||||
|
"notes",
|
||||||
|
]
|
||||||
|
|
81
src/character/migrations/0041_pet.py
Normal file
81
src/character/migrations/0041_pet.py
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
# Generated by Django 4.1.7 on 2023-03-01 14:30
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("character", "0040_character_gm_notes"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Pet",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("name", models.CharField(max_length=100, verbose_name="nom")),
|
||||||
|
(
|
||||||
|
"health_max",
|
||||||
|
models.PositiveIntegerField(verbose_name="points de vie maximum"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"health_remaining",
|
||||||
|
models.PositiveIntegerField(verbose_name="points de vie restants"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"modifier_strength",
|
||||||
|
models.IntegerField(verbose_name="modificateur force"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"modifier_dexterity",
|
||||||
|
models.IntegerField(verbose_name="modificateur dextérité"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"modifier_constitution",
|
||||||
|
models.IntegerField(verbose_name="modificateur constitution"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"modifier_intelligence",
|
||||||
|
models.IntegerField(verbose_name="modificateur intelligence"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"modifier_wisdom",
|
||||||
|
models.IntegerField(verbose_name="modificateur sagesse"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"modifier_charisma",
|
||||||
|
models.IntegerField(verbose_name="modificateur charisme"),
|
||||||
|
),
|
||||||
|
("damage", models.PositiveIntegerField(verbose_name="dégâts")),
|
||||||
|
("initiative", models.PositiveIntegerField(verbose_name="initiative")),
|
||||||
|
("defense", models.PositiveIntegerField(verbose_name="défense")),
|
||||||
|
("attack", models.PositiveIntegerField(verbose_name="attaque")),
|
||||||
|
(
|
||||||
|
"recovery",
|
||||||
|
models.CharField(
|
||||||
|
blank=True,
|
||||||
|
max_length=100,
|
||||||
|
verbose_name="récupération",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("notes", models.TextField(blank=True, verbose_name="notes")),
|
||||||
|
(
|
||||||
|
"owner",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="pets",
|
||||||
|
to="character.character",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
|
@ -1 +1 @@
|
||||||
0040_character_gm_notes
|
0041_pet
|
||||||
|
|
|
@ -468,6 +468,9 @@ class Character(models.Model):
|
||||||
def mastered_by(self, user):
|
def mastered_by(self, user):
|
||||||
return self in Character.objects.mastered_by(user)
|
return self in Character.objects.mastered_by(user)
|
||||||
|
|
||||||
|
def owned_by(self, user):
|
||||||
|
return self in Character.objects.owned_by(user)
|
||||||
|
|
||||||
def reset_stats(self):
|
def reset_stats(self):
|
||||||
self.health_remaining = self.health_max
|
self.health_remaining = self.health_max
|
||||||
self.mana_remaining = self.mana_max
|
self.mana_remaining = self.mana_max
|
||||||
|
|
43
src/character/models/pet.py
Normal file
43
src/character/models/pet.py
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Pet(models.Model):
|
||||||
|
# Fields are: name, health_max, health_remaining, modifier_strength,
|
||||||
|
# modifier_dexterity, modifier_constitution, modifier_intelligence,
|
||||||
|
# modifier_wisdom, modifier_charisma, damage, initiative, defense, attack,
|
||||||
|
# recovery and notes.
|
||||||
|
name = models.CharField(max_length=100, verbose_name="nom")
|
||||||
|
owner = models.ForeignKey(
|
||||||
|
"character.Character",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="pets",
|
||||||
|
)
|
||||||
|
health_max = models.PositiveIntegerField(verbose_name="points de vie maximum")
|
||||||
|
health_remaining = models.PositiveIntegerField(
|
||||||
|
verbose_name="points de vie restants",
|
||||||
|
)
|
||||||
|
modifier_strength = models.IntegerField(verbose_name="modificateur force")
|
||||||
|
modifier_dexterity = models.IntegerField(verbose_name="modificateur dextérité")
|
||||||
|
modifier_constitution = models.IntegerField(
|
||||||
|
verbose_name="modificateur constitution",
|
||||||
|
)
|
||||||
|
modifier_intelligence = models.IntegerField(
|
||||||
|
verbose_name="modificateur intelligence",
|
||||||
|
)
|
||||||
|
modifier_wisdom = models.IntegerField(verbose_name="modificateur sagesse")
|
||||||
|
modifier_charisma = models.IntegerField(verbose_name="modificateur charisme")
|
||||||
|
damage = models.PositiveIntegerField(verbose_name="dégâts")
|
||||||
|
initiative = models.PositiveIntegerField(verbose_name="initiative")
|
||||||
|
defense = models.PositiveIntegerField(verbose_name="défense")
|
||||||
|
attack = models.PositiveIntegerField(verbose_name="attaque")
|
||||||
|
recovery = models.CharField(max_length=100, verbose_name="récupération", blank=True)
|
||||||
|
notes = models.TextField(verbose_name="notes", blank=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def health_remaining_percent(self) -> float:
|
||||||
|
if self.health_max == 0:
|
||||||
|
return 0
|
||||||
|
return self.health_remaining / self.health_max * 100
|
|
@ -494,6 +494,15 @@
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<a href="{% url "character:create_pet" pk=character.pk %}"
|
||||||
|
id="add-pet"
|
||||||
|
class="btn btn-success mb-2"
|
||||||
|
>Nouveau familier</a>
|
||||||
|
<div class="row mb-3 row-cols-1 row-cols-sm-2 row-cols-md-3 row-cols-lg-4 g-4">
|
||||||
|
{% for pet in character.pets.all %}
|
||||||
|
{% include "character/snippets/characters_list/pet_card.html" %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6 col-lg-4 mb-3">
|
<div class="col-md-6 col-lg-4 mb-3">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
|
|
18
src/character/templates/character/pet_delete.html
Normal file
18
src/character/templates/character/pet_delete.html
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
{% extends "common/base.html" %}
|
||||||
|
{% load character_extras %}
|
||||||
|
|
||||||
|
{% block title %}Suppression familier {{ pet.name }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>Suppression familier {{ pet.name }}</h1>
|
||||||
|
<form action="{% url "character:pet_delete" pk=pet.pk %}" method=post>
|
||||||
|
{% csrf_token %}
|
||||||
|
<p>
|
||||||
|
Êtes-vous certain de vouloir supprimer le familier {{ pet.name }} ?<br>
|
||||||
|
Cette action est irréversible.
|
||||||
|
</p>
|
||||||
|
<button class="btn btn-danger" type="submit">
|
||||||
|
<i class="fa-solid fa-user-minus"></i> Supprimer le familier
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
14
src/character/templates/character/pet_form.html
Normal file
14
src/character/templates/character/pet_form.html
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{% extends "common/base.html" %}
|
||||||
|
{% load django_bootstrap5 %}
|
||||||
|
|
||||||
|
{% block title %}Création de familier{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>Création de familier</h1>
|
||||||
|
<form action="" method="post" enctype="multipart/form-data">
|
||||||
|
{% bootstrap_form_errors form %}
|
||||||
|
{% csrf_token %}
|
||||||
|
{% bootstrap_form form %}
|
||||||
|
<button type="submit" class="btn btn-primary">Enregistrer</button>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,7 @@
|
||||||
|
{% load character_extras %}
|
||||||
|
|
||||||
|
<div class="progress">
|
||||||
|
<div class="progress-bar {% if pet.health_remaining_percent > 60 %}bg-success{% elif pet.health_remaining_percent > 30 %}bg-warning{% else %}bg-danger{% endif %}" style="width: {{ pet.health_remaining_percent|floatformat:"0" }}%">
|
||||||
|
PV : {{ pet.health_remaining }}/{{ pet.health_max }}
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,60 @@
|
||||||
|
{% load character_extras %}
|
||||||
|
<div class="col">
|
||||||
|
<div class="card pet" data-id="{{ pet.pk }}">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title pet-name">
|
||||||
|
{{ pet.name }}
|
||||||
|
</h5>
|
||||||
|
<p class="card-text">
|
||||||
|
<span data-bs-toggle="tooltip" data-bs-placement="top" data-bs-title="Attaque">
|
||||||
|
⚔️ {{ pet.attack }}
|
||||||
|
</span> /
|
||||||
|
<span data-bs-toggle="tooltip" data-bs-placement="top" data-bs-title="DEF">
|
||||||
|
🛡️ {{ pet.defense }}
|
||||||
|
</span> /
|
||||||
|
<span data-bs-toggle="tooltip" data-bs-placement="top" data-bs-title="Initiative">
|
||||||
|
🎲 {{ pet.initiative }}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<div class="health">
|
||||||
|
{% include "character/snippets/character_details/pet_health_bar.html" %}
|
||||||
|
{% if pet.owner|managed_by:user %}
|
||||||
|
<div class="btn-group btn-group-sm mt-1" role="group">
|
||||||
|
<button
|
||||||
|
hx-get="{% url "character:pet_health_change" pk=pet.pk %}?value=ko"
|
||||||
|
hx-target='[data-id="{{ pet.pk }}"] .health .progress'
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
type="button"
|
||||||
|
class="btn btn-outline-danger min"><i class="fa-solid fa-battery-empty"></i></button>
|
||||||
|
<button
|
||||||
|
hx-get="{% url "character:pet_health_change" pk=pet.pk %}?value=-1"
|
||||||
|
hx-target='[data-id="{{ pet.pk }}"] .health .progress'
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
type="button"
|
||||||
|
class="btn btn-danger decrease"><i class="fa-solid fa-minus"></i></button>
|
||||||
|
<button
|
||||||
|
hx-get="{% url "character:pet_health_change" pk=pet.pk %}?value=1"
|
||||||
|
hx-target='[data-id="{{ pet.pk }}"] .health .progress'
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
type="button"
|
||||||
|
class="btn btn-success increase"><i class="fa-solid fa-plus"></i></button>
|
||||||
|
<button
|
||||||
|
hx-get="{% url "character:pet_health_change" pk=pet.pk %}?value=max"
|
||||||
|
hx-target='[data-id="{{ pet.pk }}"] .health .progress'
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
type="button"
|
||||||
|
class="btn btn-outline-success max"><i class="fa-solid fa-battery-full"></i></button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if pet.owner|managed_by:user %}
|
||||||
|
<a href="{% url "character:pet_change" pk=pet.pk %}"
|
||||||
|
class="edit">Modifier</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if pet.owner|owned_by:user %}
|
||||||
|
<a href="{% url "character:pet_delete" pk=pet.pk %}"
|
||||||
|
class="delete">Supprimer</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -46,3 +46,8 @@ def managed_by(character: Character, user: User) -> bool:
|
||||||
@register.filter
|
@register.filter
|
||||||
def mastered_by(character: Character, user: User) -> bool:
|
def mastered_by(character: Character, user: User) -> bool:
|
||||||
return character.mastered_by(user)
|
return character.mastered_by(user)
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def owned_by(character: Character, user: User) -> bool:
|
||||||
|
return character.owned_by(user)
|
||||||
|
|
|
@ -5,6 +5,7 @@ from selenium.webdriver.common.by import By
|
||||||
from selenium.webdriver.firefox.webdriver import WebDriver
|
from selenium.webdriver.firefox.webdriver import WebDriver
|
||||||
|
|
||||||
from character.models import Character
|
from character.models import Character
|
||||||
|
from character.tests.test_interactions import login
|
||||||
from common.models import User
|
from common.models import User
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,6 +17,7 @@ def test_pet_happy_path(selenium: WebDriver, live_server: LiveServer):
|
||||||
username, password = "user", "some_password"
|
username, password = "user", "some_password"
|
||||||
player = User.objects.create_user(username, password=password)
|
player = User.objects.create_user(username, password=password)
|
||||||
character = baker.make(Character, player=player)
|
character = baker.make(Character, player=player)
|
||||||
|
login(selenium, live_server, username, password)
|
||||||
|
|
||||||
# Starting on the character's sheet.
|
# Starting on the character's sheet.
|
||||||
selenium.get(live_server.url + character.get_absolute_url())
|
selenium.get(live_server.url + character.get_absolute_url())
|
||||||
|
@ -48,7 +50,7 @@ def test_pet_happy_path(selenium: WebDriver, live_server: LiveServer):
|
||||||
selenium.find_element(By.ID, "id_notes").send_keys("My pet's notes")
|
selenium.find_element(By.ID, "id_notes").send_keys("My pet's notes")
|
||||||
|
|
||||||
# Save & check redirected to character's sheet.
|
# Save & check redirected to character's sheet.
|
||||||
selenium.find_element(By.ID, "save-pet").click()
|
selenium.find_element(By.CSS_SELECTOR, "[type=submit]").click()
|
||||||
assert selenium.current_url == live_server.url + character.get_absolute_url()
|
assert selenium.current_url == live_server.url + character.get_absolute_url()
|
||||||
|
|
||||||
# Fetch pet
|
# Fetch pet
|
||||||
|
@ -57,7 +59,10 @@ def test_pet_happy_path(selenium: WebDriver, live_server: LiveServer):
|
||||||
# It now displays the pet's information.
|
# It now displays the pet's information.
|
||||||
# There can be multiple pets.
|
# There can be multiple pets.
|
||||||
assert (
|
assert (
|
||||||
selenium.find_element(By.CSS_SELECTOR, f".pet[data-id='{pet.pk}'] .name").text
|
selenium.find_element(
|
||||||
|
By.CSS_SELECTOR,
|
||||||
|
f".pet[data-id='{pet.pk}'] .pet-name",
|
||||||
|
).text
|
||||||
== "My pet"
|
== "My pet"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -73,18 +78,24 @@ def test_pet_happy_path(selenium: WebDriver, live_server: LiveServer):
|
||||||
# I can edit my pets. When I click on the edit button of a pet,
|
# I can edit my pets. When I click on the edit button of a pet,
|
||||||
# I have the same form as previously, pre-filled with the current values of my pet.
|
# I have the same form as previously, pre-filled with the current values of my pet.
|
||||||
selenium.find_element(By.CSS_SELECTOR, f".pet[data-id='{pet.pk}'] .edit").click()
|
selenium.find_element(By.CSS_SELECTOR, f".pet[data-id='{pet.pk}'] .edit").click()
|
||||||
assert selenium.find_element(By.ID, "id_name").get_attribute("value") == "My pet"
|
pet_name = selenium.find_element(By.ID, "id_name")
|
||||||
|
assert pet_name.get_attribute("value") == "My pet"
|
||||||
assert selenium.find_element(By.ID, "id_health_max").get_attribute("value") == "10"
|
assert selenium.find_element(By.ID, "id_health_max").get_attribute("value") == "10"
|
||||||
assert (
|
assert (
|
||||||
selenium.find_element(By.ID, "id_health_remaining").get_attribute("value")
|
selenium.find_element(By.ID, "id_health_remaining").get_attribute("value")
|
||||||
== "10"
|
== "9"
|
||||||
)
|
)
|
||||||
|
pet_name.clear()
|
||||||
|
pet_name.send_keys("new name")
|
||||||
|
selenium.find_element(By.CSS_SELECTOR, "[type=submit]").click()
|
||||||
|
pet.refresh_from_db()
|
||||||
|
assert pet.name == "new name"
|
||||||
|
|
||||||
# I can delete my pets. When I click on the pet's delete button,
|
# I can delete my pets. When I click on the pet's delete button,
|
||||||
# I'm redirected to a page asking confirmation of my action,
|
# I'm redirected to a page asking confirmation of my action,
|
||||||
# in order to avoid mistakes.
|
# in order to avoid mistakes.
|
||||||
selenium.find_element(By.CSS_SELECTOR, f".pet[data-id='{pet.pk}'] .delete").click()
|
selenium.find_element(By.CSS_SELECTOR, f".pet[data-id='{pet.pk}'] .delete").click()
|
||||||
assert character.pets.count() == 1
|
assert character.pets.count() == 1
|
||||||
selenium.find_element(By.ID, "delete-pet").click()
|
selenium.find_element(By.CSS_SELECTOR, "[type=submit]").click()
|
||||||
assert selenium.current_url == live_server.url + character.get_absolute_url()
|
assert selenium.current_url == live_server.url + character.get_absolute_url()
|
||||||
assert character.pets.count() == 0
|
assert character.pets.count() == 0
|
||||||
|
|
|
@ -88,6 +88,7 @@ urlpatterns = [
|
||||||
name="remove_last_in_path",
|
name="remove_last_in_path",
|
||||||
),
|
),
|
||||||
path("<int:pk>/add_path/", views.add_path, name="add_path"),
|
path("<int:pk>/add_path/", views.add_path, name="add_path"),
|
||||||
|
path("<int:pk>/create_pet/", views.create_pet, name="create_pet"),
|
||||||
path(
|
path(
|
||||||
"<int:pk>/remove_state/<int:state_pk>/",
|
"<int:pk>/remove_state/<int:state_pk>/",
|
||||||
views.remove_state,
|
views.remove_state,
|
||||||
|
@ -95,4 +96,11 @@ urlpatterns = [
|
||||||
),
|
),
|
||||||
path("<int:pk>/add_state/<int:state_pk>/", views.add_state, name="add_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"),
|
path("<int:pk>/reset_stats/", views.reset_stats, name="reset_stats"),
|
||||||
|
path(
|
||||||
|
"pet/<int:pk>/health_change/",
|
||||||
|
views.pet_health_change,
|
||||||
|
name="pet_health_change",
|
||||||
|
),
|
||||||
|
path("pet/<int:pk>/change/", views.pet_change, name="pet_change"),
|
||||||
|
path("pet/<int:pk>/delete/", views.pet_delete, name="pet_delete"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -4,8 +4,9 @@ from django.http import HttpResponse
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django_htmx.http import trigger_client_event
|
from django_htmx.http import trigger_client_event
|
||||||
|
|
||||||
from character.forms import AddPathForm, CharacterCreateForm, EquipmentForm
|
from character.forms import AddPathForm, CharacterForm, EquipmentForm, PetForm
|
||||||
from character.models import Capability, Character, HarmfulState, Path
|
from character.models import Capability, Character, HarmfulState, Path
|
||||||
|
from character.models.pet import Pet
|
||||||
from character.templatetags.character_extras import modifier
|
from character.templatetags.character_extras import modifier
|
||||||
from party.models import Party
|
from party.models import Party
|
||||||
|
|
||||||
|
@ -25,7 +26,7 @@ def characters_list(request):
|
||||||
@login_required
|
@login_required
|
||||||
def character_create(request):
|
def character_create(request):
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
form = CharacterCreateForm(request.POST, request.FILES)
|
form = CharacterForm(request.POST, request.FILES)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
character = form.save(commit=False)
|
character = form.save(commit=False)
|
||||||
character.player = request.user
|
character.player = request.user
|
||||||
|
@ -38,7 +39,7 @@ def character_create(request):
|
||||||
messages.success(request, f"{character.name} a été créé.")
|
messages.success(request, f"{character.name} a été créé.")
|
||||||
return redirect("character:list")
|
return redirect("character:list")
|
||||||
else:
|
else:
|
||||||
form = CharacterCreateForm()
|
form = CharacterForm()
|
||||||
context = {"form": form}
|
context = {"form": form}
|
||||||
return render(request, "character/character_form.html", context)
|
return render(request, "character/character_form.html", context)
|
||||||
|
|
||||||
|
@ -47,13 +48,13 @@ def character_create(request):
|
||||||
def character_change(request, pk: int):
|
def character_change(request, pk: int):
|
||||||
character = get_object_or_404(Character.objects.managed_by(request.user), pk=pk)
|
character = get_object_or_404(Character.objects.managed_by(request.user), pk=pk)
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
form = CharacterCreateForm(request.POST, request.FILES, instance=character)
|
form = CharacterForm(request.POST, request.FILES, instance=character)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
character = form.save()
|
character = form.save()
|
||||||
messages.success(request, f"{character.name} a été enregistré.")
|
messages.success(request, f"{character.name} a été enregistré.")
|
||||||
return redirect(character.get_absolute_url())
|
return redirect(character.get_absolute_url())
|
||||||
else:
|
else:
|
||||||
form = CharacterCreateForm(instance=character)
|
form = CharacterForm(instance=character)
|
||||||
context = {"form": form}
|
context = {"form": form}
|
||||||
return render(request, "character/character_form.html", context)
|
return render(request, "character/character_form.html", context)
|
||||||
|
|
||||||
|
@ -461,3 +462,71 @@ def reset_stats(request, pk: int):
|
||||||
messages.success(request, f"Les stats de {character} ont été réinitialisées.")
|
messages.success(request, f"Les stats de {character} ont été réinitialisées.")
|
||||||
return redirect(character)
|
return redirect(character)
|
||||||
return render(request, "character/character_reset_stats.html", context)
|
return render(request, "character/character_reset_stats.html", context)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def create_pet(request, pk: int):
|
||||||
|
character = get_object_or_404(Character.objects.managed_by(request.user), pk=pk)
|
||||||
|
if request.method == "POST":
|
||||||
|
form = PetForm(request.POST, request.FILES)
|
||||||
|
if form.is_valid():
|
||||||
|
pet = form.save(commit=False)
|
||||||
|
pet.owner = character
|
||||||
|
pet.save()
|
||||||
|
form.save_m2m()
|
||||||
|
messages.success(request, f"{pet.name} a été créé.")
|
||||||
|
return redirect("character:view", pk=pk)
|
||||||
|
else:
|
||||||
|
form = PetForm()
|
||||||
|
context = {"form": form}
|
||||||
|
return render(request, "character/pet_form.html", context)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def pet_change(request, pk: int):
|
||||||
|
potential_owners = Character.objects.managed_by(request.user)
|
||||||
|
pet = get_object_or_404(Pet.objects.filter(owner__in=potential_owners), pk=pk)
|
||||||
|
if request.method == "POST":
|
||||||
|
form = PetForm(request.POST, request.FILES, instance=pet)
|
||||||
|
if form.is_valid():
|
||||||
|
pet = form.save()
|
||||||
|
messages.success(request, f"{pet.name} a été enregistré.")
|
||||||
|
return redirect(pet.owner.get_absolute_url())
|
||||||
|
else:
|
||||||
|
form = PetForm(instance=pet)
|
||||||
|
context = {"form": form}
|
||||||
|
return render(request, "character/pet_form.html", context)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def pet_health_change(request, pk: int):
|
||||||
|
potential_owners = Character.objects.managed_by(request.user)
|
||||||
|
pet = get_object_or_404(
|
||||||
|
Pet.objects.filter(owner__in=potential_owners).only(
|
||||||
|
"health_max",
|
||||||
|
"health_remaining",
|
||||||
|
),
|
||||||
|
pk=pk,
|
||||||
|
)
|
||||||
|
value = get_updated_value(request, pet.health_remaining, pet.health_max)
|
||||||
|
pet.health_remaining = value
|
||||||
|
pet.save(update_fields=["health_remaining"])
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
"character/snippets/character_details/pet_health_bar.html",
|
||||||
|
{"pet": pet},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def pet_delete(request, pk: int):
|
||||||
|
potential_owners = Character.objects.owned_by(request.user)
|
||||||
|
pet = get_object_or_404(Pet.objects.filter(owner__in=potential_owners), pk=pk)
|
||||||
|
context = {"pet": pet}
|
||||||
|
if request.method == "POST":
|
||||||
|
name = pet.name
|
||||||
|
owner = pet.owner
|
||||||
|
pet.delete()
|
||||||
|
messages.success(request, f"Le familier {name} a été supprimé.")
|
||||||
|
return redirect("character:view", pk=owner.pk)
|
||||||
|
return render(request, "character/pet_delete.html", context)
|
||||||
|
|
Loading…
Reference in a new issue