From adf7f20e58d36942af0a151a231a73840dbef941 Mon Sep 17 00:00:00 2001 From: Mechseroms Date: Mon, 18 May 2026 15:02:03 -0500 Subject: [PATCH] second commit --- db.sqlite3 | Bin 262144 -> 307200 bytes main/__pycache__/admin.cpython-314.pyc | Bin 2838 -> 3463 bytes main/__pycache__/models.cpython-314.pyc | Bin 33213 -> 35886 bytes main/__pycache__/urls.cpython-314.pyc | Bin 591 -> 1351 bytes main/__pycache__/views.cpython-314.pyc | Bin 6578 -> 13432 bytes main/admin.py | 12 +- main/migrations/0001_initial.py | 131 ++-- ... 0002_alter_package_package_operations.py} | 8 +- main/migrations/0002_pin.py | 23 - main/migrations/0003_pin_pin_type.py | 18 - ...e_character_features_character_features.py | 22 - ...rmor_remove_character_charisma_and_more.py | 123 ---- .../0006_remove_character_athletics.py | 17 - ...07_remove_character_acrobatics_and_more.py | 165 ----- ...cter_hp_max_remove_character_initiative.py | 21 - main/migrations/0009_character_hp_features.py | 18 - .../0011_feature_feature_requirements.py | 18 - ...12_packagefeature_requirements_override.py | 18 - ...0013_alter_feature_feature_requirements.py | 18 - ...acter_features_alter_character_packages.py | 23 - ...acter_features_alter_character_packages.py | 23 - ...0016_alter_feature_feature_requirements.py | 18 - .../__pycache__/0001_initial.cpython-314.pyc | Bin 19010 -> 16644 bytes ...package_package_operations.cpython-314.pyc | Bin 0 -> 825 bytes ...017_feature_feature_system.cpython-314.pyc | Bin 0 -> 833 bytes ...018_package_package_system.cpython-314.pyc | Bin 0 -> 821 bytes .../0019_asset_objecttrait.cpython-314.pyc | Bin 0 -> 1707 bytes ...020_feature_feature_traits.cpython-314.pyc | Bin 0 -> 851 bytes ...ter_feature_feature_traits.cpython-314.pyc | Bin 0 -> 874 bytes ...lter_character_id_and_more.cpython-314.pyc | Bin 0 -> 1243 bytes ...lter_character_id_and_more.cpython-314.pyc | Bin 0 -> 1173 bytes main/models.py | 44 +- main/templates/main/character_sheet.html | 2 +- main/templates/main/feature.html | 341 ++++++---- main/templates/main/feature_detail.html | 78 +++ main/templates/main/feature_select_table.html | 49 ++ main/templates/main/features_list.html | 47 ++ main/templates/main/packages_list.html | 49 ++ main/templates/main/test_feature.html | 475 ++++++++++++++ main/templates/main/test_package.html | 621 ++++++++++++++++++ main/urls.py | 9 +- main/views.py | 270 ++++++-- 42 files changed, 1859 insertions(+), 802 deletions(-) rename main/migrations/{0010_alter_character_hp_features.py => 0002_alter_package_package_operations.py} (59%) delete mode 100644 main/migrations/0002_pin.py delete mode 100644 main/migrations/0003_pin_pin_type.py delete mode 100644 main/migrations/0004_remove_character_features_character_features.py delete mode 100644 main/migrations/0005_remove_character_armor_remove_character_charisma_and_more.py delete mode 100644 main/migrations/0006_remove_character_athletics.py delete mode 100644 main/migrations/0007_remove_character_acrobatics_and_more.py delete mode 100644 main/migrations/0008_remove_character_hp_max_remove_character_initiative.py delete mode 100644 main/migrations/0009_character_hp_features.py delete mode 100644 main/migrations/0011_feature_feature_requirements.py delete mode 100644 main/migrations/0012_packagefeature_requirements_override.py delete mode 100644 main/migrations/0013_alter_feature_feature_requirements.py delete mode 100644 main/migrations/0014_alter_character_features_alter_character_packages.py delete mode 100644 main/migrations/0015_alter_character_features_alter_character_packages.py delete mode 100644 main/migrations/0016_alter_feature_feature_requirements.py create mode 100644 main/migrations/__pycache__/0002_alter_package_package_operations.cpython-314.pyc create mode 100644 main/migrations/__pycache__/0017_feature_feature_system.cpython-314.pyc create mode 100644 main/migrations/__pycache__/0018_package_package_system.cpython-314.pyc create mode 100644 main/migrations/__pycache__/0019_asset_objecttrait.cpython-314.pyc create mode 100644 main/migrations/__pycache__/0020_feature_feature_traits.cpython-314.pyc create mode 100644 main/migrations/__pycache__/0021_alter_feature_feature_traits.cpython-314.pyc create mode 100644 main/migrations/__pycache__/0022_alter_asset_id_alter_character_id_and_more.cpython-314.pyc create mode 100644 main/migrations/__pycache__/0023_alter_asset_id_alter_character_id_and_more.cpython-314.pyc create mode 100644 main/templates/main/feature_detail.html create mode 100644 main/templates/main/feature_select_table.html create mode 100644 main/templates/main/features_list.html create mode 100644 main/templates/main/packages_list.html create mode 100644 main/templates/main/test_feature.html create mode 100644 main/templates/main/test_package.html diff --git a/db.sqlite3 b/db.sqlite3 index e5b772a8f0689145298b6eeccef4fcb4ee4b4fb5..949c2f316bd132970faf47954278911d4287e4e4 100644 GIT binary patch literal 307200 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 PKU8L*^4c%h<`4ZB;vM{~ delta 916 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 N;{|^buqhwo{{d$@wYC5N delta 6423 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 KflqNNy7mt_*iP&K delta 178 zcmX@kb)H2-n~#@^0SF8^oiYU&85kaeI55BoWqdxwFi~RuS6Ihh_J0vH#UKi22D5AAN_6m#rWNB7KZcbKyrY7zpVW27ic9<;k diff --git a/main/__pycache__/views.cpython-314.pyc b/main/__pycache__/views.cpython-314.pyc index 58cbd29ec4848f1e37dfdab26e6cfcee43e9ff72..8c98ff4199dad1f6f2e8eff85d2c9ea4913b5fc9 100644 GIT binary patch literal 13432 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

cDQJ9QTw#_r)c!C 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 riRb$#0wJX+McVRI`97inKnnI diff --git a/main/admin.py b/main/admin.py index 107cafd..923bab7 100644 --- a/main/admin.py +++ b/main/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin from django.contrib.auth.admin import UserAdmin -from .models import CustomUser, Character, Feature, Package, PackageFeature, Pin +from .models import CustomUser, Character, Feature, Package, PackageFeature, Pin, Asset, ObjectTrait class CustomUserAdmin(UserAdmin): list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff', 'uuid') @@ -40,3 +40,13 @@ class PackageFeatureAdmin(admin.ModelAdmin): class PinAdmin(admin.ModelAdmin): list_display = ('label', 'url', 'x', 'y') search_fields = ('label', 'url') + +@admin.register(Asset) +class AssetAdmin(admin.ModelAdmin): + list_display = ('asset_name', 'asset_system') + search_fields = ('asset_name', 'asset_system') + +@admin.register(ObjectTrait) +class ObjectTraitAdmin(admin.ModelAdmin): + list_display = ('trait_name',) + search_fields = ('trait_name',) \ No newline at end of file diff --git a/main/migrations/0001_initial.py b/main/migrations/0001_initial.py index c998229..dad3c05 100644 --- a/main/migrations/0001_initial.py +++ b/main/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 6.0 on 2025-12-14 02:30 +# Generated by Django 6.0.5 on 2026-05-10 16:07 import django.contrib.auth.models import django.contrib.auth.validators @@ -19,22 +19,34 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='Feature', + name='Asset', fields=[ ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('feature_name', models.CharField(max_length=200)), - ('feature_description', models.TextField(blank=True)), - ('feature_data', models.JSONField(blank=True, default=dict)), + ('asset_name', models.CharField(max_length=256)), + ('asset_description', models.TextField(blank=True)), + ('asset_doc_md', models.TextField(blank=True)), + ('asset_system', models.CharField(blank=True, max_length=32)), + ('asset_requirements', models.JSONField(blank=True, default=list)), ], ), migrations.CreateModel( - name='Package', + name='ObjectTrait', fields=[ ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('package_name', models.CharField(max_length=200)), - ('package_description', models.TextField(blank=True)), - ('package_type', models.CharField(blank=True, max_length=100)), - ('package_doc_md', models.TextField(blank=True)), + ('trait_name', models.CharField(max_length=64)), + ('trait_description', models.TextField(blank=True)), + ('trait_system', models.CharField(blank=True, max_length=32)), + ], + ), + migrations.CreateModel( + name='Pin', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('label', models.CharField(max_length=100)), + ('url', models.URLField(max_length=300)), + ('x', models.FloatField(help_text='X position as percentage (0-100)')), + ('y', models.FloatField(help_text='Y position as percentage (0-100)')), + ('pin_type', models.CharField(default='general', max_length=100)), ], ), migrations.CreateModel( @@ -64,10 +76,36 @@ class Migration(migrations.Migration): ('objects', django.contrib.auth.models.UserManager()), ], ), + migrations.CreateModel( + name='Feature', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('feature_name', models.CharField(max_length=200)), + ('feature_system', models.CharField(blank=True, max_length=36)), + ('feature_description', models.TextField(blank=True)), + ('feature_requirements', models.JSONField(blank=True, default=list)), + ('feature_data', models.JSONField(blank=True, default=dict)), + ('feature_traits', models.ManyToManyField(blank=True, related_name='features', to='main.objecttrait')), + ], + ), + migrations.CreateModel( + name='Package', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('package_name', models.CharField(max_length=200)), + ('package_system', models.CharField(blank=True, max_length=36)), + ('package_description', models.TextField(blank=True)), + ('package_type', models.CharField(blank=True, max_length=100)), + ('package_doc_md', models.TextField(blank=True)), + ('package_requirements', models.JSONField(blank=True, default=list)), + ('package_operations', models.JSONField(blank=True, default=dict)), + ('package_traits', models.ManyToManyField(blank=True, related_name='packages', to='main.objecttrait')), + ], + ), migrations.CreateModel( name='Character', fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), ('name', models.CharField(max_length=100)), ('level', models.IntegerField(default=0)), ('alignment', models.CharField(blank=True, max_length=50)), @@ -84,37 +122,21 @@ class Migration(migrations.Migration): ('wisdom_base', models.IntegerField(default=0)), ('charisma_base', models.IntegerField(default=0)), ('armor_base', models.IntegerField(default=0)), - ('strength', models.IntegerField(default=0)), - ('dexterity', models.IntegerField(default=0)), - ('constitution', models.IntegerField(default=0)), - ('intelligence', models.IntegerField(default=0)), - ('wisdom', models.IntegerField(default=0)), - ('charisma', models.IntegerField(default=0)), - ('armor', models.IntegerField(default=0)), - ('strength_modifier', models.IntegerField(default=0)), - ('dexterity_modifier', models.IntegerField(default=0)), - ('constitution_modifier', models.IntegerField(default=0)), - ('intelligence_modifier', models.IntegerField(default=0)), - ('wisdom_modifier', models.IntegerField(default=0)), - ('charisma_modifier', models.IntegerField(default=0)), - ('proficiency', models.IntegerField(default=0)), ('inspiration', models.BooleanField(default=False)), ('experience', models.IntegerField(default=0)), ('proficiencies_armor', models.JSONField(blank=True, default=list)), ('proficiencies_weapons', models.JSONField(blank=True, default=list)), ('proficiencies_tools', models.JSONField(blank=True, default=list)), ('languages', models.JSONField(blank=True, default=list)), - ('strength_save', models.IntegerField(default=0)), - ('dexterity_save', models.IntegerField(default=0)), - ('constitution_save', models.IntegerField(default=0)), - ('intelligence_save', models.IntegerField(default=0)), - ('wisdom_save', models.IntegerField(default=0)), - ('charisma_save', models.IntegerField(default=0)), + ('strength_save_prof', models.BooleanField(default=False)), + ('dexterity_save_prof', models.BooleanField(default=False)), + ('constitution_save_prof', models.BooleanField(default=False)), + ('intelligence_save_prof', models.BooleanField(default=False)), + ('wisdom_save_prof', models.BooleanField(default=False)), + ('charisma_save_prof', models.BooleanField(default=False)), ('hp', models.IntegerField(default=0)), - ('hp_max', models.IntegerField(default=0)), ('hp_temp', models.IntegerField(default=0)), ('hit_die', models.CharField(blank=True, max_length=20)), - ('initiative', models.IntegerField(default=0)), ('deathsaves_successes', models.IntegerField(default=0)), ('deathsaves_failures', models.IntegerField(default=0)), ('fire_resistance', models.BooleanField(default=False)), @@ -137,45 +159,6 @@ class Migration(migrations.Migration): ('blindsight', models.IntegerField(default=0)), ('tremorsense', models.IntegerField(default=0)), ('truesight', models.IntegerField(default=0)), - ('athletics', models.IntegerField(default=0)), - ('acrobatics', models.IntegerField(default=0)), - ('sleight_of_hand', models.IntegerField(default=0)), - ('stealth', models.IntegerField(default=0)), - ('arcana', models.IntegerField(default=0)), - ('history', models.IntegerField(default=0)), - ('investigation', models.IntegerField(default=0)), - ('nature', models.IntegerField(default=0)), - ('religion', models.IntegerField(default=0)), - ('animal_handling', models.IntegerField(default=0)), - ('insight', models.IntegerField(default=0)), - ('medicine', models.IntegerField(default=0)), - ('perception', models.IntegerField(default=0)), - ('survival', models.IntegerField(default=0)), - ('deception', models.IntegerField(default=0)), - ('intimidation', models.IntegerField(default=0)), - ('performance', models.IntegerField(default=0)), - ('persuasion', models.IntegerField(default=0)), - ('athletics_passive', models.IntegerField(default=0)), - ('acrobatics_passive', models.IntegerField(default=0)), - ('sleight_of_hand_passive', models.IntegerField(default=0)), - ('stealth_passive', models.IntegerField(default=0)), - ('arcana_passive', models.IntegerField(default=0)), - ('history_passive', models.IntegerField(default=0)), - ('investigation_passive', models.IntegerField(default=0)), - ('nature_passive', models.IntegerField(default=0)), - ('religion_passive', models.IntegerField(default=0)), - ('animal_handling_passive', models.IntegerField(default=0)), - ('insight_passive', models.IntegerField(default=0)), - ('medicine_passive', models.IntegerField(default=0)), - ('perception_passive', models.IntegerField(default=0)), - ('survival_passive', models.IntegerField(default=0)), - ('deception_passive', models.IntegerField(default=0)), - ('intimidation_passive', models.IntegerField(default=0)), - ('performance_passive', models.IntegerField(default=0)), - ('persuasion_passive', models.IntegerField(default=0)), - ('passive_perception', models.IntegerField(default=0)), - ('passive_investigation', models.IntegerField(default=0)), - ('passive_insight', models.IntegerField(default=0)), ('spellcasting_ability', models.CharField(blank=True, max_length=50)), ('spell_save_dc', models.IntegerField(default=0)), ('spell_attack_bonus', models.IntegerField(default=0)), @@ -192,16 +175,17 @@ class Migration(migrations.Migration): ('gp', models.IntegerField(default=0)), ('pp', models.IntegerField(default=0)), ('equipment', models.JSONField(blank=True, default=list)), - ('features', models.JSONField(blank=True, default=list)), ('traits', models.JSONField(blank=True, default=list)), ('personality_traits', models.JSONField(blank=True, default=list)), ('ideals', models.JSONField(blank=True, default=list)), ('bonds', models.JSONField(blank=True, default=list)), ('flaws', models.JSONField(blank=True, default=list)), ('notes', models.TextField(blank=True)), + ('hp_features', models.JSONField(blank=True, default=list)), ('custom_attributes', models.JSONField(blank=True, default=dict)), ('created_at', models.DateTimeField(auto_now_add=True)), ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='characters', to=settings.AUTH_USER_MODEL)), + ('features', models.ManyToManyField(blank=True, related_name='characters', to='main.feature')), ('background', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='background_characters', to='main.package')), ('char_class', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='class_characters', to='main.package')), ('packages', models.ManyToManyField(blank=True, related_name='characters', to='main.package')), @@ -215,6 +199,7 @@ class Migration(migrations.Migration): fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('priority', models.IntegerField(default=0)), + ('requirements_override', models.JSONField(blank=True, null=True)), ('feature', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.feature')), ('package', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.package')), ], diff --git a/main/migrations/0010_alter_character_hp_features.py b/main/migrations/0002_alter_package_package_operations.py similarity index 59% rename from main/migrations/0010_alter_character_hp_features.py rename to main/migrations/0002_alter_package_package_operations.py index 673e514..d970a6b 100644 --- a/main/migrations/0010_alter_character_hp_features.py +++ b/main/migrations/0002_alter_package_package_operations.py @@ -1,4 +1,4 @@ -# Generated by Django 6.0 on 2025-12-14 23:19 +# Generated by Django 6.0.5 on 2026-05-10 18:11 from django.db import migrations, models @@ -6,13 +6,13 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('main', '0009_character_hp_features'), + ('main', '0001_initial'), ] operations = [ migrations.AlterField( - model_name='character', - name='hp_features', + model_name='package', + name='package_operations', field=models.JSONField(blank=True, default=list), ), ] diff --git a/main/migrations/0002_pin.py b/main/migrations/0002_pin.py deleted file mode 100644 index 0c97808..0000000 --- a/main/migrations/0002_pin.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 6.0 on 2025-12-14 16:15 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('main', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='Pin', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('label', models.CharField(max_length=100)), - ('url', models.URLField(max_length=300)), - ('x', models.FloatField(help_text='X position as percentage (0-100)')), - ('y', models.FloatField(help_text='Y position as percentage (0-100)')), - ], - ), - ] diff --git a/main/migrations/0003_pin_pin_type.py b/main/migrations/0003_pin_pin_type.py deleted file mode 100644 index 93e6f15..0000000 --- a/main/migrations/0003_pin_pin_type.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 6.0 on 2025-12-14 18:06 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('main', '0002_pin'), - ] - - operations = [ - migrations.AddField( - model_name='pin', - name='pin_type', - field=models.CharField(default='general', max_length=100), - ), - ] diff --git a/main/migrations/0004_remove_character_features_character_features.py b/main/migrations/0004_remove_character_features_character_features.py deleted file mode 100644 index 2b28ea0..0000000 --- a/main/migrations/0004_remove_character_features_character_features.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 6.0 on 2025-12-14 21:13 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('main', '0003_pin_pin_type'), - ] - - operations = [ - migrations.RemoveField( - model_name='character', - name='features', - ), - migrations.AddField( - model_name='character', - name='features', - field=models.ManyToManyField(blank=True, related_name='characters', to='main.feature'), - ), - ] diff --git a/main/migrations/0005_remove_character_armor_remove_character_charisma_and_more.py b/main/migrations/0005_remove_character_armor_remove_character_charisma_and_more.py deleted file mode 100644 index b78653d..0000000 --- a/main/migrations/0005_remove_character_armor_remove_character_charisma_and_more.py +++ /dev/null @@ -1,123 +0,0 @@ -# Generated by Django 6.0 on 2025-12-14 21:21 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('main', '0004_remove_character_features_character_features'), - ] - - operations = [ - migrations.RemoveField( - model_name='character', - name='armor', - ), - migrations.RemoveField( - model_name='character', - name='charisma', - ), - migrations.RemoveField( - model_name='character', - name='charisma_modifier', - ), - migrations.RemoveField( - model_name='character', - name='charisma_save', - ), - migrations.RemoveField( - model_name='character', - name='constitution', - ), - migrations.RemoveField( - model_name='character', - name='constitution_modifier', - ), - migrations.RemoveField( - model_name='character', - name='constitution_save', - ), - migrations.RemoveField( - model_name='character', - name='dexterity', - ), - migrations.RemoveField( - model_name='character', - name='dexterity_modifier', - ), - migrations.RemoveField( - model_name='character', - name='dexterity_save', - ), - migrations.RemoveField( - model_name='character', - name='intelligence', - ), - migrations.RemoveField( - model_name='character', - name='intelligence_modifier', - ), - migrations.RemoveField( - model_name='character', - name='intelligence_save', - ), - migrations.RemoveField( - model_name='character', - name='proficiency', - ), - migrations.RemoveField( - model_name='character', - name='strength', - ), - migrations.RemoveField( - model_name='character', - name='strength_modifier', - ), - migrations.RemoveField( - model_name='character', - name='strength_save', - ), - migrations.RemoveField( - model_name='character', - name='wisdom', - ), - migrations.RemoveField( - model_name='character', - name='wisdom_modifier', - ), - migrations.RemoveField( - model_name='character', - name='wisdom_save', - ), - migrations.AddField( - model_name='character', - name='charisma_save_prof', - field=models.BooleanField(default=False), - ), - migrations.AddField( - model_name='character', - name='constitution_save_prof', - field=models.BooleanField(default=False), - ), - migrations.AddField( - model_name='character', - name='dexterity_save_prof', - field=models.BooleanField(default=False), - ), - migrations.AddField( - model_name='character', - name='intelligence_save_prof', - field=models.BooleanField(default=False), - ), - migrations.AddField( - model_name='character', - name='strength_save_prof', - field=models.BooleanField(default=False), - ), - migrations.AddField( - model_name='character', - name='wisdom_save_prof', - field=models.BooleanField(default=False), - ), - ] diff --git a/main/migrations/0006_remove_character_athletics.py b/main/migrations/0006_remove_character_athletics.py deleted file mode 100644 index c23223a..0000000 --- a/main/migrations/0006_remove_character_athletics.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 6.0 on 2025-12-14 21:56 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('main', '0005_remove_character_armor_remove_character_charisma_and_more'), - ] - - operations = [ - migrations.RemoveField( - model_name='character', - name='athletics', - ), - ] diff --git a/main/migrations/0007_remove_character_acrobatics_and_more.py b/main/migrations/0007_remove_character_acrobatics_and_more.py deleted file mode 100644 index cf88660..0000000 --- a/main/migrations/0007_remove_character_acrobatics_and_more.py +++ /dev/null @@ -1,165 +0,0 @@ -# Generated by Django 6.0 on 2025-12-14 22:20 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('main', '0006_remove_character_athletics'), - ] - - operations = [ - migrations.RemoveField( - model_name='character', - name='acrobatics', - ), - migrations.RemoveField( - model_name='character', - name='acrobatics_passive', - ), - migrations.RemoveField( - model_name='character', - name='animal_handling', - ), - migrations.RemoveField( - model_name='character', - name='animal_handling_passive', - ), - migrations.RemoveField( - model_name='character', - name='arcana', - ), - migrations.RemoveField( - model_name='character', - name='arcana_passive', - ), - migrations.RemoveField( - model_name='character', - name='athletics_passive', - ), - migrations.RemoveField( - model_name='character', - name='deception', - ), - migrations.RemoveField( - model_name='character', - name='deception_passive', - ), - migrations.RemoveField( - model_name='character', - name='history', - ), - migrations.RemoveField( - model_name='character', - name='history_passive', - ), - migrations.RemoveField( - model_name='character', - name='insight', - ), - migrations.RemoveField( - model_name='character', - name='insight_passive', - ), - migrations.RemoveField( - model_name='character', - name='intimidation', - ), - migrations.RemoveField( - model_name='character', - name='intimidation_passive', - ), - migrations.RemoveField( - model_name='character', - name='investigation', - ), - migrations.RemoveField( - model_name='character', - name='investigation_passive', - ), - migrations.RemoveField( - model_name='character', - name='medicine', - ), - migrations.RemoveField( - model_name='character', - name='medicine_passive', - ), - migrations.RemoveField( - model_name='character', - name='nature', - ), - migrations.RemoveField( - model_name='character', - name='nature_passive', - ), - migrations.RemoveField( - model_name='character', - name='passive_insight', - ), - migrations.RemoveField( - model_name='character', - name='passive_investigation', - ), - migrations.RemoveField( - model_name='character', - name='passive_perception', - ), - migrations.RemoveField( - model_name='character', - name='perception', - ), - migrations.RemoveField( - model_name='character', - name='perception_passive', - ), - migrations.RemoveField( - model_name='character', - name='performance', - ), - migrations.RemoveField( - model_name='character', - name='performance_passive', - ), - migrations.RemoveField( - model_name='character', - name='persuasion', - ), - migrations.RemoveField( - model_name='character', - name='persuasion_passive', - ), - migrations.RemoveField( - model_name='character', - name='religion', - ), - migrations.RemoveField( - model_name='character', - name='religion_passive', - ), - migrations.RemoveField( - model_name='character', - name='sleight_of_hand', - ), - migrations.RemoveField( - model_name='character', - name='sleight_of_hand_passive', - ), - migrations.RemoveField( - model_name='character', - name='stealth', - ), - migrations.RemoveField( - model_name='character', - name='stealth_passive', - ), - migrations.RemoveField( - model_name='character', - name='survival', - ), - migrations.RemoveField( - model_name='character', - name='survival_passive', - ), - ] diff --git a/main/migrations/0008_remove_character_hp_max_remove_character_initiative.py b/main/migrations/0008_remove_character_hp_max_remove_character_initiative.py deleted file mode 100644 index 94de5d5..0000000 --- a/main/migrations/0008_remove_character_hp_max_remove_character_initiative.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 6.0 on 2025-12-14 23:11 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('main', '0007_remove_character_acrobatics_and_more'), - ] - - operations = [ - migrations.RemoveField( - model_name='character', - name='hp_max', - ), - migrations.RemoveField( - model_name='character', - name='initiative', - ), - ] diff --git a/main/migrations/0009_character_hp_features.py b/main/migrations/0009_character_hp_features.py deleted file mode 100644 index 2725650..0000000 --- a/main/migrations/0009_character_hp_features.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 6.0 on 2025-12-14 23:18 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('main', '0008_remove_character_hp_max_remove_character_initiative'), - ] - - operations = [ - migrations.AddField( - model_name='character', - name='hp_features', - field=models.JSONField(blank=True, default=dict), - ), - ] diff --git a/main/migrations/0011_feature_feature_requirements.py b/main/migrations/0011_feature_feature_requirements.py deleted file mode 100644 index 928a659..0000000 --- a/main/migrations/0011_feature_feature_requirements.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 6.0 on 2025-12-15 00:05 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('main', '0010_alter_character_hp_features'), - ] - - operations = [ - migrations.AddField( - model_name='feature', - name='feature_requirements', - field=models.JSONField(default=dict), - ), - ] diff --git a/main/migrations/0012_packagefeature_requirements_override.py b/main/migrations/0012_packagefeature_requirements_override.py deleted file mode 100644 index c1b761e..0000000 --- a/main/migrations/0012_packagefeature_requirements_override.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 6.0 on 2025-12-15 00:09 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('main', '0011_feature_feature_requirements'), - ] - - operations = [ - migrations.AddField( - model_name='packagefeature', - name='requirements_override', - field=models.JSONField(blank=True, null=True), - ), - ] diff --git a/main/migrations/0013_alter_feature_feature_requirements.py b/main/migrations/0013_alter_feature_feature_requirements.py deleted file mode 100644 index f5f3212..0000000 --- a/main/migrations/0013_alter_feature_feature_requirements.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 6.0 on 2025-12-15 01:58 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('main', '0012_packagefeature_requirements_override'), - ] - - operations = [ - migrations.AlterField( - model_name='feature', - name='feature_requirements', - field=models.JSONField(default=list), - ), - ] diff --git a/main/migrations/0014_alter_character_features_alter_character_packages.py b/main/migrations/0014_alter_character_features_alter_character_packages.py deleted file mode 100644 index 920457b..0000000 --- a/main/migrations/0014_alter_character_features_alter_character_packages.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 6.0.5 on 2026-05-06 22:38 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('main', '0013_alter_feature_feature_requirements'), - ] - - operations = [ - migrations.AlterField( - model_name='character', - name='features', - field=models.ManyToManyField(blank=True, related_name='+', to='main.feature'), - ), - migrations.AlterField( - model_name='character', - name='packages', - field=models.ManyToManyField(blank=True, related_name='+', to='main.package'), - ), - ] diff --git a/main/migrations/0015_alter_character_features_alter_character_packages.py b/main/migrations/0015_alter_character_features_alter_character_packages.py deleted file mode 100644 index fb1df3a..0000000 --- a/main/migrations/0015_alter_character_features_alter_character_packages.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 6.0.5 on 2026-05-06 22:44 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('main', '0014_alter_character_features_alter_character_packages'), - ] - - operations = [ - migrations.AlterField( - model_name='character', - name='features', - field=models.ManyToManyField(blank=True, related_name='characters', to='main.feature'), - ), - migrations.AlterField( - model_name='character', - name='packages', - field=models.ManyToManyField(blank=True, related_name='characters', to='main.package'), - ), - ] diff --git a/main/migrations/0016_alter_feature_feature_requirements.py b/main/migrations/0016_alter_feature_feature_requirements.py deleted file mode 100644 index 7561c71..0000000 --- a/main/migrations/0016_alter_feature_feature_requirements.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 6.0.5 on 2026-05-06 23:02 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('main', '0015_alter_character_features_alter_character_packages'), - ] - - operations = [ - migrations.AlterField( - model_name='feature', - name='feature_requirements', - field=models.JSONField(blank=True, default=list), - ), - ] diff --git a/main/migrations/__pycache__/0001_initial.cpython-314.pyc b/main/migrations/__pycache__/0001_initial.cpython-314.pyc index c35b8947e601224ce83ad8119573d48cf0f41052..db8f60534e671d9d4a69ab2634a8bcf421115652 100644 GIT binary patch literal 16644 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 literal 0 HcmV?d00001 diff --git a/main/migrations/__pycache__/0017_feature_feature_system.cpython-314.pyc b/main/migrations/__pycache__/0017_feature_feature_system.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..59d63fb3f5f4ff8252dcc9539b8818db7e30bc20 GIT binary patch literal 833 zcmZuw&ubGw6rS1LY_e;uF}75UqOAp4glq*d2dgN8RMAovFCLO%nw=&S_g7~oXl^}- zXa5FI`XBgjScHVRdh!-XQ3Map>}IK;b9nQ8ynWxk@4eld%V%AnPV3ii`T+v)O_TCe z7GVBBfg{ib0ek{>pnyAuFs_1Cunq!a4FqNf?%8G0>3!QQurLGbI`AsrHT4YX#vOTu^B*np1jCU<)^>sumsTJTH5b)dPaXh!MHG0EmydAn^-jp|T(0Kwr8fXVjn%5xxl$F zsWp#l&FR{;>G}0(TaopJWxbk`9YvbNY)pFdKX*cY*PuZnXZP&A;6Ap3GPg~)B zK8$<>#rw3g;@PG`vbJ4}_&&R$sQMECzpTQ{gb@C;0rXDnC1}5QC-u!Q_01nZk-Yj3 DgUZ;T literal 0 HcmV?d00001 diff --git a/main/migrations/__pycache__/0018_package_package_system.cpython-314.pyc b/main/migrations/__pycache__/0018_package_package_system.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e553b316204520d620bce74b893a356ee3ff7be1 GIT binary patch literal 821 zcmZuvO^XvT7*1xU)6Q6Ht-GvPMYp?`B6KPUEBJvT$SS&&!Hb7Bq@8R#-T5*}(cTsm z&z}4Z`VagYE<#JLp1c*hD1rx*nU3{f4sV`^H_w~*d6Vmvvo?_H&9C472MEA7Me}#+zo}=K>#nf_nuYI*HZ@^~{*Yqs z#}N}|7<(jOgS83(YeklU1jnEYICKHmdZsK?YC&#v3!U=6f(*-MpsRI@o$7ycdT-Y? z_OvYL7WP)n$$FM+a@#G+7A;{t%X<^W1P_kzkci3(AU0*3gwLggLO;OKk#vGzqku$1 zK5FWcDK99(ArVFz3x_@lyiedjN{kTuk*K%ZH}=pW!F)o=-#shbJR?ddl_Ml95$eS!kLrMcuWQo;3PqT z&-_W~D0|NOWgThs7Ro1=@0rn7GNtOjq(K38TZVUFVFIhY(6tTo`02t1)`eNAep0H> zx3104Z_jHNXSK#jt?_Z=bK`66?rG7m?1gUD%(qYIrM%if(-4+7#?dftdHr-4=?Jp> xl(Xz?T_RDsla2Tuy&|dn{eWGTVWC3^f0_U`Pt7tk-`lh5?w9KB4gI-76&iEJ}{)m;E_b7M>3W3APWk>kaNI@RHZ#-d}y+M90@Uv z7}57YG4@)BMVYCt-M}WbeUC83^)2L(`s5Ss9=&Paw_o!Qg=buVIYH;kB}lzEt9+?I_Q&d!9=^vpHIjYL@r|8btmc?;8w5OZF{ zn4RJ!ugIIb)NW+|T_aWIaf`t%&vWKtC{548ay4ma6J|*<(qrk;{AB)Tey&~S=l<4P z`B}JP+}X?Y8UKj86rDslm37S!}k6ZP&zYc#PUCK@hf0$NmPfy zJ!I){+d`y{?WQPOPKebU(>vzof}@sShpsi2NSjdP7G*3-3yW%GnUIIk^OlW~i#$q3 z`)tNS2WHEmyfLDIJIaBL9E-5oXSE|#rxk44l&MrKp}*-6PdUVr;jhs04`5d)b4}Z0 zH`doTK7mcMerz_--|NW3c@~F(N(h+>A#cT&gE&q?_`GF0!W%BlLwI0gLLJ*fo-ej7 z)I^?zyt<7D7ER7(w}XuDh-Sv3zgY6V-MGi^;-LtJCAJSuJc1SUf{x&mFQ4s(FzVQZ zN9i}w_+`jKNGN|L>d>SV+qD)&1VVh1E5G0$U={t)caeT%TK?B<59#6c5WR_gkvF27 z%_ih(?Ut*HT1Wcu13$(H@`m_-&@7(U^?{>AV z^YqH;!C>`HZ}m>6(qFyb)wa*m`P0QgVWU^r=tTR4&%4^@TffdS{le3(wsW3-|MXt!*x5N>Pe-;+nCSK>Fcx1^td>Te1N%}(p XQt^#)LsDL;18wDJZRK~sh2ZpG_1~oF literal 0 HcmV?d00001 diff --git a/main/migrations/__pycache__/0020_feature_feature_traits.cpython-314.pyc b/main/migrations/__pycache__/0020_feature_feature_traits.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b899531c870719ce228c57c999ba6df6b13f1555 GIT binary patch literal 851 zcmZuwL5tHs6rM@aw25_Ft80ady10ie3r!V41X=VVtGG)Ed-2d6rpyN?|n1b-MHuiwd!B~($5fppW4Y+ zVFi|t6qtfz;KQe29}2i{2;(N$1Y5v2>cBU;0aYVPX2#ZO|SqR8{J~3`rn+X zvX)=CziP_2bEFs@zbIV4q?&Z4bC7o@ivxnim=U?L0!T1S1wVRQ9bp=^;@*(-MH*|G zvd{~0^h%Z)39%pnitva?L&PfOrc-7SD~%Thl!U=2aH#aKG?~2GZr^!;Fy};|^TZcq z38j??Nf#l-G!BWvWrSXjaj4mpS3zh%85bdqNEGXIKoSxKBC8NPaJy2yvnAsE@E3koLxcaD-Sz6)v^B@`d*@9uaSdgZRxPA|CHE znh5SCEY_lM4<`u^fVHy%tV`7IDq^2MwE@*-a8i-9Ln#8HI+{7I&X%cK#sGNF0 zocS5>BM|%qn2=ie%86U0PH+S}X;cp2!}Ir^^WOX3XXobHc^9bl?Bj3x2?Fq4JH;w3 z!Q#FG6L1WC_z3Jn0rw4IY=I514SZu0_+}S&>>_Ek-!XG6Oy9Z=nx!AwZ5q-Y(i4US zO=2$XVG@#v2dgXqGD0e_h?SXuE)dWM!Z@~7A-4sE)h%_Ze<$Qvk%7L^Eq7}FJX0k6 zl5gK#lJd(1QWYJ)B3$25B+p6bpkODP0wyA*+l;Sdb9Kcu1rn5_RNe^X3sNi(f^m2qFT~c}L2=yuBBBC*g6MY<#l*A#414=m48c6p+BnW$` zyE97%)(>zzK1#IBqS8%LQe4yqLR_dz3lLUw(%wiAju6vYFSOngdC!v}@dh|dUX5eo z@qp1(a4%(v)`ojHO;JQS9SuD_rnhk8LSe{W5^-LUR*B_{#>cd1hCk{mz}% OcD~kjegfrW*?#~hq3L=6 literal 0 HcmV?d00001 diff --git a/main/migrations/__pycache__/0022_alter_asset_id_alter_character_id_and_more.cpython-314.pyc b/main/migrations/__pycache__/0022_alter_asset_id_alter_character_id_and_more.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ffa6e061c455f46705102cb7f4771160814dc2bb GIT binary patch literal 1243 zcmcgs&1(}u6rcS}c9Uw1t@U87{WuteY%Rq?5kZiWN|mrGp3-5mJ836wcGuZiB%XQ@ z&;ApFJ^3$)e?cum%UnEo@)k((sxzBxg9kxDe6a6h<~Q@+o8Mz^oSU+NkjBR!q^kk& zRR*~jV`tb9&OX=yuCfJ~wCa}9P?_4$m^Ke)zyff!S>Wm|wOPy$PwbkwR|?ZzWeL=b zZ!&mTQ@PzEyA&}Jg$Xx%k&lDKlY6;Ljh*4PBJ;NZQ(VB*odVO`Qe9zs%V?H!@WhI0 z6jyB(nw7~~?k#|p)tt_wiFGws7oV}NkJemseO&c#a}@qVj^e+|QP)_>wOHA;1qWqr zJ;>jl8z@OI<0|ntAJ=qV@NoyF0pmsN6NcIW=B1dD9-@8t4EMQ}U`kLxUgDZ2D3r9p z5cM$EGi`JT4t#ETdx#>BVaiKUyNf->C?f12BVO+z67q$WmDQUN1p)&*7_pS%Gn;{v z+7vIs>~aXX4WUSu23YuI2%o1YkO@U}9>NZx2@6Pw!$@xXIL4umLyzEuN+P*^SLWH2 zwNc42x1K!Nx|hXVPgCM^L+UkdM={Qaat6YLi2?N>66KS2%5VZ9t%~zQIVZg5>_t87 zbdevu=!e)zJW67gI5CZ6%ZY>H7zQLEY0r^8JClibME9%X?lS@)@yFrW(`8GcFZ!mq z9`~s!a%QR^oaC)guff0s)~$i2PMiDN0~=TukBf6h#krH&t0xy`PO6uWtBXg~#dmA( zHx4#F7>AFZ9(sG9tK_txTlO#b#lOTa{{eosQZ--Qr`PhjYX-d{d~W$&6z)cKzn!s@ zF*52-zU>IVAc4i(BhK&8B@v3>07&MA8fc253{9Yvzkutf<|V~^V;@(RKUJ2$0}*5w F`~<0Qbat6D8ivJ=@KO2VvHC2c0oM#+OoIt<4fw_+@XZ$7vTNkWnYlyIhJC*5V8 zbyZi&^jKx}F5O*L0OTs^rWq+VZ2}=K)Yb(Es}^XxBM3)`jVrHHy$~|?_R^Sm`#4OW zbra(8fYD5FFJq~0n|nCRP((TH#GdZk8?LcN;O1W-(9F6_KbZW`41c4JSDq;-@XPAx cnGnKnHh_&Y`y#YoyT`TJkG0vaKnc0`5Bgjt?EnA( literal 0 HcmV?d00001 diff --git a/main/models.py b/main/models.py index 3fa0963..12d4cac 100644 --- a/main/models.py +++ b/main/models.py @@ -92,6 +92,8 @@ def apply_operations(base, operations, character): # Character model based on character_template.json class Character(models.Model): + objects = models.Manager() + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) owner = models.ForeignKey('CustomUser', on_delete=models.CASCADE, related_name='characters') name = models.CharField(max_length=100) level = models.IntegerField(default=0) @@ -576,6 +578,7 @@ class Character(models.Model): class Feature(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) feature_name = models.CharField(max_length=200) + feature_system = models.CharField(max_length=36, blank=True) feature_description = models.TextField(blank=True) feature_requirements = models.JSONField(default=list, blank=True) #feature requirements requires 3 keys, {"property", "value", "condition"} @@ -583,10 +586,18 @@ class Feature(models.Model): #feature_data holds a value of {"operations", "sources"} # ----> operations is a list of dictionaries that have to have 6 values # ----> {"attr", "value", "operation", "limits", "operation_requirements", "priority"} + feature_traits = models.ManyToManyField('ObjectTrait', blank=True, related_name='features') def __str__(self): return self.feature_name + def to_json(self): + return { + 'id': str(self.id), + 'feature_name': self.feature_name, + 'feature_description': self.feature_description + } + # Package <-> Feature through model for priorities class PackageFeature(models.Model): package = models.ForeignKey('Package', on_delete=models.CASCADE) @@ -605,15 +616,46 @@ class PackageFeature(models.Model): class Package(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) package_name = models.CharField(max_length=200) + package_system = models.CharField(max_length=36, blank=True) package_description = models.TextField(blank=True) package_type = models.CharField(max_length=100, blank=True) package_doc_md = models.TextField(blank=True) + package_requirements = models.JSONField(default=list, blank=True) + #feature requirements requires 3 keys, {"property", "value", "condition"} + package_operations = models.JSONField(default=list, blank=True) + #feature_data holds a value of {"operations", "sources"} + # ----> operations is a list of dictionaries that have to have 6 values + # ----> {"attr", "value", "operation", "limits", "operation_requirements", "priority"} features = models.ManyToManyField('Feature', through='PackageFeature', blank=True, related_name='packages') + package_traits = models.ManyToManyField('ObjectTrait', blank=True, related_name='packages') def __str__(self): return self.package_name -# Create your models here. + +class Asset(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + asset_name = models.CharField(max_length=256) + asset_description = models.TextField(blank=True) + asset_doc_md = models.TextField(blank=True) + asset_system = models.CharField(max_length=32, blank=True) + asset_requirements = models.JSONField(default=list, blank=True) + +class ObjectTrait(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + trait_name = models.CharField(max_length=64) + trait_description = models.TextField(blank=True) + trait_system = models.CharField(max_length=32, blank=True) + + def __str__(self): + return self.trait_name + + def to_json(self): + return { + 'id': str(self.id), + 'trait_name': self.trait_name + } + class Pin(models.Model): label = models.CharField(max_length=100) url = models.URLField(max_length=300) diff --git a/main/templates/main/character_sheet.html b/main/templates/main/character_sheet.html index d07c512..7edc39c 100644 --- a/main/templates/main/character_sheet.html +++ b/main/templates/main/character_sheet.html @@ -39,7 +39,7 @@


-
{{ character.race }} {{ character.subrace }}
+
{{ character.race }} {{ character.subrace }}
{{ character.char_class.package_name }} {{ character.subclass }}
{{ character.background }}
{{ character.alignment }}
diff --git a/main/templates/main/feature.html b/main/templates/main/feature.html index 10c932c..7718249 100644 --- a/main/templates/main/feature.html +++ b/main/templates/main/feature.html @@ -7,6 +7,15 @@ +
+
+ {% csrf_token %}
@@ -80,151 +89,209 @@
+
+
+
- + + - \ No newline at end of file + diff --git a/main/templates/main/feature_detail.html b/main/templates/main/feature_detail.html new file mode 100644 index 0000000..3fcd598 --- /dev/null +++ b/main/templates/main/feature_detail.html @@ -0,0 +1,78 @@ +{% load static %} + + + + Feature Detail + + + + +
+

Feature Detail/Edit
{{ feature.feature_name }}

+
+ {% csrf_token %} +
+ + +
+
+ + +
+
+ + + + + + + + + {% for req in feature.feature_requirements %} + + + + + {% endfor %} + +
AttributeConditionValue
+
+ +
+
+

Operations + +

+
+
+
+ +
+
+
+ + + + + + diff --git a/main/templates/main/feature_select_table.html b/main/templates/main/feature_select_table.html new file mode 100644 index 0000000..ad5b628 --- /dev/null +++ b/main/templates/main/feature_select_table.html @@ -0,0 +1,49 @@ +{% load static %} + + + + + + + + + + {% if page_obj and page_obj.object_list %} + {% for feature in page_obj.object_list %} + + + + + + {% endfor %} + {% else %} + + + + {% endif %} + +
NameDescriptionAction
{{ feature.feature_name }}{{ feature.feature_description|truncatewords:17 }} + +
No features found.
+ +{% if page_obj %} +
+{% endif %} diff --git a/main/templates/main/features_list.html b/main/templates/main/features_list.html new file mode 100644 index 0000000..c182d1c --- /dev/null +++ b/main/templates/main/features_list.html @@ -0,0 +1,47 @@ +{% load static %} + + + + Features List + + + + +
+

Features for {{ system|default:'?' }}

+ + {% if features %} + + + + + + + + + + + {% for feature in features %} + + + + + + + {% endfor %} + +
NameDescriptionRequirementsView
{{ feature.feature_name }}{{ feature.feature_description|truncatewords:15 }}{% for req in feature.feature_requirements %} +
{{ req.attr }} {{ req.condition }} {{ req.value }}
+ {% endfor %} +
+ View +
+ {% else %} +
No features found for this system.
+ {% endif %} + +
+ + + + diff --git a/main/templates/main/packages_list.html b/main/templates/main/packages_list.html new file mode 100644 index 0000000..6ef0b47 --- /dev/null +++ b/main/templates/main/packages_list.html @@ -0,0 +1,49 @@ +{% load static %} + + + + Packages List + + + + +
+

Packages for {{ system|default:'?' }}

+ + {% if packages %} + + + + + + + + + + + + {% for package in packages %} + + + + + + + + {% endfor %} + +
NameTypeDescriptionRequirementsView
{{ package.package_name }}{{ package.package_type }}{{ package.package_description|truncatewords:15 }}{% for req in package.package_requirements %} +
{{ req.attr }} {{ req.condition }} {{ req.value }}
+ {% endfor %} +
+ View +
+ {% else %} +
No packages found for this system.
+ {% endif %} + +
+ + + + diff --git a/main/templates/main/test_feature.html b/main/templates/main/test_feature.html new file mode 100644 index 0000000..1766816 --- /dev/null +++ b/main/templates/main/test_feature.html @@ -0,0 +1,475 @@ +{% load static %} + + + + Test Feature Builder + + + + + +
+

Test Feature Builder
Structured Table Entry UI

+
+ {% csrf_token %} +
+ + +
+
+
+ +
+
+
+ +
+ +
+
+
+ + + + + + + + + + + {% for req in feature.feature_requirements %} + + + + + {% endfor %} + +
AttributeConditionValue
+
+ +
+
+

Operations + +

+
+
+
+ +
+
+
+ + + + + + + + + + +
+ + + diff --git a/main/templates/main/test_package.html b/main/templates/main/test_package.html new file mode 100644 index 0000000..f5d89a4 --- /dev/null +++ b/main/templates/main/test_package.html @@ -0,0 +1,621 @@ +{% load static %} + + + + Test Package Builder + + + + + + +
+

Test Package Builder
Structured Table Entry UI

+
+ {% csrf_token %} +
+ + +
+
+ + +
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ +
+
+
+ + + + + + + + + + + {% for req in package.package_requirements %} + + + + + {% endfor %} + +
AttributeConditionValue
+
+ +
+
+ + +
+
+

Operations + +

+
+
+
+ + + + + + + + + + + + +
NameDescriptionStatusAction
+
+
+
+
+ +
+
+
+ +
+
+

Select Feature...

+ +
+ +
+ +
+
+ + + + + + + +
+ + + diff --git a/main/urls.py b/main/urls.py index 6d17c10..729bcfe 100644 --- a/main/urls.py +++ b/main/urls.py @@ -3,7 +3,14 @@ from . import views urlpatterns = [ path('', views.home, name='Home'), - path('feature', views.feature_add_view, name="feature_add"), + path('/feature/new', views.feature_new, name="feature_new"), + path('/feature/', views.feature_detail, name='feature_detail'), + path('/features/', views.features_list, name="features_list"), + path('/package/new', views.package_new, name="package_new"), + path('/package/', views.package_detail, name='package_detail'), + path('/packages/', views.packages_list, name="packages_list"), + path('features/search/', views.feature_select_search, name="feature_select_search"), + path('traits/search/', views.trait_search, name="trait_search"), path('map', views.map_page, name="Map"), path('api/pins/', views.pin_list, name='pin_list'), ] \ No newline at end of file diff --git a/main/views.py b/main/views.py index f03912b..f047da4 100644 --- a/main/views.py +++ b/main/views.py @@ -1,7 +1,8 @@ -from django.shortcuts import render -from django.http import HttpResponse +from django.shortcuts import render, get_object_or_404 +from django.http import HttpResponse, HttpResponseRedirect, JsonResponse from django.apps import apps -from .models import Character, PackageFeature, Feature, Pin +from .models import Character, PackageFeature, Feature, Pin, Package, ObjectTrait +from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from rest_framework.decorators import api_view from rest_framework.response import Response @@ -12,6 +13,50 @@ from .serializers import PinSerializer import json # Create your views here. + +from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger +from django.template.loader import render_to_string +from django.views.decorators.http import require_GET + +@require_GET +def feature_select_search(request): + system = request.GET.get('system') + q = request.GET.get('q', '').strip() + page = request.GET.get('page', 1) + per_page = 5 + print(q, system, page) + feats = Feature.objects.all() + if system: + feats = feats.filter(feature_system=system) + if q: + feats = feats.filter( + feature_name__icontains=q + ) + + paginator = Paginator(feats.order_by('feature_name'), per_page) + try: + page_obj = paginator.page(page) + except PageNotAnInteger: + page_obj = paginator.page(1) + except EmptyPage: + page_obj = paginator.page(paginator.num_pages) + + data = { + 'results': [ + { + 'id': str(feat.id), + 'feature_name': feat.feature_name, + 'feature_description': feat.feature_description, + } + for feat in page_obj.object_list + ], + 'page': page_obj.number, + 'num_pages': paginator.num_pages, + 'has_previous': page_obj.has_previous(), + 'has_next': page_obj.has_next(), + } + return JsonResponse(data) + def home(request): with open('test_character.json', 'r+') as file: character = json.load(file) @@ -25,69 +70,170 @@ def home(request): ) character.char_class.display_features = class_feature_links return render(request, 'main/character_sheet.html', {'character': character, }) + return render(request, 'main/character_sheet.html') -def feature_add_view(request): +def package_detail(request, system, package_uuid): + package = get_object_or_404(Package, id=package_uuid) + + if request.method == 'POST': + try: + payload = json.loads(request.POST.get('package_payload')) + + package.package_name = payload.get('package_name') + package.package_description = payload.get('package_description') + package.package_requirements = payload.get('package_requirements') + package.package_doc_md = payload.get('package_doc_md') + package.package_operations = payload.get('package_operations') + package.package_type = payload.get('package_type') + + trait_ids = [trait['id'] for trait in payload.get('package_traits', [])] + print(trait_ids) + if trait_ids is not None: + package.package_traits.set(trait_ids) + + feat_ids = [feat['id'] for feat in payload.get('features', [])] + print(feat_ids) + if trait_ids is not None: + package.features.set(feat_ids) + + package.save() + + return HttpResponse(''' + + ''') + + except Exception as e: + return HttpResponse(''' + + ''') + + elif request.method == 'GET': + traits = package.package_traits.all() + traits = [trait.to_json() for trait in traits] + features = package.features.all() + features = [feat.to_json() for feat in features] + return render(request, 'main/test_package.html', {'system': system, 'package': package, 'traits': traits, 'features':features }) + + +def package_new(request, system): if request.method == 'GET': - return render(request, 'main/feature.html') + return render(request, 'main/test_package.html', {'system': system}) elif request.method == 'POST': - # Parse basic fields - name = request.POST.get('feature_name') - description = request.POST.get('feature_description') + payload_json = request.POST.get('package_payload') + try: + payload = json.loads(payload_json) + print('---PAYLOAD---') + print(json.dumps(payload, indent=2)) + feature = Feature.objects.create( + feature_name=payload.get('feature_name'), + feature_description=payload.get('feature_description'), + feature_system=str(system), + feature_requirements=payload.get('feature_requirements'), + feature_data=payload.get('feature_data') + ) - # Parse Requirements - requirements = [] - req_keys = [k for k in request.POST.keys() if k.startswith('requirement_')] - idxs = set() - for k in req_keys: - try: - idxs.add(int(k.split('_')[-1])) - except: - continue - for idx in sorted(list(idxs)): - prop = request.POST.get(f'requirement_property_{idx}', '').strip() - cond = request.POST.get(f'requirement_condition_{idx}', '').strip() - val = request.POST.get(f'requirement_value_{idx}', '').strip() - if prop and cond and val: - requirements.append({'property': prop, 'condition': cond, 'value': val}) + trait_ids = [trait['id'] for trait in payload.get('feature_traits', [])] - # Parse Operations (with extra subparts) - operations = [] - op_keys = [k for k in request.POST.keys() if k.startswith('operation_attr_')] - op_idxs = [int(k.replace('operation_attr_', '')) for k in op_keys] - for op_idx in op_idxs: - attr = request.POST.get(f'operation_attr_{op_idx}', '').strip() - op = request.POST.get(f'operation_operation_{op_idx}', '').strip() - value = request.POST.get(f'operation_value_{op_idx}', '').strip() - # Subparts/limits - subparts = [] - sub_idx = 0 - while True: - kfield = f'operation_{op_idx}_limitkey_{sub_idx}' - vfield = f'operation_{op_idx}_limitval_{sub_idx}' - if kfield in request.POST and vfield in request.POST: - k = request.POST.get(kfield, '').strip() - v = request.POST.get(vfield, '').strip() - if k and v: - subparts.append({'key': k, 'value': v}) - sub_idx += 1 - else: - break - operation = {'attr': attr, 'operation': op, 'value': value} - for part in subparts: - operation[part['key']] = part['value'] - operations.append(operation) + if trait_ids is not None: + feature.feature_traits.set(trait_ids) + response = HttpResponse(status=204) + response['HX-Redirect'] = f'/{system}/feature/{feature.id}' + return response + except Exception as e: + print('Failed to parse payload:', str(e)) + payload = None - feat = Feature.objects.create( - feature_name=name, - feature_description=description, - feature_requirements=requirements, - feature_data={'operations': operations} - ) - return render(request, 'main/feature_add.html', { - 'msg': f'Feature "{feat.feature_name}" added successfully!', - }) + return HttpResponse('
Payload printed to server log.
', content_type="text/html") + +def packages_list(request, system): + packages = Package.objects.filter(package_system=system) + return render(request, "main/packages_list.html", { + "packages": packages, + "system": system + }) + + +def feature_detail(request, system, feature_uuid): + feature = get_object_or_404(Feature, id=feature_uuid) + + if request.method == 'POST': + try: + payload = json.loads(request.POST.get('feature_payload')) + + feature.feature_name = payload.get('feature_name') + feature.feature_description = payload.get('feature_description') + feature.feature_requirements = payload.get('feature_requirements') + feature.feature_data = payload.get('feature_data') + + feature.save() + + trait_ids = [trait['id'] for trait in payload.get('feature_traits', [])] + print(trait_ids) + if trait_ids is not None: + feature.feature_traits.set(trait_ids) + + return HttpResponse(''' + + ''') + + except Exception as e: + return HttpResponse(''' + + ''') + + elif request.method == 'GET': + traits = feature.feature_traits.all() + traits = [trait.to_json() for trait in traits] + return render(request, 'main/test_feature.html', {'system': system, 'feature': feature, 'traits': traits }) + + +def feature_new(request, system): + if request.method == 'GET': + return render(request, 'main/test_feature.html', {'system': system}) + elif request.method == 'POST': + payload_json = request.POST.get('feature_payload') + try: + payload = json.loads(payload_json) + print('---PAYLOAD---') + print(json.dumps(payload, indent=2)) + feature = Feature.objects.create( + feature_name=payload.get('feature_name'), + feature_description=payload.get('feature_description'), + feature_system=str(system), + feature_requirements=payload.get('feature_requirements'), + feature_data=payload.get('feature_data') + ) + + trait_ids = [trait['id'] for trait in payload.get('feature_traits', [])] + + + if trait_ids is not None: + feature.feature_traits.set(trait_ids) + response = HttpResponse(status=204) + response['HX-Redirect'] = f'/{system}/feature/{feature.id}' + return response + except Exception as e: + print('Failed to parse payload:', str(e)) + payload = None + + return HttpResponse('
Payload printed to server log.
', content_type="text/html") + +def features_list(request, system): + features = Feature.objects.filter(feature_system=system) + return render(request, "main/features_list.html", { + "features": features, + "system": system + }) def map_page(request): return render(request, "main/map.html") @@ -103,4 +249,10 @@ def pin_list(request): if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) \ No newline at end of file + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + +def trait_search(request): + query = request.GET.get('q', '') + system = request.GET.get('system', '') + traits = ObjectTrait.objects.filter(trait_system=system, trait_name__icontains=query).values('id', 'trait_name')[:20] + return JsonResponse(list(traits), safe=False) \ No newline at end of file