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 %}
+
+
+
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 @@
+
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"