mirror of
https://github.com/Crocmagnon/charasheet.git
synced 2024-11-22 14:38:03 +01:00
Add states
This commit is contained in:
parent
4564e71d7d
commit
4664ae0d9d
14 changed files with 194 additions and 5 deletions
|
@ -138,6 +138,7 @@ class CharacterAdmin(admin.ModelAdmin):
|
||||||
"attack_melee",
|
"attack_melee",
|
||||||
"attack_range",
|
"attack_range",
|
||||||
"attack_magic",
|
"attack_magic",
|
||||||
|
"states",
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -182,6 +183,7 @@ class CharacterAdmin(admin.ModelAdmin):
|
||||||
filter_horizontal = [
|
filter_horizontal = [
|
||||||
"capabilities",
|
"capabilities",
|
||||||
"weapons",
|
"weapons",
|
||||||
|
"states",
|
||||||
]
|
]
|
||||||
|
|
||||||
form = CharacterAdminForm
|
form = CharacterAdminForm
|
||||||
|
@ -196,3 +198,9 @@ class CharacterAdmin(admin.ModelAdmin):
|
||||||
class WeaponAdmin(admin.ModelAdmin):
|
class WeaponAdmin(admin.ModelAdmin):
|
||||||
list_display = ["name", "damage"]
|
list_display = ["name", "damage"]
|
||||||
search_fields = ["name", "special", "damage"]
|
search_fields = ["name", "special", "damage"]
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(models.HarmfulState)
|
||||||
|
class HarmfulStateAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ["name", "description"]
|
||||||
|
search_fields = ["name"]
|
||||||
|
|
39
src/character/management/commands/import_harmful_states.py
Normal file
39
src/character/management/commands/import_harmful_states.py
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
from django.core.management import BaseCommand
|
||||||
|
from selenium import webdriver
|
||||||
|
from selenium.webdriver.common.by import By
|
||||||
|
from selenium.webdriver.remote.webelement import WebElement
|
||||||
|
|
||||||
|
from character.models.character import HarmfulState
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
def handle(self, *args, **options) -> None:
|
||||||
|
url = "https://www.co-drs.org/fr/jeu/etats-prejudiciables"
|
||||||
|
self.setup_selenium()
|
||||||
|
self.selenium.get(url)
|
||||||
|
states = self.selenium.find_elements(By.CSS_SELECTOR, "tbody tr")
|
||||||
|
for state in states:
|
||||||
|
try:
|
||||||
|
self.import_race(url, state)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"{type(e)}: {e}")
|
||||||
|
self.stdout.write(f"Finished processing {len(states)} states.")
|
||||||
|
|
||||||
|
def import_race(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"
|
||||||
|
).text.strip()
|
||||||
|
icon_url = state_row.find_element(
|
||||||
|
By.CSS_SELECTOR, ".views-field-field-svg-icon img"
|
||||||
|
).get_attribute("src")
|
||||||
|
state, _ = HarmfulState.objects.update_or_create(
|
||||||
|
name=name,
|
||||||
|
defaults={"description": description, "url": url, "icon_url": icon_url},
|
||||||
|
)
|
||||||
|
self.stdout.write(self.style.SUCCESS(f"Created/updated state {state}"))
|
||||||
|
|
||||||
|
def setup_selenium(self) -> None:
|
||||||
|
options = webdriver.FirefoxOptions()
|
||||||
|
options.add_argument("-headless")
|
||||||
|
self.selenium = webdriver.Firefox(options=options)
|
51
src/character/migrations/0028_harmfulstate.py
Normal file
51
src/character/migrations/0028_harmfulstate.py
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
# Generated by Django 4.1.2 on 2022-11-02 20:00
|
||||||
|
|
||||||
|
import django_extensions.db.fields
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("character", "0027_character_initiative_misc"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="HarmfulState",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"name",
|
||||||
|
models.CharField(max_length=100, unique=True, verbose_name="nom"),
|
||||||
|
),
|
||||||
|
("url", models.URLField(blank=True, verbose_name="url")),
|
||||||
|
(
|
||||||
|
"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"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("description", models.TextField()),
|
||||||
|
("icon_url", models.URLField()),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"verbose_name": "État préjudiciable",
|
||||||
|
"verbose_name_plural": "États préjudiciables",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
20
src/character/migrations/0029_character_states.py
Normal file
20
src/character/migrations/0029_character_states.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Generated by Django 4.1.2 on 2022-11-02 20:04
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("character", "0028_harmfulstate"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="character",
|
||||||
|
name="states",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
related_name="characters", to="character.harmfulstate"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
20
src/character/migrations/0030_alter_character_states.py
Normal file
20
src/character/migrations/0030_alter_character_states.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Generated by Django 4.1.2 on 2022-11-02 20:05
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("character", "0029_character_states"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="character",
|
||||||
|
name="states",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
blank=True, related_name="characters", to="character.harmfulstate"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -1 +1 @@
|
||||||
0027_character_initiative_misc
|
0030_alter_character_states
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from .capabilities import Capability, Path, RacialCapability
|
from .capabilities import Capability, Path, RacialCapability
|
||||||
from .character import Character, Profile, Race
|
from .character import Character, HarmfulState, Profile, Race
|
||||||
from .equipment import Weapon
|
from .equipment import Weapon
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
@ -7,6 +7,7 @@ __all__ = [
|
||||||
"Path",
|
"Path",
|
||||||
"RacialCapability",
|
"RacialCapability",
|
||||||
"Character",
|
"Character",
|
||||||
|
"HarmfulState",
|
||||||
"Profile",
|
"Profile",
|
||||||
"Race",
|
"Race",
|
||||||
"Weapon",
|
"Weapon",
|
||||||
|
|
|
@ -49,6 +49,15 @@ class Race(DocumentedModel, UniquelyNamedModel, TimeStampedModel, models.Model):
|
||||||
verbose_name_plural = "Races"
|
verbose_name_plural = "Races"
|
||||||
|
|
||||||
|
|
||||||
|
class HarmfulState(DocumentedModel, UniquelyNamedModel, TimeStampedModel, models.Model):
|
||||||
|
description = models.TextField()
|
||||||
|
icon_url = models.URLField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "État préjudiciable"
|
||||||
|
verbose_name_plural = "États préjudiciables"
|
||||||
|
|
||||||
|
|
||||||
def modifier(value: int) -> int:
|
def modifier(value: int) -> int:
|
||||||
if not value:
|
if not value:
|
||||||
return 0
|
return 0
|
||||||
|
@ -177,6 +186,8 @@ class Character(models.Model):
|
||||||
notes = models.TextField(blank=True, verbose_name="notes", default=DEFAULT_NOTES)
|
notes = models.TextField(blank=True, verbose_name="notes", default=DEFAULT_NOTES)
|
||||||
damage_reduction = models.TextField(blank=True, verbose_name="réduction de dégâts")
|
damage_reduction = models.TextField(blank=True, verbose_name="réduction de dégâts")
|
||||||
|
|
||||||
|
states = models.ManyToManyField(HarmfulState, blank=True, related_name="characters")
|
||||||
|
|
||||||
objects = CharacterManager()
|
objects = CharacterManager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
15
src/character/templates/character/states.html
Normal file
15
src/character/templates/character/states.html
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<p id="states">
|
||||||
|
États :
|
||||||
|
{% for state in character.states.all %}
|
||||||
|
<img src="{{ state.icon_url }}" alt="{{ state.name }}" height="25" width="25"
|
||||||
|
data-bs-toggle="tooltip"
|
||||||
|
data-bs-placement="top"
|
||||||
|
data-bs-title="{{ state.name }} : {{ state.description }}"
|
||||||
|
hx-get="{% url "character:remove_state" pk=character.pk state_pk=state.pk %}"
|
||||||
|
hx-target="#states"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
>
|
||||||
|
{% empty %}
|
||||||
|
Aucun
|
||||||
|
{% endfor %}
|
||||||
|
</p>
|
|
@ -16,6 +16,7 @@
|
||||||
{{ character.race.name }} {{ character.profile.name }} niv. {{ character.level }}<br>
|
{{ character.race.name }} {{ character.profile.name }} niv. {{ character.level }}<br>
|
||||||
{{ character.get_gender_display }}, {{ character.age }} ans, {{ character.height_m }}m, {{ character.weight }}kg (IMC: {{ character.imc|floatformat }})
|
{{ character.get_gender_display }}, {{ character.age }} ans, {{ character.height_m }}m, {{ character.weight }}kg (IMC: {{ character.imc|floatformat }})
|
||||||
</p>
|
</p>
|
||||||
|
{% include "character/states.html" %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12 col-md-6 col-lg-6 col-xl">
|
<div class="col-sm-12 col-md-6 col-lg-6 col-xl">
|
||||||
<table id="fight-table" class="table table-hover table-sm">
|
<table id="fight-table" class="table table-hover table-sm">
|
||||||
|
|
|
@ -69,4 +69,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>/remove_state/<int:state_pk>/", views.remove_state, name="remove_state"
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -4,7 +4,7 @@ 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, EquipmentForm
|
from character.forms import AddPathForm, EquipmentForm
|
||||||
from character.models import Capability, Character, Path
|
from character.models import Capability, Character, HarmfulState, Path
|
||||||
from character.templatetags.character_extras import modifier
|
from character.templatetags.character_extras import modifier
|
||||||
|
|
||||||
|
|
||||||
|
@ -265,3 +265,15 @@ def remove_last_in_path(request, character_pk: int, path_pk: int):
|
||||||
"add_path_form": AddPathForm(character),
|
"add_path_form": AddPathForm(character),
|
||||||
}
|
}
|
||||||
return render(request, "character/paths_and_capabilities.html", context)
|
return render(request, "character/paths_and_capabilities.html", context)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def remove_state(request, pk: int, state_pk: int):
|
||||||
|
character: Character = get_object_or_404(
|
||||||
|
Character.objects.filter(player=request.user), pk=pk
|
||||||
|
)
|
||||||
|
state = get_object_or_404(HarmfulState, pk=state_pk)
|
||||||
|
character.states.remove(state)
|
||||||
|
context = {"character": character}
|
||||||
|
response = render(request, "character/states.html", context)
|
||||||
|
return trigger_client_event(response, "refresh_tooltips", {})
|
||||||
|
|
|
@ -42,8 +42,15 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<script src="{% static "vendor/bootstrap-5.2.2/bootstrap.bundle.min.js" %}"></script>
|
<script src="{% static "vendor/bootstrap-5.2.2/bootstrap.bundle.min.js" %}"></script>
|
||||||
<script type="application/javascript" defer>
|
<script type="application/javascript" defer>
|
||||||
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
|
let tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
|
||||||
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl));
|
let tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl));
|
||||||
|
addEventListener("refresh_tooltips", function (event) {
|
||||||
|
tooltipList.forEach(tooltip => tooltip.dispose());
|
||||||
|
setTimeout(() => {
|
||||||
|
tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
|
||||||
|
tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl));
|
||||||
|
}, 50);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
1
tasks.py
1
tasks.py
|
@ -127,6 +127,7 @@ def import_from_co_drs(ctx):
|
||||||
ctx.run("./manage.py import_profiles", pty=True, echo=True)
|
ctx.run("./manage.py import_profiles", pty=True, echo=True)
|
||||||
ctx.run("./manage.py import_paths", pty=True, echo=True)
|
ctx.run("./manage.py import_paths", pty=True, echo=True)
|
||||||
ctx.run("./manage.py import_capabilities", pty=True, echo=True)
|
ctx.run("./manage.py import_capabilities", pty=True, echo=True)
|
||||||
|
ctx.run("./manage.py import_harmful_states", pty=True, echo=True)
|
||||||
|
|
||||||
|
|
||||||
@task(pre=[import_from_co_drs, dump_initial])
|
@task(pre=[import_from_co_drs, dump_initial])
|
||||||
|
|
Loading…
Reference in a new issue