resources

This commit is contained in:
Niko Abeler 2023-06-20 21:22:56 +02:00
parent 451bd333aa
commit d4415445f3
24 changed files with 648 additions and 56 deletions

View File

@ -1,5 +1,6 @@
from django import forms
from guild.models import PlaySession, Character
from guild.models import PlaySession, Character, Reward
from django.utils.translation import gettext as _
class PlaySessionCharacterForm(forms.ModelMultipleChoiceField):
@ -23,3 +24,15 @@ class PlaySessionForm(forms.ModelForm):
"date": forms.DateInput(attrs={"type": "date"}),
"summary": forms.Textarea(attrs={"rows": 32}),
}
class RewardForm(forms.ModelForm):
character = forms.ModelChoiceField(
queryset=Character.objects.all(),
empty_label="All Guild Members",
required=False,
)
class Meta:
model = Reward
fields = ["amount", "resource", "character"]

View File

@ -0,0 +1,115 @@
# Generated by Django 4.2.1 on 2023-06-20 18:10
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
("guild", "0004_alter_playsession_options_player_address"),
]
operations = [
migrations.AddField(
model_name="adventure",
name="created_at",
field=models.DateTimeField(
auto_now_add=True,
default=django.utils.timezone.now,
verbose_name="created at",
),
preserve_default=False,
),
migrations.AddField(
model_name="adventure",
name="updated_at",
field=models.DateTimeField(auto_now=True, verbose_name="updated at"),
),
migrations.AddField(
model_name="character",
name="created_at",
field=models.DateTimeField(
auto_now_add=True,
default=django.utils.timezone.now,
verbose_name="created at",
),
preserve_default=False,
),
migrations.AddField(
model_name="character",
name="updated_at",
field=models.DateTimeField(auto_now=True, verbose_name="updated at"),
),
migrations.AddField(
model_name="player",
name="created_at",
field=models.DateTimeField(
auto_now_add=True,
default=django.utils.timezone.now,
verbose_name="created at",
),
preserve_default=False,
),
migrations.AddField(
model_name="player",
name="updated_at",
field=models.DateTimeField(auto_now=True, verbose_name="updated at"),
),
migrations.AddField(
model_name="playsession",
name="created_at",
field=models.DateTimeField(
auto_now_add=True,
default=django.utils.timezone.now,
verbose_name="created at",
),
preserve_default=False,
),
migrations.AddField(
model_name="playsession",
name="updated_at",
field=models.DateTimeField(auto_now=True, verbose_name="updated at"),
),
migrations.AddField(
model_name="resource",
name="created_at",
field=models.DateTimeField(
auto_now_add=True,
default=django.utils.timezone.now,
verbose_name="created at",
),
preserve_default=False,
),
migrations.AddField(
model_name="resource",
name="updated_at",
field=models.DateTimeField(auto_now=True, verbose_name="updated at"),
),
migrations.AddField(
model_name="resourceearned",
name="created_at",
field=models.DateTimeField(
auto_now_add=True,
default=django.utils.timezone.now,
verbose_name="created at",
),
preserve_default=False,
),
migrations.AddField(
model_name="resourceearned",
name="updated_at",
field=models.DateTimeField(auto_now=True, verbose_name="updated at"),
),
migrations.AlterField(
model_name="resourceearned",
name="character",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="guild.character",
verbose_name="character",
),
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 4.2.1 on 2023-06-20 18:23
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("guild", "0005_adventure_created_at_adventure_updated_at_and_more"),
]
operations = [
migrations.AlterField(
model_name="resource",
name="name",
field=models.CharField(max_length=255, unique=True),
),
]

View File

@ -0,0 +1,24 @@
# Generated by Django 4.2.1 on 2023-06-20 18:43
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("guild", "0006_alter_resource_name"),
]
operations = [
migrations.AddField(
model_name="resourceearned",
name="playsession",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="guild.playsession",
verbose_name="playsession",
),
),
]

View File

@ -0,0 +1,22 @@
# Generated by Django 4.2.1 on 2023-06-20 18:43
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("guild", "0007_resourceearned_playsession"),
]
operations = [
migrations.AlterField(
model_name="resourceearned",
name="playsession",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="guild.playsession",
verbose_name="playsession",
),
),
]

View File

@ -0,0 +1,20 @@
# Generated by Django 4.2.1 on 2023-06-20 18:48
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("guild", "0008_alter_resourceearned_playsession"),
]
operations = [
migrations.RenameModel(
old_name="ResourceEarned",
new_name="Reward",
),
migrations.AlterModelOptions(
name="reward",
options={"verbose_name": "reward", "verbose_name_plural": "rewards"},
),
]

View File

@ -8,6 +8,9 @@ class Player(models.Model):
address = models.TextField(_("address"), blank=True)
created_at = models.DateTimeField(_("created at"), auto_now_add=True)
updated_at = models.DateTimeField(_("updated at"), auto_now=True)
class Meta:
verbose_name = _("player")
verbose_name_plural = _("players")
@ -20,9 +23,12 @@ class Player(models.Model):
class Resource(models.Model):
name = models.CharField(max_length=255)
name = models.CharField(max_length=255, unique=True)
description = models.TextField()
created_at = models.DateTimeField(_("created at"), auto_now_add=True)
updated_at = models.DateTimeField(_("updated at"), auto_now=True)
class Meta:
verbose_name = _("resource")
verbose_name_plural = _("resources")
@ -46,6 +52,9 @@ class Adventure(models.Model):
blank=True,
)
created_at = models.DateTimeField(_("created at"), auto_now_add=True)
updated_at = models.DateTimeField(_("updated at"), auto_now=True)
@property
def last_session(self):
return self.playsession_set.order_by("-date").first()
@ -82,6 +91,9 @@ class Character(models.Model):
blank=True,
)
created_at = models.DateTimeField(_("created at"), auto_now_add=True)
updated_at = models.DateTimeField(_("updated at"), auto_now=True)
class Meta:
verbose_name = _("character")
verbose_name_plural = _("characters")
@ -102,6 +114,9 @@ class PlaySession(models.Model):
summary = models.TextField()
created_at = models.DateTimeField(_("created at"), auto_now_add=True)
updated_at = models.DateTimeField(_("updated at"), auto_now=True)
class Meta:
verbose_name = _("playsession")
verbose_name_plural = _("playsessions")
@ -118,25 +133,34 @@ class PlaySession(models.Model):
)
class ResourceEarned(models.Model):
class Reward(models.Model):
resource = models.ForeignKey(
"Resource", verbose_name=_("resource"), on_delete=models.CASCADE
)
character = models.ForeignKey(
"Character",
verbose_name=_("character"),
on_delete=models.CASCADE,
on_delete=models.SET_NULL,
null=True,
blank=True,
)
playsession = models.ForeignKey(
"PlaySession",
verbose_name=_("playsession"),
on_delete=models.CASCADE,
null=False,
)
amount = models.IntegerField()
created_at = models.DateTimeField(_("created at"), auto_now_add=True)
updated_at = models.DateTimeField(_("updated at"), auto_now=True)
class Meta:
verbose_name = _("resourceearned")
verbose_name_plural = _("resourceearneds")
verbose_name = _("reward")
verbose_name_plural = _("rewards")
def __str__(self):
return self.name
return f"{self.resource.name} ({self.amount}) - {self.character}"
def get_absolute_url(self):
return reverse("guild:resourceearned_detail", kwargs={"pk": self.pk})
return reverse("guild:reward_detail", kwargs={"pk": self.pk})

View File

@ -42,7 +42,9 @@
{% else %}
{% for playsession in adventure.playsession_set.all %}
<div class="row section-line">
<h3 class="column column-75 section-item">{{ playsession.date }}</h3>
<a class="column column-75 section-item" href="{% url 'guild:playsession_detail' playsession.id %}">
<h3 class="section-item">{{ playsession.date }}</h3>
</a>
<a class="column button button-outline section-item" href="{% url 'guild:playsession_update' adventure.id playsession.id %}">Edit</a>
<a class="column button button-clear section-item" href="{% url 'guild:playsession_delete' adventure.id playsession.id %}">Delete</a>
</div>
@ -51,7 +53,7 @@
<ul>
{% for character in playsession.characters.all %}
<li>{{character.name}}</li>
<li><a href="{% url 'guild:character_detail' character.id %}">{{ character.name }} ({{character.player.name}})</a></li>
{% endfor %}
</ul>

View File

@ -10,10 +10,12 @@
<h1>Character: {{ character.name }}</h1>
<h4>
{{ character.get_status_display }}
{% if character.player %}
, played by:
<a href="{% url 'guild:player_detail' character.player.id %}">
{{ character.player.name }}
</a>
{% endif %}
</h4>
</hrgroup>
</div>
@ -42,4 +44,35 @@
</ul>
{% endif %}
<h2>Rewards</h2>
<ul>
{% for resource, amount in resources.items %}
<li> {{ amount }} {{ resource }} </li>
{% endfor %}
</ul>
<h3>Reward History</h3>
{% if not character.reward_set.count %}
<p>No rewards.</p>
{% else %}
<ul>
{% for reward in rewards %}
<li>
{% if reward.character %}
<strong>
{% endif %}
{{reward.amount}} {{ reward.resource.name }} -
<a href="{% url 'guild:playsession_detail' reward.playsession.id %}">
{{ reward.playsession.date }} - {{ reward.playsession.adventure.name }}
</a>
{% if reward.character %}
</strong>
{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
{% endblock content %}

View File

@ -12,7 +12,6 @@
</div>
</div>
<ul>
{% for adventure in adventures %}
<li>
@ -28,6 +27,14 @@
{% endfor %}
</ul>
<h2>Total Guild Rewards</h2>
<ul>
{% for resource, amount in resources.items %}
<li> {{ amount }} {{ resource }} </li>
{% endfor %}
</ul>
<div class="row">
<div class="column column-75">
<h2>Characters</h2>

View File

@ -0,0 +1,77 @@
{% extends 'base.html' %}
{% load guild_extras %}
{% block content %}
<div class="row">
<div class="column column-75">
<hrgroup>
<h1>Session: {{ playsession.date }}</h1>
<h4>Adventure:
<a href="{% url 'guild:adventure_detail' playsession.adventure.id %}">
{{ playsession.adventure.name }}
</a>
</h4>
</hrgroup>
</div>
<div class="column">
<a class="button button-outline" href="{% url 'guild:playsession_update' playsession.adventure.id playsession.id %}">Edit</a>
</div>
<div class="column">
<a class="button button-clear" href="{% url 'guild:playsession_delete' playsession.adventure.id playsession.id %}">Delete</a>
</div>
</div>
<hr>
<h3>Characters</h3>
<ul>
{% for character in playsession.characters.all %}
<li><a href="{% url 'guild:character_detail' character.id %}">{{ character.name }} ({{character.player.name}})</a></li>
{% endfor %}
</ul>
<h3>Rewards</h3>
<a href="{% url 'guild:create_reward' playsession.id %}">Add Reward</a>
<table>
<thead>
<tr>
<th>Amount</th>
<th>Resource</th>
<th>Character</th>
<th></th>
</tr>
</thead>
<tbody>
{% for reward in playsession.reward_set.all %}
<tr>
<td>{{ reward.amount }}</td>
<td>{{ reward.resource.name }}</td>
<td>
{% if reward.character %}
<a href="{% url 'guild:character_detail' reward.character.id %}">{{ reward.character.name }}</a>
{% else %}
<em>Every Guild Member</em>
{% endif %}
</td>
<td>
<a href="{% url 'guild:reward_delete' playsession.id reward.id %}">Remove</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if playsession.reward_set.count == 0 %}
<p>No rewards earned.</p>
{% endif %}
<h3>Summary</h3>
<p>{{playsession.summary|md|safe}}</p>
{% endblock content %}

View File

@ -0,0 +1,16 @@
{% extends 'base.html' %}
{% block content %}
<div class="row">
<div class="column column-75">
<h1>Resource: {{ resource.name }}</h1>
</div>
<div class="column">
<a class="button button-outline" href="{% url 'guild:resource_update' resource.id %}">Edit</a>
</div>
</div>
<p>
{{ resource.description|linebreaks }}
</p>
{% endblock content %}

View File

@ -0,0 +1,13 @@
{% extends 'base.html' %}
{% block content %}
<form action="" method="post">
{% csrf_token %}
{{ form.as_p }}
{% if object %}
<input type="submit" value="Update Resource">
{% else %}
<input type="submit" value="Create Resource">
{% endif %}
</form>
{% endblock content %}

View File

@ -0,0 +1,23 @@
{% extends 'base.html' %}
{% block content %}
<h1>Reward Delete</h1>
<p>Are you sure you want to delete
<strong>
{{ reward.amount }} {{ reward.resource.name }} -
{% if reward.character %}
{{ reward.character.name }}
{% else %}
<em>Every Guild Member</em>
{% endif %}
</strong>?
</p>
<form action="" method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Delete Player">
</form>
{% endblock content %}

View File

@ -0,0 +1,13 @@
{% extends 'base.html' %}
{% block content %}
<form action="" method="post">
{% csrf_token %}
{{ form.as_p }}
{% if object %}
<input type="submit" value="Update Reward">
{% else %}
<input type="submit" value="Create Reward">
{% endif %}
</form>
{% endblock content %}

View File

@ -0,0 +1,26 @@
{% extends 'base.html' %}
{% block content %}
<h1>Settings</h1>
<div class="row">
<div class="column column-75">
<h2>Resources</h2>
</div>
<div class="column column-25">
<a class="button button-outline" href="{% url 'guild:create_resource' %}">Create New Resource</a>
</div>
</div>
<ul>
{% for resource in resources %}
<li>
<a href="{% url 'guild:resource_detail' resource.id %}">
{{ resource.name }}
</a>
</li>
{% endfor %}
</ul>
{% endblock content %}

View File

@ -3,6 +3,9 @@ import guild.views as views
import guild.views.player as player_views
import guild.views.character as character_views
import guild.views.adventure as adventure_views
import guild.views.playsession as playsession_views
import guild.views.settings as settings_views
import guild.views.resource as resource_views
from django.contrib.auth import views as auth_views
urlpatterns = [
@ -86,17 +89,52 @@ urlpatterns = [
),
path(
"adventures/<int:pk>/playsession/create/",
adventure_views.PlaySessionCreateView.as_view(),
playsession_views.PlaySessionCreateView.as_view(),
name="create_playsession",
),
path(
"adventures/<int:pk>/playsession/<int:playsession_pk>/update/",
adventure_views.PlaySessionUpdateView.as_view(),
playsession_views.PlaySessionUpdateView.as_view(),
name="playsession_update",
),
path(
"adventures/<int:pk>/playsession/<int:playsession_pk>/delete/",
adventure_views.PlaySessionDeleteView.as_view(),
playsession_views.PlaySessionDeleteView.as_view(),
name="playsession_delete",
),
path(
"playsessions/<int:pk>/",
playsession_views.PlaySessionDetailView.as_view(),
name="playsession_detail",
),
path(
"playsessions/<int:pk>/rewards/create/",
playsession_views.CreateRewardView.as_view(),
name="create_reward",
),
path(
"playsessions/<int:pk>/rewards/<int:reward_pk>/delete/",
playsession_views.RewardDeleteView.as_view(),
name="reward_delete",
),
path(
"settings/",
settings_views.SettingsView.as_view(),
name="settings",
),
path(
"resources/create/",
resource_views.CreateResourceView.as_view(),
name="create_resource",
),
path(
"resources/<int:pk>/",
resource_views.ResourceDetailView.as_view(),
name="resource_detail",
),
path(
"resources/<int:pk>/update/",
resource_views.ResourceUpdateView.as_view(),
name="resource_update",
),
]

View File

@ -3,7 +3,7 @@ from django.views.generic import TemplateView, ListView, DetailView
from django.views.generic.edit import CreateView
from django.contrib.auth.mixins import LoginRequiredMixin
from guild.models import Adventure, Character, Player
from guild.models import Adventure, Character, Player, Reward
# Create your views here.
@ -21,4 +21,13 @@ class HomeView(LoginRequiredMixin, TemplateView):
context["adventures"] = advs
context["players"] = Player.objects.all()
context["characters"] = Character.objects.all()
context["rewards"] = Reward.objects.filter(character=None).order_by(
"-playsession__date"
)
context["resources"] = {}
for reward in context["rewards"]:
if reward.resource.name not in context["resources"]:
context["resources"][reward.resource.name] = 0
context["resources"][reward.resource.name] += reward.amount
return context

View File

@ -39,38 +39,3 @@ class AdventureUpdateView(LoginRequiredMixin, UpdateView):
class AdventureDeleteView(LoginRequiredMixin, DeleteView):
model = Adventure
success_url = "/"
class PlaySessionCreateView(LoginRequiredMixin, CreateView):
model = PlaySession
form_class = PlaySessionForm
def dispatch(self, request, *args, **kwargs):
self.adventure = get_object_or_404(Adventure, pk=kwargs["pk"])
return super().dispatch(request, *args, **kwargs)
def form_valid(self, form: BaseModelForm) -> HttpResponse:
form.instance.adventure = self.adventure
return super().form_valid(form)
class PlaySessionUpdateView(LoginRequiredMixin, UpdateView):
model = PlaySession
form_class = PlaySessionForm
pk_url_kwarg = "playsession_pk"
def dispatch(self, request, *args, **kwargs):
self.adventure = get_object_or_404(Adventure, pk=kwargs["pk"])
return super().dispatch(request, *args, **kwargs)
class PlaySessionDeleteView(LoginRequiredMixin, DeleteView):
model = PlaySession
pk_url_kwarg = "playsession_pk"
def dispatch(self, request, *args, **kwargs):
self.adventure = get_object_or_404(Adventure, pk=kwargs["pk"])
return super().dispatch(request, *args, **kwargs)
def get_success_url(self) -> str:
return self.adventure.get_absolute_url()

View File

@ -1,8 +1,8 @@
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 Character
from django.db.models import Q
from guild.models import Character, Reward
class CharacterListView(LoginRequiredMixin, ListView):
@ -18,6 +18,18 @@ class CreateCharacterView(LoginRequiredMixin, CreateView):
class CharacterDetailView(LoginRequiredMixin, DetailView):
model = Character
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["rewards"] = Reward.objects.filter(
Q(character=self.object) | Q(character=None)
).order_by("-playsession__date")
context["resources"] = {}
for reward in context["rewards"]:
if reward.resource.name not in context["resources"]:
context["resources"][reward.resource.name] = 0
context["resources"][reward.resource.name] += reward.amount
return context
class CharacterUpdateView(LoginRequiredMixin, UpdateView):
model = Character

View File

@ -0,0 +1,78 @@
from typing import Any, Dict, Optional, Type
from django.forms.models import BaseModelForm
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from django.urls import reverse
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.forms import PlaySessionForm, RewardForm
from guild.models import Adventure, PlaySession, Reward
class PlaySessionCreateView(LoginRequiredMixin, CreateView):
model = PlaySession
form_class = PlaySessionForm
def dispatch(self, request, *args, **kwargs):
self.adventure = get_object_or_404(Adventure, pk=kwargs["pk"])
return super().dispatch(request, *args, **kwargs)
def form_valid(self, form: BaseModelForm) -> HttpResponse:
form.instance.adventure = self.adventure
return super().form_valid(form)
class PlaySessionDetailView(LoginRequiredMixin, DetailView):
model = PlaySession
class PlaySessionUpdateView(LoginRequiredMixin, UpdateView):
model = PlaySession
form_class = PlaySessionForm
pk_url_kwarg = "playsession_pk"
def dispatch(self, request, *args, **kwargs):
self.adventure = get_object_or_404(Adventure, pk=kwargs["pk"])
return super().dispatch(request, *args, **kwargs)
class PlaySessionDeleteView(LoginRequiredMixin, DeleteView):
model = PlaySession
pk_url_kwarg = "playsession_pk"
def dispatch(self, request, *args, **kwargs):
self.adventure = get_object_or_404(Adventure, pk=kwargs["pk"])
return super().dispatch(request, *args, **kwargs)
def get_success_url(self) -> str:
return self.adventure.get_absolute_url()
class CreateRewardView(LoginRequiredMixin, CreateView):
model = Reward
form_class = RewardForm
def dispatch(self, request, *args, **kwargs):
self.playsession = get_object_or_404(PlaySession, pk=kwargs["pk"])
return super().dispatch(request, *args, **kwargs)
def form_valid(self, form: BaseModelForm) -> HttpResponse:
form.instance.playsession = self.playsession
return super().form_valid(form)
def get_success_url(self) -> str:
return reverse("guild:playsession_detail", kwargs={"pk": self.playsession.pk})
class RewardDeleteView(LoginRequiredMixin, DeleteView):
model = Reward
pk_url_kwarg = "reward_pk"
def dispatch(self, request, *args, **kwargs):
self.playsession = get_object_or_404(PlaySession, pk=kwargs["pk"])
return super().dispatch(request, *args, **kwargs)
def get_success_url(self) -> str:
return reverse("guild:playsession_detail", kwargs={"pk": self.playsession.pk})

28
guild/views/resource.py Normal file
View File

@ -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 django.urls import reverse
from guild.models import Resource
class CreateResourceView(LoginRequiredMixin, CreateView):
model = Resource
fields = ["name", "description"]
def get_success_url(self):
return reverse("guild:settings")
class ResourceDetailView(LoginRequiredMixin, DetailView):
model = Resource
class ResourceUpdateView(LoginRequiredMixin, UpdateView):
model = Resource
fields = ["name", "description"]
class ResourceDeleteView(LoginRequiredMixin, DeleteView):
model = Resource
success_url = "/"

12
guild/views/settings.py Normal file
View File

@ -0,0 +1,12 @@
from django.views.generic import TemplateView
from guild.models import Resource
class SettingsView(TemplateView):
template_name = "guild/settings.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["resources"] = Resource.objects.all()
return context

View File

@ -87,5 +87,10 @@
{% endblock %}
</main>
<footer class="container">
<hr>
<a href="{% url 'guild:settings' %}">Settings</a>
</footer>
</body>
</html>