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

Update api and structure to support json schema #1178

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
40 changes: 40 additions & 0 deletions admissions/admissions/fields/jsonschemaquestionfield.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import inspect
import json
import os
from jsonschema import validate, exceptions as jsonschema_exceptions

from django.core import exceptions
from django.db.models import JSONField


class JSONSchemaQuestionField(JSONField):
schema = "schemas/question_types.json"

@property
def _schema_data(self):
model_file = inspect.getfile(self.model)
dirname = os.path.dirname(model_file)
# schema file related to model.py path
p = os.path.join(dirname, self.schema)
with open(p, "r") as file:
return json.loads(file.read())

def _validate_schema(self, value):
# Disable validation when migrations are faked
if self.model.__module__ == "__fake__":
return True
try:
status = validate(value, self._schema_data)
except jsonschema_exceptions.ValidationError as e:
raise exceptions.ValidationError(e.message, code="invalid")
return status

def validate(self, value, model_instance):
super().validate(value, model_instance)
self._validate_schema(value)

def pre_save(self, model_instance, add):
value = super().pre_save(model_instance, add)
if value and not self.null:
self._validate_schema(value)
return value
42 changes: 42 additions & 0 deletions admissions/admissions/fields/jsonschemaresponsefield.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import inspect
import json
import os
from jsonschema import validate, exceptions as jsonschema_exceptions

from django.core import exceptions
from django.db.models import JSONField


class JSONSchemaResponseField(JSONField):
schema = "schemas/question_responses.json"

@property
def _schema_data(self):
model_file = inspect.getfile(self.model)
dirname = os.path.dirname(model_file)
# schema file related to model.py path
p = os.path.join(dirname, self.schema)
with open(p, "r") as file:
return json.loads(file.read())

def _validate_schema(self, value):
# Disable validation when migrations are faked
if self.model.__module__ == "__fake__":
return True
try:
status = validate(value, self._schema_data)
except jsonschema_exceptions.ValidationError as e:
raise exceptions.ValidationError(e.message, code="invalid")
# TODO Validate towards the specific questions submitted

return status

def validate(self, value, model_instance):
super().validate(value, model_instance)
self._validate_schema(value)

def pre_save(self, model_instance, add):
value = super().pre_save(model_instance, add)
if value and not self.null:
self._validate_schema(value)
return value
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Generated by Django 4.2.3 on 2023-07-24 05:00

import admissions.admissions.fields.jsonschemaquestionfield
import admissions.admissions.fields.jsonschemaresponsefield
from django.db import migrations


class Migration(migrations.Migration):
dependencies = [
("admissions", "0010_admission_admin_groups"),
]

operations = [
migrations.RemoveField(
model_name="group",
name="response_label",
),
migrations.RemoveField(
model_name="groupapplication",
name="text",
),
migrations.RemoveField(
model_name="userapplication",
name="phone_number",
),
migrations.RemoveField(
model_name="userapplication",
name="text",
),
migrations.AddField(
model_name="admission",
name="questions",
field=admissions.admissions.fields.jsonschemaquestionfield.JSONSchemaQuestionField(
blank=True, default=dict
),
),
migrations.AddField(
model_name="group",
name="questions",
field=admissions.admissions.fields.jsonschemaquestionfield.JSONSchemaQuestionField(
blank=True, default=dict
),
),
migrations.AddField(
model_name="groupapplication",
name="responses",
field=admissions.admissions.fields.jsonschemaresponsefield.JSONSchemaResponseField(
blank=True, default=dict
),
),
migrations.AddField(
model_name="userapplication",
name="responses",
field=admissions.admissions.fields.jsonschemaresponsefield.JSONSchemaResponseField(
blank=True, default=dict
),
),
]
20 changes: 11 additions & 9 deletions admissions/admissions/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from django.utils import timezone

from admissions.admissions import constants
from admissions.admissions.fields.jsonschemaquestionfield import JSONSchemaQuestionField
from admissions.admissions.fields.jsonschemaresponsefield import JSONSchemaResponseField
from admissions.utils.models import TimeStampModel


Expand Down Expand Up @@ -55,7 +57,7 @@ def is_member_of_webkom(self):
class Group(models.Model):
name = models.CharField(max_length=50, unique=True)
description = models.TextField(blank=True, max_length=300)
response_label = models.TextField(blank=True, max_length=300)
questions = JSONSchemaQuestionField(default=dict, blank=True)
logo = models.URLField(null=True, blank=True)
detail_link = models.CharField(max_length=150, default="")

Expand All @@ -67,17 +69,18 @@ def __str__(self):


class Admission(models.Model):
created_by = models.ForeignKey(
LegoUser, null=True, related_name="admissions", on_delete=models.CASCADE
)
title = models.CharField(max_length=255, unique=True)
slug = models.SlugField(max_length=200, unique=True, null=False)
description = models.TextField(default="", blank=True)
open_from = models.DateTimeField()
public_deadline = models.DateTimeField()
closed_from = models.DateTimeField()
created_by = models.ForeignKey(
LegoUser, null=True, related_name="admissions", on_delete=models.CASCADE
)
groups = models.ManyToManyField(Group, through="AdmissionGroup")
admin_groups = models.ManyToManyField(Group, related_name="admin_groups")
groups = models.ManyToManyField(Group, through="AdmissionGroup")
description = models.TextField(default="", blank=True)
questions = JSONSchemaQuestionField(default=dict, blank=True)

def __str__(self):
return self.title
Expand Down Expand Up @@ -112,8 +115,7 @@ class UserApplication(TimeStampModel):
Admission, related_name="applications", on_delete=models.CASCADE
)
user = models.ForeignKey(LegoUser, on_delete=models.CASCADE)
text = models.TextField(blank=True)
phone_number = models.CharField(max_length=20)
responses = JSONSchemaResponseField(default=dict, blank=True)

class Meta:
constraints = [
Expand Down Expand Up @@ -149,7 +151,7 @@ class GroupApplication(TimeStampModel):
group = models.ForeignKey(
Group, related_name="applications", on_delete=models.CASCADE
)
text = models.TextField(blank=True)
responses = JSONSchemaResponseField(default=dict, blank=True)


class Membership(models.Model):
Expand Down
5 changes: 5 additions & 0 deletions admissions/admissions/schemas/question_responses.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"title": "Possible questions response types",
"type": "object",
"additionalProperties": { "type": "string" }
}
62 changes: 62 additions & 0 deletions admissions/admissions/schemas/question_types.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{
"title": "Possible questions types",
"type": "array",
"items": {
"oneOf": [
{
"name": "text",
"type": "object",
"properties": {
"type": { "enum": ["text"] },
"text": { "type": "string" }
},
"required": ["type", "text"]
},
{
"name": "textinput",
"type": "object",
"properties": {
"type": { "enum": ["textinput"] },
"id": { "type": "string" },
"name": { "type": "string" },
"label": { "type": "string" }
},
"required": ["type", "id", "name", "label"]
},
{
"name": "textarea",
"type": "object",
"properties": {
"type": { "enum": ["textarea"] },
"id": { "type": "string" },
"name": { "type": "string" },
"label": { "type": "string" },
"placeholder": { "type": "string" }
},
"required": ["type", "id", "name", "label"]
},
{
"name": "numberinput",
"type": "object",
"properties": {
"type": { "enum": ["numberinput"] },
"id": { "type": "string" },
"name": { "type": "string" },
"label": { "type": "string" }
},
"required": ["type", "id", "name", "label"]
},
{
"name": "phoneinput",
"type": "object",
"properties": {
"type": { "enum": ["phoneinput"] },
"id": { "type": "string" },
"name": { "type": "string" },
"label": { "type": "string" }
},
"required": ["type", "id", "name", "label"]
}
]
}
}