mirror of
https://github.com/Crocmagnon/charasheet.git
synced 2024-12-22 22:01:48 +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):
|
||||
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
|
||||
|
|
|
@ -510,6 +510,9 @@
|
|||
</div>
|
||||
{% 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 %}
|
||||
|
|
|
@ -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
|
||||
</a>
|
||||
</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 }}
|
||||
</div>
|
||||
|
|
|
@ -10,6 +10,10 @@
|
|||
<i class="fa-solid fa-save"></i> Save
|
||||
</a>
|
||||
</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 %}
|
||||
<textarea class="form-control" name="notes" rows="25">{{ character.notes }}</textarea>
|
||||
</form>
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -24,6 +24,11 @@ urlpatterns = [
|
|||
name="luck_points_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>/defense_misc_change/",
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue