From 71b0d1cac831eeddebe3adf3be122a04504567ec Mon Sep 17 00:00:00 2001 From: Lukas Winkler Date: Sun, 22 Aug 2021 20:10:29 +0200 Subject: [PATCH] initial version --- .gitignore | 6 + manage.py | 22 ++ notes/__init__.py | 0 notes/admin.py | 10 + notes/apps.py | 6 + notes/forms.py | 21 ++ notes/migrations/0001_initial.py | 83 +++++ notes/migrations/0002_auto_20210822_1109.py | 33 ++ notes/migrations/0003_auto_20210822_1110.py | 38 +++ ...ction_historicallocation_historicalloot.py | 138 ++++++++ notes/migrations/0005_auto_20210822_1259.py | 40 +++ notes/migrations/0006_auto_20210822_1421.py | 123 +++++++ notes/migrations/0007_auto_20210822_1551.py | 31 ++ notes/migrations/0008_auto_20210822_1610.py | 24 ++ notes/migrations/0009_auto_20210822_1702.py | 23 ++ .../migrations/0010_alter_character_image.py | 19 ++ notes/migrations/__init__.py | 0 notes/models/__init__.py | 7 + notes/models/basemodel.py | 24 ++ notes/models/campaign.py | 21 ++ notes/models/character.py | 33 ++ notes/models/descriptionmodel.py | 16 + notes/models/faction.py | 10 + notes/models/location.py | 11 + notes/models/loot.py | 25 ++ notes/templates/base.html | 26 ++ notes/templates/navbarbase.html | 29 ++ .../notes/campaign_confirm_delete.html | 14 + notes/templates/notes/campaign_detail.html | 5 + notes/templates/notes/campaign_edit.html | 20 ++ notes/templates/notes/campaign_overview.html | 8 + notes/templates/notes/character_detail.html | 37 ++ notes/templates/notes/loot_edit.html | 26 ++ notes/templates/notes/loot_overview.html | 49 +++ .../notes/macros/character-pillar.html | 25 ++ notes/templatetags/__init__.py | 0 notes/templatetags/thumbutils.py | 18 + notes/tests.py | 3 + notes/urls.py | 20 ++ notes/utils/__init__.py | 0 notes/utils/assets.py | 30 ++ notes/utils/colors.py | 301 ++++++++++++++++ notes/utils/markdown.py | 19 ++ notes/views/__init__.py | 4 + notes/views/campaign_views.py | 38 +++ notes/views/character_views.py | 69 ++++ notes/views/debugviews.py | 13 + notes/views/loot_views.py | 51 +++ package-lock.json | 48 +++ package.json | 5 + poetry.lock | 323 ++++++++++++++++++ pyproject.toml | 24 ++ rpg_notes/__init__.py | 0 rpg_notes/asgi.py | 16 + rpg_notes/settings.py | 115 +++++++ rpg_notes/urls.py | 31 ++ rpg_notes/wsgi.py | 16 + static/libs/bootstrap.min.css | 1 + static/libs/bootstrap.min.css.map | 1 + static/libs/bootstrap.min.js | 1 + static/libs/bootstrap.min.js.map | 1 + static/scss/_misc.scss | 54 +++ static/scss/_variables.scss | 0 static/scss/main.scss | 5 + 64 files changed, 2210 insertions(+) create mode 100644 .gitignore create mode 100755 manage.py create mode 100644 notes/__init__.py create mode 100644 notes/admin.py create mode 100644 notes/apps.py create mode 100644 notes/forms.py create mode 100644 notes/migrations/0001_initial.py create mode 100644 notes/migrations/0002_auto_20210822_1109.py create mode 100644 notes/migrations/0003_auto_20210822_1110.py create mode 100644 notes/migrations/0004_historicalcampaign_historicalcharacter_historicalfaction_historicallocation_historicalloot.py create mode 100644 notes/migrations/0005_auto_20210822_1259.py create mode 100644 notes/migrations/0006_auto_20210822_1421.py create mode 100644 notes/migrations/0007_auto_20210822_1551.py create mode 100644 notes/migrations/0008_auto_20210822_1610.py create mode 100644 notes/migrations/0009_auto_20210822_1702.py create mode 100644 notes/migrations/0010_alter_character_image.py create mode 100644 notes/migrations/__init__.py create mode 100644 notes/models/__init__.py create mode 100644 notes/models/basemodel.py create mode 100644 notes/models/campaign.py create mode 100644 notes/models/character.py create mode 100644 notes/models/descriptionmodel.py create mode 100644 notes/models/faction.py create mode 100644 notes/models/location.py create mode 100644 notes/models/loot.py create mode 100644 notes/templates/base.html create mode 100644 notes/templates/navbarbase.html create mode 100644 notes/templates/notes/campaign_confirm_delete.html create mode 100644 notes/templates/notes/campaign_detail.html create mode 100644 notes/templates/notes/campaign_edit.html create mode 100644 notes/templates/notes/campaign_overview.html create mode 100644 notes/templates/notes/character_detail.html create mode 100644 notes/templates/notes/loot_edit.html create mode 100644 notes/templates/notes/loot_overview.html create mode 100644 notes/templates/notes/macros/character-pillar.html create mode 100644 notes/templatetags/__init__.py create mode 100644 notes/templatetags/thumbutils.py create mode 100644 notes/tests.py create mode 100644 notes/urls.py create mode 100644 notes/utils/__init__.py create mode 100644 notes/utils/assets.py create mode 100644 notes/utils/colors.py create mode 100644 notes/utils/markdown.py create mode 100644 notes/views/__init__.py create mode 100644 notes/views/campaign_views.py create mode 100644 notes/views/character_views.py create mode 100644 notes/views/debugviews.py create mode 100644 notes/views/loot_views.py create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 poetry.lock create mode 100644 pyproject.toml create mode 100644 rpg_notes/__init__.py create mode 100644 rpg_notes/asgi.py create mode 100644 rpg_notes/settings.py create mode 100644 rpg_notes/urls.py create mode 100644 rpg_notes/wsgi.py create mode 120000 static/libs/bootstrap.min.css create mode 120000 static/libs/bootstrap.min.css.map create mode 120000 static/libs/bootstrap.min.js create mode 120000 static/libs/bootstrap.min.js.map create mode 100644 static/scss/_misc.scss create mode 100644 static/scss/_variables.scss create mode 100644 static/scss/main.scss diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8626e35 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.idea/ +secrets.py +media/ +__pycache__/ +static/css +node_modules diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..264ada0 --- /dev/null +++ b/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'rpg_notes.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/notes/__init__.py b/notes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/notes/admin.py b/notes/admin.py new file mode 100644 index 0000000..d88358d --- /dev/null +++ b/notes/admin.py @@ -0,0 +1,10 @@ +from django.contrib import admin + +# Register your models here. +from notes.models import Character, Campaign, Faction, Location, Loot + +admin.site.register(Campaign) +admin.site.register(Character) +admin.site.register(Faction) +admin.site.register(Location) +admin.site.register(Loot) diff --git a/notes/apps.py b/notes/apps.py new file mode 100644 index 0000000..832dd3f --- /dev/null +++ b/notes/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class NotesConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'notes' diff --git a/notes/forms.py b/notes/forms.py new file mode 100644 index 0000000..5c69d00 --- /dev/null +++ b/notes/forms.py @@ -0,0 +1,21 @@ +from django.forms import ModelForm + +from notes.models import Campaign, Loot, Character + + +class CampaignForm(ModelForm): + class Meta: + model = Campaign + fields = "__all__" + + +class LootForm(ModelForm): + class Meta: + model = Loot + fields = ["name", "description_md", "quantity", "value_gold", "owner", "magic_item"] + + +class CharacterForm(ModelForm): + class Meta: + model = Character + fields = ["name", "description_md", "subtitle", "player", "faction", "location", "color", "image"] diff --git a/notes/migrations/0001_initial.py b/notes/migrations/0001_initial.py new file mode 100644 index 0000000..b04ff3f --- /dev/null +++ b/notes/migrations/0001_initial.py @@ -0,0 +1,83 @@ +# Generated by Django 3.2.6 on 2021-08-22 11:02 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Campaign', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=1000)), + ('slug', models.SlugField(unique=True)), + ('notes', models.TextField()), + ('created', models.DateTimeField(auto_now_add=True)), + ('last_modified', models.DateTimeField(auto_now=True)), + ('dm', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='Loot', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=1000)), + ('description', models.TextField()), + ('quantity', models.PositiveSmallIntegerField()), + ('value_gold', models.DecimalField(decimal_places=2, max_digits=7)), + ('magic_item', models.BooleanField(default=False)), + ('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')), + ('owner', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='Location', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=1000)), + ('slug', models.SlugField(unique=True)), + ('notes', models.TextField()), + ('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')), + ], + ), + migrations.CreateModel( + name='Faction', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=1000)), + ('slug', models.SlugField(unique=True)), + ('notes', models.TextField()), + ('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')), + ], + ), + migrations.CreateModel( + name='Character', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=1000)), + ('slug', models.SlugField(unique=True)), + ('notes', models.TextField()), + ('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')), + ('faction', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='notes.faction')), + ('location', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='notes.location')), + ('player', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/notes/migrations/0002_auto_20210822_1109.py b/notes/migrations/0002_auto_20210822_1109.py new file mode 100644 index 0000000..6118aee --- /dev/null +++ b/notes/migrations/0002_auto_20210822_1109.py @@ -0,0 +1,33 @@ +# Generated by Django 3.2.6 on 2021-08-22 11:09 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('notes', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='campaign', + name='slug', + field=models.SlugField(editable=False, unique=True), + ), + migrations.AlterField( + model_name='character', + name='slug', + field=models.SlugField(editable=False, unique=True), + ), + migrations.AlterField( + model_name='faction', + name='slug', + field=models.SlugField(editable=False, unique=True), + ), + migrations.AlterField( + model_name='location', + name='slug', + field=models.SlugField(editable=False, unique=True), + ), + ] diff --git a/notes/migrations/0003_auto_20210822_1110.py b/notes/migrations/0003_auto_20210822_1110.py new file mode 100644 index 0000000..7f113b3 --- /dev/null +++ b/notes/migrations/0003_auto_20210822_1110.py @@ -0,0 +1,38 @@ +# Generated by Django 3.2.6 on 2021-08-22 11:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('notes', '0002_auto_20210822_1109'), + ] + + operations = [ + migrations.AlterField( + model_name='campaign', + name='notes', + field=models.TextField(blank=True), + ), + migrations.AlterField( + model_name='character', + name='notes', + field=models.TextField(blank=True), + ), + migrations.AlterField( + model_name='faction', + name='notes', + field=models.TextField(blank=True), + ), + migrations.AlterField( + model_name='location', + name='notes', + field=models.TextField(blank=True), + ), + migrations.AlterField( + model_name='loot', + name='description', + field=models.TextField(blank=True), + ), + ] diff --git a/notes/migrations/0004_historicalcampaign_historicalcharacter_historicalfaction_historicallocation_historicalloot.py b/notes/migrations/0004_historicalcampaign_historicalcharacter_historicalfaction_historicallocation_historicalloot.py new file mode 100644 index 0000000..c0c263c --- /dev/null +++ b/notes/migrations/0004_historicalcampaign_historicalcharacter_historicalfaction_historicallocation_historicalloot.py @@ -0,0 +1,138 @@ +# Generated by Django 3.2.6 on 2021-08-22 11:38 + +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', '0003_auto_20210822_1110'), + ] + + operations = [ + migrations.CreateModel( + name='HistoricalLoot', + fields=[ + ('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')), + ('name', models.CharField(max_length=1000)), + ('description', models.TextField(blank=True)), + ('quantity', models.PositiveSmallIntegerField()), + ('value_gold', models.DecimalField(decimal_places=2, max_digits=7)), + ('magic_item', models.BooleanField(default=False)), + ('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)), + ('owner', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'historical loot', + 'ordering': ('-history_date', '-history_id'), + 'get_latest_by': 'history_date', + }, + bases=(simple_history.models.HistoricalChanges, models.Model), + ), + migrations.CreateModel( + name='HistoricalLocation', + fields=[ + ('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')), + ('name', models.CharField(max_length=1000)), + ('slug', models.SlugField(editable=False)), + ('notes', models.TextField(blank=True)), + ('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 location', + 'ordering': ('-history_date', '-history_id'), + 'get_latest_by': 'history_date', + }, + bases=(simple_history.models.HistoricalChanges, models.Model), + ), + migrations.CreateModel( + name='HistoricalFaction', + fields=[ + ('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')), + ('name', models.CharField(max_length=1000)), + ('slug', models.SlugField(editable=False)), + ('notes', models.TextField(blank=True)), + ('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 faction', + 'ordering': ('-history_date', '-history_id'), + 'get_latest_by': 'history_date', + }, + bases=(simple_history.models.HistoricalChanges, models.Model), + ), + migrations.CreateModel( + name='HistoricalCharacter', + fields=[ + ('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')), + ('name', models.CharField(max_length=1000)), + ('slug', models.SlugField(editable=False)), + ('notes', models.TextField(blank=True)), + ('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')), + ('faction', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='notes.faction')), + ('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), + ('location', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='notes.location')), + ('player', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'historical character', + 'ordering': ('-history_date', '-history_id'), + 'get_latest_by': 'history_date', + }, + bases=(simple_history.models.HistoricalChanges, models.Model), + ), + migrations.CreateModel( + name='HistoricalCampaign', + fields=[ + ('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')), + ('name', models.CharField(max_length=1000)), + ('slug', models.SlugField(editable=False)), + ('notes', models.TextField(blank=True)), + ('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)), + ('dm', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL)), + ('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 campaign', + 'ordering': ('-history_date', '-history_id'), + 'get_latest_by': 'history_date', + }, + bases=(simple_history.models.HistoricalChanges, models.Model), + ), + ] diff --git a/notes/migrations/0005_auto_20210822_1259.py b/notes/migrations/0005_auto_20210822_1259.py new file mode 100644 index 0000000..6886ec4 --- /dev/null +++ b/notes/migrations/0005_auto_20210822_1259.py @@ -0,0 +1,40 @@ +# Generated by Django 3.2.6 on 2021-08-22 12:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('notes', '0004_historicalcampaign_historicalcharacter_historicalfaction_historicallocation_historicalloot'), + ] + + operations = [ + migrations.AlterField( + model_name='character', + name='slug', + field=models.SlugField(editable=False), + ), + migrations.AlterField( + model_name='faction', + name='slug', + field=models.SlugField(editable=False), + ), + migrations.AlterField( + model_name='location', + name='slug', + field=models.SlugField(editable=False), + ), + migrations.AlterUniqueTogether( + name='character', + unique_together={('campaign', 'slug')}, + ), + migrations.AlterUniqueTogether( + name='faction', + unique_together={('campaign', 'slug')}, + ), + migrations.AlterUniqueTogether( + name='location', + unique_together={('campaign', 'slug')}, + ), + ] diff --git a/notes/migrations/0006_auto_20210822_1421.py b/notes/migrations/0006_auto_20210822_1421.py new file mode 100644 index 0000000..824882c --- /dev/null +++ b/notes/migrations/0006_auto_20210822_1421.py @@ -0,0 +1,123 @@ +# Generated by Django 3.2.6 on 2021-08-22 14:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('notes', '0005_auto_20210822_1259'), + ] + + operations = [ + migrations.RenameField( + model_name='campaign', + old_name='notes', + new_name='description_md', + ), + migrations.RenameField( + model_name='character', + old_name='notes', + new_name='description_md', + ), + migrations.RenameField( + model_name='faction', + old_name='notes', + new_name='description_md', + ), + migrations.RenameField( + model_name='historicalcampaign', + old_name='notes', + new_name='description_md', + ), + migrations.RenameField( + model_name='historicalcharacter', + old_name='notes', + new_name='description_md', + ), + migrations.RenameField( + model_name='historicalfaction', + old_name='notes', + new_name='description_md', + ), + migrations.RenameField( + model_name='historicalloot', + old_name='description', + new_name='description_md', + ), + migrations.RenameField( + model_name='loot', + old_name='description', + new_name='description_md', + ), + migrations.AddField( + model_name='campaign', + name='description_html', + field=models.TextField(blank=True, editable=False), + ), + migrations.AddField( + model_name='character', + name='description_html', + field=models.TextField(blank=True, editable=False), + ), + migrations.AddField( + model_name='faction', + name='description_html', + field=models.TextField(blank=True, editable=False), + ), + migrations.AddField( + model_name='historicalcampaign', + name='description_html', + field=models.TextField(blank=True, editable=False), + ), + migrations.AddField( + model_name='historicalcharacter', + name='description_html', + field=models.TextField(blank=True, editable=False), + ), + migrations.AddField( + model_name='historicalfaction', + name='description_html', + field=models.TextField(blank=True, editable=False), + ), + migrations.AddField( + model_name='historicallocation', + name='description_html', + field=models.TextField(blank=True, editable=False), + ), + migrations.AddField( + model_name='historicallocation', + name='description_md', + field=models.TextField(blank=True), + ), + migrations.AddField( + model_name='historicalloot', + name='description_html', + field=models.TextField(blank=True, editable=False), + ), + migrations.AddField( + model_name='location', + name='description_html', + field=models.TextField(blank=True, editable=False), + ), + migrations.AddField( + model_name='location', + name='description_md', + field=models.TextField(blank=True), + ), + migrations.AddField( + model_name='loot', + name='description_html', + field=models.TextField(blank=True, editable=False), + ), + migrations.AlterField( + model_name='historicalloot', + name='value_gold', + field=models.DecimalField(decimal_places=2, max_digits=7, verbose_name='Value (Gold)'), + ), + migrations.AlterField( + model_name='loot', + name='value_gold', + field=models.DecimalField(decimal_places=2, max_digits=7, verbose_name='Value (Gold)'), + ), + ] diff --git a/notes/migrations/0007_auto_20210822_1551.py b/notes/migrations/0007_auto_20210822_1551.py new file mode 100644 index 0000000..e15c605 --- /dev/null +++ b/notes/migrations/0007_auto_20210822_1551.py @@ -0,0 +1,31 @@ +# Generated by Django 3.2.6 on 2021-08-22 15:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('notes', '0006_auto_20210822_1421'), + ] + + operations = [ + migrations.AlterModelOptions( + name='character', + options={'ordering': ['name']}, + ), + migrations.AddField( + model_name='character', + name='subtitle', + field=models.CharField(blank=True, max_length=100), + ), + migrations.AddField( + model_name='historicalcharacter', + name='subtitle', + field=models.CharField(blank=True, max_length=100), + ), + migrations.AlterUniqueTogether( + name='character', + unique_together=set(), + ), + ] diff --git a/notes/migrations/0008_auto_20210822_1610.py b/notes/migrations/0008_auto_20210822_1610.py new file mode 100644 index 0000000..b5ce7af --- /dev/null +++ b/notes/migrations/0008_auto_20210822_1610.py @@ -0,0 +1,24 @@ +# Generated by Django 3.2.6 on 2021-08-22 16:10 + +from django.db import migrations, models +import notes.utils.colors + + +class Migration(migrations.Migration): + + dependencies = [ + ('notes', '0007_auto_20210822_1551'), + ] + + operations = [ + migrations.AddField( + model_name='character', + name='color', + field=models.CharField(default=notes.utils.colors.get_random_color, max_length=6), + ), + migrations.AddField( + model_name='historicalcharacter', + name='color', + field=models.CharField(default=notes.utils.colors.get_random_color, max_length=6), + ), + ] diff --git a/notes/migrations/0009_auto_20210822_1702.py b/notes/migrations/0009_auto_20210822_1702.py new file mode 100644 index 0000000..8de03ab --- /dev/null +++ b/notes/migrations/0009_auto_20210822_1702.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.6 on 2021-08-22 17:02 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('notes', '0008_auto_20210822_1610'), + ] + + operations = [ + migrations.AddField( + model_name='character', + name='image', + field=models.ImageField(blank=True, null=True, upload_to='character_images'), + ), + migrations.AddField( + model_name='historicalcharacter', + name='image', + field=models.TextField(blank=True, max_length=100, null=True), + ), + ] diff --git a/notes/migrations/0010_alter_character_image.py b/notes/migrations/0010_alter_character_image.py new file mode 100644 index 0000000..e7f6129 --- /dev/null +++ b/notes/migrations/0010_alter_character_image.py @@ -0,0 +1,19 @@ +# Generated by Django 3.2.6 on 2021-08-22 17:09 + +from django.db import migrations +import sorl.thumbnail.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('notes', '0009_auto_20210822_1702'), + ] + + operations = [ + migrations.AlterField( + model_name='character', + name='image', + field=sorl.thumbnail.fields.ImageField(blank=True, null=True, upload_to='character_images'), + ), + ] diff --git a/notes/migrations/__init__.py b/notes/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/notes/models/__init__.py b/notes/models/__init__.py new file mode 100644 index 0000000..b6a318e --- /dev/null +++ b/notes/models/__init__.py @@ -0,0 +1,7 @@ +from .descriptionmodel import DescriptionModel +from .campaign import Campaign +from .basemodel import BaseModel +from .faction import Faction +from .location import Location +from .character import Character +from .loot import Loot diff --git a/notes/models/basemodel.py b/notes/models/basemodel.py new file mode 100644 index 0000000..b872f6a --- /dev/null +++ b/notes/models/basemodel.py @@ -0,0 +1,24 @@ +from django.db import models +from django.utils.text import slugify + +from notes.models import Campaign + + +class BaseModel(models.Model): + campaign = models.ForeignKey(Campaign, on_delete=models.PROTECT) + name = models.CharField(max_length=1000) + slug = models.SlugField(editable=False) + + class Meta: + abstract = True + unique_together = ["campaign", "slug"] + + def save(self, *args, **kwargs): + if not self.id: + # Newly created object, so set slug + self.slug = slugify(self.name) + + super(BaseModel, self).save(*args, **kwargs) + + def __str__(self): + return self.name diff --git a/notes/models/campaign.py b/notes/models/campaign.py new file mode 100644 index 0000000..6f7b649 --- /dev/null +++ b/notes/models/campaign.py @@ -0,0 +1,21 @@ +from django.conf import settings +from django.db import models +from django.urls import reverse +from simple_history.models import HistoricalRecords + +from notes.models import DescriptionModel + + +class Campaign(DescriptionModel): + name = models.CharField(max_length=1000) + slug = models.SlugField(unique=True, editable=False) + dm = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT) + created = models.DateTimeField(auto_now_add=True) + last_modified = models.DateTimeField(auto_now=True) + history = HistoricalRecords() + + def get_absolute_url(self): + return reverse('campaigndetail', args=[str(self.slug)]) + + def __str__(self): + return self.name diff --git a/notes/models/character.py b/notes/models/character.py new file mode 100644 index 0000000..9a3b89f --- /dev/null +++ b/notes/models/character.py @@ -0,0 +1,33 @@ +from django.conf import settings +from django.db import models +from django.urls import reverse +from simple_history.models import HistoricalRecords +from sorl.thumbnail import ImageField + +from notes.models import Faction, Location, BaseModel, DescriptionModel +from notes.utils.colors import get_random_color, is_bright_color + + +class Character(BaseModel, DescriptionModel): + subtitle = models.CharField(max_length=100, blank=True) + player = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, blank=True, null=True) + faction = models.ForeignKey(Faction, on_delete=models.PROTECT, blank=True, null=True) + location = models.ForeignKey(Location, on_delete=models.PROTECT, blank=True, null=True) + color = models.CharField(max_length=6, default=get_random_color) + image = ImageField(upload_to="character_images", blank=True, null=True) + + created = models.DateTimeField(auto_now_add=True) + last_modified = models.DateTimeField(auto_now=True) + history = HistoricalRecords() + + class Meta: + ordering = ["name"] + + def get_absolute_url(self): + return reverse('characterdetail', args=[self.campaign.slug, self.slug]) + + def initials(self): + return "".join([word[0] for word in self.name.split()][:2]).upper() + + def text_color(self): + return "black" if is_bright_color(self.color) else "white" diff --git a/notes/models/descriptionmodel.py b/notes/models/descriptionmodel.py new file mode 100644 index 0000000..1cbcb00 --- /dev/null +++ b/notes/models/descriptionmodel.py @@ -0,0 +1,16 @@ +from django.db import models + +from notes.utils.markdown import md_to_html + + +class DescriptionModel(models.Model): + description_md = models.TextField(blank=True) + description_html = models.TextField(blank=True, editable=False) + + class Meta: + abstract = True + + def save(self, *args, **kwargs): + self.description_html = md_to_html(self.description_md) + + super(DescriptionModel, self).save(*args, **kwargs) diff --git a/notes/models/faction.py b/notes/models/faction.py new file mode 100644 index 0000000..c5012d3 --- /dev/null +++ b/notes/models/faction.py @@ -0,0 +1,10 @@ +from django.db import models +from simple_history.models import HistoricalRecords + +from notes.models import BaseModel, DescriptionModel + + +class Faction(BaseModel, DescriptionModel): + created = models.DateTimeField(auto_now_add=True) + last_modified = models.DateTimeField(auto_now=True) + history = HistoricalRecords() diff --git a/notes/models/location.py b/notes/models/location.py new file mode 100644 index 0000000..1f31dc5 --- /dev/null +++ b/notes/models/location.py @@ -0,0 +1,11 @@ +from django.db import models +from simple_history.models import HistoricalRecords + +from notes.models import BaseModel, DescriptionModel + + +class Location(BaseModel, DescriptionModel): + notes = models.TextField(blank=True) + created = models.DateTimeField(auto_now_add=True) + last_modified = models.DateTimeField(auto_now=True) + history = HistoricalRecords() diff --git a/notes/models/loot.py b/notes/models/loot.py new file mode 100644 index 0000000..edb88ad --- /dev/null +++ b/notes/models/loot.py @@ -0,0 +1,25 @@ +from django.conf import settings +from django.db import models +from simple_history.models import HistoricalRecords + +from notes.models import Campaign, DescriptionModel + + +class Loot(DescriptionModel): + campaign = models.ForeignKey(Campaign, on_delete=models.PROTECT) + + name = models.CharField(max_length=1000) + quantity = models.PositiveSmallIntegerField() + value_gold = models.DecimalField("Value (Gold)", max_digits=7, decimal_places=2) + owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, blank=True, null=True) + magic_item = models.BooleanField(default=False) + created = models.DateTimeField(auto_now_add=True) + last_modified = models.DateTimeField(auto_now=True) + history = HistoricalRecords() + + @property + def value_per_unit(self): + return self.value_gold / self.quantity + + def __str__(self): + return self.name diff --git a/notes/templates/base.html b/notes/templates/base.html new file mode 100644 index 0000000..26a9ba6 --- /dev/null +++ b/notes/templates/base.html @@ -0,0 +1,26 @@ +{% load static %} + + + + + + + + {% block title %}{% endblock %} + + + {% if debug %} + + {% else %} + + {% endif %} + + + +
+ {% block mainpage %}{% endblock %} +
+ +{% block extra_js %}{% endblock %} + + diff --git a/notes/templates/navbarbase.html b/notes/templates/navbarbase.html new file mode 100644 index 0000000..557f6fd --- /dev/null +++ b/notes/templates/navbarbase.html @@ -0,0 +1,29 @@ +{% extends "base.html" %} + +{% block mainpage %} + + {% block heading %}{% endblock %} + + {% block content %} + {% endblock %} +{% endblock %} diff --git a/notes/templates/notes/campaign_confirm_delete.html b/notes/templates/notes/campaign_confirm_delete.html new file mode 100644 index 0000000..8bbe57c --- /dev/null +++ b/notes/templates/notes/campaign_confirm_delete.html @@ -0,0 +1,14 @@ +{% extends 'navbarbase.html' %} +{% load django_bootstrap5 %} +{% load static %} + +{% block heading %} +

Add new Loot

+{% endblock %} + +{% block content %} + +
{% csrf_token %} +

Are you sure you want to delete "{{ object }}"?

+ +
{% endblock %} diff --git a/notes/templates/notes/campaign_detail.html b/notes/templates/notes/campaign_detail.html new file mode 100644 index 0000000..8acb532 --- /dev/null +++ b/notes/templates/notes/campaign_detail.html @@ -0,0 +1,5 @@ +{% extends "navbarbase.html" %} + +{% block content %} + {{ object }} +{% endblock %} diff --git a/notes/templates/notes/campaign_edit.html b/notes/templates/notes/campaign_edit.html new file mode 100644 index 0000000..84c5bd6 --- /dev/null +++ b/notes/templates/notes/campaign_edit.html @@ -0,0 +1,20 @@ +{% extends 'base.html' %} +{% load django_bootstrap5 %} +{% load static %} + + +{% block mainpage %} +

Add new campaign

+ +
+ {% csrf_token %} + {% bootstrap_form form %} + +
+{% endblock %} + +{#{% block extra_head %}#} +{# #} +{# #} +{# #} +{#{% endblock %}#} diff --git a/notes/templates/notes/campaign_overview.html b/notes/templates/notes/campaign_overview.html new file mode 100644 index 0000000..2ef5931 --- /dev/null +++ b/notes/templates/notes/campaign_overview.html @@ -0,0 +1,8 @@ +{% extends "base.html" %} + +{% block mainpage %} + {% for campaign in campaigns %} +

{{ campaign.name }}

+ {% endfor %} + create Campaign +{% endblock %} diff --git a/notes/templates/notes/character_detail.html b/notes/templates/notes/character_detail.html new file mode 100644 index 0000000..4a977e5 --- /dev/null +++ b/notes/templates/notes/character_detail.html @@ -0,0 +1,37 @@ +{% extends "navbarbase.html" %} + +{% block content %} +
+
+ + + Add Character +
+
+
+

+ {{ character.name }} + + edit + +

+

{{ character.subtitle }}

+
+
+ {% if character.player %} +
Player:
+
{{ character.player }}
+ {% endif %} +
+ {{ character.description_html|safe }} +
+
+{% endblock %} diff --git a/notes/templates/notes/loot_edit.html b/notes/templates/notes/loot_edit.html new file mode 100644 index 0000000..bffafe6 --- /dev/null +++ b/notes/templates/notes/loot_edit.html @@ -0,0 +1,26 @@ +{% extends 'navbarbase.html' %} +{% load django_bootstrap5 %} +{% load static %} + +{% block heading %} + {% if edit %} +

Edit "{{ object.name }}"

+ {% else %} +

Add new

+ {% endif %} +{% endblock %} + +{% block content %} + +
+ {% csrf_token %} + {% bootstrap_form form %} + +
+{% endblock %} + +{#{% block extra_head %}#} +{# #} +{# #} +{# #} +{#{% endblock %}#} diff --git a/notes/templates/notes/loot_overview.html b/notes/templates/notes/loot_overview.html new file mode 100644 index 0000000..dcf4be4 --- /dev/null +++ b/notes/templates/notes/loot_overview.html @@ -0,0 +1,49 @@ +{% extends "navbarbase.html" %} + +{% block heading %} +

Loot

+{% endblock %} +{% block content %} + + + + + + + + + + + + {% for l in loot %} + + + + + + + + + + + {% endfor %} + +
ItemQuantityTotal ValueOwner
+
+

{{ l.name }} edit

+ {{ l.description_html|safe }} +

Value each: {{ l.value_per_unit|floatformat:-2 }} Gold

+
+
+ + Add Loot + +{% endblock %} + diff --git a/notes/templates/notes/macros/character-pillar.html b/notes/templates/notes/macros/character-pillar.html new file mode 100644 index 0000000..a5563ec --- /dev/null +++ b/notes/templates/notes/macros/character-pillar.html @@ -0,0 +1,25 @@ +{% load thumbutils %} +{% load thumbnail %} + diff --git a/notes/templatetags/__init__.py b/notes/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/notes/templatetags/thumbutils.py b/notes/templatetags/thumbutils.py new file mode 100644 index 0000000..6f1a9fe --- /dev/null +++ b/notes/templatetags/thumbutils.py @@ -0,0 +1,18 @@ +from django import template +from sorl.thumbnail.templatetags.thumbnail import resolution + +from rpg_notes import settings + +register = template.Library() + + +@register.filter() +def srcset(filename): + """ Automatically generate the srcset value based on + THUMBNAIL_ALTERNATIVE_RESOLUTIONS settings + """ + lines = [filename] + for res in settings.THUMBNAIL_ALTERNATIVE_RESOLUTIONS: + res_string = "{}x".format(res) + lines.append("{} {}".format(resolution(filename, res_string), res_string)) + return ','.join(lines) diff --git a/notes/tests.py b/notes/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/notes/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/notes/urls.py b/notes/urls.py new file mode 100644 index 0000000..4ea6264 --- /dev/null +++ b/notes/urls.py @@ -0,0 +1,20 @@ +from django.urls import path + +from notes import views + +urlpatterns = [ + path("", views.CampaignListView.as_view(), name="campaignlist"), + path("c/add", views.CampaignCreateView.as_view(), name="campaigncreate"), + path("c/", views.CampaignDetailView.as_view(), name="campaigndetail"), + path("c//edit", views.CampaignEditView.as_view(), name="campaignedit"), + path("c//delete", views.CampaignDeleteView.as_view(), name="campaigndelete"), + path("c//loot", views.LootListView.as_view(), name="lootlist"), + path("c//loot//edit", views.LootEditView.as_view(), name="lootedit"), + path("c//loot//delete", views.LootDeleteView.as_view(), name="lootdelete"), + path("c//loot/add", views.LootCreateView.as_view(), name="lootadd"), + path("c//character/", views.list_character_redirect, name="characterlist"), + path("c//character/add", views.CharacterCreateView.as_view(), name="characteradd"), + path("c//character/", views.CharacterDetailView.as_view(), name="characterdetail"), + path("c//character//edit", views.CharacterEditView.as_view(), name="characteredit"), + path("c//character//delete", views.CharacterDeleteView.as_view(), name="characterdelete"), +] diff --git a/notes/utils/__init__.py b/notes/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/notes/utils/assets.py b/notes/utils/assets.py new file mode 100644 index 0000000..3d97842 --- /dev/null +++ b/notes/utils/assets.py @@ -0,0 +1,30 @@ +from pathlib import Path + +import sass + +basedir = Path(__file__).resolve().parent.parent.parent + +inputdir = basedir / "static/scss/" +inputfile = inputdir / "main.scss" +outputfile = basedir / "static/css/main.css" +sourcemap = outputfile.with_suffix(".css.map") + + +def get_css(debug=False): + sourcemap_name = "css_sourcemap" if debug else str(sourcemap) + css, sourcemap_text = sass.compile( + filename=str(inputfile), + output_style="nested" if debug else "compressed", + include_paths=[str(inputdir), str(basedir)], + source_map_filename=sourcemap_name, + source_map_contents=True + ) + return css, sourcemap_text + + +def save_css(): + css, sourcemap_text = get_css() + with outputfile.open("w") as f: + f.write(css) + with sourcemap.open("w") as f: + f.write(sourcemap_text) diff --git a/notes/utils/colors.py b/notes/utils/colors.py new file mode 100644 index 0000000..7535aec --- /dev/null +++ b/notes/utils/colors.py @@ -0,0 +1,301 @@ +""" +colors from https://stackoverflow.com/a/33295456/4398037 +""" +import random +from typing import Tuple + +color_string = """#B88183 +#922329 +#5A0007 +#D7BFC2 +#D86A78 +#FF8A9A +#3B000A +#E20027 +#943A4D +#5B4E51 +#B05B6F +#FEB2C6 +#D83D66 +#895563 +#FF1A59 +#FFDBE5 +#CC0744 +#CB7E98 +#997D87 +#6A3A4C +#FF2F80 +#6B002C +#A74571 +#C6005A +#FF5DA7 +#300018 +#B894A6 +#FF90C9 +#7C6571 +#A30059 +#DA007C +#5B113C +#402334 +#D157A0 +#DDB6D0 +#885578 +#962B75 +#A97399 +#D20096 +#E773CE +#AA5199 +#E704C4 +#6B3A64 +#FFA0F2 +#6F0062 +#B903AA +#C895C5 +#FF34FF +#320033 +#DBD5DD +#EEC3FF +#BC23FF +#671190 +#201625 +#F5E1FF +#BC65E9 +#D790FF +#72418F +#4A3B53 +#9556BD +#B4A8BD +#7900D7 +#A079BF +#958A9F +#837393 +#64547B +#3A2465 +#353339 +#BCB1E5 +#9F94F0 +#9695C5 +#0000A6 +#000035 +#636375 +#00005F +#97979E +#7A7BFF +#3C3E6E +#6367A9 +#494B5A +#3B5DFF +#C8D0F6 +#6D80BA +#8FB0FF +#0045D2 +#7A87A1 +#324E72 +#00489C +#0060CD +#789EC9 +#012C58 +#99ADC0 +#001325 +#DDEFFF +#59738A +#0086ED +#75797C +#BDC9D2 +#3E89BE +#8CD0FF +#0AA3F7 +#6B94AA +#29607C +#404E55 +#006FA6 +#013349 +#0AA6D8 +#658188 +#5EBCD1 +#456D75 +#0089A3 +#B5F4FF +#02525F +#1CE6FF +#001C1E +#203B3C +#A3C8C9 +#00A6AA +#00C6C8 +#006A66 +#518A87 +#E4FFFC +#66E1D3 +#004D43 +#809693 +#15A08A +#00846F +#00C2A0 +#00FECF +#78AFA1 +#02684E +#C2FFED +#47675D +#00D891 +#004B28 +#8ADBB4 +#0CBD66 +#549E79 +#1A3A2A +#6C8F7D +#008941 +#63FFAC +#1BE177 +#006C31 +#B5D6C3 +#3D4F44 +#4B8160 +#66796D +#71BB8C +#04F757 +#001E09 +#D2DCD5 +#00B433 +#9FB2A4 +#003109 +#A3F3AB +#456648 +#51A058 +#83A485 +#7ED379 +#D1F7CE +#A1C299 +#061203 +#1E6E00 +#5EFF03 +#55813B +#3B9700 +#4FC601 +#1B4400 +#C2FF99 +#788D66 +#868E7E +#83AB58 +#374527 +#98D058 +#C6DC99 +#A4E804 +#76912F +#8BB400 +#34362D +#4C6001 +#DFFB71 +#6A714A +#222800 +#6B7900 +#3A3F00 +#BEC459 +#FEFFE6 +#A3A489 +#9FA064 +#FFFF00 +#61615A +#FFFFFE +#9B9700 +#CFCDAC +#797868 +#575329 +#FFF69F +#8D8546 +#F4D749 +#7E6405 +#1D1702 +#CCAA35 +#CCB87C +#453C23 +#513A01 +#FFB500 +#A77500 +#D68E01 +#B79762 +#7A4900 +#372101 +#886F4C +#A45B02 +#E7AB63 +#FAD09F +#C0B9B2 +#938A81 +#A38469 +#D16100 +#A76F42 +#5B4534 +#5B3213 +#CA834E +#FF913F +#953F00 +#D0AC94 +#7D5A44 +#BE4700 +#FDE8DC +#772600 +#A05837 +#EA8B66 +#391406 +#FF6832 +#C86240 +#29201D +#B77B68 +#806C66 +#FFAA92 +#89412E +#E83000 +#A88C85 +#F7C9BF +#643127 +#E98176 +#7B4F4B +#1E0200 +#9C6966 +#BF5650 +#BA0900 +#FF4A46 +#F4ABAA +#000000 +#452C2C +#C8A1A1""" + + +def get_all_colors(): + colors = [] + for c in color_string.split("\n"): + colors.append(c.strip()[1:]) + return colors + + +def get_random_color() -> str: + return random.choice(get_all_colors()) + + +## color math from +## https://stackoverflow.com/a/56678483/4398037 + +def gamma_correction(value: float) -> float: + if value < 0.04045: + return value / 12.92 + else: + return ((value + 0.055) / 1.055) ** 2.4 + + +def get_luminance(rgb: Tuple[int, ...]) -> float: + r, g, b = list(map(gamma_correction, rgb)) + return 0.2126 * r + 0.7152 * g + 0.0722 * b + + +def get_percieved_lightness(rgb: Tuple[int, ...]) -> float: + Y = get_luminance(rgb) + if Y <= (216 / 24389): + return Y * (24389 / 27) + else: + return Y ** (1 / 3) * 116 - 16 + + +def is_bright_color(bg_color: str) -> bool: + rgb = tuple(int(bg_color[i:i + 2], 16) for i in (0, 2, 4)) + return get_percieved_lightness(rgb) > 50 diff --git a/notes/utils/markdown.py b/notes/utils/markdown.py new file mode 100644 index 0000000..07ebdee --- /dev/null +++ b/notes/utils/markdown.py @@ -0,0 +1,19 @@ +import bleach +import markdown +from bleach_allowlist import markdown_tags, markdown_attrs + + +def md_to_html(md: str) -> str: + html = markdown.markdown( + md, + output_format="html", + extensions=[ + "nl2br", + ] + ) + html = bleach.clean( + html, + tags=markdown_tags, + attributes=markdown_attrs + ) + return html diff --git a/notes/views/__init__.py b/notes/views/__init__.py new file mode 100644 index 0000000..233e54f --- /dev/null +++ b/notes/views/__init__.py @@ -0,0 +1,4 @@ +from .campaign_views import * +from .loot_views import * +from .character_views import * +from .debugviews import * diff --git a/notes/views/campaign_views.py b/notes/views/campaign_views.py new file mode 100644 index 0000000..5e363b8 --- /dev/null +++ b/notes/views/campaign_views.py @@ -0,0 +1,38 @@ +from django.urls import reverse_lazy +from django.views import generic + +from notes.forms import CampaignForm +from notes.models import Campaign + + +class CampaignListView(generic.ListView): + template_name = "notes/campaign_overview.html" + model = Campaign + context_object_name = "campaigns" + + +class CampaignDetailView(generic.DetailView): + template_name = "notes/campaign_detail.html" + model = Campaign + slug_url_kwarg = "campslug" + + +class CampaignCreateView(generic.CreateView): + template_name = "notes/campaign_edit.html" + model = Campaign + form_class = CampaignForm + slug_url_kwarg = "campslug" + + +class CampaignEditView(generic.UpdateView): + template_name = "notes/campaign_edit.html" + model = Campaign + form_class = CampaignForm + slug_url_kwarg = "campslug" + + +class CampaignDeleteView(generic.DeleteView): + template_name = "notes/campaign_confirm_delete.html" + model = Campaign + slug_url_kwarg = "campslug" + success_url = reverse_lazy('campaignlist') diff --git a/notes/views/character_views.py b/notes/views/character_views.py new file mode 100644 index 0000000..17f9894 --- /dev/null +++ b/notes/views/character_views.py @@ -0,0 +1,69 @@ +from django.shortcuts import redirect +from django.urls import reverse_lazy +from django.views import generic + +from notes.forms import CharacterForm +from notes.models import Character, Campaign + + +# class CharacterListView(generic.ListView): +# template_name = "notes/character_overview.html" +# model = Character +# context_object_name = "character" +# +# +# + +def list_character_redirect(request, *args, **kwargs): + first_character:Character=Character.objects.first() + + return redirect(first_character) + + +class CharacterDetailView(generic.DetailView): + template_name = "notes/character_detail.html" + model = Character + context_object_name = "character" + + def get_context_data(self, **kwargs): + data = super().get_context_data(**kwargs) + 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) + return data + + def get_object(self, queryset=None): + return Character.objects.get(campaign__slug=self.kwargs['campslug'], slug=self.kwargs['charslug']) + + +class CharacterCreateView(generic.CreateView): + template_name = "notes/loot_edit.html" + model = Character + form_class = CharacterForm + 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 CharacterEditView(generic.UpdateView): + template_name = "notes/loot_edit.html" + model = Character + form_class = CharacterForm + + def get_object(self, queryset=None): + return Character.objects.get(campaign__slug=self.kwargs['campslug'], slug=self.kwargs['charslug']) + + def get_context_data(self, **kwargs): + data = super().get_context_data(**kwargs) + data['edit'] = True + return data + + +class CharacterDeleteView(generic.DeleteView): + template_name = "notes/campaign_confirm_delete.html" + model = Character + success_url = reverse_lazy('characterlist') diff --git a/notes/views/debugviews.py b/notes/views/debugviews.py new file mode 100644 index 0000000..38ecb76 --- /dev/null +++ b/notes/views/debugviews.py @@ -0,0 +1,13 @@ +from django.http import HttpResponse + +from notes.utils.assets import get_css + + +def debug_css(request): + css, source_map = get_css(debug=True) + return HttpResponse(css, content_type="text/css") + + +def debug_css_sourcemap(request): + css, source_map = get_css(debug=True) + return HttpResponse(source_map, content_type="application/json") diff --git a/notes/views/loot_views.py b/notes/views/loot_views.py new file mode 100644 index 0000000..eff97b2 --- /dev/null +++ b/notes/views/loot_views.py @@ -0,0 +1,51 @@ +from django.urls import reverse_lazy, reverse +from django.views import generic + +from notes.forms import LootForm +from notes.models import Loot, Campaign + + +class LootListView(generic.ListView): + template_name = "notes/loot_overview.html" + model = Loot + context_object_name = "loot" + + def get_queryset(self): + return Loot.objects.filter(campaign__slug=self.kwargs['campslug']) + + +class LootDetailView(generic.DetailView): + template_name = "notes/loot_detail.html" + model = Loot + + +class LootCreateView(generic.CreateView): + template_name = "notes/loot_edit.html" + model = Loot + form_class = LootForm + + def form_valid(self, form): + form.instance.campaign = Campaign.objects.get(slug=self.kwargs['campslug']) + return super().form_valid(form) + + success_url = reverse_lazy('lootlist') + + +class LootEditView(generic.UpdateView): + template_name = "notes/loot_edit.html" + model = Loot + form_class = LootForm + context_object_name = "object" + + 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 + return data + + +class LootDeleteView(generic.DeleteView): + template_name = "notes/loot_confirm_delete.html" + model = Loot + success_url = reverse_lazy('lootlist') diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..979ac13 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,48 @@ +{ + "name": "RPGnotes", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "dependencies": { + "bootstrap": "^5.1.0" + } + }, + "node_modules/@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, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/bootstrap": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.0.tgz", + "integrity": "sha512-bs74WNI9BgBo3cEovmdMHikSKoXnDgA6VQjJ7TyTotU6L7d41ZyCEEelPwkYEzsG/Zjv3ie9IE3EMAje0W9Xew==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + }, + "peerDependencies": { + "@popperjs/core": "^2.9.3" + } + } + }, + "dependencies": { + "@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 + }, + "bootstrap": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.0.tgz", + "integrity": "sha512-bs74WNI9BgBo3cEovmdMHikSKoXnDgA6VQjJ7TyTotU6L7d41ZyCEEelPwkYEzsG/Zjv3ie9IE3EMAje0W9Xew==", + "requires": {} + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..6f97120 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "bootstrap": "^5.1.0" + } +} diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..bc4a97b --- /dev/null +++ b/poetry.lock @@ -0,0 +1,323 @@ +[[package]] +name = "asgiref" +version = "3.4.1" +description = "ASGI specs, helper code, and adapters" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"] + +[[package]] +name = "beautifulsoup4" +version = "4.9.3" +description = "Screen-scraping library" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +soupsieve = {version = ">1.2", markers = "python_version >= \"3.0\""} + +[package.extras] +html5lib = ["html5lib"] +lxml = ["lxml"] + +[[package]] +name = "bleach" +version = "4.0.0" +description = "An easy safelist-based HTML-sanitizing tool." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +packaging = "*" +six = ">=1.9.0" +webencodings = "*" + +[[package]] +name = "bleach-allowlist" +version = "1.0.3" +description = "Curated lists of tags and attributes for sanitizing html" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "django" +version = "3.2.6" +description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +asgiref = ">=3.3.2,<4" +pytz = "*" +sqlparse = ">=0.2.2" + +[package.extras] +argon2 = ["argon2-cffi (>=19.1.0)"] +bcrypt = ["bcrypt"] + +[[package]] +name = "django-bootstrap5" +version = "2.1.2" +description = "Bootstrap 5 for Django" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +beautifulsoup4 = ">=4.8.0" +Django = ">=2.2" + +[[package]] +name = "django-simple-history" +version = "3.0.0" +description = "Store model history and view/revert changes from admin site." +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "libsass" +version = "0.21.0" +description = "Sass for Python: A straightforward binding of libsass for Python." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +six = "*" + +[[package]] +name = "markdown" +version = "3.3.4" +description = "Python implementation of Markdown." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +testing = ["coverage", "pyyaml"] + +[[package]] +name = "packaging" +version = "21.0" +description = "Core utilities for Python packages" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pyparsing = ">=2.0.2" + +[[package]] +name = "pillow" +version = "8.3.1" +description = "Python Imaging Library (Fork)" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "psycopg2" +version = "2.9.1" +description = "psycopg2 - Python-PostgreSQL Database Adapter" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "pyparsing" +version = "2.4.7" +description = "Python parsing module" +category = "main" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "pytz" +version = "2021.1" +description = "World timezone definitions, modern and historical" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "sorl-thumbnail" +version = "12.7.0" +description = "Thumbnails for Django" +category = "main" +optional = false +python-versions = ">=3.4" + +[[package]] +name = "soupsieve" +version = "2.2.1" +description = "A modern CSS selector implementation for Beautiful Soup." +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "sqlparse" +version = "0.4.1" +description = "A non-validating SQL parser." +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "webencodings" +version = "0.5.1" +description = "Character encoding aliases for legacy web content" +category = "main" +optional = false +python-versions = "*" + +[metadata] +lock-version = "1.1" +python-versions = "^3.9" +content-hash = "245e8ddb9d285feb2d4ab950fa117b8422d0634fece9a59938b663cbeb4f45ae" + +[metadata.files] +asgiref = [ + {file = "asgiref-3.4.1-py3-none-any.whl", hash = "sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214"}, + {file = "asgiref-3.4.1.tar.gz", hash = "sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9"}, +] +beautifulsoup4 = [ + {file = "beautifulsoup4-4.9.3-py2-none-any.whl", hash = "sha256:4c98143716ef1cb40bf7f39a8e3eec8f8b009509e74904ba3a7b315431577e35"}, + {file = "beautifulsoup4-4.9.3-py3-none-any.whl", hash = "sha256:fff47e031e34ec82bf17e00da8f592fe7de69aeea38be00523c04623c04fb666"}, + {file = "beautifulsoup4-4.9.3.tar.gz", hash = "sha256:84729e322ad1d5b4d25f805bfa05b902dd96450f43842c4e99067d5e1369eb25"}, +] +bleach = [ + {file = "bleach-4.0.0-py2.py3-none-any.whl", hash = "sha256:c1685a132e6a9a38bf93752e5faab33a9517a6c0bb2f37b785e47bf253bdb51d"}, + {file = "bleach-4.0.0.tar.gz", hash = "sha256:ffa9221c6ac29399cc50fcc33473366edd0cf8d5e2cbbbb63296dc327fb67cc8"}, +] +bleach-allowlist = [ + {file = "bleach-allowlist-1.0.3.tar.gz", hash = "sha256:56e22086079a0e6a3100ae99e4dbaf20eb2585486598eb0e994008a11428da9b"}, + {file = "bleach_allowlist-1.0.3-py2.py3-none-any.whl", hash = "sha256:6427a3ac0baddf549703f1003ea53f411f3d7888524c94c00eb6ef1deb378899"}, +] +django = [ + {file = "Django-3.2.6-py3-none-any.whl", hash = "sha256:7f92413529aa0e291f3be78ab19be31aefb1e1c9a52cd59e130f505f27a51f13"}, + {file = "Django-3.2.6.tar.gz", hash = "sha256:f27f8544c9d4c383bbe007c57e3235918e258364577373d4920e9162837be022"}, +] +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-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"}, +] +libsass = [ + {file = "libsass-0.21.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:06c8776417fe930714bdc930a3d7e795ae3d72be6ac883ff72a1b8f7c49e5ffb"}, + {file = "libsass-0.21.0-cp27-cp27m-win32.whl", hash = "sha256:a005f298f64624f313a3ac618ab03f844c71d84ae4f4a4aec4b68d2a4ffe75eb"}, + {file = "libsass-0.21.0-cp27-cp27m-win_amd64.whl", hash = "sha256:6b984510ed94993708c0d697b4fef2d118929bbfffc3b90037be0f5ccadf55e7"}, + {file = "libsass-0.21.0-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1e25dd9047a9392d3c59a0b869e0404f2b325a03871ee45285ee33b3664f5613"}, + {file = "libsass-0.21.0-cp36-abi3-macosx_10_14_x86_64.whl", hash = "sha256:12f39712de38689a8b785b7db41d3ba2ea1d46f9379d81ea4595802d91fa6529"}, + {file = "libsass-0.21.0-cp36-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e2b1a7d093f2e76dc694c17c0c285e846d0b0deb0e8b21dc852ba1a3a4e2f1d6"}, + {file = "libsass-0.21.0-cp36-abi3-win32.whl", hash = "sha256:abc29357ee540849faf1383e1746d40d69ed5cb6d4c346df276b258f5aa8977a"}, + {file = "libsass-0.21.0-cp36-abi3-win_amd64.whl", hash = "sha256:659ae41af8708681fa3ec73f47b9735a6725e71c3b66ff570bfce78952f2314e"}, + {file = "libsass-0.21.0.tar.gz", hash = "sha256:d5ba529d9ce668be9380563279f3ffe988f27bc5b299c5a28453df2e0b0fbaf2"}, +] +markdown = [ + {file = "Markdown-3.3.4-py3-none-any.whl", hash = "sha256:96c3ba1261de2f7547b46a00ea8463832c921d3f9d6aba3f255a6f71386db20c"}, + {file = "Markdown-3.3.4.tar.gz", hash = "sha256:31b5b491868dcc87d6c24b7e3d19a0d730d59d3e46f4eea6430a321bed387a49"}, +] +packaging = [ + {file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"}, + {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"}, +] +pillow = [ + {file = "Pillow-8.3.1-1-cp36-cp36m-win_amd64.whl", hash = "sha256:fd7eef578f5b2200d066db1b50c4aa66410786201669fb76d5238b007918fb24"}, + {file = "Pillow-8.3.1-1-cp37-cp37m-win_amd64.whl", hash = "sha256:75e09042a3b39e0ea61ce37e941221313d51a9c26b8e54e12b3ececccb71718a"}, + {file = "Pillow-8.3.1-1-cp38-cp38-win_amd64.whl", hash = "sha256:c0e0550a404c69aab1e04ae89cca3e2a042b56ab043f7f729d984bf73ed2a093"}, + {file = "Pillow-8.3.1-1-cp39-cp39-win_amd64.whl", hash = "sha256:479ab11cbd69612acefa8286481f65c5dece2002ffaa4f9db62682379ca3bb77"}, + {file = "Pillow-8.3.1-1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:f156d6ecfc747ee111c167f8faf5f4953761b5e66e91a4e6767e548d0f80129c"}, + {file = "Pillow-8.3.1-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:196560dba4da7a72c5e7085fccc5938ab4075fd37fe8b5468869724109812edd"}, + {file = "Pillow-8.3.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c9569049d04aaacd690573a0398dbd8e0bf0255684fee512b413c2142ab723"}, + {file = "Pillow-8.3.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c088a000dfdd88c184cc7271bfac8c5b82d9efa8637cd2b68183771e3cf56f04"}, + {file = "Pillow-8.3.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:fc214a6b75d2e0ea7745488da7da3c381f41790812988c7a92345978414fad37"}, + {file = "Pillow-8.3.1-cp36-cp36m-win32.whl", hash = "sha256:a17ca41f45cf78c2216ebfab03add7cc350c305c38ff34ef4eef66b7d76c5229"}, + {file = "Pillow-8.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:67b3666b544b953a2777cb3f5a922e991be73ab32635666ee72e05876b8a92de"}, + {file = "Pillow-8.3.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:ff04c373477723430dce2e9d024c708a047d44cf17166bf16e604b379bf0ca14"}, + {file = "Pillow-8.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9364c81b252d8348e9cc0cb63e856b8f7c1b340caba6ee7a7a65c968312f7dab"}, + {file = "Pillow-8.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a2f381932dca2cf775811a008aa3027671ace723b7a38838045b1aee8669fdcf"}, + {file = "Pillow-8.3.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d0da39795049a9afcaadec532e7b669b5ebbb2a9134576ebcc15dd5bdae33cc0"}, + {file = "Pillow-8.3.1-cp37-cp37m-win32.whl", hash = "sha256:2b6dfa068a8b6137da34a4936f5a816aba0ecc967af2feeb32c4393ddd671cba"}, + {file = "Pillow-8.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a4eef1ff2d62676deabf076f963eda4da34b51bc0517c70239fafed1d5b51500"}, + {file = "Pillow-8.3.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:660a87085925c61a0dcc80efb967512ac34dbb256ff7dd2b9b4ee8dbdab58cf4"}, + {file = "Pillow-8.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:15a2808e269a1cf2131930183dcc0419bc77bb73eb54285dde2706ac9939fa8e"}, + {file = "Pillow-8.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:969cc558cca859cadf24f890fc009e1bce7d7d0386ba7c0478641a60199adf79"}, + {file = "Pillow-8.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2ee77c14a0299d0541d26f3d8500bb57e081233e3fa915fa35abd02c51fa7fae"}, + {file = "Pillow-8.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c11003197f908878164f0e6da15fce22373ac3fc320cda8c9d16e6bba105b844"}, + {file = "Pillow-8.3.1-cp38-cp38-win32.whl", hash = "sha256:3f08bd8d785204149b5b33e3b5f0ebbfe2190ea58d1a051c578e29e39bfd2367"}, + {file = "Pillow-8.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:70af7d222df0ff81a2da601fab42decb009dc721545ed78549cb96e3a1c5f0c8"}, + {file = "Pillow-8.3.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:37730f6e68bdc6a3f02d2079c34c532330d206429f3cee651aab6b66839a9f0e"}, + {file = "Pillow-8.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4bc3c7ef940eeb200ca65bd83005eb3aae8083d47e8fcbf5f0943baa50726856"}, + {file = "Pillow-8.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c35d09db702f4185ba22bb33ef1751ad49c266534339a5cebeb5159d364f6f82"}, + {file = "Pillow-8.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b2efa07f69dc395d95bb9ef3299f4ca29bcb2157dc615bae0b42c3c20668ffc"}, + {file = "Pillow-8.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cc866706d56bd3a7dbf8bac8660c6f6462f2f2b8a49add2ba617bc0c54473d83"}, + {file = "Pillow-8.3.1-cp39-cp39-win32.whl", hash = "sha256:9a211b663cf2314edbdb4cf897beeb5c9ee3810d1d53f0e423f06d6ebbf9cd5d"}, + {file = "Pillow-8.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:c2a5ff58751670292b406b9f06e07ed1446a4b13ffced6b6cab75b857485cbc8"}, + {file = "Pillow-8.3.1-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:c379425c2707078dfb6bfad2430728831d399dc95a7deeb92015eb4c92345eaf"}, + {file = "Pillow-8.3.1-pp36-pypy36_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:114f816e4f73f9ec06997b2fde81a92cbf0777c9e8f462005550eed6bae57e63"}, + {file = "Pillow-8.3.1-pp36-pypy36_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8960a8a9f4598974e4c2aeb1bff9bdd5db03ee65fd1fce8adf3223721aa2a636"}, + {file = "Pillow-8.3.1-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:147bd9e71fb9dcf08357b4d530b5167941e222a6fd21f869c7911bac40b9994d"}, + {file = "Pillow-8.3.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1fd5066cd343b5db88c048d971994e56b296868766e461b82fa4e22498f34d77"}, + {file = "Pillow-8.3.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f4ebde71785f8bceb39dcd1e7f06bcc5d5c3cf48b9f69ab52636309387b097c8"}, + {file = "Pillow-8.3.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:1c03e24be975e2afe70dfc5da6f187eea0b49a68bb2b69db0f30a61b7031cee4"}, + {file = "Pillow-8.3.1.tar.gz", hash = "sha256:2cac53839bfc5cece8fdbe7f084d5e3ee61e1303cccc86511d351adcb9e2c792"}, +] +psycopg2 = [ + {file = "psycopg2-2.9.1-cp36-cp36m-win32.whl", hash = "sha256:7f91312f065df517187134cce8e395ab37f5b601a42446bdc0f0d51773621854"}, + {file = "psycopg2-2.9.1-cp36-cp36m-win_amd64.whl", hash = "sha256:830c8e8dddab6b6716a4bf73a09910c7954a92f40cf1d1e702fb93c8a919cc56"}, + {file = "psycopg2-2.9.1-cp37-cp37m-win32.whl", hash = "sha256:89409d369f4882c47f7ea20c42c5046879ce22c1e4ea20ef3b00a4dfc0a7f188"}, + {file = "psycopg2-2.9.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7640e1e4d72444ef012e275e7b53204d7fab341fb22bc76057ede22fe6860b25"}, + {file = "psycopg2-2.9.1-cp38-cp38-win32.whl", hash = "sha256:079d97fc22de90da1d370c90583659a9f9a6ee4007355f5825e5f1c70dffc1fa"}, + {file = "psycopg2-2.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:2c992196719fadda59f72d44603ee1a2fdcc67de097eea38d41c7ad9ad246e62"}, + {file = "psycopg2-2.9.1-cp39-cp39-win32.whl", hash = "sha256:2087013c159a73e09713294a44d0c8008204d06326006b7f652bef5ace66eebb"}, + {file = "psycopg2-2.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:bf35a25f1aaa8a3781195595577fcbb59934856ee46b4f252f56ad12b8043bcf"}, + {file = "psycopg2-2.9.1.tar.gz", hash = "sha256:de5303a6f1d0a7a34b9d40e4d3bef684ccc44a49bbe3eb85e3c0bffb4a131b7c"}, +] +pyparsing = [ + {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, + {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, +] +pytz = [ + {file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"}, + {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"}, +] +six = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] +sorl-thumbnail = [ + {file = "sorl-thumbnail-12.7.0.tar.gz", hash = "sha256:fbe6dfd66a1aceb7e0203895ff5622775e50266f8d8cfd841fe1500bd3e19018"}, + {file = "sorl_thumbnail-12.7.0-py3-none-any.whl", hash = "sha256:c56cd651feab3bdc415d5301600198e2e70c08234dad48b8f6cfa4746cc102c7"}, +] +soupsieve = [ + {file = "soupsieve-2.2.1-py3-none-any.whl", hash = "sha256:c2c1c2d44f158cdbddab7824a9af8c4f83c76b1e23e049479aa432feb6c4c23b"}, + {file = "soupsieve-2.2.1.tar.gz", hash = "sha256:052774848f448cf19c7e959adf5566904d525f33a3f8b6ba6f6f8f26ec7de0cc"}, +] +sqlparse = [ + {file = "sqlparse-0.4.1-py3-none-any.whl", hash = "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0"}, + {file = "sqlparse-0.4.1.tar.gz", hash = "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8"}, +] +webencodings = [ + {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, + {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..e4e1128 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,24 @@ +[tool.poetry] +name = "rpgnotes" +version = "0.1.0" +description = "" +authors = ["Lukas Winkler "] + +[tool.poetry.dependencies] +python = "^3.9" +Django = "^3.2.6" +psycopg2 = "^2.9.1" +django-simple-history = "^3.0.0" +django-bootstrap5 = "^2.1.2" +Markdown = "^3.3.4" +bleach = "^4.0.0" +bleach-allowlist = "^1.0.3" +Pillow = "^8.3.1" +sorl-thumbnail = "^12.7.0" +libsass = "^0.21.0" + +[tool.poetry.dev-dependencies] + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/rpg_notes/__init__.py b/rpg_notes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rpg_notes/asgi.py b/rpg_notes/asgi.py new file mode 100644 index 0000000..f63366d --- /dev/null +++ b/rpg_notes/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for rpg_notes project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'rpg_notes.settings') + +application = get_asgi_application() diff --git a/rpg_notes/settings.py b/rpg_notes/settings.py new file mode 100644 index 0000000..ae04e9f --- /dev/null +++ b/rpg_notes/settings.py @@ -0,0 +1,115 @@ +""" +Django settings for rpg_notes project. + +Generated by 'django-admin startproject' using Django 3.2.6. + +For more information on this file, see +https://docs.djangoproject.com/en/3.2/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/3.2/ref/settings/ +""" + +from pathlib import Path + +from .secrets import * + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ + + +ALLOWED_HOSTS = [] + +# Application definition + +INSTALLED_APPS = [ + 'notes.apps.NotesConfig', + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'simple_history', + 'django_bootstrap5', + 'sorl.thumbnail' +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'simple_history.middleware.HistoryRequestMiddleware' +] + +ROOT_URLCONF = 'rpg_notes.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [BASE_DIR / 'templates'], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'rpg_notes.wsgi.application' + +# Password validation +# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + +# Internationalization +# https://docs.djangoproject.com/en/3.2/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/3.2/howto/static-files/ + +STATIC_URL = '/static/' + +MEDIA_URL = '/media/' + +# Default primary key field type +# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +THUMBNAIL_ALTERNATIVE_RESOLUTIONS = [2, 3] diff --git a/rpg_notes/urls.py b/rpg_notes/urls.py new file mode 100644 index 0000000..1017512 --- /dev/null +++ b/rpg_notes/urls.py @@ -0,0 +1,31 @@ +"""rpg_notes URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/3.2/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +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')) +""" +from django.conf.urls.static import static +from django.contrib import admin +from django.urls import path, include + +from notes import views +from rpg_notes import settings + +urlpatterns = [ + path('admin/', admin.site.urls), + path('', include("notes.urls")) +] + +if settings.DEBUG: + 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")) diff --git a/rpg_notes/wsgi.py b/rpg_notes/wsgi.py new file mode 100644 index 0000000..3e948a6 --- /dev/null +++ b/rpg_notes/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for rpg_notes project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'rpg_notes.settings') + +application = get_wsgi_application() diff --git a/static/libs/bootstrap.min.css b/static/libs/bootstrap.min.css new file mode 120000 index 0000000..93c3bac --- /dev/null +++ b/static/libs/bootstrap.min.css @@ -0,0 +1 @@ +../../node_modules/bootstrap/dist/css/bootstrap.min.css \ No newline at end of file diff --git a/static/libs/bootstrap.min.css.map b/static/libs/bootstrap.min.css.map new file mode 120000 index 0000000..7a98d20 --- /dev/null +++ b/static/libs/bootstrap.min.css.map @@ -0,0 +1 @@ +../../node_modules/bootstrap/dist/css/bootstrap.min.css.map \ No newline at end of file diff --git a/static/libs/bootstrap.min.js b/static/libs/bootstrap.min.js new file mode 120000 index 0000000..e734ff9 --- /dev/null +++ b/static/libs/bootstrap.min.js @@ -0,0 +1 @@ +../../node_modules/bootstrap/dist/js/bootstrap.min.js \ No newline at end of file diff --git a/static/libs/bootstrap.min.js.map b/static/libs/bootstrap.min.js.map new file mode 120000 index 0000000..6b00217 --- /dev/null +++ b/static/libs/bootstrap.min.js.map @@ -0,0 +1 @@ +../../node_modules/bootstrap/dist/js/bootstrap.min.js.map \ No newline at end of file diff --git a/static/scss/_misc.scss b/static/scss/_misc.scss new file mode 100644 index 0000000..26e53b4 --- /dev/null +++ b/static/scss/_misc.scss @@ -0,0 +1,54 @@ +.collapse-cell { + padding: 0 !important; + border: none !important; +} + +.chev { + transition: transform .2s; + width: 13px; + height: auto; +} + +tr.collapsed .chev { + transform: rotate(90deg); +} + +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; + position: relative; + + > div { + padding: 0.5rem; + + } + + .text-col { + a { + text-decoration: none; + &:focus,&:active,&:hover{ + text-decoration: underline; + } + } + } + +} +.character-heading { + border-bottom: solid 2px; +} diff --git a/static/scss/_variables.scss b/static/scss/_variables.scss new file mode 100644 index 0000000..e69de29 diff --git a/static/scss/main.scss b/static/scss/main.scss new file mode 100644 index 0000000..698d964 --- /dev/null +++ b/static/scss/main.scss @@ -0,0 +1,5 @@ +@import "variables"; + +@import "node_modules/bootstrap/scss/bootstrap"; + +@import "misc";