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 %}
+