Skip to content

Commit

Permalink
fix for multiple JSON Schema
Browse files Browse the repository at this point in the history
  • Loading branch information
thinkAmi committed Dec 25, 2023
1 parent 134af38 commit a9da4ff
Show file tree
Hide file tree
Showing 12 changed files with 497 additions and 81 deletions.
20 changes: 15 additions & 5 deletions api/serializers.py
Expand Up @@ -5,14 +5,20 @@
from django.conf import settings
from jsonschema.validators import Draft7Validator
from rest_framework import serializers
from jsonschema import validate, ValidationError

from diary.models import Diary


class DiarySerializer(serializers.ModelSerializer):
BASE_JSON_SCHEMA_FILE_DIR = settings.BASE_DIR / 'frontend' / 'src' / 'jsonSchemas'

def get_json_schema_file_name(self):
if self.instance:
return self.BASE_JSON_SCHEMA_FILE_DIR / f'diary_{self.instance.version}.json'
return self.BASE_JSON_SCHEMA_FILE_DIR / f'diary_{settings.CURRENT_JSON_SCHEMA_VERSION}.json'

def validate(self, attrs):
with open(settings.BASE_DIR / 'frontend' / 'src' / 'jsonSchemas' / 'diary.json') as f:
with open(self.get_json_schema_file_name()) as f:
json_schema = json.load(f)

errors = Draft7Validator(json_schema).iter_errors(attrs)
Expand All @@ -27,7 +33,10 @@ def create(self, validated_data):
ModelClass = self.Meta.model

target_data = deepcopy(validated_data)
target_data['name'] = validated_data['content']['name']

variety = validated_data['content'].get('variety')
target_data['name'] = variety or validated_data['content']['name']
target_data['version'] = settings.CURRENT_JSON_SCHEMA_VERSION

try:
instance = ModelClass._default_manager.create(**target_data)
Expand Down Expand Up @@ -59,11 +68,12 @@ def update(self, instance, validated_data):
setattr(instance, attr, value)

if attr == 'content':
setattr(instance, 'name', value['name'])
variety = value.get('variety')
setattr(instance, 'name', variety or value['name'])

instance.save()
return instance

class Meta:
model = Diary
fields = ['id', 'name', 'content', 'updated_at']
fields = ['id', 'name', 'content', 'version', 'updated_at']
235 changes: 179 additions & 56 deletions api/tests/test_serializers.py
@@ -1,81 +1,204 @@
import pytest

from api.serializers import DiarySerializer


def test_JSONSchemaの仕様を満たす():
input_data = {
'content': {
'name': '3文字',
'note': '5文字です'
class TestVersion20231224:
@pytest.fixture
def version(self, settings):
settings.CURRENT_JSON_SCHEMA_VERSION = '2023_1224'

def test_JSONSchemaの仕様を満たす(self, version):
input_data = {
'content': {
'name': '3文字',
'note': '5文字です'
}
}

serializer = DiarySerializer(data=input_data)
assert serializer.is_valid()
assert serializer.errors == {}

def test_nameが2文字(self, version):
input_data = {
'content': {
'name': '2字',
'note': '5文字です'
}
}
}

serializer = DiarySerializer(data=input_data)
assert serializer.is_valid()
assert serializer.errors == {}
serializer = DiarySerializer(data=input_data)
assert serializer.is_valid() is False
assert serializer.errors['non_field_errors'] == ["'2字' is too short"]

def test_noteが4文字(self, version):
input_data = {
'content': {
'name': '3文字',
'note': '4文字だ'
}
}

serializer = DiarySerializer(data=input_data)
assert serializer.is_valid() is False
assert serializer.errors['non_field_errors'] == ["'4文字だ' is too short"]

def test_nameが2文字():
input_data = {
'content': {
'name': '2字',
'note': '5文字です'
def test_nameとnoteが短すぎる(self, version):
input_data = {
'content': {
'name': '2字',
'note': '4文字だ'
}
}
}

serializer = DiarySerializer(data=input_data)
assert serializer.is_valid() is False
assert serializer.errors['non_field_errors'] == ["'2字' is too short"]
serializer = DiarySerializer(data=input_data)
assert serializer.is_valid() is False
assert serializer.errors['non_field_errors'] == ["'2字' is too short", "'4文字だ' is too short"]

def test_nameとnoteが数字(self, version):
input_data = {
'content': {
'name': 123,
'note': 12345
}
}

def test_noteが4文字():
input_data = {
'content': {
'name': '3文字',
'note': '4文字だ'
serializer = DiarySerializer(data=input_data)
assert serializer.is_valid() is False
assert serializer.errors['non_field_errors'] == ["123 is not of type 'string'", "12345 is not of type 'string'"]

def test_追加のプロパティが存在するためNG(self, version):
input_data = {
'content': {
'name': '3文字',
'note': '5文字です',
'additional': '追加したデータ'
}
}
}

serializer = DiarySerializer(data=input_data)
assert serializer.is_valid() is False
assert serializer.errors['non_field_errors'] == ["'4文字だ' is too short"]
serializer = DiarySerializer(data=input_data)
assert serializer.is_valid() is False
assert serializer.errors['non_field_errors'] \
== ["Additional properties are not allowed ('additional' was unexpected)"]


def test_nameとnoteが短すぎる():
input_data = {
'content': {
'name': '2字',
'note': '4文字だ'
class TestVersion20231225:
@pytest.fixture
def version(self, settings):
settings.CURRENT_JSON_SCHEMA_VERSION = '2023_1225'

def test_黄色のシナノゴールドで仕様を満たす(self, version):
input_data = {
'content': {
'color': '黄',
'name': 'シナノゴールド',
'note': 'おいしいです'
}
}
}

serializer = DiarySerializer(data=input_data)
assert serializer.is_valid() is False
assert serializer.errors['non_field_errors'] == ["'2字' is too short", "'4文字だ' is too short"]
serializer = DiarySerializer(data=input_data)
assert serializer.is_valid()
assert serializer.errors == {}

def test_緑色のブラムリーで仕様を満たす(self, version):
input_data = {
'content': {
'color': '緑',
'name': 'ブラムリー',
'note': 'おいしいです'
}
}

serializer = DiarySerializer(data=input_data)
assert serializer.is_valid()
assert serializer.errors == {}

def test_赤色の秋映で仕様を満たす(self, version):
input_data = {
'content': {
'color': '赤',
'name': '秋映',
'note': 'おいしいです'
}
}

serializer = DiarySerializer(data=input_data)
assert serializer.is_valid()
assert serializer.errors == {}


def test_nameとnoteが数字():
input_data = {
'content': {
'name': 123,
'note': 12345
def test_赤色のその他の名前で仕様を満たす(self, version):
input_data = {
'content': {
'color': '赤',
'name': 'その他',
'variety': '奥州ロマン',
'note': 'おいしいです'
}
}
}

serializer = DiarySerializer(data=input_data)
assert serializer.is_valid() is False
assert serializer.errors['non_field_errors'] == ["123 is not of type 'string'", "12345 is not of type 'string'"]
serializer = DiarySerializer(data=input_data)
assert serializer.is_valid()
assert serializer.errors == {}


def test_追加のプロパティが存在するためNG():
input_data = {
'content': {
'name': '3文字',
'note': '5文字です',
'additional': '追加したデータ'
def test_色と名前が不一致(self, version):
input_data = {
'content': {
'color': '赤',
'name': 'シナノゴールド',
'note': 'おいしいです'
}
}

serializer = DiarySerializer(data=input_data)
assert serializer.is_valid() is False
assert serializer.errors['non_field_errors'] == \
["{'color': '赤', 'name': 'シナノゴールド', 'note': 'おいしいです'} is not valid under any of the given schemas"]

def test_色が存在しない(self, version):
input_data = {
'content': {
'color': '青',
'name': 'その他',
'variety': 'ブルーロマン',
'note': 'おいしいです'
}
}

serializer = DiarySerializer(data=input_data)
assert serializer.is_valid() is False
assert serializer.errors['non_field_errors'] == \
[
"'青' is not valid under any of the given schemas",
"{'color': '青', 'name': 'その他', 'variety': 'ブルーロマン', 'note': 'おいしいです'} is not valid under any of the given schemas"
]

def test_その他の時にvarietyがない(self, version):
input_data = {
'content': {
'color': '赤',
'name': 'その他',
'note': 'おいしいです'
}
}

serializer = DiarySerializer(data=input_data)
assert serializer.is_valid() is False
assert serializer.errors['non_field_errors'] == ["'variety' is a required property"]

def test_その他でない時にvarietyがある(self, version):
input_data = {
'content': {
'color': '赤',
'name': '秋映',
'note': 'おいしいです',
'variety': 'シナノスイート'
}
}
}

serializer = DiarySerializer(data=input_data)
assert serializer.is_valid() is False
assert serializer.errors['non_field_errors'] \
== ["Additional properties are not allowed ('additional' was unexpected)"]
serializer = DiarySerializer(data=input_data)
assert serializer.is_valid() is False
assert serializer.errors['non_field_errors'] \
== ["{'color': '赤', 'name': '秋映', 'note': 'おいしいです', 'variety': 'シナノスイート'} should not be valid under {'required': ['variety']}"]
6 changes: 5 additions & 1 deletion config/settings.py
Expand Up @@ -9,8 +9,9 @@
For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.2/ref/settings/
"""

import os
from pathlib import Path
from dotenv import load_dotenv

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
Expand Down Expand Up @@ -147,3 +148,6 @@
"http://localhost:8000",
"http://localhost:5173",
]

# for JSON Schema current version
CURRENT_JSON_SCHEMA_VERSION = str(os.getenv('CURRENT_JSON_SCHEMA_VERSION', '2023_1225'))
18 changes: 18 additions & 0 deletions diary/migrations/0002_diary_version.py
@@ -0,0 +1,18 @@
# Generated by Django 4.2.8 on 2023-12-25 05:04

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('diary', '0001_initial'),
]

operations = [
migrations.AddField(
model_name='diary',
name='version',
field=models.CharField(default='2023_1224', max_length=50),
),
]
1 change: 1 addition & 0 deletions diary/models.py
Expand Up @@ -4,4 +4,5 @@
class Diary(models.Model):
name = models.CharField(max_length=50, blank=True, null=False)
content = models.JSONField(blank=True, null=False)
version = models.CharField(max_length=50, null=False, default='2023_1224')
updated_at = models.DateTimeField(auto_now=True)

0 comments on commit a9da4ff

Please sign in to comment.