diff --git a/guild/forms.py b/guild/forms.py index 04eec4f..105ac29 100644 --- a/guild/forms.py +++ b/guild/forms.py @@ -1,5 +1,6 @@ +from typing import Any from django import forms -from guild.models import PlaySession, Character, Reward +from guild.models import NPC, PlaySession, Character, Reward from django.utils.translation import gettext as _ @@ -11,14 +12,22 @@ class PlaySessionCharacterForm(forms.ModelMultipleChoiceField): return character.name +class PlaySessionNPCForm(forms.ModelMultipleChoiceField): + def label_from_instance(self, npc): + return npc.name + + class PlaySessionForm(forms.ModelForm): characters = PlaySessionCharacterForm( queryset=Character.objects.all(), widget=forms.CheckboxSelectMultiple ) + npcs = PlaySessionNPCForm( + queryset=NPC.objects.all(), widget=forms.CheckboxSelectMultiple, required=False + ) class Meta: model = PlaySession - fields = ["date", "characters", "summary"] + fields = ["date", "characters", "summary", "npcs"] # set date wideget to type="date" widgets = { "date": forms.DateInput(attrs={"type": "date"}), diff --git a/guild/migrations/0013_npc_playsession_npcs.py b/guild/migrations/0013_npc_playsession_npcs.py new file mode 100644 index 0000000..cf407e4 --- /dev/null +++ b/guild/migrations/0013_npc_playsession_npcs.py @@ -0,0 +1,54 @@ +# Generated by Django 4.2.1 on 2023-08-25 18:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("guild", "0012_alter_character_options_alter_character_picture"), + ] + + operations = [ + migrations.CreateModel( + name="NPC", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=255)), + ("description", models.TextField()), + ( + "picture", + models.ImageField( + blank=True, + null=True, + upload_to="uploads/", + verbose_name="NPC Picture", + ), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="created at"), + ), + ( + "updated_at", + models.DateTimeField(auto_now=True, verbose_name="updated at"), + ), + ], + options={ + "verbose_name": "npc", + "verbose_name_plural": "npcs", + }, + ), + migrations.AddField( + model_name="playsession", + name="npcs", + field=models.ManyToManyField(to="guild.npc", verbose_name="npcs"), + ), + ] diff --git a/guild/migrations/0014_alter_playsession_npcs.py b/guild/migrations/0014_alter_playsession_npcs.py new file mode 100644 index 0000000..94bc21c --- /dev/null +++ b/guild/migrations/0014_alter_playsession_npcs.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.1 on 2023-08-25 18:52 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("guild", "0013_npc_playsession_npcs"), + ] + + operations = [ + migrations.AlterField( + model_name="playsession", + name="npcs", + field=models.ManyToManyField( + blank=True, to="guild.npc", verbose_name="npcs" + ), + ), + ] diff --git a/guild/models.py b/guild/models.py index c7248a9..10fc08a 100644 --- a/guild/models.py +++ b/guild/models.py @@ -116,6 +116,7 @@ class PlaySession(models.Model): "Adventure", verbose_name=_("adventure"), on_delete=models.CASCADE ) characters = models.ManyToManyField("Character", verbose_name=_("characters")) + npcs = models.ManyToManyField("NPC", verbose_name=_("npcs"), blank=True) summary = models.TextField() @@ -199,3 +200,25 @@ class Reward(models.Model): def get_absolute_url(self): return reverse("guild:reward_detail", kwargs={"pk": self.pk}) + + +class NPC(models.Model): + name = models.CharField(max_length=255) + description = models.TextField() + + picture = models.ImageField( + _("NPC Picture"), null=True, blank=True, upload_to="uploads/" + ) + + created_at = models.DateTimeField(_("created at"), auto_now_add=True) + updated_at = models.DateTimeField(_("updated at"), auto_now=True) + + class Meta: + verbose_name = _("npc") + verbose_name_plural = _("npcs") + + def __str__(self): + return self.name + + def get_absolute_url(self): + return reverse("guild:npc_detail", kwargs={"pk": self.pk}) diff --git a/guild/templates/guild/adventure_detail.html b/guild/templates/guild/adventure_detail.html index a336661..54d74ed 100644 --- a/guild/templates/guild/adventure_detail.html +++ b/guild/templates/guild/adventure_detail.html @@ -8,11 +8,13 @@

Adventure: {{ adventure.name }}

-

Mastered by: - - {{ adventure.master }} - -

+ {% if adventure.master %} +

Mastered by: + + {{ adventure.master }} + +

+ {% endif %}
diff --git a/guild/templates/guild/home.html b/guild/templates/guild/home.html index ba360f0..0eee71c 100644 --- a/guild/templates/guild/home.html +++ b/guild/templates/guild/home.html @@ -55,6 +55,25 @@ {% endfor %} +
+
+

NPCs

+
+ +
+ + +

Players

diff --git a/guild/templates/guild/npc_confirm_delete.html b/guild/templates/guild/npc_confirm_delete.html new file mode 100644 index 0000000..2ed26de --- /dev/null +++ b/guild/templates/guild/npc_confirm_delete.html @@ -0,0 +1,14 @@ +{% extends 'base.html' %} + +{% block content %} + +

Session Delete

+ +

Are you sure you want to delete {{ character.name}}?

+ +
+ {% csrf_token %} + {{ form.as_p }} + +
+{% endblock content %} \ No newline at end of file diff --git a/guild/templates/guild/npc_detail.html b/guild/templates/guild/npc_detail.html new file mode 100644 index 0000000..4683f06 --- /dev/null +++ b/guild/templates/guild/npc_detail.html @@ -0,0 +1,52 @@ +{% extends 'base.html' %} + +{% load guild_extras %} + +{% block content %} + +
+
+ +

NPCs: {{ npc.name }}

+

+ {{ npc.get_status_display }} +

+
+
+
+ Edit +
+
+ Delete +
+
+ +
+
+ {% if npc.picture %} + + + + {% endif %} +

{{npc.description|md|safe}}

+
+
+ +
+ +

Session History

+ +{% if not npc.playsession_set.count %} +

No appearance yet.

+{% else %} + +{% endif %} + +{% endblock content %} \ No newline at end of file diff --git a/guild/templates/guild/npc_form.html b/guild/templates/guild/npc_form.html new file mode 100644 index 0000000..d671779 --- /dev/null +++ b/guild/templates/guild/npc_form.html @@ -0,0 +1,13 @@ +{% extends 'base.html' %} + +{% block content %} +
+ {% csrf_token %} + {{ form.as_p }} + {% if object %} + + {% else %} + + {% endif %} +
+{% endblock content %} \ No newline at end of file diff --git a/guild/templates/guild/playsession_detail.html b/guild/templates/guild/playsession_detail.html index 8a386ea..358f2c0 100644 --- a/guild/templates/guild/playsession_detail.html +++ b/guild/templates/guild/playsession_detail.html @@ -74,4 +74,14 @@

{{playsession.summary|md|safe}}

+ +

NPC Appearances

+ +
    + {% for npc in playsession.npcs.all %} +
  • {{ npc.name }}
  • + {% endfor %} +
+ + {% endblock content %} \ No newline at end of file diff --git a/guild/urls.py b/guild/urls.py index 7a05c54..72f4466 100644 --- a/guild/urls.py +++ b/guild/urls.py @@ -2,6 +2,7 @@ from django.urls import path import guild.views as views import guild.views.player as player_views import guild.views.character as character_views +import guild.views.npc as npc_views import guild.views.adventure as adventure_views import guild.views.playsession as playsession_views import guild.views.settings as settings_views @@ -137,4 +138,24 @@ urlpatterns = [ resource_views.ResourceUpdateView.as_view(), name="resource_update", ), + path( + "npcs/create/", + npc_views.CreateNPCView.as_view(), + name="create_npc", + ), + path( + "npcs//", + npc_views.NPCDetailView.as_view(), + name="npc_detail", + ), + path( + "npcs//update/", + npc_views.NPCUpdateView.as_view(), + name="npc_update", + ), + path( + "npcs//delete/", + npc_views.NPCDeleteView.as_view(), + name="npc_delete", + ), ] diff --git a/guild/views/__init__.py b/guild/views/__init__.py index bffea01..97b5784 100644 --- a/guild/views/__init__.py +++ b/guild/views/__init__.py @@ -5,7 +5,7 @@ from django.views.generic.edit import CreateView from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.models import User from django.contrib.auth.forms import UserCreationForm -from guild.models import Adventure, Character, Player, Reward +from guild.models import NPC, Adventure, Character, Player, Reward # Create your views here. @@ -23,6 +23,7 @@ class HomeView(LoginRequiredMixin, TemplateView): context["adventures"] = advs context["players"] = Player.objects.all() context["characters"] = Character.objects.all() + context["npcs"] = NPC.objects.all() context["rewards"] = Reward.objects.filter(character=None).order_by( "-playsession__date" diff --git a/guild/views/npc.py b/guild/views/npc.py new file mode 100644 index 0000000..9a43ce8 --- /dev/null +++ b/guild/views/npc.py @@ -0,0 +1,28 @@ +from django.views.generic import TemplateView, ListView, DetailView +from django.views.generic.edit import CreateView, UpdateView, DeleteView +from django.contrib.auth.mixins import LoginRequiredMixin +from guild.models import NPC + + +class NPCListView(LoginRequiredMixin, ListView): + model = NPC + context_object_name = "NPCs" + + +class CreateNPCView(LoginRequiredMixin, CreateView): + model = NPC + fields = ["name", "picture", "description"] + + +class NPCDetailView(LoginRequiredMixin, DetailView): + model = NPC + + +class NPCUpdateView(LoginRequiredMixin, UpdateView): + model = NPC + fields = ["name", "picture", "description"] + + +class NPCDeleteView(LoginRequiredMixin, DeleteView): + model = NPC + success_url = "/"