mirror of
https://github.com/Crocmagnon/charasheet.git
synced 2024-11-22 22:48:03 +01:00
Add very basic battle effect version
This commit is contained in:
parent
98184c72d5
commit
5c9718d14a
14 changed files with 320 additions and 16 deletions
|
@ -18,6 +18,7 @@
|
||||||
<excludeFolder url="file://$MODULE_DIR$/src/public" />
|
<excludeFolder url="file://$MODULE_DIR$/src/public" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/src/test_reports" />
|
<excludeFolder url="file://$MODULE_DIR$/src/test_reports" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/src/character/tests/test_reports" />
|
<excludeFolder url="file://$MODULE_DIR$/src/character/tests/test_reports" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/src/common/static/vendor" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="jdk" jdkName="Python 3.11 (charasheet)" jdkType="Python SDK" />
|
<orderEntry type="jdk" jdkName="Python 3.11 (charasheet)" jdkType="Python SDK" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
addopts = """
|
addopts = """
|
||||||
--html=test_reports/pytest_result/pytest.html --color=yes --durations 20
|
--html=test_reports/pytest_result/pytest.html --color=yes --durations 20
|
||||||
--no-cov-on-fail --strict-markers
|
--no-cov-on-fail --strict-markers --reuse-db
|
||||||
--driver=Firefox
|
--driver=Firefox
|
||||||
-W error
|
-W error
|
||||||
-W "ignore:capabilities and desired_capabilities have been deprecated:DeprecationWarning:pytest_selenium.pytest_selenium"
|
-W "ignore:capabilities and desired_capabilities have been deprecated:DeprecationWarning:pytest_selenium.pytest_selenium"
|
||||||
|
@ -68,3 +68,6 @@ flake8-bandit = [
|
||||||
"-S106", # Possible hardcoded password.
|
"-S106", # Possible hardcoded password.
|
||||||
"-S311", # Standard pseudo-random generators are not suitable for security/cryptographic purposes.
|
"-S311", # Standard pseudo-random generators are not suitable for security/cryptographic purposes.
|
||||||
]
|
]
|
||||||
|
flake8-bugbear = [
|
||||||
|
"-B011", # Do not call assert False since python -O removes these calls.
|
||||||
|
]
|
||||||
|
|
|
@ -22,3 +22,18 @@ img.profile-pic {
|
||||||
max-height: 240px;
|
max-height: 240px;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#effects-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.effect {
|
||||||
|
width: 100px;
|
||||||
|
margin-right: 3em;
|
||||||
|
align-self: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.effect .bar {
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import pytest
|
import pytest
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
|
from selenium.webdriver.remote.webdriver import WebDriver
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session", autouse=True)
|
@pytest.fixture(scope="session", autouse=True)
|
||||||
|
@ -7,12 +8,25 @@ def collectstatic():
|
||||||
call_command("collectstatic", "--clear", "--noinput", "--verbosity=0")
|
call_command("collectstatic", "--clear", "--noinput", "--verbosity=0")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def live_server(settings, live_server):
|
||||||
|
settings.STATICFILES_STORAGE = "whitenoise.storage.CompressedStaticFilesStorage"
|
||||||
|
return live_server
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def firefox_options(firefox_options):
|
def firefox_options(firefox_options):
|
||||||
firefox_options.add_argument("-headless")
|
firefox_options.add_argument("-headless")
|
||||||
return firefox_options
|
return firefox_options
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def selenium(selenium: WebDriver) -> WebDriver:
|
||||||
|
selenium.implicitly_wait(3)
|
||||||
|
selenium.set_window_size(3860, 2140)
|
||||||
|
return selenium
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def settings(settings):
|
def settings(settings):
|
||||||
settings.DEBUG_TOOLBAR = False
|
settings.DEBUG_TOOLBAR = False
|
||||||
|
|
|
@ -3,7 +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
|
from character.models import Character
|
||||||
from party.models import Party
|
from party.models import BattleEffect, Party
|
||||||
|
|
||||||
|
|
||||||
class PartyForm(forms.ModelForm):
|
class PartyForm(forms.ModelForm):
|
||||||
|
@ -38,3 +38,9 @@ class PartyForm(forms.ModelForm):
|
||||||
ValidationError(f"{character} is already a group member."),
|
ValidationError(f"{character} is already a group member."),
|
||||||
)
|
)
|
||||||
return invited
|
return invited
|
||||||
|
|
||||||
|
|
||||||
|
class BattleEffectForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = BattleEffect
|
||||||
|
fields = ["name", "target", "description", "remaining_rounds"]
|
||||||
|
|
79
src/party/migrations/0003_battleeffect.py
Normal file
79
src/party/migrations/0003_battleeffect.py
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
# Generated by Django 4.1.5 on 2023-01-16 16:21
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django_extensions.db.fields
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
("party", "0002_party_invited_characters"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="BattleEffect",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"created",
|
||||||
|
django_extensions.db.fields.CreationDateTimeField(
|
||||||
|
auto_now_add=True, verbose_name="created"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"modified",
|
||||||
|
django_extensions.db.fields.ModificationDateTimeField(
|
||||||
|
auto_now=True, verbose_name="modified"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("name", models.CharField(max_length=100, verbose_name="nom")),
|
||||||
|
("target", models.CharField(max_length=100, verbose_name="cible")),
|
||||||
|
(
|
||||||
|
"description",
|
||||||
|
models.TextField(blank=True, verbose_name="description"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"remaining_rounds",
|
||||||
|
models.SmallIntegerField(
|
||||||
|
default=-1,
|
||||||
|
help_text="-1 pour un effet permanent",
|
||||||
|
verbose_name="nombre de tours restants",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"created_by",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="effects",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
verbose_name="créé par",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"party",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="effects",
|
||||||
|
to="party.party",
|
||||||
|
verbose_name="groupe",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"get_latest_by": "modified",
|
||||||
|
"abstract": False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
|
@ -1 +1 @@
|
||||||
0002_party_invited_characters
|
0003_battleeffect
|
||||||
|
|
|
@ -14,6 +14,12 @@ class PartyQuerySet(models.QuerySet):
|
||||||
def played_by(self, user):
|
def played_by(self, user):
|
||||||
return self.filter(characters__in=Character.objects.filter(player=user))
|
return self.filter(characters__in=Character.objects.filter(player=user))
|
||||||
|
|
||||||
|
def played_or_mastered_by(self, user):
|
||||||
|
return self.filter(
|
||||||
|
Q(game_master=user)
|
||||||
|
| Q(characters__in=Character.objects.filter(player=user))
|
||||||
|
).distinct()
|
||||||
|
|
||||||
def related_to(self, user):
|
def related_to(self, user):
|
||||||
return self.filter(
|
return self.filter(
|
||||||
Q(game_master=user)
|
Q(game_master=user)
|
||||||
|
@ -61,3 +67,36 @@ class Party(UniquelyNamedModel, TimeStampedModel, models.Model):
|
||||||
def reset_stats(self):
|
def reset_stats(self):
|
||||||
for character in self.characters.all():
|
for character in self.characters.all():
|
||||||
character.reset_stats()
|
character.reset_stats()
|
||||||
|
|
||||||
|
|
||||||
|
class BattleEffectManager(models.Manager):
|
||||||
|
def decrease_all_remaining_rounds(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
description = models.TextField(blank=True, null=False, verbose_name="description")
|
||||||
|
remaining_rounds = models.SmallIntegerField(
|
||||||
|
blank=False,
|
||||||
|
default=-1,
|
||||||
|
verbose_name="nombre de tours restants",
|
||||||
|
help_text="-1 pour un effet permanent",
|
||||||
|
)
|
||||||
|
party = models.ForeignKey(
|
||||||
|
"party.Party",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="effects",
|
||||||
|
verbose_name="groupe",
|
||||||
|
)
|
||||||
|
created_by = models.ForeignKey(
|
||||||
|
"common.User",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="effects",
|
||||||
|
verbose_name="créé par",
|
||||||
|
)
|
||||||
|
|
||||||
|
objects = BattleEffectManager()
|
||||||
|
|
|
@ -8,7 +8,8 @@
|
||||||
<h1>{{ party.name }}</h1>
|
<h1>{{ party.name }}</h1>
|
||||||
<p>MJ : {{ party.game_master.get_full_name|default:party.game_master.username }}</p>
|
<p>MJ : {{ party.game_master.get_full_name|default:party.game_master.username }}</p>
|
||||||
<p>
|
<p>
|
||||||
<a href="{% url "party:reset_stats" pk=party.pk %}" id="reset-stats">Réinitialiser les stats</a>
|
<a href="{% url "party:reset_stats" pk=party.pk %}" id="reset-stats">Réinitialiser
|
||||||
|
les stats</a>
|
||||||
</p>
|
</p>
|
||||||
<h2>Personnages</h2>
|
<h2>Personnages</h2>
|
||||||
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 row-cols-xl-4 g-4">
|
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 row-cols-xl-4 g-4">
|
||||||
|
@ -28,4 +29,6 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
<h2>Combat</h2>
|
||||||
|
{% include "party/snippets/effects.html" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
8
src/party/templates/party/snippets/add_effect_form.html
Normal file
8
src/party/templates/party/snippets/add_effect_form.html
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{% load django_bootstrap5 %}
|
||||||
|
<form>
|
||||||
|
{% csrf_token %}
|
||||||
|
{% bootstrap_form form %}
|
||||||
|
<button class="btn btn-primary" type="submit" hx-post="{% url "party:add_effect" pk=party.pk %}"
|
||||||
|
hx-target="#effects" hx-swap="outerHTML"
|
||||||
|
>Enregistrer</button>
|
||||||
|
</form>
|
22
src/party/templates/party/snippets/effects.html
Normal file
22
src/party/templates/party/snippets/effects.html
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<div id="effects">
|
||||||
|
<div id="effects-form">
|
||||||
|
<button
|
||||||
|
hx-get="{% url "party:add_effect" pk=party.pk %}"
|
||||||
|
hx-target="#effects-form"
|
||||||
|
hx-swap="innerHTML"
|
||||||
|
type="button"
|
||||||
|
id="add-effect"
|
||||||
|
class="btn btn-primary"><i class="fa-solid fa-plus"></i> Ajouter un effet
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div id="effects-list">
|
||||||
|
{% for effect in party.effects.all %}
|
||||||
|
<div class="effect">
|
||||||
|
<div style="height: calc({{ effect.remaining_rounds }}px * 20)" class="bar"></div>
|
||||||
|
<div class="name">{{ effect.name }}</div>
|
||||||
|
<div class="target">sur : {{ effect.target }}</div>
|
||||||
|
<div class="description text-secondary small">{{ effect.description }}</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -1,8 +1,8 @@
|
||||||
import pytest
|
import pytest
|
||||||
from django.core.management import call_command
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from model_bakery import baker
|
from model_bakery import baker
|
||||||
from pytest_django.live_server_helper import LiveServer
|
from pytest_django.live_server_helper import LiveServer
|
||||||
|
from selenium.webdriver import Keys
|
||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
from selenium.webdriver.firefox.webdriver import WebDriver
|
from selenium.webdriver.firefox.webdriver import WebDriver
|
||||||
from selenium.webdriver.support.select import Select
|
from selenium.webdriver.support.select import Select
|
||||||
|
@ -10,13 +10,13 @@ from selenium.webdriver.support.select import Select
|
||||||
from character.models import Character, Profile
|
from character.models import Character, Profile
|
||||||
from character.tests.test_interactions import create_hurt_character, login
|
from character.tests.test_interactions import create_hurt_character, login
|
||||||
from common.models import User
|
from common.models import User
|
||||||
from party.models import Party
|
from party.models import BattleEffect, Party
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_add_character_to_existing_group(selenium: WebDriver, live_server: LiveServer):
|
def test_add_character_to_existing_group(
|
||||||
call_command("loaddata", "initial_data")
|
selenium: WebDriver, live_server: LiveServer, initial_data: None
|
||||||
|
):
|
||||||
username, password = "gm", "password"
|
username, password = "gm", "password"
|
||||||
gm = User.objects.create_user(username, password=password)
|
gm = User.objects.create_user(username, password=password)
|
||||||
player = User.objects.create_user("player")
|
player = User.objects.create_user("player")
|
||||||
|
@ -40,10 +40,8 @@ def test_add_character_to_existing_group(selenium: WebDriver, live_server: LiveS
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_gm_observe_invited_character_in_group(
|
def test_gm_observe_invited_character_in_group(
|
||||||
selenium: WebDriver, live_server: LiveServer
|
selenium: WebDriver, live_server: LiveServer, initial_data: None
|
||||||
):
|
):
|
||||||
call_command("loaddata", "initial_data")
|
|
||||||
|
|
||||||
username, password = "gm", "password"
|
username, password = "gm", "password"
|
||||||
gm = User.objects.create_user(username, password=password)
|
gm = User.objects.create_user(username, password=password)
|
||||||
player = User.objects.create_user("player")
|
player = User.objects.create_user("player")
|
||||||
|
@ -66,10 +64,8 @@ def test_gm_observe_invited_character_in_group(
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_gm_observe_invited_character_in_two_groups(
|
def test_gm_observe_invited_character_in_two_groups(
|
||||||
selenium: WebDriver, live_server: LiveServer
|
selenium: WebDriver, live_server: LiveServer, initial_data: None
|
||||||
):
|
):
|
||||||
call_command("loaddata", "initial_data")
|
|
||||||
|
|
||||||
username, password = "gm", "password"
|
username, password = "gm", "password"
|
||||||
gm = User.objects.create_user(username, password=password)
|
gm = User.objects.create_user(username, password=password)
|
||||||
player = User.objects.create_user("player")
|
player = User.objects.create_user("player")
|
||||||
|
@ -118,3 +114,102 @@ def test_reset_stats_view(
|
||||||
assert character.mana_remaining == character.mana_max
|
assert character.mana_remaining == character.mana_max
|
||||||
assert character.recovery_points_remaining == character.recovery_points_max
|
assert character.recovery_points_remaining == character.recovery_points_max
|
||||||
assert character.luck_points_remaining == character.luck_points_max
|
assert character.luck_points_remaining == character.luck_points_max
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_player_can_add_effect_to_group(
|
||||||
|
selenium: WebDriver, live_server: LiveServer, initial_data: None
|
||||||
|
):
|
||||||
|
"""Any member of a group can add effects to the group."""
|
||||||
|
user, password = "player", "password"
|
||||||
|
player = User.objects.create_user(user, password=password)
|
||||||
|
character = baker.make(Character, player=player)
|
||||||
|
party = baker.make(Party)
|
||||||
|
party.characters.add(character)
|
||||||
|
|
||||||
|
assert BattleEffect.objects.count() == 0
|
||||||
|
|
||||||
|
login(selenium, live_server, user, password)
|
||||||
|
|
||||||
|
url = reverse("party:details", kwargs={"pk": party.pk})
|
||||||
|
selenium.get(live_server.url + url)
|
||||||
|
selenium.find_element(By.ID, "add-effect").click()
|
||||||
|
selenium.find_element(By.ID, "id_name").send_keys("Agrandissement")
|
||||||
|
selenium.find_element(By.ID, "id_target").send_keys("Joueur 4")
|
||||||
|
selenium.find_element(By.ID, "id_description").send_keys(
|
||||||
|
"Le Magicien ou une cible volontaire (au contact) voit sa taille augmenter de "
|
||||||
|
"50% pendant [5 + Mod. d'INT] tours. Il gagne +2 aux DM au contact et aux "
|
||||||
|
"tests de FOR. Pataud, il subit un malus de -2 aux tests de DEX."
|
||||||
|
)
|
||||||
|
selenium.find_element(By.ID, "id_remaining_rounds").send_keys(Keys.DELETE, "8")
|
||||||
|
selenium.find_element(By.CSS_SELECTOR, "button[type=submit]").click()
|
||||||
|
|
||||||
|
assert BattleEffect.objects.count() == 1
|
||||||
|
# Todo: assert effect is displayed
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_gm_can_add_effect_to_group(
|
||||||
|
selenium: WebDriver, live_server: LiveServer, initial_data: None
|
||||||
|
):
|
||||||
|
"""The GM of a group can add effects to the group."""
|
||||||
|
user, password = "gm", "password"
|
||||||
|
gm = User.objects.create_user(user, password=password)
|
||||||
|
party = baker.make(Party, game_master=gm)
|
||||||
|
|
||||||
|
assert BattleEffect.objects.count() == 0
|
||||||
|
|
||||||
|
login(selenium, live_server, user, password)
|
||||||
|
|
||||||
|
url = reverse("party:details", kwargs={"pk": party.pk})
|
||||||
|
selenium.get(live_server.url + url)
|
||||||
|
selenium.find_element(By.ID, "add-effect").click()
|
||||||
|
selenium.find_element(By.ID, "id_name").send_keys("Brûlé")
|
||||||
|
selenium.find_element(By.ID, "id_target").send_keys("Boss 2")
|
||||||
|
selenium.find_element(By.ID, "id_description").send_keys(
|
||||||
|
"Le Magicien choisit une cible située à moins de 30 mètres. Si son attaque "
|
||||||
|
"magique réussit, la cible encaisse [1d6 + Mod. d'INT] DM et la flèche "
|
||||||
|
"enflamme ses vêtements. Chaque tour de combat suivant, le feu inflige 1d6 "
|
||||||
|
"dégâts supplémentaires. Sur un résultat de 1 à 2, les flammes s'éteignent et "
|
||||||
|
"le sort prend fin."
|
||||||
|
)
|
||||||
|
selenium.find_element(By.ID, "id_remaining_rounds").send_keys(Keys.DELETE, "-1")
|
||||||
|
selenium.find_element(By.CSS_SELECTOR, "button[type=submit]").click()
|
||||||
|
|
||||||
|
assert BattleEffect.objects.count() == 1
|
||||||
|
# Todo: assert effect is displayed
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_gm_can_change_remaining_rounds(
|
||||||
|
selenium: WebDriver, live_server: LiveServer, initial_data: None
|
||||||
|
):
|
||||||
|
"""The GM of a group can increase or decrease the remaining rounds of effects."""
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_gm_can_update_existing_effect(
|
||||||
|
selenium: WebDriver, live_server: LiveServer, initial_data: None
|
||||||
|
):
|
||||||
|
"""The GM of a group can update existing effect, except group and creator."""
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_gm_can_delete_any_existing_effect(
|
||||||
|
selenium: WebDriver, live_server: LiveServer, initial_data: None
|
||||||
|
):
|
||||||
|
"""The GM of a group can delete any existing effect, running or terminated."""
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_player_cant_change_existing_running_effect(
|
||||||
|
selenium: WebDriver, live_server: LiveServer, initial_data: None
|
||||||
|
):
|
||||||
|
"""Members of the group can only view existing running effects, no update."""
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_player_can_delete_terminated_effect(
|
||||||
|
selenium: WebDriver, live_server: LiveServer, initial_data: None
|
||||||
|
):
|
||||||
|
"""Members of the group can delete terminated effects."""
|
||||||
|
|
|
@ -10,6 +10,7 @@ urlpatterns = [
|
||||||
path("<int:pk>/change/", views.party_change, name="change"),
|
path("<int:pk>/change/", views.party_change, name="change"),
|
||||||
path("<int:pk>/delete/", views.party_delete, name="delete"),
|
path("<int:pk>/delete/", views.party_delete, name="delete"),
|
||||||
path("<int:pk>/reset_stats/", views.party_reset_stats, name="reset_stats"),
|
path("<int:pk>/reset_stats/", views.party_reset_stats, name="reset_stats"),
|
||||||
|
path("<int:pk>/add_effect/", views.party_add_effect, name="add_effect"),
|
||||||
path("<int:pk>/leave/<int:character_pk>/", views.party_leave, name="leave"),
|
path("<int:pk>/leave/<int:character_pk>/", views.party_leave, name="leave"),
|
||||||
path("<int:pk>/join/<int:character_pk>/", views.party_join, name="join"),
|
path("<int:pk>/join/<int:character_pk>/", views.party_join, name="join"),
|
||||||
path("<int:pk>/refuse/<int:character_pk>/", views.party_refuse, name="refuse"),
|
path("<int:pk>/refuse/<int:character_pk>/", views.party_refuse, name="refuse"),
|
||||||
|
|
|
@ -3,7 +3,7 @@ from django.contrib.auth.decorators import login_required
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
|
|
||||||
from character.models import Character, HarmfulState
|
from character.models import Character, HarmfulState
|
||||||
from party.forms import PartyForm
|
from party.forms import BattleEffectForm, PartyForm
|
||||||
from party.models import Party
|
from party.models import Party
|
||||||
|
|
||||||
|
|
||||||
|
@ -68,6 +68,24 @@ def party_reset_stats(request, pk):
|
||||||
return render(request, "party/party_reset_stats.html", context)
|
return render(request, "party/party_reset_stats.html", context)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def party_add_effect(request, pk):
|
||||||
|
party = get_object_or_404(Party.objects.played_or_mastered_by(request.user), pk=pk)
|
||||||
|
context = {"party": party}
|
||||||
|
if request.method == "GET":
|
||||||
|
form = BattleEffectForm()
|
||||||
|
else:
|
||||||
|
form = BattleEffectForm(request.POST or None)
|
||||||
|
if form.is_valid():
|
||||||
|
effect = form.save(commit=False)
|
||||||
|
effect.party = party
|
||||||
|
effect.created_by = request.user
|
||||||
|
effect.save()
|
||||||
|
return render(request, "party/snippets/effects.html", context)
|
||||||
|
context["form"] = form
|
||||||
|
return render(request, "party/snippets/add_effect_form.html", context)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def party_change(request, pk):
|
def party_change(request, pk):
|
||||||
party = get_object_or_404(Party.objects.managed_by(request.user), pk=pk)
|
party = get_object_or_404(Party.objects.managed_by(request.user), pk=pk)
|
||||||
|
|
Loading…
Reference in a new issue