Skip to content

Commit

Permalink
Merge pull request #231 from open5e/staging
Browse files Browse the repository at this point in the history
Release: Fixes to spells which were deleted by accident, and some magic items.
  • Loading branch information
eepMoody authored May 18, 2023
2 parents 5b3b130 + e243372 commit 2727e3e
Show file tree
Hide file tree
Showing 28 changed files with 11,183 additions and 403 deletions.
19 changes: 19 additions & 0 deletions api/management/commands/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,25 @@ def import_spell(self, spell_json, import_spec) -> ImportResult:
i.save()
return result

def import_spell_list(self, spell_list_json, import_spec) -> ImportResult:
""" Create or update a spell list. Spells must be present before importing the list."""
new = False
exists = False
slug = slugify(spell_list_json["name"])
if models.SpellList.objects.filter(slug=slug).exists():
i = models.SpellList.objects.get(slug=slug)
exists = True
else:
i = models.SpellList(document=self._last_document_imported)
new = True

i.import_from_json_v1(json=spell_list_json)

result = self._determine_import_result(new, exists)
if result is not ImportResult.SKIPPED:
i.save()
return result

def import_weapon(self, weapon_json, import_spec) -> ImportResult:
"""Create or update a single Weapon model from a JSON object."""
new = False
Expand Down
1 change: 1 addition & 0 deletions api/management/commands/populatedb.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ def _populate_from_directory(self, directory: Path) -> None:
importer.import_magic_item,
),
ImportSpec("spells.json", models.Spell, importer.import_spell),
ImportSpec("spelllist.json", models.SpellList, importer.import_spell_list),
ImportSpec("monsters.json", models.Monster, importer.import_monster),
ImportSpec("planes.json", models.Plane, importer.import_plane),
ImportSpec("sections.json", models.Section, importer.import_section),
Expand Down
36 changes: 36 additions & 0 deletions api/migrations/0026_auto_20230511_1741.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Generated by Django 3.2.18 on 2023-05-11 17:41

import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import uuid


class Migration(migrations.Migration):

dependencies = [
('api', '0025_auto_20230423_0234'),
]

operations = [
migrations.AlterField(
model_name='spell',
name='target_range_sort',
field=models.IntegerField(help_text='Sortable distance ranking to the target. 0 for self, 1 for touch, sight is 9999, unlimited (same plane) is 99990, unlimited any plane is 99999. All other values in feet.', validators=[django.core.validators.MinValueValidator(0)]),
),
migrations.CreateModel(
name='SpellList',
fields=[
('slug', models.CharField(default=uuid.uuid1, help_text='Short name for the game content item.', max_length=255, primary_key=True, serialize=False, unique=True)),
('name', models.TextField(help_text='Name of the game content item.')),
('desc', models.TextField(help_text='Description of the game content item. Markdown.')),
('created_at', models.DateTimeField(auto_now_add=True)),
('page_no', models.IntegerField(null=True)),
('document', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.document')),
('spells', models.ManyToManyField(help_text='The set of spells.', to='api.Spell')),
],
options={
'abstract': False,
},
),
]
44 changes: 44 additions & 0 deletions api/migrations/0026_auto_20230513_1436.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Generated by Django 3.2.18 on 2023-05-13 14:36

import django.core.validators
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('api', '0025_auto_20230423_0234'),
]

operations = [
migrations.AlterField(
model_name='armor',
name='plus_con_mod',
field=models.BooleanField(default=False),
),
migrations.AlterField(
model_name='armor',
name='plus_dex_mod',
field=models.BooleanField(default=False),
),
migrations.AlterField(
model_name='armor',
name='plus_flat_mod',
field=models.IntegerField(default=False),
),
migrations.AlterField(
model_name='armor',
name='plus_max',
field=models.IntegerField(default=0),
),
migrations.AlterField(
model_name='armor',
name='plus_wis_mod',
field=models.BooleanField(default=False),
),
migrations.AlterField(
model_name='spell',
name='target_range_sort',
field=models.IntegerField(help_text='Sortable distance ranking to the target. 0 for self, 1 for touch, sight is 9999, unlimited (same plane) is 99990, unlimited any plane is 99999. All other values in feet.', validators=[django.core.validators.MinValueValidator(0)]),
),
]
18 changes: 18 additions & 0 deletions api/migrations/0027_alter_spelllist_spells.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.18 on 2023-05-11 17:57

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('api', '0026_auto_20230511_1741'),
]

operations = [
migrations.AlterField(
model_name='spelllist',
name='spells',
field=models.ManyToManyField(help_text='The set of spells.', related_name='spell_lists', to='api.Spell'),
),
]
14 changes: 14 additions & 0 deletions api/migrations/0028_merge_20230516_1753.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Generated by Django 3.2.18 on 2023-05-16 17:53

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('api', '0026_auto_20230513_1436'),
('api', '0027_alter_spelllist_spells'),
]

operations = [
]
1 change: 1 addition & 0 deletions api/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@
from .models import Weapon
from .models import Armor
from .spell import Spell
from .spell import SpellList
from .monster import Monster
from .monster import MonsterSpell
10 changes: 5 additions & 5 deletions api/models/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,11 +329,11 @@ class Armor(GameContent):
stealth_disadvantage = models.BooleanField(
'Boolean representing whether wearing the armor results in stealth disadvantage for the wearer.')
base_ac = models.IntegerField()
plus_dex_mod = models.BooleanField(null=True)
plus_con_mod = models.BooleanField(null=True)
plus_wis_mod = models.BooleanField(null=True)
plus_flat_mod = models.IntegerField(null=True) # Build a shield this way.
plus_max = models.IntegerField(null=True)
plus_dex_mod = models.BooleanField(default=False)
plus_con_mod = models.BooleanField(default=False)
plus_wis_mod = models.BooleanField(default=False)
plus_flat_mod = models.IntegerField(default=False) # Build a shield this way.
plus_max = models.IntegerField(default=0)

def ac_string(self):
ac = str(self.base_ac)
Expand Down
24 changes: 24 additions & 0 deletions api/models/spell.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,3 +223,27 @@ def import_from_json_v1(self, json):
def plural_str() -> str:
"""Return a string specifying the plural name of this model."""
return "Spells"

class SpellList(GameContent):
""" A list of spells to be referenced by classes and subclasses"""

spells = models.ManyToManyField(Spell,
related_name="spell_lists",
help_text='The set of spells.')

def import_from_json_v1(self, json):
"""Log to import a single object from a standard json structure."""
self.name = json["name"]
self.slug = slugify(json["name"])
if "desc" in json:
self.desc = json["desc"]

for spell_slug in json["spell_list"]:
#spell_obj = Spell.objects.filter(slug=spell_slug)
self.spells.add(spell_slug)


@staticmethod
def plural_str() -> str:
"""Return a string specifying the plural name of this model."""
return "SpellLists"
26 changes: 23 additions & 3 deletions api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ class Meta:
'document__url'
)

class SpellSerializer(DynamicFieldsModelSerializer, serializers.HyperlinkedModelSerializer):
class SpellSerializer(DynamicFieldsModelSerializer):

ritual = serializers.CharField(source='v1_ritual')
level_int = serializers.IntegerField(source='spell_level')
Expand Down Expand Up @@ -152,6 +152,7 @@ class Meta:
'spell_level',
'school',
'dnd_class',
'spell_lists',
'archetype',
'circles',
'document__slug',
Expand All @@ -160,6 +161,21 @@ class Meta:
'document__url'
)

class SpellListSerializer(DynamicFieldsModelSerializer):
#spells = SpellSerializer(many=True, read_only=True, context={'request': ''}) #Passing a blank request.
class Meta:
model = models.SpellList
fields = (
'slug',
'name',
'desc',
'spells',
'document__slug',
'document__title',
'document__license_url',
'document__url'
)

class BackgroundSerializer(DynamicFieldsModelSerializer, serializers.HyperlinkedModelSerializer):
class Meta:
model = models.Background
Expand Down Expand Up @@ -239,7 +255,6 @@ class Meta:
'document__url'
)


class RaceSerializer(DynamicFieldsModelSerializer, serializers.HyperlinkedModelSerializer):
subraces = SubraceSerializer(many=True,read_only=True)
class Meta:
Expand Down Expand Up @@ -348,13 +363,18 @@ class Meta:
'document__title',
'document__license_url',
'document__url',
'base_ac',
'plus_dex_mod',
'plus_con_mod',
'plus_wis_mod',
'plus_flat_mod',
'plus_max',
'ac_string',
'strength_requirement',
'cost',
'weight',
'stealth_disadvantage')


class AggregateSerializer(HighlighterMixin, HaystackSerializer):

class Meta:
Expand Down
107 changes: 107 additions & 0 deletions api/tests/test_imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from api.management.commands.importer import ImportSpec
from api.management.commands.importer import ImportOptions

from django.template.defaultfilters import slugify

from api.models import Subrace

# Create your tests here.
Expand Down Expand Up @@ -39,6 +41,7 @@ def test_get_root_list(self):

self.assertContains(response, 'manifest', count=2)
self.assertContains(response, 'spells', count=2)
self.assertContains(response, 'spelllist', count=2)
self.assertContains(response, 'monsters', count=2)
self.assertContains(response, 'documents', count=2)
self.assertContains(response, 'backgrounds', count=2)
Expand Down Expand Up @@ -226,6 +229,110 @@ def test_get_spell_data(self):
f'Mismatched value of unequal field: {field_names}')


class SpellListTestCase(APITestCase):
"""Testing for the spell list API endpoint."""

def setUp(self):
"""Create the spell endpoint test data."""

self.test_document_json = """
{
"title": "Test Reference Document",
"slug": "test-doc",
"desc": "This is a test document",
"license": "Open Gaming License",
"author": "John Doe",
"organization": "Open5e Test Org",
"version": "9.9",
"copyright": "",
"url": "http://example.com"
}
"""
self.test_spell_json = """
{
"name": "Magic Missile",
"desc": "You create three glowing darts of magical force. Each dart hits a creature of your choice that you can see within range. A dart deals 1d4 + 1 force damage to its target. The darts all strike simultaneously, and you can direct them to hit one creature or several.",
"higher_level": "When you cast this spell using a spell slot of 2nd level or higher, the spell creates one more dart for each slot level above 1st.",
"page": "phb 257",
"range": "120 feet",
"components": "V, S",
"ritual": "no",
"duration": "Instantaneous",
"concentration": "no",
"casting_time": "1 action",
"level": "1st-level",
"level_int": 1,
"school": "Evocation",
"class": "Sorcerer, Wizard"
}
"""

self.test_spell_list_json = """
{
"name":"wizard",
"spell_list":[
"magic-missile"
]
}
"""

i = Importer(ImportOptions(update=True, append=False, testrun=False))
i.import_document(
json.loads(
self.test_document_json),
ImportSpec(
"test_filename",
"test_model_class",
"import_spell"))
i.import_spell(
json.loads(
self.test_spell_json),
ImportSpec(
"test_filename",
"test_model_class",
"import_spell"))
i.import_spell_list(
json.loads(
self.test_spell_list_json),
ImportSpec(
"test_filename",
"test_model_class",
"import_spell_list")
)

def test_get_spell_lists(self):
"""Confirm that the list result has the proper elements."""
response = self.client.get(f'/spelllist/?format=json')
self.assertContains(response, 'count', count=1)
self.assertContains(response, 'next', count=1)
self.assertContains(response, 'previous', count=1)
self.assertContains(response, 'results', count=1)

def test_get_spell_list_data(self):
"""Confirm that the result itself has the proper formatting and values."""
import json
response = self.client.get(f'/spelllist/?format=json')
in_spell_list = json.loads(self.test_spell_list_json)
out_spell_list = response.json()['results'][0]
equal_fields = [
'name']
for field_name in equal_fields:
self.assertEqual(
in_spell_list[field_name],
out_spell_list[field_name],
f'Mismatched value of: {field_name}')

def test_get_spell_list_contents(self):
"""Make sure that the response data is the same as the original spell data."""
import json
response = self.client.get(f'/spelllist/?format=json')
in_spell_list = json.loads(self.test_spell_list_json)
in_spell = json.loads(self.test_spell_json)
out_spell_list = response.json()['results'][0]

self.assertEqual(slugify(in_spell['name']), out_spell_list['spells'][0])


class MonstersTestCase(APITestCase):
"""Test case for the monster API endpoint."""

Expand Down
Loading

0 comments on commit 2727e3e

Please sign in to comment.