Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Features/jsonschema model #2

Merged
merged 6 commits into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Welcome to django-json-schema's documentation!

|python-versions| |django-versions| |pypi-version|

<One liner describing the project>
A reusable Django app to store JSON schemas.

.. contents::

Expand All @@ -21,8 +21,10 @@ Welcome to django-json-schema's documentation!
Features
========

* ...
* ...
* JsonSchemaModel consisting of
- name CharField
- schema JsonField
- validate(json) method to validate JSON against the schema.

Installation
============
Expand All @@ -32,6 +34,7 @@ Requirements

* Python 3.10 or above
* Django 4.2 or newer
* A database supporting django.db.models.JSONField


Install
Expand All @@ -45,7 +48,19 @@ Install
Usage
=====

<document or refer to docs>
.. code-block:: python

from django_json_schema.models import JsonSchema

class ProductType(models.Model):
schema = models.ForeignKey(JsonSchema, on_delete=models.PROTECT)

class Product(models.Model):
json = models.JsonField()
type = models.ForeignKey(ProductType, on_delete=models.CASCADE)

def clean(self):
self.type.schema.validate(self.json)

Local development
=================
Expand Down
25 changes: 25 additions & 0 deletions django_json_schema/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import json

from django import forms
from django.contrib import admin

from .models import JsonSchema


class IndentedJSONEncoder(json.JSONEncoder):
def __init__(self, *args, indent, sort_keys, **kwargs):
super().__init__(*args, indent=2, **kwargs)


class JsonSchemaAdminForm(forms.ModelForm):
schema = forms.JSONField(encoder=IndentedJSONEncoder)

class Meta:
model = JsonSchema
fields = "__all__"


@admin.register(JsonSchema)
class JsonSchemaAdmin(admin.ModelAdmin):
form = JsonSchemaAdminForm
search_fields = ["name"]
43 changes: 43 additions & 0 deletions django_json_schema/locale/nl/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-07 05:36-0600\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <[email protected]>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#: models.py:10
msgid "name"
msgstr "naam"

#: models.py:10
msgid "Name of the json schema."
msgstr "Naam van het json schema."

#: models.py:14
msgid "schema"
msgstr ""

#: models.py:14
msgid "The schema that can be validated against."
msgstr "Het schema waartegen gevalideerd kan worden."

#: models.py:18
msgid "Json schema"
msgstr ""

#: models.py:19
msgid "Json Schemas"
msgstr "Json Schema's"
45 changes: 45 additions & 0 deletions django_json_schema/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Generated by Django 4.2.17 on 2025-01-03 16:37

from django.db import migrations, models


class Migration(migrations.Migration):
initial = True

dependencies = []

operations = [
migrations.CreateModel(
name="JsonSchema",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"name",
models.CharField(
help_text="Name of the json schema.",
max_length=200,
verbose_name="name",
),
),
(
"schema",
models.JSONField(
help_text="The schema that can be validated against.",
verbose_name="schema",
),
),
],
options={
"verbose_name": "Json schema",
"verbose_name_plural": "Json Schemas",
},
),
]
Empty file.
38 changes: 38 additions & 0 deletions django_json_schema/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import gettext_lazy as _

from jsonschema import Draft202012Validator, validate
from jsonschema.exceptions import (
SchemaError,
ValidationError as JsonSchemaValidationError,
)


class JsonSchema(models.Model):
name = models.CharField(
_("name"), help_text=_("Name of the json schema."), max_length=200
)

schema = models.JSONField(
_("schema"), help_text=_("The schema that can be validated against.")
)

class Meta:
verbose_name = _("Json schema")
verbose_name_plural = _("Json Schemas")

def __str__(self):
return self.name

def clean(self):
try:
Draft202012Validator.check_schema(self.schema)
except SchemaError as e:
raise ValidationError(e.message)

def validate(self, json: dict) -> None:
try:
validate(json, self.schema, cls=Draft202012Validator)
except JsonSchemaValidationError as e:
raise ValidationError(e.message)
8 changes: 5 additions & 3 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ Welcome to django-json-schema's documentation!
..
|docs| |python-versions| |django-versions| |pypi-version|

<One liner describing the project>
A reusable Django app to store JSON schemas.

Features
========

* ...
* ...
* JsonSchemaModel consisting of
- name CharField
- schema JsonField
- validate(json) method to validate JSON against the schema.

.. toctree::
:maxdepth: 2
Expand Down
15 changes: 14 additions & 1 deletion docs/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,17 @@ Install from PyPI with pip:
Usage
=====

<document how to use the app here>
.. code-block:: python

from django.db import models
from django_json_schema.models import JsonSchema

class ProductType(models.Model):
schema = models.ForeignKey(JsonSchema, on_delete=models.PROTECT)

class Product(models.Model):
json = models.JsonField()
type = models.ForeignKey(ProductType, on_delete=models.CASCADE)

def clean(self):
self.type.schema.validate(self.json)
10 changes: 10 additions & 0 deletions manage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env python
import os
import sys

if __name__ == "__main__":
os.environ["DJANGO_SETTINGS_MODULE"] = "testapp.settings"

from django.core.management import execute_from_command_line

execute_from_command_line(sys.argv)
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ classifiers = [
]
requires-python = ">=3.10"
dependencies = [
"django>=4.2"
"django>=4.2",
"jsonschema>=4.23",
]

[project.urls]
Expand Down
2 changes: 0 additions & 2 deletions tests/test_dummy.py

This file was deleted.

43 changes: 43 additions & 0 deletions tests/test_json_schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from django.core.exceptions import ValidationError
from django.test import TestCase

from django_json_schema.models import JsonSchema


class TestJsonSchema(TestCase):
def setUp(self):
self.schema = JsonSchema.objects.create(
name="schema",
schema={
"type": "object",
"properties": {
"price": {"type": "number"},
"name": {"type": "string"},
},
"required": ["price", "name"],
},
)

def test_valid_json(self):
self.schema.validate(
{
"price": 10,
"name": "test",
}
)

def test_invalid_json(self):
with self.assertRaisesMessage(
ValidationError, "'price' is a required property"
):
self.schema.validate({"name": "Eggs"})

def test_clean_with_invalid_schema(self):
self.schema.schema = {"type": []}
with self.assertRaisesMessage(
ValidationError, "[] is not valid under any of the given schemas"
):
self.schema.clean()

def test_clean_with_valid_schema(self):
self.schema.clean()
Loading