Skip to content

Commit

Permalink
Merge pull request #14 from akvo/feature/12-arf-editor-backend
Browse files Browse the repository at this point in the history
Feature/12 arf editor backend
  • Loading branch information
dedenbangkit authored Sep 6, 2023
2 parents 47a7b18 + ed0242e commit d0bafef
Show file tree
Hide file tree
Showing 13 changed files with 2,766 additions and 11 deletions.
2 changes: 1 addition & 1 deletion backend/akvo/core_forms/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class QuestionTypes:
number: "number",
geo: "geo",
option: "option",
multiple_option: "multiple_pption",
multiple_option: "multiple_option",
cascade: "cascade",
photo: "photo",
date: "date",
Expand Down
115 changes: 113 additions & 2 deletions backend/akvo/core_forms/serializers/form.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,22 @@
from drf_spectacular.utils import extend_schema_field
from drf_spectacular.types import OpenApiTypes

from akvo.core_forms.models import Forms, QuestionGroups
from akvo.core_forms.models import (
Forms,
QuestionGroups,
Questions
)
from akvo.core_forms.serializers.question_group import (
ListQuestionGroupSerializer
ListQuestionGroupSerializer,
AddQuestionGroupSerializer
)
from akvo.utils.custom_serializer_fields import (
CustomIntegerField,
CustomCharField,
CustomListField
)
from akvo.utils.custom_serializer_fields import validate_serializers_message
from akvo.core_data.models import Answers


class ListFormSerializer(serializers.ModelSerializer):
Expand Down Expand Up @@ -55,3 +67,102 @@ class Meta:
"translations",
"question_group"
]


class AddFormSerializer(serializers.Serializer):
id = CustomIntegerField()
name = CustomCharField()
description = CustomCharField(required=False, allow_null=True)
default_language = CustomCharField(
required=False, allow_null=True, default="en")
languages = CustomListField(
required=False, allow_null=True, default=["en"])
version = CustomIntegerField(
required=False, allow_null=True, default=1)
translations = CustomListField(required=False, allow_null=True)
question_group = AddQuestionGroupSerializer(many=True, required=False)

def __init__(self, *args, **kwargs):
# Get the value
default_language = kwargs.pop('defaultLanguage', None)
super(AddFormSerializer, self).__init__(*args, **kwargs)
# Set the value
if default_language:
self.fields['default_language'].initial = default_language

def validate_question_group(self, value):
serializer = AddQuestionGroupSerializer(data=value, many=True)
if not serializer.is_valid():
print('QG ERROR', serializer.errors)
raise serializers.ValidationError({
"message": validate_serializers_message(serializer.errors),
"details": serializer.errors,
})
return value

def create(self, validated_data):
question_groups_data = validated_data.pop("question_group", [])
form = Forms.objects.create(**validated_data)
for qg in question_groups_data:
serializer = AddQuestionGroupSerializer(data=qg)
if not serializer.is_valid():
raise serializers.ValidationError({
"message": validate_serializers_message(serializer.errors),
"details": serializer.errors,
})
serializer.save(form=form)
return form

def update(self, instance, validated_data):
# update form
instance.name = validated_data.get(
'name', instance.name)
instance.description = validated_data.get(
'description', instance.description)
instance.version = validated_data.get(
'version', instance.version)
instance.languages = validated_data.get(
'languages', instance.languages)
instance.default_language = validated_data.get(
'default_language', instance.default_language)
instance.translations = validated_data.get(
'translations', instance.translations)

# check and delete question group
current_qgs = QuestionGroups.objects.filter(form=instance).all()
current_qg_ids = [cqg.id for cqg in current_qgs]

new_qg_data = validated_data.get('question_group', [])
new_qg_ids = [nqg.get('id') for nqg in new_qg_data]

# create or update question group
for qg in new_qg_data:
current_qg = QuestionGroups.objects.filter(id=qg.get('id')).first()
serializer = AddQuestionGroupSerializer(data=qg)
if not serializer.is_valid():
raise serializers.ValidationError({
"message": validate_serializers_message(serializer.errors),
"details": serializer.errors,
})
if not current_qg:
serializer.save(form=instance)
if current_qg:
serializer.update(
instance=current_qg,
validated_data=serializer.validated_data)

missing_qg_ids = list(set(current_qg_ids) - set(new_qg_ids))
# delete missing question groups
for qgid in missing_qg_ids:
questions = Questions.objects.filter(question_group_id=qgid).all()
qids = [q.id for q in questions]
# check answers
answers = Answers.objects.filter(question_id__in=qids).count()
if answers:
raise serializers.ValidationError({
"message": "Can't delete question group",
"details": f"Question in group {qgid} has answers",
})
QuestionGroups.objects.filter(id=qgid).delete()
instance.save()
return instance
36 changes: 36 additions & 0 deletions backend/akvo/core_forms/serializers/option.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
from rest_framework import serializers

from akvo.core_forms.models import Options
from akvo.utils.custom_serializer_fields import (
CustomIntegerField,
CustomCharField,
CustomListField
)


class ListOptionSerializer(serializers.ModelSerializer):
Expand All @@ -15,3 +20,34 @@ def to_representation(self, instance):
class Meta:
model = Options
fields = ["id", "code", "name", "order", "color", "translations"]


class AddOptionSerializer(serializers.Serializer):
question = CustomIntegerField(read_only=True)
id = CustomIntegerField()
name = CustomCharField()
order = CustomIntegerField()
code = CustomCharField(required=False, allow_null=True)
color = CustomCharField(required=False, allow_null=True)
translations = CustomListField(required=False, allow_null=True)

def __init__(self, **kwargs):
super().__init__(**kwargs)

def create(self, validated_data):
opt = Options.objects.create(**validated_data)
return opt

def update(self, instance, validated_data):
instance.name = validated_data.get(
'name', instance.name)
instance.order = validated_data.get(
'order', instance.order)
instance.code = validated_data.get(
'code', instance.code)
instance.color = validated_data.get(
'color', instance.color)
instance.translations = validated_data.get(
'translations', instance.translations)
instance.save()
return instance
152 changes: 150 additions & 2 deletions backend/akvo/core_forms/serializers/question.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,23 @@
from rest_framework import serializers

from akvo.core_forms.constants import QuestionTypes
from akvo.core_forms.models import Questions
from akvo.core_forms.serializers.option import ListOptionSerializer
from akvo.core_forms.models import (
Questions,
Options
)
from akvo.core_forms.serializers.option import (
ListOptionSerializer,
AddOptionSerializer,
)
from akvo.utils.custom_serializer_fields import (
CustomIntegerField,
CustomCharField,
CustomJSONField,
CustomListField,
CustomBooleanField,
)
from akvo.utils.custom_serializer_fields import validate_serializers_message
from akvo.core_data.models import Answers


class ListQuestionSerializer(serializers.ModelSerializer):
Expand Down Expand Up @@ -113,3 +128,136 @@ class Meta:
"dataApiUrl",
"option",
]


class AddQuestionSerializer(serializers.Serializer):
form = CustomIntegerField(read_only=True)
question_group = CustomIntegerField(read_only=True)
id = CustomIntegerField()
name = CustomCharField()
order = CustomIntegerField()
type = CustomCharField()
tooltip = CustomJSONField(required=False, allow_null=True)
required = CustomBooleanField(
required=False, allow_null=True, default=False)
meta = CustomBooleanField(
required=False, allow_null=True, default=False)
rule = CustomJSONField(required=False, allow_null=True)
dependency = CustomListField(required=False, allow_null=True)
api = CustomJSONField(required=False, allow_null=True)
extra = CustomListField(required=False, allow_null=True)
autofield = CustomJSONField(required=False, allow_null=True)
data_api_url = CustomCharField(required=False, allow_null=True)
translations = CustomListField(required=False, allow_null=True)
option = AddOptionSerializer(many=True, required=False, allow_null=True)

def __init__(self, *args, **kwargs):
# Get the value
data_api_url = kwargs.pop('dataApiUrl', None)
autofield = kwargs.pop('fn', None)
super(AddQuestionSerializer, self).__init__(*args, **kwargs)
# Set the value
if data_api_url:
self.fields['data_api_url'].initial = data_api_url
if autofield:
self.fields['autofield'].initial = autofield

def validate_type(self, value):
qtype = getattr(QuestionTypes, value)
if not qtype:
raise serializers.ValidationError("Invalid question type")
return value

def validate_option(self, value):
if not value:
return None
serializer = AddOptionSerializer(data=value, many=True)
if not serializer.is_valid():
print('OPT ERROR', serializer.errors)
raise serializers.ValidationError({
"message": validate_serializers_message(serializer.errors),
"details": serializer.errors,
})
return value

def create(self, validated_data):
options_data = validated_data.pop("option", [])
qtype = validated_data.pop("type", None)
validated_data["type"] = getattr(QuestionTypes, qtype)
q = Questions.objects.create(**validated_data)
for opt in options_data:
serializer = AddOptionSerializer(data=opt)
if not serializer.is_valid():
raise serializers.ValidationError({
"message": validate_serializers_message(serializer.errors),
"details": serializer.errors,
})
serializer.save(question=q)
return q

def update(self, instance, validated_data):
# get question type
qtype = validated_data.get('type', instance.type)
qtype = getattr(QuestionTypes, qtype)
# check if question type change
if instance.type != qtype:
answers = Answers.objects.filter(question=instance).count()
if answers:
raise serializers.ValidationError({
"message": "Can't update question type",
"details": f"Question {instance.id} has answers",
})
# update question
instance.type = qtype
instance.name = validated_data.get(
'name', instance.name)
instance.order = validated_data.get(
'order', instance.order)
instance.tooltip = validated_data.get(
'tooltip', instance.tooltip)
instance.required = validated_data.get(
'required', instance.required)
instance.meta = validated_data.get(
'meta', instance.meta)
instance.rule = validated_data.get(
'rule', instance.rule)
instance.dependency = validated_data.get(
'dependency', instance.dependency)
instance.api = validated_data.get(
'api', instance.api)
instance.extra = validated_data.get(
'extra', instance.extra)
instance.autofield = validated_data.get(
'autofield', instance.autofield)
instance.data_api_url = validated_data.get(
'data_api_url', instance.data_api_url)
instance.translations = validated_data.get(
'translations', instance.translations)

# check and delete options
current_options = Options.objects.filter(question=instance).all()
current_opt_ids = [co.id for co in current_options]

new_option_data = validated_data.get('option', [])
new_opt_ids = [no.get('id') for no in new_option_data]

for opt in new_option_data:
current_opt = Options.objects.filter(id=opt.get('id')).first()
serializer = AddOptionSerializer(data=opt)
if not serializer.is_valid():
raise serializers.ValidationError({
"message": validate_serializers_message(serializer.errors),
"details": serializer.errors,
})
if not current_opt:
serializer.save(question=instance)
if current_opt:
serializer.update(
instance=current_opt,
validated_data=serializer.validated_data)

missing_opt_ids = list(set(current_opt_ids) - set(new_opt_ids))
# delete old options then create new
Options.objects.filter(id__in=missing_opt_ids).delete()
instance.save()
return instance
Loading

0 comments on commit d0bafef

Please sign in to comment.