diff --git a/.gitignore b/.gitignore index 7a0a341..03baf9d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,7 @@ db.sqlite3 .hidden_notes.txt env/ *pyc +*.py[cod] __pycache__/ xxxthemexxx/ +gurps_vars.sh diff --git a/django_gurps/settings.py b/django_gurps/settings.py index 2e29cbc..7cf1c57 100644 --- a/django_gurps/settings.py +++ b/django_gurps/settings.py @@ -21,29 +21,28 @@ BASE_DIR = Path(__file__).resolve().parent.parent # See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = "django-insecure-x%rs&gy9iphje-l+!^!g@s@w$4@oc0^honvnr-!edwm+uujiu2" +SECRET_KEY = ( + "django-insecure-x%rs&gy9iphje-l+!^!g@s@w$4@oc0^honvnr-!edwm+uujiu2" +) # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True ALLOWED_HOSTS = [ - "gcs.neill.id.au", - "localhost", - "127.0.0.1", - ] + "gcs.neill.id.au", + "localhost", + "127.0.0.1", +] CSRF_TRUSTED_ORIGINS = [ "https://gcs.neill.id.au", - ] +] AUTHENTICATION_BACKENDS = [ - # Needed to log in by username in Django admin, regardless of `allauth` - 'django.contrib.auth.backends.ModelBackend', - + "django.contrib.auth.backends.ModelBackend", # `allauth` specific authentication methods, such as login by email - 'allauth.account.auth_backends.AuthenticationBackend', - + "allauth.account.auth_backends.AuthenticationBackend", ] # Application definition @@ -55,20 +54,18 @@ INSTALLED_APPS = [ "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", - - 'allauth', - 'allauth.account', - 'allauth.socialaccount', + "allauth", + "allauth.account", + "allauth.socialaccount", # ... include the providers you want to enable: - 'allauth.socialaccount.providers.amazon', - 'allauth.socialaccount.providers.apple', - 'allauth.socialaccount.providers.google', - + "allauth.socialaccount.providers.amazon", + "allauth.socialaccount.providers.apple", + "allauth.socialaccount.providers.google", "django.contrib.staticfiles", # "tailwind", # "theme", "django_browser_reload", - 'django_bootstrap_icons', + "django_bootstrap_icons", ] MIDDLEWARE = [ @@ -97,7 +94,7 @@ TEMPLATES = [ "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", - 'django.template.context_processors.request', + "django.template.context_processors.request", ], }, }, @@ -169,7 +166,7 @@ DATABASES = { } } -TAILWIND_APP_NAME = 'theme' +TAILWIND_APP_NAME = "theme" INTERNAL_IPS = [ "127.0.0.1", diff --git a/docs/design.md b/docs/design.md new file mode 100644 index 0000000..2b642a7 --- /dev/null +++ b/docs/design.md @@ -0,0 +1,29 @@ +# Design Notes + +## Stories + +As a user I want to upload a character so my GM can see it + +As a user I want to update a character so my GM can see it + +As a GM I want to invite players + +As a GM I want to review characters and pssibly appreove them + +As a GM I want to remove players from my campaign + +As a GM I want to send feedback on a character to a players + +As an admin I want to create campaigns + +As an admin I want to invite people to GM a campaign + +As an admin I want to remove players + +As an admin I want to remove campaigns + +As an admin I want to remove GM from a campaign + +As a player I want to add session notes to a campaign. (It would be really cool if multiple players could edit these notes. Bonus points for uploading images) + + diff --git a/gurps_character/__pycache__/apps.cpython-312.pyc b/gurps_character/__pycache__/apps.cpython-312.pyc deleted file mode 100644 index 7e1d579..0000000 Binary files a/gurps_character/__pycache__/apps.cpython-312.pyc and /dev/null differ diff --git a/gurps_character/__pycache__/forms.cpython-312.pyc b/gurps_character/__pycache__/forms.cpython-312.pyc deleted file mode 100644 index 6c4f44f..0000000 Binary files a/gurps_character/__pycache__/forms.cpython-312.pyc and /dev/null differ diff --git a/gurps_character/admin.py b/gurps_character/admin.py index bf5197e..f699e60 100644 --- a/gurps_character/admin.py +++ b/gurps_character/admin.py @@ -1,8 +1,13 @@ from django.contrib import admin # Register your models here. -from .models import GURPSCharacter, GameSystem, Campaign +from .models import GURPSCharacter, GameSystem, Campaign, GM, Player, CampaignPlayer admin.site.register(GURPSCharacter) admin.site.register(GameSystem) admin.site.register(Campaign) + + +admin.site.register(GM) +admin.site.register(Player) +admin.site.register(CampaignPlayer) diff --git a/gurps_character/forms.py b/gurps_character/forms.py index 772fe64..710585c 100644 --- a/gurps_character/forms.py +++ b/gurps_character/forms.py @@ -2,7 +2,7 @@ import json from django import forms from django.core.exceptions import ValidationError - + def validate_file(value): if value.size > 10**6: raise ValidationError("File too large") @@ -13,16 +13,16 @@ def validate_file(value): raise ValidationError("Not a GCS file - json decode") try: - version = data['version'] + version = data["version"] except KeyError: - import bpdb;bpdb.set_trace() raise ValidationError("Not a GCS file - key error") if version < 4: raise ValidationError( - f"The file version ({version}) is too old. Please use a newer version (5.20.3) of GCS." - ) + f"The file version ({version}) is too old. Please use a newer " + "version (5.20.3) of GCS." + ) class UploadFileForm(forms.Form): - file = forms.FileField(validators=[validate_file]) + file = forms.FileField(validators=[validate_file]) diff --git a/gurps_character/management/commands/clear_data.py b/gurps_character/management/commands/clear_data.py index 358f7c7..e1ee7dd 100644 --- a/gurps_character/management/commands/clear_data.py +++ b/gurps_character/management/commands/clear_data.py @@ -1,24 +1,24 @@ -from django.contrib.auth.models import User +from django.db import connection from django.core.management import BaseCommand -def create_users(): - user = User.objects.create_user( - username="neill", - is_superuser=True, - first_name="Neill", - last_name="Cox", - email="neill@neill.id.au", - is_staff=True, - is_active=True, - date_joined="2024-07-31T00:00:00.000Z", - password="password", - ) - # user.save() +def drop_tables(): + with connection.cursor() as cursor: + for table in [ + "gurps_character", + "gurps_character_campaign", + "gurps_character_campaign_gm", + "gurps_character_campaignplayer", + "gurps_character_gm", + "gurps_character_player", + "gurps_character_gamesystem", + "gurps_character_gurpscharacter"]: + cursor.execute(f'DROP TABLE IF EXISTS {table} cascade') + cursor.execute("DELETE FROM django_migrations WHERE app = 'gurps_character'") class Command(BaseCommand): - help = "Load some initial data" + help = "Drop all the tables!" def handle(self, *args, **options): - create_users() + drop_tables() diff --git a/gurps_character/migrations/0001_initial.py b/gurps_character/migrations/0001_initial.py index 581e4bd..4e9e0bf 100644 --- a/gurps_character/migrations/0001_initial.py +++ b/gurps_character/migrations/0001_initial.py @@ -1,14 +1,120 @@ -# Generated by Django 5.0.1 on 2024-01-08 10:25 +# Generated by Django 5.0.6 on 2024-09-07 01:08 +import django.db.models.deletion +from django.conf import settings from django.db import migrations, models class Migration(migrations.Migration): + initial = True - dependencies = [] + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] operations = [ + migrations.CreateModel( + name="GameSystem", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=255, unique=True)), + ("description", models.TextField(null=True)), + ], + ), + migrations.CreateModel( + name="Campaign", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=255, unique=True)), + ("description", models.TextField(null=True)), + ( + "game_system", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="gurps_character.gamesystem", + ), + ), + ], + ), + migrations.CreateModel( + name="GM", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "campaign", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="campaign", + to="gurps_character.campaign", + ), + ), + ( + "gm", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ], + ), + migrations.AddField( + model_name="campaign", + name="gm", + field=models.ManyToManyField( + related_name="GM", to="gurps_character.gm" + ), + ), + migrations.CreateModel( + name="Player", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "status", + models.CharField(max_length=255, unique=True), + ), + ( + "user", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ], + ), migrations.CreateModel( name="GURPSCharacter", fields=[ @@ -23,6 +129,61 @@ class Migration(migrations.Migration): ), ("uuid", models.CharField(max_length=128, unique=True)), ("name", models.CharField(max_length=255, unique=True)), + ("details", models.JSONField()), + ( + "campaign", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="gurps_character.campaign", + ), + ), + ( + "player", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="gurps_character.player", + ), + ), + ], + ), + migrations.CreateModel( + name="CampaignPlayer", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "status", + models.CharField( + choices=[ + ("I", "Invited"), + ("A", "Accepted"), + ("D", "Declined"), + ] + ), + ), + ( + "campaign", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="gurps_character.campaign", + ), + ), + ( + "player", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="gurps_character.player", + ), + ), ], ), ] diff --git a/gurps_character/migrations/0002_alter_gm_gm.py b/gurps_character/migrations/0002_alter_gm_gm.py new file mode 100644 index 0000000..fcbf6fa --- /dev/null +++ b/gurps_character/migrations/0002_alter_gm_gm.py @@ -0,0 +1,25 @@ +# Generated by Django 5.0.6 on 2024-09-07 01:22 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("gurps_character", "0001_initial"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AlterField( + model_name="gm", + name="gm", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ] diff --git a/gurps_character/migrations/0002_gurpscharacter_details.py b/gurps_character/migrations/0002_gurpscharacter_details.py deleted file mode 100644 index f1f69a9..0000000 --- a/gurps_character/migrations/0002_gurpscharacter_details.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.0.1 on 2024-01-10 07:52 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("gurps_character", "0001_initial"), - ] - - operations = [ - migrations.AddField( - model_name="gurpscharacter", - name="details", - field=models.JSONField(default={}), - preserve_default=False, - ), - ] diff --git a/gurps_character/migrations/0003_alter_campaign_gm_alter_gm_campaign_and_more.py b/gurps_character/migrations/0003_alter_campaign_gm_alter_gm_campaign_and_more.py new file mode 100644 index 0000000..3a20bac --- /dev/null +++ b/gurps_character/migrations/0003_alter_campaign_gm_alter_gm_campaign_and_more.py @@ -0,0 +1,67 @@ +# Generated by Django 5.1.7 on 2025-03-08 09:56 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("gurps_character", "0002_alter_gm_gm"), + ] + + operations = [ + migrations.AlterField( + model_name="campaign", + name="gm", + field=models.ManyToManyField( + blank=True, related_name="GM", to="gurps_character.gm" + ), + ), + migrations.AlterField( + model_name="gm", + name="campaign", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="campaign", + to="gurps_character.campaign", + ), + ), + migrations.AlterField( + model_name="player", + name="status", + field=models.CharField( + choices=[ + ("I", "Invited"), + ("A", "Accepted"), + ("D", "Declined"), + ("R", "Requested"), + ] + ), + ), + migrations.CreateModel( + name="CampaignNotes", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("timestamp", models.DateTimeField(auto_now_add=True)), + ("notes", models.TextField(null=True)), + ( + "campaign", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="gurps_character.campaign", + ), + ), + ], + ), + ] diff --git a/gurps_character/migrations/0003_gamesystem.py b/gurps_character/migrations/0003_gamesystem.py deleted file mode 100644 index b26628c..0000000 --- a/gurps_character/migrations/0003_gamesystem.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 5.0.1 on 2024-01-16 07:12 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("gurps_character", "0002_gurpscharacter_details"), - ] - - operations = [ - migrations.CreateModel( - name="GameSystem", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("name", models.CharField(max_length=255, unique=True)), - ("description", models.TextField(null=True)), - ], - ), - ] diff --git a/gurps_character/migrations/0004_gurpscharacter_player_campaign_and_more.py b/gurps_character/migrations/0004_gurpscharacter_player_campaign_and_more.py deleted file mode 100644 index e89b49f..0000000 --- a/gurps_character/migrations/0004_gurpscharacter_player_campaign_and_more.py +++ /dev/null @@ -1,57 +0,0 @@ -# Generated by Django 5.0.6 on 2024-07-14 09:14 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("gurps_character", "0003_gamesystem"), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.AddField( - model_name="gurpscharacter", - name="player", - field=models.ForeignKey( - null=True, - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.CreateModel( - name="Campaign", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("name", models.CharField(max_length=255, unique=True)), - ("description", models.TextField(null=True)), - ( - "game_system", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to="gurps_character.gamesystem", - ), - ), - ], - ), - migrations.AddField( - model_name="gurpscharacter", - name="campaign", - field=models.ForeignKey( - null=True, - on_delete=django.db.models.deletion.CASCADE, - to="gurps_character.campaign", - ), - ), - ] diff --git a/gurps_character/migrations/0005_campaign_gm.py b/gurps_character/migrations/0005_campaign_gm.py deleted file mode 100644 index c9d8a19..0000000 --- a/gurps_character/migrations/0005_campaign_gm.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 5.0.6 on 2024-07-14 09:25 - -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("gurps_character", "0004_gurpscharacter_player_campaign_and_more"), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.AddField( - model_name="campaign", - name="gm", - field=models.ManyToManyField(to=settings.AUTH_USER_MODEL), - ), - ] diff --git a/gurps_character/migrations/__pycache__/0001_initial.cpython-312.pyc b/gurps_character/migrations/__pycache__/0001_initial.cpython-312.pyc deleted file mode 100644 index b48ce8a..0000000 Binary files a/gurps_character/migrations/__pycache__/0001_initial.cpython-312.pyc and /dev/null differ diff --git a/gurps_character/migrations/__pycache__/0002_gurpscharacter_details.cpython-312.pyc b/gurps_character/migrations/__pycache__/0002_gurpscharacter_details.cpython-312.pyc deleted file mode 100644 index 79d2c45..0000000 Binary files a/gurps_character/migrations/__pycache__/0002_gurpscharacter_details.cpython-312.pyc and /dev/null differ diff --git a/gurps_character/migrations/__pycache__/0003_gamesystem.cpython-312.pyc b/gurps_character/migrations/__pycache__/0003_gamesystem.cpython-312.pyc deleted file mode 100644 index 9c7536a..0000000 Binary files a/gurps_character/migrations/__pycache__/0003_gamesystem.cpython-312.pyc and /dev/null differ diff --git a/gurps_character/migrations/__pycache__/__init__.cpython-312.pyc b/gurps_character/migrations/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 24fb52f..0000000 Binary files a/gurps_character/migrations/__pycache__/__init__.cpython-312.pyc and /dev/null differ diff --git a/gurps_character/models.py b/gurps_character/models.py index 8b04fe6..40211ae 100644 --- a/gurps_character/models.py +++ b/gurps_character/models.py @@ -1,5 +1,6 @@ from django.db import models from django.contrib.auth.models import User + # Create your models here. @@ -12,120 +13,194 @@ class GameSystem(models.Model): class GM(models.Model): - gm = models.ForeignKey(User, on_delete=models.CASCADE) - campaign = models.ForeignKey("Campaign", null=True, on_delete=models.CASCADE, related_name='campaign') + gm = models.ForeignKey(User, on_delete=models.CASCADE, null=True) + campaign = models.ForeignKey( + "Campaign", + null=True, + blank=True, + on_delete=models.CASCADE, + related_name="campaign", + ) + + def __str__(self): + return self.campaign.name + " - " + self.gm.get_full_name() class Player(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) - status = models.CharField(max_length=255, unique=True) + status = models.CharField( + choices={"I": "Invited", "A": "Accepted", "D": "Declined", "R": "Requested"} + ) -# -# class Players(models.Model): -# player = models.ForeignKey(User, on_delete=models.CASCADE) -# status = models.CharField(max_length=255, unique=True) + def __str__(self): + return self.user.get_full_name() + " - " + self.get_status_display() class Campaign(models.Model): name = models.CharField(max_length=255, unique=True) description = models.TextField(null=True) - game_system = models.ForeignKey(GameSystem, on_delete=models.CASCADE) - gm = models.ManyToManyField('GM', related_name='GM') - player = models.ManyToManyField('Player') + game_system = models.ForeignKey( + GameSystem, on_delete=models.CASCADE + ) + gm = models.ManyToManyField("GM", related_name="GM", blank=True) + # player = models.ManyToManyField('Player') def __str__(self): return self.name +class CampaignPlayer(models.Model): + campaign = models.ForeignKey(Campaign, on_delete=models.CASCADE) + player = models.ForeignKey( + Player, on_delete=models.CASCADE + ) # TODO: Check this behaviour + status = models.CharField( + choices={"I": "Invited", "A": "Accepted", "D": "Declined"} + ) # This should be one of invited, accepted, rejected? + + def __str__(self): + return self.campaign.name + " - " + self.player.user.get_full_name() + " - " + self.get_status_display() + +class CampaignNotes(models.Model): + campaign = models.ForeignKey(Campaign, on_delete=models.CASCADE) + timestamp = models.DateTimeField(auto_now_add=True) + notes = models.TextField(null=True) + class GURPSCharacter(models.Model): uuid = models.CharField(max_length=128, unique=True) name = models.CharField(max_length=255, unique=True) - player = models.ForeignKey(Player, null=True, on_delete=models.CASCADE) - campaign = models.ForeignKey(Campaign, null=True, on_delete=models.CASCADE) + player = models.ForeignKey( + Player, null=True, on_delete=models.CASCADE + ) + campaign = models.ForeignKey( + Campaign, null=True, on_delete=models.CASCADE + ) details = models.JSONField() - # owner => user - # campaign => campaign def __str__(self): - return self.name + " Player " + str(self.player) + " Campaign " + str(self.campaign) + return ( + str(self.name) + + " Player " + + str(self.player) + + " Campaign " + + str(self.campaign) + ) def adv_points(self): - return sum([a['calc']['points'] for a in self.details.get('advantages', []) if a['calc']['points'] > 0]) + return sum( + [ + a["calc"]["points"] + for a in self.details.get("advantages", []) + if a["calc"]["points"] > 0 + ] + ) def disadv_points(self): - return sum([a['calc']['points'] for a in self.details.get('advantages', []) if a['calc']['points'] < -1]) + return sum( + [ + a["calc"]["points"] + for a in self.details.get("advantages", []) + if a["calc"]["points"] < -1 + ] + ) def quirks_points(self): - return sum([a['calc']['points'] for a in self.details.get('advantages', []) if a['calc']['points'] == -1]) + return sum( + [ + a["calc"]["points"] + for a in self.details.get("advantages", []) + if a["calc"]["points"] == -1 + ] + ) def attr_points(self): - return sum([a['calc']['points'] for a in self.details['attributes']]) + return sum( + [a["calc"]["points"] for a in self.details["attributes"]] + ) def skills_points(self): - return sum([s['points'] for s in self.details.get('skills', [])]) + return sum( + [s["points"] for s in self.details.get("skills", [])] + ) def spells_points(self): - return sum([s['points'] for s in self.details.get('spells', [])]) + return sum( + [s["points"] for s in self.details.get("spells", [])] + ) def race_points(self): - return sum([s['points'] for s in self.details.get('race', [])]) # Are we sure? + # Are we sure? + return sum([s["points"] for s in self.details.get("race", [])]) def unspent_points(self): - return self.details['total_points'] - self.adv_points() - \ - self.disadv_points() - self.attr_points() - \ - self.skills_points() - self.spells_points() - \ - self.race_points() - self.quirks_points() + return ( + self.details["total_points"] + - self.adv_points() + - self.disadv_points() + - self.attr_points() + - self.skills_points() + - self.spells_points() + - self.race_points() + - self.quirks_points() + ) def get_primary_attr(self, attr_id): - return [a['calc'] for a in self.details['attributes'] if a['attr_id'] == attr_id][0] + return [ + a["calc"] + for a in self.details["attributes"] + if a["attr_id"] == attr_id + ][0] def st(self): - return self.get_primary_attr('st') + return self.get_primary_attr("st") def iq(self): - return self.get_primary_attr('iq') + return self.get_primary_attr("iq") def dx(self): - return self.get_primary_attr('dx') + return self.get_primary_attr("dx") def ht(self): - return self.get_primary_attr('ht') + return self.get_primary_attr("ht") def will(self): - return self.get_primary_attr('will') + return self.get_primary_attr("will") def fright_check(self): - return self.get_primary_attr('fright_check') + return self.get_primary_attr("fright_check") def per(self): - return self.get_primary_attr('per') + return self.get_primary_attr("per") def vision(self): - return self.get_primary_attr('vision') + return self.get_primary_attr("vision") def hearing(self): - return self.get_primary_attr('hearing') + return self.get_primary_attr("hearing") def taste_smell(self): - return self.get_primary_attr('taste_smell') + return self.get_primary_attr("taste_smell") def touch(self): - return self.get_primary_attr('touch') + return self.get_primary_attr("touch") def basic_move(self): - return self.get_primary_attr('basic_move') + return self.get_primary_attr("basic_move") def basic_speed(self): - return self.get_primary_attr('basic_speed') + return self.get_primary_attr("basic_speed") def hp(self): - return self.get_primary_attr('hp') + return self.get_primary_attr("hp") def fp(self): - return self.get_primary_attr('fp') + return self.get_primary_attr("fp") def weight_carried(self): - items = [i['calc']['extended_weight'] for i in self.details['equipment']] + items = [ + i["calc"]["extended_weight"] + for i in self.details["equipment"] + ] total_weight = 0 for i in items: @@ -150,59 +225,77 @@ class GURPSCharacter(models.Model): dodge = self.details["calc"]["dodge"][0] basic_move = self.basic_move()["value"] return [ - { - "max_load": round(self.basic_lift()), - "move": basic_move, - "dodge": dodge, - "current": "current" * (enc_level == 0) - }, - { - "max_load": round(self.basic_lift()) * 2, - "move": basic_move - 2, - "dodge": dodge - 1, - "current": "current" * (enc_level == 1) - }, - { - "max_load": round(self.basic_lift()) * 3, - "move": basic_move - 3, - "dodge": dodge - 2, - "current": "current" * (enc_level == 2) - }, - { - "max_load": round(self.basic_lift()) * 6, - "move": basic_move - 4, - "dodge": dodge - 3, - "current": "current" * (enc_level == 3) - }, - { - "max_load": round(self.basic_lift()) * 10, - "move": basic_move - 5, - "dodge": dodge - 4, - "current": "current" * (enc_level == 4) - }, - ] - + { + "max_load": round(self.basic_lift()), + "move": basic_move, + "dodge": dodge, + "current": "current" * (enc_level == 0), + }, + { + "max_load": round(self.basic_lift()) * 2, + "move": basic_move - 2, + "dodge": dodge - 1, + "current": "current" * (enc_level == 1), + }, + { + "max_load": round(self.basic_lift()) * 3, + "move": basic_move - 3, + "dodge": dodge - 2, + "current": "current" * (enc_level == 2), + }, + { + "max_load": round(self.basic_lift()) * 6, + "move": basic_move - 4, + "dodge": dodge - 3, + "current": "current" * (enc_level == 3), + }, + { + "max_load": round(self.basic_lift()) * 10, + "move": basic_move - 5, + "dodge": dodge - 4, + "current": "current" * (enc_level == 4), + }, + ] + def basic_lift(self): return float(self.details["calc"]["basic_lift"].split()[0]) def weight_unit(self): - return self.details['settings']['default_weight_units'] + return self.details["settings"]["default_weight_units"] def lift_table(self): return [ - {"value": round(self.basic_lift()), "label": "Basic Lift"}, - {"value": round(self.basic_lift()) * 2, "label": "One-Handed Lift"}, - {"value": round(self.basic_lift()) * 8, "label": "Two-Handed Lift"}, - {"value": round(self.basic_lift()) * 12, "label": "Shove &ersand; Knock Over"}, - {"value": round(self.basic_lift()) * 24, "label": "Running Shove & Knock Over"}, - {"value": round(self.basic_lift()) * 15, "label": "Carry on Back"}, - {"value": round(self.basic_lift()) * 50, "label": "Shift Slightly"}, - ] + {"value": round(self.basic_lift()), "label": "Basic Lift"}, + { + "value": round(self.basic_lift()) * 2, + "label": "One-Handed Lift", + }, + { + "value": round(self.basic_lift()) * 8, + "label": "Two-Handed Lift", + }, + { + "value": round(self.basic_lift()) * 12, + "label": "Shove &ersand; Knock Over", + }, + { + "value": round(self.basic_lift()) * 24, + "label": "Running Shove & Knock Over", + }, + { + "value": round(self.basic_lift()) * 15, + "label": "Carry on Back", + }, + { + "value": round(self.basic_lift()) * 50, + "label": "Shift Slightly", + }, + ] def reaction_modifiers(self): modifiers = [] - for a in self.details.get('advantages', []): + for a in self.details.get("advantages", []): if "features" in a: for f in a["features"]: if f["type"] == "reaction_bonus": @@ -213,7 +306,7 @@ class GURPSCharacter(models.Model): def conditional_modifiers(self): modifiers = [] - for a in self.details.get('advantages', []): + for a in self.details.get("advantages", []): if "features" in a: for f in a["features"]: if f["type"] == "conditional_modifier": @@ -222,10 +315,24 @@ class GURPSCharacter(models.Model): return modifiers def weapons(self): - return [w for w in self.details['equipment'] if "weapons" in w] + \ - [a for a in self.details.get("advantages", []) if "weapons" in a] + \ - [a for a in self.details.get("traits", []) if "weapons" in a] + \ - [s for s in self.details.get("spells", []) if "weapons" in s] + return ( + [w for w in self.details["equipment"] if "weapons" in w] + + [ + a + for a in self.details.get("advantages", []) + if "weapons" in a + ] + + [ + a + for a in self.details.get("traits", []) + if "weapons" in a + ] + + [ + s + for s in self.details.get("spells", []) + if "weapons" in s + ] + ) def melee_weapons(self): mw = [] @@ -237,16 +344,18 @@ class GURPSCharacter(models.Model): else: name = item["name"] mw.append( - { - "name": name, - "usage": weapon["usage"], - "skill_level": weapon["calc"].get("level", 0), - "parry": weapon["calc"].get("parry", "No"), - "block": weapon["calc"].get("block", "No"), - "damage": weapon["calc"]["damage"], - "reach": weapon["calc"].get("reach", ""), - "strength": weapon.get("strength", " ") - } + { + "name": name, + "usage": weapon["usage"], + "skill_level": weapon["calc"].get( + "level", 0 + ), + "parry": weapon["calc"].get("parry", "No"), + "block": weapon["calc"].get("block", "No"), + "damage": weapon["calc"]["damage"], + "reach": weapon["calc"].get("reach", ""), + "strength": weapon.get("strength", " "), + } ) return mw @@ -260,7 +369,7 @@ class GURPSCharacter(models.Model): wpn_range = float(wpn_range[1:]) wpn_range = wpn_range * self.st()["value"] wpn_range = str(wpn_range) - wpn_range = wpn_range[:wpn_range.index(".")] + wpn_range = wpn_range[: wpn_range.index(".")] new_ranges.append(wpn_range) return "/".join(new_ranges) @@ -276,19 +385,21 @@ class GURPSCharacter(models.Model): else: name = item["name"] rw.append( - { - "name": name, - "bulk": weapon.get("bulk", " "), - "usage": weapon.get("usage", " "), - "skill_level": weapon["calc"]["level"], - "damage": weapon["calc"]["damage"], - "strength": weapon.get("strength", " "), - "acc": weapon.get("accuracy", 0), - "range": muscle_range(weapon.get("range", " ")), - "rof": weapon.get("rate_of_fire", " "), - "shots": weapon.get("shots", " "), - "recoil": weapon.get("recoil", " ") - } + { + "name": name, + "bulk": weapon.get("bulk", " "), + "usage": weapon.get("usage", " "), + "skill_level": weapon["calc"]["level"], + "damage": weapon["calc"]["damage"], + "strength": weapon.get("strength", " "), + "acc": weapon.get("accuracy", 0), + "range": muscle_range( + weapon.get("range", " ") + ), + "rof": weapon.get("rate_of_fire", " "), + "shots": weapon.get("shots", " "), + "recoil": weapon.get("recoil", " "), + } ) return rw @@ -299,8 +410,15 @@ class GURPSCharacter(models.Model): cost = advantage["calc"]["points"] name = advantage["name"] - if "categories" in advantage and "Language" in advantage["categories"]: - levels = [m for m in advantage['modifiers'] if "disabled" not in m] + if ( + "categories" in advantage + and "Language" in advantage["categories"] + ): + levels = [ + m + for m in advantage["modifiers"] + if "disabled" not in m + ] notes = "" for level in levels: @@ -315,82 +433,97 @@ class GURPSCharacter(models.Model): elif "notes" in advantage: notes = advantage["notes"] elif "modifiers" in advantage: - notes = [m for m in advantage['modifiers'] if m["cost"] == cost][0]["name"] + notes = [ + m + for m in advantage["modifiers"] + if m["cost"] == cost + ][0]["name"] else: notes = "" - traits.append({"name": name, "notes": notes, "points": cost, "reference": advantage["reference"]}) + traits.append( + { + "name": name, + "notes": notes, + "points": cost, + "reference": advantage["reference"], + } + ) return traits def spells(self): def get_casting_details(spell_details): - level = spell_details['calc']['level'] + level = spell_details["calc"]["level"] if level < 10: descr = ( - "Ritual: Need both hands and both feet " - "free, and must speak .Time: 2x." - ) + "Ritual: Need both hands and both feet " + "free, and must speak .Time: 2x." + ) elif level < 15: descr = ( - "Ritual: Must speak a few quiet words " - "and make a gesture." - ) + "Ritual: Must speak a few quiet words " + "and make a gesture." + ) elif level < 20: - descr = ( - "Ritual: Must speak a word or two or make " - "a small gesture. May move one yard per second " - "while concentrating. Cost: -1." - ) - elif level < 25: - descr = "Ritual: None. Time: / 2 (round up). Cost: -2." - elif level < 30: descr = ( - "Ritual: None. Time: / 4 (round up). Cost: -3." - ) + "Ritual: Must speak a word or two or make " + "a small gesture. May move one yard per second " + "while concentrating. Cost: -1." + ) + elif level < 25: + descr = "Ritual: None. Time: / 2 (round up). Cost: -2." + elif level < 30: + descr = "Ritual: None. Time: / 4 (round up). Cost: -3." else: delta = int((level - 25) / 5) power = 2 + delta # divisor = 2**power cost = 3 + delta - descr = ( - f"Ritual: None. Time: / {power} round up. Cost: " - f"-{cost}" - ) + descr = ( + f"Ritual: None. Time: / { + power} round up. Cost: " + f"-{cost}" + ) return descr spells = [] - for spell in self.details.get('spells', []): + for spell in self.details.get("spells", []): notes = ( - f"{get_casting_details(spell)}
Class: {spell['spell_class']}; " - f"Cost: {spell['casting_cost']}; Maintain: {spell['maintenance_cost']};" - f" Time: {spell['casting_time']}; Duration: {spell['duration']}; " - ) + f"{get_casting_details(spell)}
Class: { + spell['spell_class']}; " + f"Cost: {spell['casting_cost']}; Maintain: { + spell['maintenance_cost']};" + f" Time: {spell['casting_time']}; Duration: { + spell['duration']}; " + ) spells.append( { - 'name': spell["name"], + "name": spell["name"], "college": ", ".join(spell["college"]), - "level": spell["calc"]["level"], + "level": spell["calc"]["level"], "rsl": spell["calc"]["rsl"], "points": spell["points"], "reference": spell["reference"], - "notes": notes + "notes": notes, } ) return spells def skills(self): skills = [] - for skill in self.details.get('skills', []): - skills.append({ - 'name': skill["name"], - "level": skill["calc"]["level"], - "rsl": skill["calc"]["rsl"], - "points": skill["points"], - "reference": skill["reference"] - }) + for skill in self.details.get("skills", []): + skills.append( + { + "name": skill["name"], + "level": skill["calc"]["level"], + "rsl": skill["calc"]["rsl"], + "points": skill["points"], + "reference": skill["reference"], + } + ) return skills def equipment(self): @@ -399,49 +532,52 @@ class GURPSCharacter(models.Model): for item_details in parent["children"]: equipment_details = { - "name": item_details["description"], - "uses": "", - "tech_level": item_details["tech_level"], - "lc": "", - "value": item_details["value"], - "weight": item_details["weight"], - "quantity": item_details.get("quantity", 1), - "ref": item_details["reference"], - "ext_weight": item_details["calc"]["extended_weight"], - "ext_value": item_details["calc"]["extended_value"], - "notes": item_details.get("notes", ""), - "equipped": item_details["equipped"], - "level": level - } + "name": item_details["description"], + "uses": "", + "tech_level": item_details["tech_level"], + "lc": "", + "value": item_details["value"], + "weight": item_details["weight"], + "quantity": item_details.get("quantity", 1), + "ref": item_details["reference"], + "ext_weight": item_details["calc"][ + "extended_weight" + ], + "ext_value": item_details["calc"]["extended_value"], + "notes": item_details.get("notes", ""), + "equipped": item_details["equipped"], + "level": level, + } children.append(equipment_details) if "children" in item_details: children += get_children(item_details, level + 1) return children - equipment = self.details['equipment'] + + equipment = self.details["equipment"] equipment_list = [] for item in equipment: equipment_list.append( - { - "name": item["description"], - "uses": "", - "tech_level": item["tech_level"], - "lc": "", - "level": 0, - "value": item["value"], - "weight": item["weight"], - "quantity": item.get("quantity", 1), - "ref": item.get("reference", ""), - "ext_weight": item["calc"]["extended_weight"], - "ext_value": item["calc"]["extended_value"], - "notes": item.get("notes", ""), - "equipped": item["equipped"] - } + { + "name": item["description"], + "uses": "", + "tech_level": item["tech_level"], + "lc": "", + "level": 0, + "value": item["value"], + "weight": item["weight"], + "quantity": item.get("quantity", 1), + "ref": item.get("reference", ""), + "ext_weight": item["calc"]["extended_weight"], + "ext_value": item["calc"]["extended_value"], + "notes": item.get("notes", ""), + "equipped": item["equipped"], + } ) - if 'children' in item: + if "children" in item: equipment_list += get_children(item) return equipment_list @@ -449,7 +585,9 @@ class GURPSCharacter(models.Model): def hit_locations(self): try: # v2 - return self.details["settings"]["hit_locations"]["locations"] + return self.details["settings"]["hit_locations"][ + "locations" + ] except KeyError: # v4 return self.details["settings"]["body_type"]["locations"] diff --git a/gurps_character/templates/campaigns/details.html b/gurps_character/templates/campaigns/details.html new file mode 100644 index 0000000..c783536 --- /dev/null +++ b/gurps_character/templates/campaigns/details.html @@ -0,0 +1,21 @@ +{% extends "base.html" %} +{% block content %} +

{{ campaign.name }}

+ +

Players

+ + + +

Characters

+ + +{% endblock %} + diff --git a/gurps_character/templates/campaigns/list.html b/gurps_character/templates/campaigns/list.html new file mode 100644 index 0000000..21953e0 --- /dev/null +++ b/gurps_character/templates/campaigns/list.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} +{% block content %} + + +Upload new character +
+ {% csrf_token %} + {{ form }} + +
+{% endblock %} diff --git a/gurps_character/templates/home.html b/gurps_character/templates/home.html index 9668191..48be4cb 100644 --- a/gurps_character/templates/home.html +++ b/gurps_character/templates/home.html @@ -8,11 +8,11 @@ {% if user.is_authenticated %}

Welcome {% if user.first_name %}{{ user.first_name }}{% else %}{{ user.username }}{% endif %}

- {% if user.campaign_set.all %} + {% if user.gm_set.all %}

Campaigns you run

@@ -20,7 +20,7 @@

Characters you play