From f3f3b33c1290f422b3ebd1da76912029aed78fe8 Mon Sep 17 00:00:00 2001 From: Gabriel Augendre Date: Wed, 28 Dec 2022 09:27:27 +0100 Subject: [PATCH] Implement GM notes. Closes #42 --- .../migrations/0040_character_gm_notes.py | 18 +++++++++++++ src/character/migrations/max_migration.txt | 2 +- src/character/models/character.py | 27 +++++++++++++++++++ .../character/character_details.html | 3 +++ .../character_details/gm_notes_display.html | 15 +++++++++++ .../character_details/gm_notes_update.html | 17 ++++++++++++ .../character_details/notes_display.html | 4 +++ .../character_details/notes_update.html | 4 +++ .../templatetags/character_extras.py | 5 ++++ src/character/tests/test_access.py | 14 +++++++--- src/character/urls.py | 5 ++++ src/character/views.py | 5 ++++ 12 files changed, 115 insertions(+), 4 deletions(-) create mode 100644 src/character/migrations/0040_character_gm_notes.py create mode 100644 src/character/templates/character/snippets/character_details/gm_notes_display.html create mode 100644 src/character/templates/character/snippets/character_details/gm_notes_update.html diff --git a/src/character/migrations/0040_character_gm_notes.py b/src/character/migrations/0040_character_gm_notes.py new file mode 100644 index 0000000..f4ffc5f --- /dev/null +++ b/src/character/migrations/0040_character_gm_notes.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.4 on 2022-12-28 07:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("character", "0039_alter_character_profile_picture"), + ] + + operations = [ + migrations.AddField( + model_name="character", + name="gm_notes", + field=models.TextField(blank=True, verbose_name="notes MJ"), + ), + ] diff --git a/src/character/migrations/max_migration.txt b/src/character/migrations/max_migration.txt index 654d274..e90ad0b 100644 --- a/src/character/migrations/max_migration.txt +++ b/src/character/migrations/max_migration.txt @@ -1 +1 @@ -0038_character_profile_picture +0040_character_gm_notes diff --git a/src/character/models/character.py b/src/character/models/character.py index 979c9b7..15397b8 100644 --- a/src/character/models/character.py +++ b/src/character/models/character.py @@ -79,16 +79,35 @@ class CharacterManager(models.Manager): class CharacterQuerySet(models.QuerySet): def managed_by(self, user): + """ + Return characters managed by the given user. + + Characters are managed by a user if they own the character + or if they are the game master for a group in which the character plays. + """ from party.models import Party return self.filter( Q(player=user) | Q(parties__in=Party.objects.managed_by(user)) ) + def mastered_by(self, user): + """Return characters in groups where the given user is the game master.""" + from party.models import Party + + return self.filter(parties__in=Party.objects.managed_by(user)) + def owned_by(self, user): + """Return characters either owned by the given user.""" return self.filter(player=user) def friendly_to(self, user): + """ + Return characters friendly to the given users. + + Friendly characters are either owned by the given user + or in a party related to the given user. + """ from party.models import Party return self.filter( @@ -229,6 +248,7 @@ class Character(models.Model): ) notes = models.TextField(blank=True, verbose_name="notes", default=DEFAULT_NOTES) + gm_notes = models.TextField(blank=True, verbose_name="notes MJ") damage_reduction = models.TextField(blank=True, verbose_name="réduction de dégâts") states = models.ManyToManyField(HarmfulState, blank=True, related_name="characters") @@ -402,6 +422,10 @@ class Character(models.Model): md = markdown.Markdown(extensions=["extra", "nl2br"]) return md.convert(self.notes) + def get_formatted_gm_notes(self) -> str: + md = markdown.Markdown(extensions=["extra", "nl2br"]) + return md.convert(self.gm_notes) + def get_missing_states(self) -> Iterable[HarmfulState]: return HarmfulState.objects.exclude( pk__in=self.states.all().values_list("pk", flat=True) @@ -410,6 +434,9 @@ class Character(models.Model): def managed_by(self, user): return self in Character.objects.managed_by(user) + def mastered_by(self, user): + return self in Character.objects.mastered_by(user) + def reset_stats(self): self.health_remaining = self.health_max self.mana_remaining = self.mana_max diff --git a/src/character/templates/character/character_details.html b/src/character/templates/character/character_details.html index 87536ff..46b90ba 100644 --- a/src/character/templates/character/character_details.html +++ b/src/character/templates/character/character_details.html @@ -510,6 +510,9 @@ {% include "character/snippets/character_details/paths_and_capabilities.html" %} + {% if character|mastered_by:user %} + {% include "character/snippets/character_details/gm_notes_display.html" %} + {% endif %} {% if character|managed_by:user %} {% include "character/snippets/character_details/notes_display.html" %} {% endif %} diff --git a/src/character/templates/character/snippets/character_details/gm_notes_display.html b/src/character/templates/character/snippets/character_details/gm_notes_display.html new file mode 100644 index 0000000..8ac24c2 --- /dev/null +++ b/src/character/templates/character/snippets/character_details/gm_notes_display.html @@ -0,0 +1,15 @@ +{% load character_extras %} +
+

+ Notes MJ + + Edit + +

+
Le joueur ne peut pas voir ces notes.
+ {{ character.get_formatted_gm_notes|safe }} +
diff --git a/src/character/templates/character/snippets/character_details/gm_notes_update.html b/src/character/templates/character/snippets/character_details/gm_notes_update.html new file mode 100644 index 0000000..a9b0fc1 --- /dev/null +++ b/src/character/templates/character/snippets/character_details/gm_notes_update.html @@ -0,0 +1,17 @@ +
+
+

+ Notes MJ + + Save + +

+
Ces notes ne sont pas visibles par le joueur.
+ {% csrf_token %} + +
+
diff --git a/src/character/templates/character/snippets/character_details/notes_display.html b/src/character/templates/character/snippets/character_details/notes_display.html index 4015d09..89086e5 100644 --- a/src/character/templates/character/snippets/character_details/notes_display.html +++ b/src/character/templates/character/snippets/character_details/notes_display.html @@ -10,5 +10,9 @@ Edit +
+ Le {% if character|mastered_by:user %}joueur{% else %}MJ{% endif %} + peut également voir et modifier ces notes. +
{{ character.get_formatted_notes|safe }} diff --git a/src/character/templates/character/snippets/character_details/notes_update.html b/src/character/templates/character/snippets/character_details/notes_update.html index 4312cbd..1f56d7d 100644 --- a/src/character/templates/character/snippets/character_details/notes_update.html +++ b/src/character/templates/character/snippets/character_details/notes_update.html @@ -10,6 +10,10 @@ Save +
+ Ces notes ne sont visibles que par le joueur et les MJ + des groupes auquel ce personnage appartient. +
{% csrf_token %} diff --git a/src/character/templatetags/character_extras.py b/src/character/templatetags/character_extras.py index 1c63cd5..f300549 100644 --- a/src/character/templatetags/character_extras.py +++ b/src/character/templatetags/character_extras.py @@ -43,3 +43,8 @@ def max_rank(path: Path, character: Character) -> int: @register.filter def managed_by(character: Character, user: User) -> bool: return character.managed_by(user) + + +@register.filter +def mastered_by(character: Character, user: User) -> bool: + return character.mastered_by(user) diff --git a/src/character/tests/test_access.py b/src/character/tests/test_access.py index b2c74c6..60bacec 100644 --- a/src/character/tests/test_access.py +++ b/src/character/tests/test_access.py @@ -11,13 +11,15 @@ def test_can_access_own_character(client): player = User.objects.create_user("username", password="password") notes = "Some notes" - character = baker.make(Character, player=player, notes=notes) + gm_notes = "Some GM notes" + character = baker.make(Character, player=player, notes=notes, gm_notes=gm_notes) client.force_login(player) res = client.get(character.get_absolute_url()) assert res.status_code == 200 body = res.content.decode("utf-8") assert notes in body + assert gm_notes not in body @pytest.mark.django_db @@ -38,7 +40,10 @@ def test_can_access_character_in_party(client): character = baker.make(Character, player=player) notes = "Some notes" - friend_character = baker.make(Character, player=friend, notes=notes) + gm_notes = "Some GM notes" + friend_character = baker.make( + Character, player=friend, notes=notes, gm_notes=gm_notes + ) party = baker.make(Party) party.characters.add(character) party.characters.add(friend_character) @@ -48,6 +53,7 @@ def test_can_access_character_in_party(client): body = res.content.decode("utf-8") assert notes not in body + assert gm_notes not in body @pytest.mark.django_db @@ -56,7 +62,8 @@ def test_game_master_can_access_character_in_party(client): gm = User.objects.create_user("gm", password="password") notes = "Some notes" - character = baker.make(Character, player=player, notes=notes) + gm_notes = "Some GM notes" + character = baker.make(Character, player=player, notes=notes, gm_notes=gm_notes) party = baker.make(Party, game_master=gm) party.characters.add(character) client.force_login(gm) @@ -65,3 +72,4 @@ def test_game_master_can_access_character_in_party(client): body = res.content.decode("utf-8") assert notes in body + assert gm_notes in body diff --git a/src/character/urls.py b/src/character/urls.py index 1684631..2ef18a9 100644 --- a/src/character/urls.py +++ b/src/character/urls.py @@ -24,6 +24,11 @@ urlpatterns = [ name="luck_points_change", ), path("/notes_change/", views.character_notes_change, name="notes_change"), + path( + "/gm_notes_change/", + views.character_gm_notes_change, + name="gm_notes_change", + ), path("/get_defense/", views.character_get_defense, name="get_defense"), path( "/defense_misc_change/", diff --git a/src/character/views.py b/src/character/views.py index a34bb07..21d2554 100644 --- a/src/character/views.py +++ b/src/character/views.py @@ -290,6 +290,11 @@ def character_notes_change(request, pk: int): return update_text_field(request, pk, "notes") +@login_required +def character_gm_notes_change(request, pk: int): + return update_text_field(request, pk, "gm_notes") + + @login_required def character_equipment_change(request, pk: int): field = "equipment"