diff --git a/src/character/admin.py b/src/character/admin.py index 617f319..2fdd4cc 100644 --- a/src/character/admin.py +++ b/src/character/admin.py @@ -138,6 +138,7 @@ class CharacterAdmin(admin.ModelAdmin): "attack_melee", "attack_range", "attack_magic", + "states", ] }, ), @@ -182,6 +183,7 @@ class CharacterAdmin(admin.ModelAdmin): filter_horizontal = [ "capabilities", "weapons", + "states", ] form = CharacterAdminForm @@ -196,3 +198,9 @@ class CharacterAdmin(admin.ModelAdmin): class WeaponAdmin(admin.ModelAdmin): list_display = ["name", "damage"] search_fields = ["name", "special", "damage"] + + +@admin.register(models.HarmfulState) +class HarmfulStateAdmin(admin.ModelAdmin): + list_display = ["name", "description"] + search_fields = ["name"] diff --git a/src/character/management/commands/import_harmful_states.py b/src/character/management/commands/import_harmful_states.py new file mode 100644 index 0000000..bdc093d --- /dev/null +++ b/src/character/management/commands/import_harmful_states.py @@ -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) diff --git a/src/character/migrations/0028_harmfulstate.py b/src/character/migrations/0028_harmfulstate.py new file mode 100644 index 0000000..4f1ca5d --- /dev/null +++ b/src/character/migrations/0028_harmfulstate.py @@ -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", + }, + ), + ] diff --git a/src/character/migrations/0029_character_states.py b/src/character/migrations/0029_character_states.py new file mode 100644 index 0000000..59dcf62 --- /dev/null +++ b/src/character/migrations/0029_character_states.py @@ -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" + ), + ), + ] diff --git a/src/character/migrations/0030_alter_character_states.py b/src/character/migrations/0030_alter_character_states.py new file mode 100644 index 0000000..24bc81d --- /dev/null +++ b/src/character/migrations/0030_alter_character_states.py @@ -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" + ), + ), + ] diff --git a/src/character/migrations/max_migration.txt b/src/character/migrations/max_migration.txt index 3642965..8f8b1da 100644 --- a/src/character/migrations/max_migration.txt +++ b/src/character/migrations/max_migration.txt @@ -1 +1 @@ -0027_character_initiative_misc +0030_alter_character_states diff --git a/src/character/models/__init__.py b/src/character/models/__init__.py index 2dde838..c945787 100644 --- a/src/character/models/__init__.py +++ b/src/character/models/__init__.py @@ -1,5 +1,5 @@ from .capabilities import Capability, Path, RacialCapability -from .character import Character, Profile, Race +from .character import Character, HarmfulState, Profile, Race from .equipment import Weapon __all__ = [ @@ -7,6 +7,7 @@ __all__ = [ "Path", "RacialCapability", "Character", + "HarmfulState", "Profile", "Race", "Weapon", diff --git a/src/character/models/character.py b/src/character/models/character.py index ab68831..1dd20c4 100644 --- a/src/character/models/character.py +++ b/src/character/models/character.py @@ -49,6 +49,15 @@ class Race(DocumentedModel, UniquelyNamedModel, TimeStampedModel, models.Model): 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: if not value: return 0 @@ -177,6 +186,8 @@ class Character(models.Model): 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") + states = models.ManyToManyField(HarmfulState, blank=True, related_name="characters") + objects = CharacterManager() class Meta: diff --git a/src/character/templates/character/states.html b/src/character/templates/character/states.html new file mode 100644 index 0000000..0fdcf58 --- /dev/null +++ b/src/character/templates/character/states.html @@ -0,0 +1,15 @@ +

+ États : + {% for state in character.states.all %} + {{ state.name }} + {% empty %} + Aucun + {% endfor %} +

diff --git a/src/character/templates/character/view.html b/src/character/templates/character/view.html index e63d3f3..246c7ee 100644 --- a/src/character/templates/character/view.html +++ b/src/character/templates/character/view.html @@ -16,6 +16,7 @@ {{ character.race.name }} {{ character.profile.name }} niv. {{ character.level }}
{{ character.get_gender_display }}, {{ character.age }} ans, {{ character.height_m }}m, {{ character.weight }}kg (IMC: {{ character.imc|floatformat }})

+ {% include "character/states.html" %}
diff --git a/src/character/urls.py b/src/character/urls.py index 3731ced..a333358 100644 --- a/src/character/urls.py +++ b/src/character/urls.py @@ -69,4 +69,7 @@ urlpatterns = [ name="remove_last_in_path", ), path("/add_path/", views.add_path, name="add_path"), + path( + "/remove_state//", views.remove_state, name="remove_state" + ), ] diff --git a/src/character/views.py b/src/character/views.py index cec4101..0d5ba34 100644 --- a/src/character/views.py +++ b/src/character/views.py @@ -4,7 +4,7 @@ from django.shortcuts import get_object_or_404, redirect, render from django_htmx.http import trigger_client_event 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 @@ -265,3 +265,15 @@ def remove_last_in_path(request, character_pk: int, path_pk: int): "add_path_form": AddPathForm(character), } 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", {}) diff --git a/src/common/templates/common/base.html b/src/common/templates/common/base.html index 506118e..2b5e04b 100644 --- a/src/common/templates/common/base.html +++ b/src/common/templates/common/base.html @@ -42,8 +42,15 @@ {% endif %} diff --git a/tasks.py b/tasks.py index 7d63c2e..fce74b0 100644 --- a/tasks.py +++ b/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_paths", 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])