1
0
Fork 0
mirror of https://github.com/Findus23/RPGnotes.git synced 2024-09-19 15:43:45 +02:00

lots of changes

This commit is contained in:
Lukas Winkler 2021-08-28 19:52:07 +02:00
parent 71b0d1cac8
commit 3c884d4e1d
Signed by: lukas
GPG key ID: 54DE4D798D244853
37 changed files with 777 additions and 58 deletions

View file

@ -1,10 +1,13 @@
from django.contrib import admin
# Register your models here.
from notes.models import Character, Campaign, Faction, Location, Loot
from simple_history.admin import SimpleHistoryAdmin
admin.site.register(Campaign)
admin.site.register(Character)
admin.site.register(Faction)
admin.site.register(Location)
admin.site.register(Loot)
from notes.models import Character, Campaign, Faction, Location, Loot, IngameDay, Session
admin.site.register(Campaign, SimpleHistoryAdmin)
admin.site.register(Character, SimpleHistoryAdmin)
admin.site.register(Faction, SimpleHistoryAdmin)
admin.site.register(Location, SimpleHistoryAdmin)
admin.site.register(Loot, SimpleHistoryAdmin)
admin.site.register(IngameDay, SimpleHistoryAdmin)
admin.site.register(Session, SimpleHistoryAdmin)

View file

@ -1,6 +1,6 @@
from django.forms import ModelForm
from django.forms import ModelForm, ModelMultipleChoiceField, CheckboxSelectMultiple
from notes.models import Campaign, Loot, Character
from notes.models import Campaign, Loot, Character, IngameDay, Session
class CampaignForm(ModelForm):
@ -19,3 +19,14 @@ class CharacterForm(ModelForm):
class Meta:
model = Character
fields = ["name", "description_md", "subtitle", "player", "faction", "location", "color", "image"]
class DayForm(ModelForm):
sessions = ModelMultipleChoiceField(
queryset=Session.objects.all(),
widget=CheckboxSelectMultiple()
)
class Meta:
model = IngameDay
fields = ["day", "description_md", "sessions"]

View file

@ -0,0 +1,95 @@
# Generated by Django 3.2.6 on 2021-08-22 18:28
import datetime
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import simple_history.models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('notes', '0010_alter_character_image'),
]
operations = [
migrations.CreateModel(
name='IngameDay',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('day', models.PositiveIntegerField()),
('created', models.DateTimeField(auto_now_add=True)),
('last_modified', models.DateTimeField(auto_now=True)),
('campaign', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='notes.campaign')),
],
options={
'ordering': ['-day'],
},
),
migrations.AlterModelOptions(
name='loot',
options={'ordering': ['name']},
),
migrations.CreateModel(
name='Session',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('description_md', models.TextField(blank=True)),
('description_html', models.TextField(blank=True, editable=False)),
('day', models.DateField(default=datetime.date.today)),
('created', models.DateTimeField(auto_now_add=True)),
('last_modified', models.DateTimeField(auto_now=True)),
('campaign', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='notes.campaign')),
('ingame_days', models.ManyToManyField(related_name='sessions', to='notes.IngameDay')),
],
options={
'ordering': ['-day'],
},
),
migrations.CreateModel(
name='HistoricalSession',
fields=[
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('description_md', models.TextField(blank=True)),
('description_html', models.TextField(blank=True, editable=False)),
('day', models.DateField(default=datetime.date.today)),
('created', models.DateTimeField(blank=True, editable=False)),
('last_modified', models.DateTimeField(blank=True, editable=False)),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField()),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('campaign', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='notes.campaign')),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'historical session',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': 'history_date',
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name='HistoricalIngameDay',
fields=[
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('day', models.PositiveIntegerField()),
('created', models.DateTimeField(blank=True, editable=False)),
('last_modified', models.DateTimeField(blank=True, editable=False)),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField()),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('campaign', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='notes.campaign')),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'historical ingame day',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': 'history_date',
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
]

View file

@ -0,0 +1,30 @@
# Generated by Django 3.2.6 on 2021-08-23 11:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('notes', '0011_auto_20210822_1828'),
]
operations = [
migrations.AddField(
model_name='ingameday',
name='sessions',
field=models.ManyToManyField(related_name='ingame_days', to='notes.Session'),
),
migrations.AlterUniqueTogether(
name='ingameday',
unique_together={('campaign', 'day')},
),
migrations.AlterUniqueTogether(
name='session',
unique_together={('campaign', 'day')},
),
migrations.RemoveField(
model_name='session',
name='ingame_days',
),
]

View file

@ -0,0 +1,49 @@
# Generated by Django 3.2.6 on 2021-08-23 12:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('notes', '0012_auto_20210823_1333'),
]
operations = [
migrations.RemoveField(
model_name='historicalsession',
name='description_html',
),
migrations.RemoveField(
model_name='historicalsession',
name='description_md',
),
migrations.RemoveField(
model_name='session',
name='description_html',
),
migrations.RemoveField(
model_name='session',
name='description_md',
),
migrations.AddField(
model_name='historicalingameday',
name='description_html',
field=models.TextField(blank=True, editable=False),
),
migrations.AddField(
model_name='historicalingameday',
name='description_md',
field=models.TextField(blank=True),
),
migrations.AddField(
model_name='ingameday',
name='description_html',
field=models.TextField(blank=True, editable=False),
),
migrations.AddField(
model_name='ingameday',
name='description_md',
field=models.TextField(blank=True),
),
]

View file

@ -5,3 +5,5 @@ from .faction import Faction
from .location import Location
from .character import Character
from .loot import Loot
from .session import Session
from .ingameday import IngameDay

31
notes/models/ingameday.py Normal file
View file

@ -0,0 +1,31 @@
from django.contrib.humanize.templatetags.humanize import ordinal
from django.db import models
from django.urls import reverse
from simple_history.models import HistoricalRecords
from notes.models import Campaign, Session, DescriptionModel
class IngameDay(DescriptionModel):
campaign = models.ForeignKey(Campaign, on_delete=models.PROTECT)
day = models.PositiveIntegerField()
sessions = models.ManyToManyField(Session, related_name="ingame_days")
created = models.DateTimeField(auto_now_add=True)
last_modified = models.DateTimeField(auto_now=True)
history = HistoricalRecords()
class Meta:
ordering = ["-day"]
unique_together = ["campaign", "day"]
def get_absolute_url(self):
return reverse('daydetail', args=[self.campaign.slug, self.day])
@property
def prettyname(self):
return ordinal(self.day) + " day"
def __str__(self):
return self.prettyname

View file

@ -17,6 +17,9 @@ class Loot(DescriptionModel):
last_modified = models.DateTimeField(auto_now=True)
history = HistoricalRecords()
class Meta:
ordering = ["name"]
@property
def value_per_unit(self):
return self.value_gold / self.quantity

26
notes/models/session.py Normal file
View file

@ -0,0 +1,26 @@
from datetime import date
from django.db import models
from django.urls import reverse
from simple_history.models import HistoricalRecords
from notes.models import Campaign
class Session(models.Model):
campaign = models.ForeignKey(Campaign, on_delete=models.PROTECT)
day = models.DateField(default=date.today)
created = models.DateTimeField(auto_now_add=True)
last_modified = models.DateTimeField(auto_now=True)
history = HistoricalRecords()
class Meta:
ordering = ["-day"]
unique_together = ["campaign", "day"]
def get_absolute_url(self):
return reverse('sessiondetail', args=[self.campaign.slug, self.id])
def __str__(self):
return str(self.day)

View file

@ -15,6 +15,9 @@
<li class="nav-item">
<a class="nav-link" href="{% url "characterlist" view.kwargs.campslug %}">Characters</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url "daylist" view.kwargs.campslug %}">Timeline</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url "lootlist" view.kwargs.campslug %}">Loot</a>
</li>

View file

@ -3,7 +3,7 @@
{% load static %}
{% block heading %}
<h1>Add new Loot</h1>
<h1>Delete Object</h1>
{% endblock %}
{% block content %}

View file

@ -1,4 +1,7 @@
{% extends "navbarbase.html" %}
{% load thumbutils %}
{% load thumbnail %}
{% load humanize %}
{% block content %}
<div class="row">
@ -17,6 +20,19 @@
</div>
<div class="col-8">
<div class="character-heading" style="border-bottom-color: #{{ character.color }}">
{% if character.image %}
{% thumbnail character.image "150x150" crop="center" as im %}
<a href="{{ character.image.url }}" target="_blank">
<img class="avatar avatar-image avatar-large rounded-circle" src="{{ im.url }}" width="{{ im.width }}"
height="{{ im.height }}"
srcset="{{ im.url|srcset }}">
</a>
{% endthumbnail %}
{% else %}
<div class="avatar avatar-text avatar-large"
style="background: #{{ character.color }};color:{{ character.text_color }}">{{ character.initials }}</div>
{% endif %}
<h1>
{{ character.name }}
<a href="{% url "characteredit" character.campaign.slug character.slug %}">
@ -32,6 +48,10 @@
{% endif %}
</dl>
{{ character.description_html|safe }}
<div>
<small>Last updated: {{ character.last_modified|naturaltime }} by {{ character.history.first.history_user }} </small>
</div>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,42 @@
{% extends "navbarbase.html" %}
{% load thumbutils %}
{% load thumbnail %}
{% load humanize %}
{% block content %}
<div class="row">
<div class="col-4">
<ul class="nav nav-pills flex-column">
{% for d in days %}
<li class="nav-item">
<a href="{% url "daydetail" day.campaign.slug d.day %}"
class="nav-link {% if d.id == day.id %}active{% endif %}">
{{ d.prettyname }}
</a>
</li>
{% endfor %}
</ul>
<a class="btn btn-primary add-button" href="{% url "dayadd" day.campaign.slug %}">Add Day</a>
</div>
<div class="col-8">
<div class="day-heading">
<h1>
{{ day.prettyname }}
<a href="{% url "dayedit" day.campaign.slug day.day %}">
edit
</a>
</h1>
</div>
{{ day.description_html|safe }}
<dl>
<dt>Sessions:</dt>
<dd>{{ day.sessions.all|join:", " }}</dd>
</dl>
<div>
<small>Last updated: {{ day.last_modified|naturaltime }}
by {{ day.history.first.history_user }} </small>
</div>
</div>
</div>
{% endblock %}

View file

@ -5,6 +5,9 @@
{% block heading %}
{% if edit %}
<h1>Edit "{{ object.name }}"</h1>
{% if request.resolver_match.view_name %}
<a class="btn btn-danger" href="{% url "lootdelete" object.campaign.slug object.id %}">Delete</a>
{% endif %}
{% else %}
<h1>Add new</h1>
{% endif %}
@ -14,13 +17,15 @@
<form method="post">
{% csrf_token %}
<input type="submit" class="btn btn-primary" value="Update">
{% bootstrap_form form %}
<input type="submit" class="btn btn-primary" value="Update">
</form>
{% endblock %}
{#{% block extra_head %}#}
{# <script src="{% static "libs/codemirror.js" %}"></script>#}
{# <script src="{% static "libs/markdown.js" %}"></script>#}
{# <script src="{% static "libs/tagify.min.js" %}"></script>#}
{#{% endblock %}#}
{% block extra_js %}
<script src="{% static "libs/easymde.min.js" %}"></script>
<script src="{% static "libs/fontawesome-solid.min.js" %}"></script>
<script src="{% static "libs/fontawesome.min.js" %}"></script>
<script src="{% static "js/markdown.js" %}"></script>
{% endblock %}

View file

@ -1,4 +1,5 @@
{% extends "navbarbase.html" %}
{% load formatters %}
{% block heading %}
<h1>Loot</h1>
@ -22,7 +23,7 @@
aria-controls="row-{{ l.id }}">
<td>{{ l.name }}</td>
<td>{{ l.quantity }}</td>
<td>{{ l.value_gold }}</td>
<td>{{ l.value_gold|format_money_html }}</td>
<td>{{ l.owner.first_name }}</td>
<td>
<svg class="chev" viewBox="0 0 640 1024" xmlns="http://www.w3.org/2000/svg">
@ -35,14 +36,17 @@
<div class="collapse" id="row-{{ l.id }}">
<h3>{{ l.name }} <a href="{% url "lootedit" l.campaign.slug l.id %}">edit</a></h3>
{{ l.description_html|safe }}
<p>Value each: {{ l.value_per_unit|floatformat:-2 }} Gold</p>
<p>Value each: {{ l.value_per_unit|format_money_html }}</p>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<dl>
<dd>Total value:</dd>
<dt>{{ total_value|format_money_html }}</dt>
</dl>
<a href="{% url "lootadd" loot.0.campaign.slug %}" class="btn btn-primary">Add Loot</a>
{% endblock %}

View file

@ -5,12 +5,12 @@
<div class="image-col">
{% if character.image %}
{% thumbnail character.image "48x48" crop="center" as im %}
<img class="rounded-circle" src="{{ im.url }}" width="{{ im.width }}"
<img class="avatar avatar-image rounded-circle" src="{{ im.url }}" width="{{ im.width }}"
height="{{ im.height }}"
srcset="{{ im.url|srcset }}">
{% endthumbnail %}
{% else %}
<div class="avatar"
<div class="avatar avatar-text"
style="background: #{{ character.color }};color:{{ character.text_color }}">{{ character.initials }}</div>
{% endif %}
</div>

View file

@ -0,0 +1,16 @@
from django import template
from django.template.defaultfilters import safe
from notes.utils.money import format_money as money_formatter
register = template.Library()
@register.filter()
def format_money(money):
return money_formatter(money)
@register.filter()
def format_money_html(money):
return safe(money_formatter(money, html=True))

View file

@ -5,16 +5,21 @@ from notes import views
urlpatterns = [
path("", views.CampaignListView.as_view(), name="campaignlist"),
path("c/add", views.CampaignCreateView.as_view(), name="campaigncreate"),
path("c/<str:campslug>", views.CampaignDetailView.as_view(), name="campaigndetail"),
path("c/<str:campslug>/edit", views.CampaignEditView.as_view(), name="campaignedit"),
path("c/<str:campslug>/delete", views.CampaignDeleteView.as_view(), name="campaigndelete"),
path("c/<str:campslug>/loot", views.LootListView.as_view(), name="lootlist"),
path("c/<str:campslug>/loot/<int:pk>/edit", views.LootEditView.as_view(), name="lootedit"),
path("c/<str:campslug>/loot/<int:pk>/delete", views.LootDeleteView.as_view(), name="lootdelete"),
path("c/<str:campslug>/loot/add", views.LootCreateView.as_view(), name="lootadd"),
path("c/<str:campslug>/character/", views.list_character_redirect, name="characterlist"),
path("c/<str:campslug>/character/add", views.CharacterCreateView.as_view(), name="characteradd"),
path("c/<str:campslug>/character/<str:charslug>", views.CharacterDetailView.as_view(), name="characterdetail"),
path("c/<str:campslug>/character/<str:charslug>/edit", views.CharacterEditView.as_view(), name="characteredit"),
path("c/<str:campslug>/character/<str:charslug>/delete", views.CharacterDeleteView.as_view(), name="characterdelete"),
path("c/<slug:campslug>", views.CampaignDetailView.as_view(), name="campaigndetail"),
path("c/<slug:campslug>/edit", views.CampaignEditView.as_view(), name="campaignedit"),
path("c/<slug:campslug>/delete", views.CampaignDeleteView.as_view(), name="campaigndelete"),
path("c/<slug:campslug>/loot", views.LootListView.as_view(), name="lootlist"),
path("c/<slug:campslug>/loot/<int:pk>/edit", views.LootEditView.as_view(), name="lootedit"),
path("c/<slug:campslug>/loot/<int:pk>/delete", views.LootDeleteView.as_view(), name="lootdelete"),
path("c/<slug:campslug>/loot/add", views.LootCreateView.as_view(), name="lootadd"),
path("c/<slug:campslug>/character/", views.list_character_redirect, name="characterlist"),
path("c/<slug:campslug>/character/add", views.CharacterCreateView.as_view(), name="characteradd"),
path("c/<slug:campslug>/character/<slug:charslug>", views.CharacterDetailView.as_view(), name="characterdetail"),
path("c/<slug:campslug>/character/<slug:charslug>/edit", views.CharacterEditView.as_view(), name="characteredit"),
path("c/<slug:campslug>/character/<slug:charslug>/delete", views.CharacterDeleteView.as_view(), name="characterdelete"),
path("c/<slug:campslug>/day/", views.list_day_redirect, name="daylist"),
path("c/<slug:campslug>/day/add", views.DayCreateView.as_view(), name="dayadd"),
path("c/<slug:campslug>/day/<int:day>", views.DayDetailView.as_view(), name="daydetail"),
path("c/<slug:campslug>/day/<int:day>/edit", views.DayEditView.as_view(), name="dayedit"),
path("c/<slug:campslug>/day/<int:day>/delete", views.DayDeleteView.as_view(), name="daydelete"),
]

43
notes/utils/money.py Normal file
View file

@ -0,0 +1,43 @@
from decimal import Decimal
gold = Decimal(1)
silver = gold / 10
copper = silver / 10
platinum = gold * 10
currencies_text = ["CP", "SP", "GP", "PP"]
currencies_HTML = [
"<span class='cp'>CP</span>",
"<span class='sp'>SP</span>",
"<span class='gp'>GP</span>",
"<span class='pp'>PP</span>"
]
use_platinum=False
def digitize(n):
print("n", n)
digits = []
i = 0
if n == 0:
return [0]
while n:
print(digits)
i += 1
if i > (3 if use_platinum else 2):
digits.append(n)
return digits
n, d = divmod(n, 10)
digits.append(d)
return digits
def format_money(money: Decimal, html=False) -> str:
currencies = currencies_HTML if html else currencies_text
output = []
cp = round(money / copper, ndigits=0)
for i, value in enumerate(digitize(cp)):
if value:
output.append(str(value) + " " + currencies[i])
return " ".join(reversed(output))

View file

@ -1,4 +1,5 @@
from .campaign_views import *
from .loot_views import *
from .character_views import *
from .day_views import *
from .debugviews import *

View file

@ -30,12 +30,16 @@ class CharacterDetailView(generic.DetailView):
data["player_characters"] = Character.objects.filter(
campaign__slug=self.kwargs['campslug'],
player__isnull=False
)
data["npcs"] = Character.objects.filter(campaign__slug=self.kwargs['campslug'], player__isnull=True)
).select_related()
data["npcs"] = Character.objects.filter(
campaign__slug=self.kwargs['campslug'], player__isnull=True
).select_related()
return data
def get_object(self, queryset=None):
return Character.objects.get(campaign__slug=self.kwargs['campslug'], slug=self.kwargs['charslug'])
return Character.objects.get(
campaign__slug=self.kwargs['campslug'], slug=self.kwargs['charslug']
)
class CharacterCreateView(generic.CreateView):

58
notes/views/day_views.py Normal file
View file

@ -0,0 +1,58 @@
from django.shortcuts import redirect
from django.urls import reverse_lazy
from django.views import generic
from notes.forms import DayForm
from notes.models import Session, Campaign, IngameDay
def list_day_redirect(request, *args, **kwargs):
latest_day: IngameDay = IngameDay.objects.first()
return redirect(latest_day)
class DayDetailView(generic.DetailView):
template_name = "notes/day_detail.html"
model = IngameDay
context_object_name = "day"
def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
data["days"] = IngameDay.objects.filter(campaign__slug=self.kwargs['campslug'])
return data
def get_object(self, queryset=None):
return IngameDay.objects.get(campaign__slug=self.kwargs['campslug'], day=self.kwargs['day'])
class DayCreateView(generic.CreateView):
template_name = "notes/loot_edit.html"
model = IngameDay
form_class = DayForm
context_object_name = "object"
def form_valid(self, form):
form.instance.campaign = Campaign.objects.get(slug=self.kwargs['campslug'])
return super().form_valid(form)
class DayEditView(generic.UpdateView):
template_name = "notes/loot_edit.html"
model = IngameDay
form_class = DayForm
def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
data['edit'] = True
return data
def get_object(self, queryset=None):
return IngameDay.objects.get(campaign__slug=self.kwargs['campslug'], day=self.kwargs['day'])
class DayDeleteView(generic.DeleteView):
template_name = "notes/campaign_confirm_delete.html"
model = IngameDay
success_url = reverse_lazy('daylist')
def get_object(self, queryset=None):
return IngameDay.objects.get(campaign__slug=self.kwargs['campslug'], day=self.kwargs['day'])

View file

@ -1,13 +1,14 @@
from django.http import HttpResponse
from django.views.decorators.cache import cache_page
from notes.utils.assets import get_css
# @cache_page(60 * 15)
def debug_css(request):
css, source_map = get_css(debug=True)
return HttpResponse(css, content_type="text/css")
# @cache_page(60 * 15)
def debug_css_sourcemap(request):
css, source_map = get_css(debug=True)
return HttpResponse(source_map, content_type="application/json")

View file

@ -1,3 +1,4 @@
from django.db.models import Sum
from django.urls import reverse_lazy, reverse
from django.views import generic
@ -13,6 +14,12 @@ class LootListView(generic.ListView):
def get_queryset(self):
return Loot.objects.filter(campaign__slug=self.kwargs['campslug'])
def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
data['total_value'] = self.get_queryset().aggregate(Sum("value_gold"))["value_gold__sum"]
print(data['total_value'])
return data
class LootDetailView(generic.DetailView):
template_name = "notes/loot_detail.html"
@ -28,7 +35,8 @@ class LootCreateView(generic.CreateView):
form.instance.campaign = Campaign.objects.get(slug=self.kwargs['campslug'])
return super().form_valid(form)
success_url = reverse_lazy('lootlist')
def get_success_url(self):
return reverse("lootlist", kwargs={"campslug": self.kwargs.get("campslug")})
class LootEditView(generic.UpdateView):
@ -39,6 +47,7 @@ class LootEditView(generic.UpdateView):
def get_success_url(self):
return reverse("lootlist", kwargs={"campslug": self.kwargs.get("campslug")})
def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
data['edit'] = True
@ -46,6 +55,7 @@ class LootEditView(generic.UpdateView):
class LootDeleteView(generic.DeleteView):
template_name = "notes/loot_confirm_delete.html"
template_name = "notes/campaign_confirm_delete.html"
model = Loot
success_url = reverse_lazy('lootlist')
def get_success_url(self):
return reverse("lootlist", kwargs={"campslug": self.kwargs.get("campslug")})

146
package-lock.json generated
View file

@ -5,7 +5,18 @@
"packages": {
"": {
"dependencies": {
"bootstrap": "^5.1.0"
"@fortawesome/fontawesome-free": "^5.15.4",
"bootstrap": "^5.1.0",
"easymde": "^2.15.0"
}
},
"node_modules/@fortawesome/fontawesome-free": {
"version": "5.15.4",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.15.4.tgz",
"integrity": "sha512-eYm8vijH/hpzr/6/1CJ/V/Eb1xQFW2nnUKArb3z+yUWv7HTwj6M7SP957oMjfZjAHU6qpoNc2wQvIxBLWYa/Jg==",
"hasInstallScript": true,
"engines": {
"node": ">=6"
}
},
"node_modules/@popperjs/core": {
@ -18,6 +29,32 @@
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@types/codemirror": {
"version": "0.0.109",
"resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-0.0.109.tgz",
"integrity": "sha512-cSdiHeeLjvGn649lRTNeYrVCDOgDrtP+bDDSFDd1TF+i0jKGPDRozno2NOJ9lTniso+taiv4kiVS8dgM8Jm5lg==",
"dependencies": {
"@types/tern": "*"
}
},
"node_modules/@types/estree": {
"version": "0.0.50",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz",
"integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw=="
},
"node_modules/@types/marked": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@types/marked/-/marked-2.0.4.tgz",
"integrity": "sha512-L9VRSe0Id8xbPL99mUo/4aKgD7ZoRwFZqUQScNKHi2pFjF9ZYSMNShUHD6VlMT6J/prQq0T1mxuU25m3R7dFzg=="
},
"node_modules/@types/tern": {
"version": "0.23.4",
"resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.4.tgz",
"integrity": "sha512-JAUw1iXGO1qaWwEOzxTKJZ/5JxVeON9kvGZ/osgZaJImBnyjyn0cjovPsf6FNLmyGY8Vw9DoXZCMlfMkMwHRWg==",
"dependencies": {
"@types/estree": "*"
}
},
"node_modules/bootstrap": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.0.tgz",
@ -29,20 +66,127 @@
"peerDependencies": {
"@popperjs/core": "^2.9.3"
}
},
"node_modules/codemirror": {
"version": "5.62.3",
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.62.3.tgz",
"integrity": "sha512-zZAyOfN8TU67ngqrxhOgtkSAGV9jSpN1snbl8elPtnh9Z5A11daR405+dhLzLnuXrwX0WCShWlybxPN3QC/9Pg=="
},
"node_modules/codemirror-spell-checker": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/codemirror-spell-checker/-/codemirror-spell-checker-1.1.2.tgz",
"integrity": "sha1-HGYPkIlIPMtRE7m6nKGcP0mTNx4=",
"dependencies": {
"typo-js": "*"
}
},
"node_modules/easymde": {
"version": "2.15.0",
"resolved": "https://registry.npmjs.org/easymde/-/easymde-2.15.0.tgz",
"integrity": "sha512-9jMRIVvKt1d0UjRN45yotUYECAM4xvw0TTAQw8sYDONP++keWJVnd8Xrn+V+vQEN/v9/X0SWEoo1rFSgCooGpw==",
"dependencies": {
"@types/codemirror": "0.0.109",
"@types/marked": "^2.0.2",
"codemirror": "^5.61.0",
"codemirror-spell-checker": "1.1.2",
"marked": "^2.0.3"
}
},
"node_modules/marked": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/marked/-/marked-2.1.3.tgz",
"integrity": "sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA==",
"bin": {
"marked": "bin/marked"
},
"engines": {
"node": ">= 10"
}
},
"node_modules/typo-js": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/typo-js/-/typo-js-1.2.0.tgz",
"integrity": "sha512-dELuLBVa2jvWdU/CHTKi2L/POYaRupv942k+vRsFXsM17acXesQGAiGCio82RW7fvcr7bkuD/Zj8XpUh6aPC2A=="
}
},
"dependencies": {
"@fortawesome/fontawesome-free": {
"version": "5.15.4",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.15.4.tgz",
"integrity": "sha512-eYm8vijH/hpzr/6/1CJ/V/Eb1xQFW2nnUKArb3z+yUWv7HTwj6M7SP957oMjfZjAHU6qpoNc2wQvIxBLWYa/Jg=="
},
"@popperjs/core": {
"version": "2.9.3",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.9.3.tgz",
"integrity": "sha512-xDu17cEfh7Kid/d95kB6tZsLOmSWKCZKtprnhVepjsSaCij+lM3mItSJDuuHDMbCWTh8Ejmebwb+KONcCJ0eXQ==",
"peer": true
},
"@types/codemirror": {
"version": "0.0.109",
"resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-0.0.109.tgz",
"integrity": "sha512-cSdiHeeLjvGn649lRTNeYrVCDOgDrtP+bDDSFDd1TF+i0jKGPDRozno2NOJ9lTniso+taiv4kiVS8dgM8Jm5lg==",
"requires": {
"@types/tern": "*"
}
},
"@types/estree": {
"version": "0.0.50",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz",
"integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw=="
},
"@types/marked": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@types/marked/-/marked-2.0.4.tgz",
"integrity": "sha512-L9VRSe0Id8xbPL99mUo/4aKgD7ZoRwFZqUQScNKHi2pFjF9ZYSMNShUHD6VlMT6J/prQq0T1mxuU25m3R7dFzg=="
},
"@types/tern": {
"version": "0.23.4",
"resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.4.tgz",
"integrity": "sha512-JAUw1iXGO1qaWwEOzxTKJZ/5JxVeON9kvGZ/osgZaJImBnyjyn0cjovPsf6FNLmyGY8Vw9DoXZCMlfMkMwHRWg==",
"requires": {
"@types/estree": "*"
}
},
"bootstrap": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.0.tgz",
"integrity": "sha512-bs74WNI9BgBo3cEovmdMHikSKoXnDgA6VQjJ7TyTotU6L7d41ZyCEEelPwkYEzsG/Zjv3ie9IE3EMAje0W9Xew==",
"requires": {}
},
"codemirror": {
"version": "5.62.3",
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.62.3.tgz",
"integrity": "sha512-zZAyOfN8TU67ngqrxhOgtkSAGV9jSpN1snbl8elPtnh9Z5A11daR405+dhLzLnuXrwX0WCShWlybxPN3QC/9Pg=="
},
"codemirror-spell-checker": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/codemirror-spell-checker/-/codemirror-spell-checker-1.1.2.tgz",
"integrity": "sha1-HGYPkIlIPMtRE7m6nKGcP0mTNx4=",
"requires": {
"typo-js": "*"
}
},
"easymde": {
"version": "2.15.0",
"resolved": "https://registry.npmjs.org/easymde/-/easymde-2.15.0.tgz",
"integrity": "sha512-9jMRIVvKt1d0UjRN45yotUYECAM4xvw0TTAQw8sYDONP++keWJVnd8Xrn+V+vQEN/v9/X0SWEoo1rFSgCooGpw==",
"requires": {
"@types/codemirror": "0.0.109",
"@types/marked": "^2.0.2",
"codemirror": "^5.61.0",
"codemirror-spell-checker": "1.1.2",
"marked": "^2.0.3"
}
},
"marked": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/marked/-/marked-2.1.3.tgz",
"integrity": "sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA=="
},
"typo-js": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/typo-js/-/typo-js-1.2.0.tgz",
"integrity": "sha512-dELuLBVa2jvWdU/CHTKi2L/POYaRupv942k+vRsFXsM17acXesQGAiGCio82RW7fvcr7bkuD/Zj8XpUh6aPC2A=="
}
}
}

View file

@ -1,5 +1,7 @@
{
"dependencies": {
"bootstrap": "^5.1.0"
"bootstrap": "^5.1.0",
"easymde": "^2.15.0",
"@fortawesome/fontawesome-free": "^5.15.4"
}
}

18
poetry.lock generated
View file

@ -74,6 +74,18 @@ python-versions = ">=3.6"
beautifulsoup4 = ">=4.8.0"
Django = ">=2.2"
[[package]]
name = "django-debug-toolbar"
version = "3.2.2"
description = "A configurable set of panels that display various debug information about the current request/response."
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
Django = ">=2.2"
sqlparse = ">=0.2.0"
[[package]]
name = "django-simple-history"
version = "3.0.0"
@ -190,7 +202,7 @@ python-versions = "*"
[metadata]
lock-version = "1.1"
python-versions = "^3.9"
content-hash = "245e8ddb9d285feb2d4ab950fa117b8422d0634fece9a59938b663cbeb4f45ae"
content-hash = "de84127badac66220a787600d18455d7cf562aba0c81c05d7219f39f54a97cf9"
[metadata.files]
asgiref = [
@ -218,6 +230,10 @@ django-bootstrap5 = [
{file = "django-bootstrap5-2.1.2.tar.gz", hash = "sha256:ac05ca69b990e62657c0bf40db14946c22fd64c7fb9dfe094a9f22ecc58f86c5"},
{file = "django_bootstrap5-2.1.2-py3-none-any.whl", hash = "sha256:0021355df9212c511876e3f5336ba470416a6dd52f61bf5acec5bb783ab41ff5"},
]
django-debug-toolbar = [
{file = "django-debug-toolbar-3.2.2.tar.gz", hash = "sha256:8c5b13795d4040008ee69ba82dcdd259c49db346cf7d0de6e561a49d191f0860"},
{file = "django_debug_toolbar-3.2.2-py3-none-any.whl", hash = "sha256:d7bab7573fab35b0fd029163371b7182f5826c13da69734beb675c761d06a4d3"},
]
django-simple-history = [
{file = "django-simple-history-3.0.0.tar.gz", hash = "sha256:66fe76c560054be393c52b1799661e104fbe372918d37d151e5d41c676158118"},
{file = "django_simple_history-3.0.0-py2.py3-none-any.whl", hash = "sha256:a312adfe8fbec4c450b08e641b11249a8a589a7e7d1ba2404764b8b5bed53552"},

View file

@ -16,6 +16,7 @@ bleach-allowlist = "^1.0.3"
Pillow = "^8.3.1"
sorl-thumbnail = "^12.7.0"
libsass = "^0.21.0"
django-debug-toolbar = "^3.2.2"
[tool.poetry.dev-dependencies]

View file

@ -33,12 +33,15 @@ INSTALLED_APPS = [
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.humanize',
'simple_history',
'django_bootstrap5',
'sorl.thumbnail'
'sorl.thumbnail',
'debug_toolbar'
]
MIDDLEWARE = [
'debug_toolbar.middleware.DebugToolbarMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
@ -92,7 +95,7 @@ AUTH_PASSWORD_VALIDATORS = [
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
TIME_ZONE = 'Europe/Vienna'
USE_I18N = True

View file

@ -13,6 +13,7 @@ Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
import debug_toolbar
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path, include
@ -26,6 +27,7 @@ urlpatterns = [
]
if settings.DEBUG:
urlpatterns.append(path('__debug__/', include(debug_toolbar.urls)), )
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns.append(path("css", views.debug_css, name="css"))
urlpatterns.append(path("css_sourcemap", views.debug_css_sourcemap, name="css_sourcemap"))

39
static/js/markdown.js Normal file
View file

@ -0,0 +1,39 @@
/**
* Copyright (C) 2020 Lukas Winkler
*
* @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later
*
*/
document.addEventListener('DOMContentLoaded', function () {
const ids = ["id_description_md"];
ids.forEach(function (id) {
const element = document.getElementById(id);
if (!element) {
return
}
const easyMDE = new EasyMDE({
element: element,
forceSync: true, // for "required" to work
spellChecker: false,
nativeSpellcheck: true,
autoDownloadFontAwesome: false,
autosave: {
delay: 1000,
submit_delay: 5000,
timeFormat: {
locale: 'de-AT',
format: {
year: 'numeric',
month: 'long',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
},
},
}
});
});
});

1
static/libs/easymde.min.js vendored Symbolic link
View file

@ -0,0 +1 @@
../../node_modules/easymde/dist/easymde.min.js

1
static/libs/fontawesome-solid.min.js vendored Symbolic link
View file

@ -0,0 +1 @@
../../node_modules/@fortawesome/fontawesome-free/js/solid.min.js

1
static/libs/fontawesome.min.js vendored Symbolic link
View file

@ -0,0 +1 @@
../../node_modules/@fortawesome/fontawesome-free/js/fontawesome.min.js

27
static/scss/_avatar.scss Normal file
View file

@ -0,0 +1,27 @@
.avatar {
display: block;
&.avatar-text {
font-size: 1rem;
width: 48px;
height: 48px;
line-height: 48px;
text-align: center;
border-radius: 50%;
vertical-align: middle;
color: white;
font-family: sans-serif;
&.avatar-large {
font-size: 2.5rem;
}
}
&.avatar-large {
width: 150px;
height: 150px;
line-height: 150px;
margin: 1rem auto;
}
}

View file

@ -17,18 +17,6 @@ tr.collapse-button:not(.collapsed) {
background: darkgray;
}
.avatar {
display: block;
font-size: 1em;
width: 48px;
height: 48px;
line-height: 48px;
text-align: center;
border-radius: 50%;
vertical-align: middle;
color: white;
font-family: sans-serif;
}
.side-card {
display: flex;
@ -42,13 +30,43 @@ tr.collapse-button:not(.collapsed) {
.text-col {
a {
text-decoration: none;
&:focus,&:active,&:hover{
&:focus, &:active, &:hover {
text-decoration: underline;
}
}
}
}
.character-heading {
border-bottom: solid 2px;
}
.cp:after {
background: #B87333;
}
.sp:after {
background: #CFD0D1;
}
.gp:after {
background: #FFD700;
}
.pp:after {
background: #B7B4AE;
}
.cp, .sp, .gp, .pp {
&:after {
content: "";
width: 19px;
height: 19px;
display: inline-block;
border-radius: 50%;
vertical-align: middle;
margin: 0 5px;
}
}

View file

@ -1,5 +1,7 @@
@import "variables";
@import "node_modules/bootstrap/scss/bootstrap";
@import "node_modules/easymde/dist/easymde.min";
@import "misc";
@import "avatar";