Skip to content

Commit

Permalink
feat(form): multiple files in file questions - test and logic
Browse files Browse the repository at this point in the history
Ensure saving files and retrieving them from an answer works as expected.
  • Loading branch information
winged committed Jul 1, 2022
1 parent 10854c2 commit acab7b1
Show file tree
Hide file tree
Showing 20 changed files with 306 additions and 305 deletions.
75 changes: 53 additions & 22 deletions caluma/caluma_form/domain_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,51 @@ 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 rverse
# 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 = []

to_update = {
str(ex_f.pk): ex_f
for ex_f in answer.files.filter(
pk__in=[f["id"] for f in files if "id" in f]
)
}
for file_ in files:
file_id = str(file_.get("id"))
file_name = file_["name"]
if file_id in to_update:
if to_update[file_id].name != file_name:
to_update[file_id].rename(file_name)
to_update[file_id].save()
# else: already there, no change required
else:
# need to be created, regardless whether the file_id
# exists or not
answer.files.create(name=file_name)

@staticmethod
def validate_for_save(
data: dict, user: BaseUser, answer: models.Answer = None, origin: bool = False
Expand All @@ -68,8 +106,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 +129,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 +167,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
2 changes: 1 addition & 1 deletion caluma/caluma_form/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ class DocumentFilterSet(MetaFilterSet):
"form__name",
"form__description",
"answers__value",
"answers__file__name",
"answers__files__name",
)
)
root_document = GlobalIDFilter(field_name="family")
Expand Down
2 changes: 1 addition & 1 deletion caluma/caluma_form/ordering.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def get_ordering_value(
Question.TYPE_CHOICE: "value",
Question.TYPE_TEXTAREA: "value",
Question.TYPE_TEXT: "value",
Question.TYPE_FILE: "file__name",
Question.TYPE_FILES: "file__name",
Question.TYPE_DYNAMIC_CHOICE: "value",
}

Expand Down
22 changes: 15 additions & 7 deletions caluma/caluma_form/serializers.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import graphene
from django.db import transaction
from rest_framework import exceptions
from rest_framework.serializers import (
Expand All @@ -8,6 +9,7 @@
IntegerField,
ListField,
PrimaryKeyRelatedField,
Serializer,
)

from ..caluma_core import serializers
Expand Down Expand Up @@ -423,7 +425,7 @@ class Meta(SaveQuestionSerializer.Meta):

class SaveFileQuestionSerializer(SaveQuestionSerializer):
def validate(self, data):
data["type"] = models.Question.TYPE_FILE
data["type"] = models.Question.TYPE_FILES
return super().validate(data)

class Meta(SaveQuestionSerializer.Meta):
Expand Down Expand Up @@ -593,14 +595,20 @@ class Meta(SaveAnswerSerializer.Meta):
pass


class SaveDocumentFileAnswerSerializer(SaveAnswerSerializer):
value = CharField(write_only=True, source="file", required=False)
value_id = PrimaryKeyRelatedField(read_only=True, source="file", required=False)
class SaveFile(Serializer):
id = graphene.String()
name = graphene.String()


class SaveDocumentFilesAnswerSerializer(SaveAnswerSerializer):
value = ListField(
source="files",
required=False,
help_text="List of files for this answer",
)

class Meta(SaveAnswerSerializer.Meta):
fields = SaveAnswerSerializer.Meta.fields + [
"value_id",
]
fields = SaveAnswerSerializer.Meta.fields


class RemoveAnswerSerializer(serializers.ModelSerializer):
Expand Down
4 changes: 2 additions & 2 deletions caluma/caluma_form/structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ def value(self):
for row in self.children()
]

elif self.question.type == Question.TYPE_FILE:
return self.answer.file
elif self.question.type == Question.TYPE_FILES:
return "; ".join([f.name for f in self.answer.files.all()])

elif self.question.type == Question.TYPE_DATE:
return self.answer.date
Expand Down
95 changes: 0 additions & 95 deletions caluma/caluma_form/tests/__snapshots__/test_answer.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@
}),
})
# ---
# name: test_save_calculated_dependency_default_answer[float-0.1]
Answer(document=None, question=Question(slug=environmental-ten, type=float), value=0.1)
# ---
# name: test_save_default_answer_graphql[choice-option-slug-None-SaveDefaultStringAnswer-True-option-slug-False]
dict({
'saveDefaultStringAnswer': dict({
Expand Down Expand Up @@ -135,50 +132,6 @@
}),
})
# ---
# name: test_save_default_answer_graphql[table-None-None-SaveDefaultTableAnswer-True-option-slug-False]
dict({
'saveDefaultTableAnswer': dict({
'answer': dict({
'__typename': 'TableAnswer',
'table_value': list([
dict({
'form': dict({
'slug': 'eight-traditional',
}),
}),
dict({
'form': dict({
'slug': 'eight-traditional',
}),
}),
]),
}),
'clientMutationId': 'testid',
}),
})
# ---
# name: test_save_default_answer_graphql[table-None-None-SaveDefaultTableAnswer-True-option-slug-True]
dict({
'saveDefaultTableAnswer': dict({
'answer': dict({
'__typename': 'TableAnswer',
'table_value': list([
dict({
'form': dict({
'slug': 'eight-traditional',
}),
}),
dict({
'form': dict({
'slug': 'eight-traditional',
}),
}),
]),
}),
'clientMutationId': 'testid',
}),
})
# ---
# name: test_save_default_answer_graphql[text-Test-None-SaveDefaultStringAnswer-True-option-slug-False]
dict({
'saveDefaultStringAnswer': dict({
Expand Down Expand Up @@ -223,51 +176,3 @@
}),
})
# ---
# name: test_save_default_answer_python_api[choice-option-slug-None-True-option-slug-False]
Answer(document=None, question=Question(slug=environmental-ten, type=choice), value='option-slug')
# ---
# name: test_save_default_answer_python_api[choice-option-slug-None-True-option-slug-True]
Answer(document=None, question=Question(slug=environmental-ten, type=choice), value='option-slug')
# ---
# name: test_save_default_answer_python_api[date-None-2019-02-22-True-option-slug-False]
Answer(document=None, question=Question(slug=environmental-ten, type=date), value=None)
# ---
# name: test_save_default_answer_python_api[date-None-2019-02-22-True-option-slug-True]
Answer(document=None, question=Question(slug=environmental-ten, type=date), value=None)
# ---
# name: test_save_default_answer_python_api[float-2.1-None-True-option-slug-False]
Answer(document=None, question=Question(slug=environmental-ten, type=float), value=2.1)
# ---
# name: test_save_default_answer_python_api[float-2.1-None-True-option-slug-True]
Answer(document=None, question=Question(slug=environmental-ten, type=float), value=2.1)
# ---
# name: test_save_default_answer_python_api[integer-1-None-True-option-slug-False]
Answer(document=None, question=Question(slug=environmental-ten, type=integer), value=1)
# ---
# name: test_save_default_answer_python_api[integer-1-None-True-option-slug-True]
Answer(document=None, question=Question(slug=environmental-ten, type=integer), value=1)
# ---
# name: test_save_default_answer_python_api[multiple_choice-answer__value5-None-True-option-slug-False]
Answer(document=None, question=Question(slug=environmental-ten, type=multiple_choice), value=['option-slug'])
# ---
# name: test_save_default_answer_python_api[multiple_choice-answer__value5-None-True-option-slug-True]
Answer(document=None, question=Question(slug=environmental-ten, type=multiple_choice), value=['option-slug'])
# ---
# name: test_save_default_answer_python_api[table-None-None-True-option-slug-False]
Answer(document=None, question=Question(slug=environmental-ten, type=table), value=None)
# ---
# name: test_save_default_answer_python_api[table-None-None-True-option-slug-True]
Answer(document=None, question=Question(slug=environmental-ten, type=table), value=None)
# ---
# name: test_save_default_answer_python_api[text-Test-None-True-option-slug-False]
Answer(document=None, question=Question(slug=environmental-ten, type=text), value='Test')
# ---
# name: test_save_default_answer_python_api[text-Test-None-True-option-slug-True]
Answer(document=None, question=Question(slug=environmental-ten, type=text), value='Test')
# ---
# name: test_save_default_answer_python_api[textarea-Test-None-True-option-slug-False]
Answer(document=None, question=Question(slug=environmental-ten, type=textarea), value='Test')
# ---
# name: test_save_default_answer_python_api[textarea-Test-None-True-option-slug-True]
Answer(document=None, question=Question(slug=environmental-ten, type=textarea), value='Test')
# ---
Loading

0 comments on commit acab7b1

Please sign in to comment.