Skip to content

Commit

Permalink
Merge pull request #1809 from winged/feat_multi_file_question
Browse files Browse the repository at this point in the history
feat(form): multiple files in file questions
  • Loading branch information
winged authored Jul 5, 2022
2 parents 8ab72fd + 89ce59b commit dc3a224
Show file tree
Hide file tree
Showing 31 changed files with 894 additions and 369 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
FROM
"caluma_form_document" INNER JOIN "caluma_form_form" ON ("caluma_form_document"."form_id" = "caluma_form_form"."slug") -- qs ref
),
answer_b4215 AS (SELECT "caluma_form_answer"."created_at",
answer_af542 AS (SELECT "caluma_form_answer"."created_at",
"caluma_form_answer"."modified_at",
"caluma_form_answer"."created_by_user",
"caluma_form_answer"."created_by_group",
Expand All @@ -76,8 +76,7 @@
"caluma_form_answer"."value",
"caluma_form_answer"."meta",
"caluma_form_answer"."document_id",
"caluma_form_answer"."date",
"caluma_form_answer"."file_id"
"caluma_form_answer"."date"
FROM
"caluma_form_answer" INNER JOIN "caluma_form_question" ON ("caluma_form_answer"."question_id" = "caluma_form_question"."slug") -- qs ref
)
Expand All @@ -92,22 +91,22 @@
FROM document_2a07e AS "document_2a07e"
LEFT JOIN (
SELECT DISTINCT ON (document_id)
(answer_b4215.value #>>'{}') AS "analytics_result_blablub",
(answer_af542.value #>>'{}') AS "analytics_result_blablub",
"document_id"
FROM answer_b4215 AS "answer_b4215"
FROM answer_af542 AS "answer_af542"
WHERE "question_id" = 'top_question'
ORDER BY document_id


) AS "answer_b4215_caea5" ON (document_2a07e.id = "answer_b4215_caea5".document_id)
) AS "answer_af542_caea5" ON (document_2a07e.id = "answer_af542_caea5".document_id)
WHERE form_id = 'top_form'
ORDER BY id


) AS "document_2a07e_27f15" ON (case_ac50e.document_id = "document_2a07e_27f15".id)


) AS analytics_247d1
) AS analytics_2a8de
-- PARAMS:

''',
Expand Down Expand Up @@ -156,7 +155,7 @@
FROM
"caluma_form_document" INNER JOIN "caluma_form_form" ON ("caluma_form_document"."form_id" = "caluma_form_form"."slug") -- qs ref
),
answer_b4215 AS (SELECT "caluma_form_answer"."created_at",
answer_af542 AS (SELECT "caluma_form_answer"."created_at",
"caluma_form_answer"."modified_at",
"caluma_form_answer"."created_by_user",
"caluma_form_answer"."created_by_group",
Expand All @@ -167,8 +166,7 @@
"caluma_form_answer"."value",
"caluma_form_answer"."meta",
"caluma_form_answer"."document_id",
"caluma_form_answer"."date",
"caluma_form_answer"."file_id"
"caluma_form_answer"."date"
FROM
"caluma_form_answer" INNER JOIN "caluma_form_question" ON ("caluma_form_answer"."question_id" = "caluma_form_question"."slug") -- qs ref
)
Expand All @@ -183,22 +181,22 @@
FROM document_2a07e AS "document_2a07e"
LEFT JOIN (
SELECT DISTINCT ON (document_id)
(answer_b4215.value #>>'{}') AS "analytics_result_blablub",
(answer_af542.value #>>'{}') AS "analytics_result_blablub",
"document_id"
FROM answer_b4215 AS "answer_b4215"
FROM answer_af542 AS "answer_af542"
WHERE "question_id" = 'top_question'
ORDER BY document_id


) AS "answer_b4215_caea5" ON (document_2a07e.id = "answer_b4215_caea5".document_id)
) AS "answer_af542_caea5" ON (document_2a07e.id = "answer_af542_caea5".document_id)
WHERE form_id = 'top_form'
ORDER BY id


) AS "document_2a07e_27f15" ON (case_ac50e.document_id = "document_2a07e_27f15".id)


) AS analytics_247d1
) AS analytics_2a8de
WHERE analytics_result_blablub IN (%(flt_analytics_result_blablub_8201d)s, %(flt_analytics_result_blablub_8e5e6)s)
-- PARAMS:
-- flt_analytics_result_blablub_8201d: Shelly Watson
Expand Down Expand Up @@ -246,7 +244,7 @@
FROM
"caluma_form_document" INNER JOIN "caluma_form_form" ON ("caluma_form_document"."form_id" = "caluma_form_form"."slug") -- qs ref
),
answer_b4215 AS (SELECT "caluma_form_answer"."created_at",
answer_af542 AS (SELECT "caluma_form_answer"."created_at",
"caluma_form_answer"."modified_at",
"caluma_form_answer"."created_by_user",
"caluma_form_answer"."created_by_group",
Expand All @@ -257,8 +255,7 @@
"caluma_form_answer"."value",
"caluma_form_answer"."meta",
"caluma_form_answer"."document_id",
"caluma_form_answer"."date",
"caluma_form_answer"."file_id"
"caluma_form_answer"."date"
FROM
"caluma_form_answer" INNER JOIN "caluma_form_question" ON ("caluma_form_answer"."question_id" = "caluma_form_question"."slug") -- qs ref
)
Expand All @@ -273,22 +270,22 @@
FROM document_2a07e AS "document_2a07e"
LEFT JOIN (
SELECT DISTINCT ON (document_id)
(answer_b4215.value #>>'{}') AS "analytics_result_blablub",
(answer_af542.value #>>'{}') AS "analytics_result_blablub",
"document_id"
FROM answer_b4215 AS "answer_b4215"
FROM answer_af542 AS "answer_af542"
WHERE "question_id" = 'top_question'
ORDER BY document_id


) AS "answer_b4215_caea5" ON (document_2a07e.id = "answer_b4215_caea5".document_id)
) AS "answer_af542_caea5" ON (document_2a07e.id = "answer_af542_caea5".document_id)
WHERE form_id = 'top_form'
ORDER BY id


) AS "document_2a07e_27f15" ON (case_ac50e.document_id = "document_2a07e_27f15".id)


) AS analytics_247d1
) AS analytics_2a8de
-- PARAMS:

''',
Expand Down Expand Up @@ -333,7 +330,7 @@
FROM
"caluma_form_document" INNER JOIN "caluma_form_form" ON ("caluma_form_document"."form_id" = "caluma_form_form"."slug") -- qs ref
),
answer_b4215 AS (SELECT "caluma_form_answer"."created_at",
answer_af542 AS (SELECT "caluma_form_answer"."created_at",
"caluma_form_answer"."modified_at",
"caluma_form_answer"."created_by_user",
"caluma_form_answer"."created_by_group",
Expand All @@ -344,8 +341,7 @@
"caluma_form_answer"."value",
"caluma_form_answer"."meta",
"caluma_form_answer"."document_id",
"caluma_form_answer"."date",
"caluma_form_answer"."file_id"
"caluma_form_answer"."date"
FROM
"caluma_form_answer" INNER JOIN "caluma_form_question" ON ("caluma_form_answer"."question_id" = "caluma_form_question"."slug") -- qs ref
)
Expand All @@ -360,22 +356,22 @@
FROM document_2a07e AS "document_2a07e"
LEFT JOIN (
SELECT DISTINCT ON (document_id)
(answer_b4215.value #>>'{}') AS "analytics_result_blablub",
(answer_af542.value #>>'{}') AS "analytics_result_blablub",
"document_id"
FROM answer_b4215 AS "answer_b4215"
FROM answer_af542 AS "answer_af542"
WHERE "question_id" = 'top_question'
ORDER BY document_id


) AS "answer_b4215_caea5" ON (document_2a07e.id = "answer_b4215_caea5".document_id)
) AS "answer_af542_caea5" ON (document_2a07e.id = "answer_af542_caea5".document_id)
WHERE form_id = 'top_form'
ORDER BY id


) AS "document_2a07e_27f15" ON (case_ac50e.document_id = "document_2a07e_27f15".id)


) AS analytics_247d1
) AS analytics_2a8de
WHERE analytics_result_blablub IN (%(flt_analytics_result_blablub_8201d)s, %(flt_analytics_result_blablub_8e5e6)s)
-- PARAMS:
-- flt_analytics_result_blablub_8201d: Shelly Watson
Expand Down
13 changes: 12 additions & 1 deletion caluma/caluma_core/mutation.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ class Meta:
permission_classes = None

@classmethod
def __init_subclass_with_meta__(
def __init_subclass_with_meta__( # noqa: C901
cls,
lookup_field=None,
lookup_input_kwarg=None,
Expand Down Expand Up @@ -154,6 +154,17 @@ def __init_subclass_with_meta__(

input_fields = fields_for_serializer(serializer, fields, exclude, is_input=True)

if hasattr(cls, "Input"):
# Allow input type override in case the serializer fields cannot
# define exactly what we need
input_obj = cls.Input()
explicit_inputs = [
name for name in dir(input_obj) if not name.startswith("_")
]
for name in explicit_inputs:
input_type = getattr(input_obj, name)
input_fields[name] = input_type

if return_field_name is None:
model_name = model_class.__name__
return_field_name = model_name[:1].lower() + model_name[1:]
Expand Down
88 changes: 66 additions & 22 deletions caluma/caluma_form/domain_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from rest_framework.exceptions import ValidationError

from caluma.caluma_core.models import BaseModel
from caluma.caluma_core.relay import extract_global_id
from caluma.caluma_form import models, validators
from caluma.caluma_user.models import BaseUser
from caluma.utils import update_model
Expand Down Expand Up @@ -42,13 +43,63 @@ def get_new_answer(cls, data, user, answer):
cls.validate_for_save(data, user, answer=answer, origin=True)
)

files = validated_data.pop("files", None)
if answer is None:
answer = cls.create(validated_data, user)
else:
answer = cls.update(answer, validated_data, user)

# FILES type needs a bit more work due to the reverse
# relation of file -> answer
if answer.question.type == models.Question.TYPE_FILES:
if files is None:
raise ValidationError("Files input must be a list")
cls.update_answer_files(answer, files)

return cls.post_save(answer)

@classmethod
def update_answer_files(cls, answer: models.Answer, files: list):
"""Update the files of a "FILES" answer.
The files parameter is expected to be a list of dicts, where
each entry has a "name" (the file name), and optionally an "id"
for the case when the given file already exists.
"""
if not files:
files = []

updated = []

for file_ in files:
file_id = extract_global_id(file_["id"]) if "id" in file_ else None
file_name = file_["name"]
file_model = (
answer.files.filter(pk=file_id).first()
if file_id
else models.File(answer=answer, name=file_name)
)
if not file_model:
# Client wants to update a file that doesn't exist anymore.
# Reject call - this is an inconsistency as either
# the file was never created, or was deleted in the mean
# time.
raise ValidationError(
f"File with id={file_id} for given answer not found"
)

if file_model.name != file_name and file_id:
# Renaming existing file
file_model.rename(file_name)
file_model.save()
elif not file_id:
# New file
file_model.save()
updated.append(file_model.pk)

for file in answer.files.exclude(pk__in=updated):
file.delete()

@staticmethod
def validate_for_save(
data: dict, user: BaseUser, answer: models.Answer = None, origin: bool = False
Expand All @@ -68,8 +119,8 @@ def validate_for_save(
else models.Document.objects.none()
)
del data["value"]
elif question.type == models.Question.TYPE_FILE and not data.get("file"):
data["file"] = data["value"]
elif question.type == models.Question.TYPE_FILES and not data.get("files"):
data["files"] = data["value"]
del data["value"]
elif question.type == models.Question.TYPE_DATE and not data.get("date"):
data["date"] = data["value"]
Expand All @@ -91,35 +142,35 @@ def post_save(answer: models.Answer) -> models.Answer:
# TODO emit events
return answer

@staticmethod
@classmethod
@transaction.atomic
def create(validated_data: dict, user: Optional[BaseUser] = None) -> models.Answer:
if validated_data["question"].type == models.Question.TYPE_FILE:
validated_data = __class__.set_file(validated_data)
def create(
cls, validated_data: dict, user: Optional[BaseUser] = None
) -> models.Answer:

if validated_data["question"].type == models.Question.TYPE_TABLE:
documents = validated_data.pop("documents")

files = validated_data.pop("files", None)
answer = BaseLogic.create(models.Answer, validated_data, user)

if validated_data["question"].type == models.Question.TYPE_FILES:
cls.update_answer_files(answer, files)

if answer.question.type == models.Question.TYPE_TABLE:
answer.create_answer_documents(documents)

return answer

@staticmethod
@classmethod
@transaction.atomic
def update(answer, validated_data, user: Optional[BaseUser] = None):
def update(cls, answer, validated_data, user: Optional[BaseUser] = None):
if answer.question.type == models.Question.TYPE_TABLE:
documents = validated_data.pop("documents")
answer.unlink_unused_rows(docs_to_keep=documents)

if (
answer.question.type == models.Question.TYPE_FILE
and answer.file.name is not validated_data["file"]
):
answer.file.delete()
validated_data = __class__.set_file(validated_data)
if answer.question.type == models.Question.TYPE_FILES:
cls.update_answer_files(answer, validated_data.pop("files", None))

BaseLogic.update(answer, validated_data, user)

Expand All @@ -129,21 +180,14 @@ def update(answer, validated_data, user: Optional[BaseUser] = None):
answer.refresh_from_db()
return answer

@staticmethod
def set_file(validated_data):
file_name = validated_data.get("file")
file = models.File.objects.create(name=file_name)
validated_data["file"] = file
return validated_data


class SaveDefaultAnswerLogic(SaveAnswerLogic):
@staticmethod
def validate_for_save(
data: dict, user: BaseUser, answer: models.Answer = None, origin: bool = False
) -> dict:
if data["question"].type in [
models.Question.TYPE_FILE,
models.Question.TYPE_FILES,
models.Question.TYPE_STATIC,
models.Question.TYPE_DYNAMIC_CHOICE,
models.Question.TYPE_DYNAMIC_MULTIPLE_CHOICE,
Expand Down
Loading

0 comments on commit dc3a224

Please sign in to comment.