diff --git a/db.sqlite3 b/db.sqlite3 index e5b772a..949c2f3 100644 Binary files a/db.sqlite3 and b/db.sqlite3 differ diff --git a/main/__pycache__/admin.cpython-314.pyc b/main/__pycache__/admin.cpython-314.pyc index 6ed249c..7d77299 100644 Binary files a/main/__pycache__/admin.cpython-314.pyc and b/main/__pycache__/admin.cpython-314.pyc differ diff --git a/main/__pycache__/models.cpython-314.pyc b/main/__pycache__/models.cpython-314.pyc index 5c6715c..63ae8bf 100644 Binary files a/main/__pycache__/models.cpython-314.pyc and b/main/__pycache__/models.cpython-314.pyc differ diff --git a/main/__pycache__/urls.cpython-314.pyc b/main/__pycache__/urls.cpython-314.pyc index c394f83..7e5d907 100644 Binary files a/main/__pycache__/urls.cpython-314.pyc and b/main/__pycache__/urls.cpython-314.pyc differ diff --git a/main/__pycache__/views.cpython-314.pyc b/main/__pycache__/views.cpython-314.pyc index 58cbd29..8c98ff4 100644 Binary files a/main/__pycache__/views.cpython-314.pyc and b/main/__pycache__/views.cpython-314.pyc differ 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 c35b894..db8f605 100644 Binary files a/main/migrations/__pycache__/0001_initial.cpython-314.pyc and b/main/migrations/__pycache__/0001_initial.cpython-314.pyc differ diff --git a/main/migrations/__pycache__/0002_alter_package_package_operations.cpython-314.pyc b/main/migrations/__pycache__/0002_alter_package_package_operations.cpython-314.pyc new file mode 100644 index 0000000..b71abc4 Binary files /dev/null and b/main/migrations/__pycache__/0002_alter_package_package_operations.cpython-314.pyc differ 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 0000000..59d63fb Binary files /dev/null and b/main/migrations/__pycache__/0017_feature_feature_system.cpython-314.pyc differ 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 0000000..e553b31 Binary files /dev/null and b/main/migrations/__pycache__/0018_package_package_system.cpython-314.pyc differ diff --git a/main/migrations/__pycache__/0019_asset_objecttrait.cpython-314.pyc b/main/migrations/__pycache__/0019_asset_objecttrait.cpython-314.pyc new file mode 100644 index 0000000..7ca20da Binary files /dev/null and b/main/migrations/__pycache__/0019_asset_objecttrait.cpython-314.pyc differ 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 0000000..b899531 Binary files /dev/null and b/main/migrations/__pycache__/0020_feature_feature_traits.cpython-314.pyc differ diff --git a/main/migrations/__pycache__/0021_alter_feature_feature_traits.cpython-314.pyc b/main/migrations/__pycache__/0021_alter_feature_feature_traits.cpython-314.pyc new file mode 100644 index 0000000..8daba44 Binary files /dev/null and b/main/migrations/__pycache__/0021_alter_feature_feature_traits.cpython-314.pyc differ 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 0000000..ffa6e06 Binary files /dev/null and b/main/migrations/__pycache__/0022_alter_asset_id_alter_character_id_and_more.cpython-314.pyc differ diff --git a/main/migrations/__pycache__/0023_alter_asset_id_alter_character_id_and_more.cpython-314.pyc b/main/migrations/__pycache__/0023_alter_asset_id_alter_character_id_and_more.cpython-314.pyc new file mode 100644 index 0000000..d504c7e Binary files /dev/null and b/main/migrations/__pycache__/0023_alter_asset_id_alter_character_id_and_more.cpython-314.pyc differ 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