mirror of
https://github.com/Crocmagnon/charasheet.git
synced 2024-11-22 14:38:03 +01:00
parent
fb0b97e8bb
commit
f3f3b33c12
12 changed files with 115 additions and 4 deletions
18
src/character/migrations/0040_character_gm_notes.py
Normal file
18
src/character/migrations/0040_character_gm_notes.py
Normal file
|
@ -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"),
|
||||||
|
),
|
||||||
|
]
|
|
@ -1 +1 @@
|
||||||
0038_character_profile_picture
|
0040_character_gm_notes
|
||||||
|
|
|
@ -79,16 +79,35 @@ class CharacterManager(models.Manager):
|
||||||
|
|
||||||
class CharacterQuerySet(models.QuerySet):
|
class CharacterQuerySet(models.QuerySet):
|
||||||
def managed_by(self, user):
|
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
|
from party.models import Party
|
||||||
|
|
||||||
return self.filter(
|
return self.filter(
|
||||||
Q(player=user) | Q(parties__in=Party.objects.managed_by(user))
|
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):
|
def owned_by(self, user):
|
||||||
|
"""Return characters either owned by the given user."""
|
||||||
return self.filter(player=user)
|
return self.filter(player=user)
|
||||||
|
|
||||||
def friendly_to(self, 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
|
from party.models import Party
|
||||||
|
|
||||||
return self.filter(
|
return self.filter(
|
||||||
|
@ -229,6 +248,7 @@ 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)
|
||||||
|
gm_notes = models.TextField(blank=True, verbose_name="notes MJ")
|
||||||
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")
|
states = models.ManyToManyField(HarmfulState, blank=True, related_name="characters")
|
||||||
|
@ -402,6 +422,10 @@ class Character(models.Model):
|
||||||
md = markdown.Markdown(extensions=["extra", "nl2br"])
|
md = markdown.Markdown(extensions=["extra", "nl2br"])
|
||||||
return md.convert(self.notes)
|
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]:
|
def get_missing_states(self) -> Iterable[HarmfulState]:
|
||||||
return HarmfulState.objects.exclude(
|
return HarmfulState.objects.exclude(
|
||||||
pk__in=self.states.all().values_list("pk", flat=True)
|
pk__in=self.states.all().values_list("pk", flat=True)
|
||||||
|
@ -410,6 +434,9 @@ class Character(models.Model):
|
||||||
def managed_by(self, user):
|
def managed_by(self, user):
|
||||||
return self in Character.objects.managed_by(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):
|
def reset_stats(self):
|
||||||
self.health_remaining = self.health_max
|
self.health_remaining = self.health_max
|
||||||
self.mana_remaining = self.mana_max
|
self.mana_remaining = self.mana_max
|
||||||
|
|
|
@ -510,6 +510,9 @@
|
||||||
</div>
|
</div>
|
||||||
{% include "character/snippets/character_details/paths_and_capabilities.html" %}
|
{% 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 %}
|
{% if character|managed_by:user %}
|
||||||
{% include "character/snippets/character_details/notes_display.html" %}
|
{% include "character/snippets/character_details/notes_display.html" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
{% load character_extras %}
|
||||||
|
<div class="mt-3" id="gm-notes">
|
||||||
|
<h2>
|
||||||
|
Notes MJ
|
||||||
|
<a hx-get="{% url "character:gm_notes_change" pk=character.pk %}"
|
||||||
|
hx-target="#gm-notes"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
class="btn btn-primary btn-sm"
|
||||||
|
>
|
||||||
|
<i class="fa-solid fa-pen-to-square"></i> Edit
|
||||||
|
</a>
|
||||||
|
</h2>
|
||||||
|
<div class="alert alert-info">Le joueur ne peut pas voir ces notes.</div>
|
||||||
|
{{ character.get_formatted_gm_notes|safe }}
|
||||||
|
</div>
|
|
@ -0,0 +1,17 @@
|
||||||
|
<div class="mt-3" id="gm-notes">
|
||||||
|
<form>
|
||||||
|
<h2>
|
||||||
|
Notes MJ
|
||||||
|
<a hx-post="{% url "character:gm_notes_change" pk=character.pk %}"
|
||||||
|
hx-target="#gm-notes"
|
||||||
|
hx-swap="innerHTML"
|
||||||
|
class="btn btn-primary btn-sm"
|
||||||
|
>
|
||||||
|
<i class="fa-solid fa-save"></i> Save
|
||||||
|
</a>
|
||||||
|
</h2>
|
||||||
|
<div class="alert alert-info">Ces notes ne sont pas visibles par le joueur.</div>
|
||||||
|
{% csrf_token %}
|
||||||
|
<textarea class="form-control" name="gm_notes" rows="10">{{ character.gm_notes }}</textarea>
|
||||||
|
</form>
|
||||||
|
</div>
|
|
@ -10,5 +10,9 @@
|
||||||
<i class="fa-solid fa-pen-to-square"></i> Edit
|
<i class="fa-solid fa-pen-to-square"></i> Edit
|
||||||
</a>
|
</a>
|
||||||
</h2>
|
</h2>
|
||||||
|
<div class="alert alert-info">
|
||||||
|
Le {% if character|mastered_by:user %}joueur{% else %}MJ{% endif %}
|
||||||
|
peut également voir et modifier ces notes.
|
||||||
|
</div>
|
||||||
{{ character.get_formatted_notes|safe }}
|
{{ character.get_formatted_notes|safe }}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -10,6 +10,10 @@
|
||||||
<i class="fa-solid fa-save"></i> Save
|
<i class="fa-solid fa-save"></i> Save
|
||||||
</a>
|
</a>
|
||||||
</h2>
|
</h2>
|
||||||
|
<div class="alert alert-info">
|
||||||
|
Ces notes ne sont visibles que par le joueur et les MJ
|
||||||
|
des groupes auquel ce personnage appartient.
|
||||||
|
</div>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<textarea class="form-control" name="notes" rows="25">{{ character.notes }}</textarea>
|
<textarea class="form-control" name="notes" rows="25">{{ character.notes }}</textarea>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -43,3 +43,8 @@ def max_rank(path: Path, character: Character) -> int:
|
||||||
@register.filter
|
@register.filter
|
||||||
def managed_by(character: Character, user: User) -> bool:
|
def managed_by(character: Character, user: User) -> bool:
|
||||||
return character.managed_by(user)
|
return character.managed_by(user)
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def mastered_by(character: Character, user: User) -> bool:
|
||||||
|
return character.mastered_by(user)
|
||||||
|
|
|
@ -11,13 +11,15 @@ def test_can_access_own_character(client):
|
||||||
player = User.objects.create_user("username", password="password")
|
player = User.objects.create_user("username", password="password")
|
||||||
|
|
||||||
notes = "Some notes"
|
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)
|
client.force_login(player)
|
||||||
res = client.get(character.get_absolute_url())
|
res = client.get(character.get_absolute_url())
|
||||||
assert res.status_code == 200
|
assert res.status_code == 200
|
||||||
|
|
||||||
body = res.content.decode("utf-8")
|
body = res.content.decode("utf-8")
|
||||||
assert notes in body
|
assert notes in body
|
||||||
|
assert gm_notes not in body
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
|
@ -38,7 +40,10 @@ def test_can_access_character_in_party(client):
|
||||||
|
|
||||||
character = baker.make(Character, player=player)
|
character = baker.make(Character, player=player)
|
||||||
notes = "Some notes"
|
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 = baker.make(Party)
|
||||||
party.characters.add(character)
|
party.characters.add(character)
|
||||||
party.characters.add(friend_character)
|
party.characters.add(friend_character)
|
||||||
|
@ -48,6 +53,7 @@ def test_can_access_character_in_party(client):
|
||||||
|
|
||||||
body = res.content.decode("utf-8")
|
body = res.content.decode("utf-8")
|
||||||
assert notes not in body
|
assert notes not in body
|
||||||
|
assert gm_notes not in body
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@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")
|
gm = User.objects.create_user("gm", password="password")
|
||||||
|
|
||||||
notes = "Some notes"
|
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 = baker.make(Party, game_master=gm)
|
||||||
party.characters.add(character)
|
party.characters.add(character)
|
||||||
client.force_login(gm)
|
client.force_login(gm)
|
||||||
|
@ -65,3 +72,4 @@ def test_game_master_can_access_character_in_party(client):
|
||||||
|
|
||||||
body = res.content.decode("utf-8")
|
body = res.content.decode("utf-8")
|
||||||
assert notes in body
|
assert notes in body
|
||||||
|
assert gm_notes in body
|
||||||
|
|
|
@ -24,6 +24,11 @@ urlpatterns = [
|
||||||
name="luck_points_change",
|
name="luck_points_change",
|
||||||
),
|
),
|
||||||
path("<int:pk>/notes_change/", views.character_notes_change, name="notes_change"),
|
path("<int:pk>/notes_change/", views.character_notes_change, name="notes_change"),
|
||||||
|
path(
|
||||||
|
"<int:pk>/gm_notes_change/",
|
||||||
|
views.character_gm_notes_change,
|
||||||
|
name="gm_notes_change",
|
||||||
|
),
|
||||||
path("<int:pk>/get_defense/", views.character_get_defense, name="get_defense"),
|
path("<int:pk>/get_defense/", views.character_get_defense, name="get_defense"),
|
||||||
path(
|
path(
|
||||||
"<int:pk>/defense_misc_change/",
|
"<int:pk>/defense_misc_change/",
|
||||||
|
|
|
@ -290,6 +290,11 @@ def character_notes_change(request, pk: int):
|
||||||
return update_text_field(request, pk, "notes")
|
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
|
@login_required
|
||||||
def character_equipment_change(request, pk: int):
|
def character_equipment_change(request, pk: int):
|
||||||
field = "equipment"
|
field = "equipment"
|
||||||
|
|
Loading…
Reference in a new issue