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

Define date range of the date questions #2081

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
3 changes: 3 additions & 0 deletions caluma/caluma_form/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ class QuestionFactory(DjangoModelFactory):
)
calc_dependents = []

min_date = None
max_date = None

# action button question
action = Maybe(
"is_action_button",
Expand Down
24 changes: 24 additions & 0 deletions caluma/caluma_form/models.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import uuid
from functools import wraps

import dateutil.parser
from django.contrib.postgres.fields import ArrayField
from django.contrib.postgres.indexes import GinIndex
from django.core.serializers.json import DjangoJSONEncoder, json
from django.db import models, transaction
from django.utils.functional import cached_property
from localized_fields.fields import LocalizedField, LocalizedTextField
Expand Down Expand Up @@ -220,6 +222,28 @@ def min_value(self):
def min_value(self, value):
self.configuration["min_value"] = value

@property
def max_date(self):
if (max_date := self.configuration.get("max_date")) and max_date:
return dateutil.parser.parse(json.loads(max_date)).date()

@max_date.setter
def max_date(self, value):
self.configuration["max_date"] = (
json.dumps(value, cls=DjangoJSONEncoder) if value is not None else None
)

@property
def min_date(self):
if (min_date := self.configuration.get("min_date")) and min_date:
return dateutil.parser.parse(json.loads(min_date)).date()

@min_date.setter
def min_date(self, value):
self.configuration["min_date"] = (
json.dumps(value, cls=DjangoJSONEncoder) if value is not None else None
)

@property
def action(self):
return self.configuration.get("action")
Expand Down
2 changes: 2 additions & 0 deletions caluma/caluma_form/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,8 @@ class Meta:

class DateQuestion(QuestionQuerysetMixin, FormDjangoObjectType):
hint_text = graphene.String()
min_date = graphene.Date()
max_date = graphene.Date()
default_answer = graphene.Field("caluma.caluma_form.schema.DateAnswer")

class Meta:
Expand Down
18 changes: 17 additions & 1 deletion caluma/caluma_form/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,12 +240,28 @@ class Meta(SaveQuestionSerializer.Meta):


class SaveDateQuestionSerializer(SaveQuestionSerializer):
min_date = DateField("%Y-%m-%d", required=False, allow_null=True)
max_date = DateField("%Y-%m-%d", required=False, allow_null=True)

def validate(self, data):
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@winged there is a validation in the backend, but since i finish this PR, it's waiting for review

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

projectcaluma/ember-caluma#2521

here is the implementation of the frontend side

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, but I think if we do this, we should add validation to the answers as well. This only validates the question, but no restriction happens when people actually fill out the forms.

Look at the answer validator to see what I mean.

There's already _validate_question_float() and _validate_question_integer() that implement the same pattern. I think therefore, you should extend _validate_question_date() to match.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's right, i will implement that too, and i will include it in my todo for this week

if (
(min_date := data.get("min_date"))
and (max_date := data.get("max_date"))
and max_date < min_date
):
raise exceptions.ValidationError(
f"max_value {max_date} is smaller than {min_date}"
)

data["type"] = models.Question.TYPE_DATE
return super().validate(data)

class Meta(SaveQuestionSerializer.Meta):
fields = SaveQuestionSerializer.Meta.fields + ["hint_text"]
fields = SaveQuestionSerializer.Meta.fields + [
"hint_text",
"min_date",
"max_date",
]


class SaveQuestionOptionsMixin(object):
Expand Down
114 changes: 114 additions & 0 deletions caluma/caluma_form/tests/__snapshots__/test_question.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,120 @@
}),
})
# ---
# name: test_save_date_question[False-date-2022-01-31-2022-12-31-True]
dict({
'saveDateQuestion': dict({
'clientMutationId': 'testid',
'question': dict({
'__typename': 'DateQuestion',
'defaultAnswer': None,
'hintText': '',
'id': 'RGF0ZVF1ZXN0aW9uOmVudmlyb25tZW50YWwtdGVu',
'label': 'Bonnie Moreno',
'maxDate': '2022-12-31',
'meta': dict({
}),
'minDate': '2022-01-31',
'slug': 'environmental-ten',
}),
}),
})
# ---
# name: test_save_date_question[False-date-2022-01-31-None-True]
dict({
'saveDateQuestion': dict({
'clientMutationId': 'testid',
'question': dict({
'__typename': 'DateQuestion',
'defaultAnswer': None,
'hintText': '',
'id': 'RGF0ZVF1ZXN0aW9uOmVudmlyb25tZW50YWwtdGVu',
'label': 'Bonnie Moreno',
'maxDate': None,
'meta': dict({
}),
'minDate': '2022-01-31',
'slug': 'environmental-ten',
}),
}),
})
# ---
# name: test_save_date_question[False-date-None-2022-12-31-True]
dict({
'saveDateQuestion': dict({
'clientMutationId': 'testid',
'question': dict({
'__typename': 'DateQuestion',
'defaultAnswer': None,
'hintText': '',
'id': 'RGF0ZVF1ZXN0aW9uOmVudmlyb25tZW50YWwtdGVu',
'label': 'Bonnie Moreno',
'maxDate': '2022-12-31',
'meta': dict({
}),
'minDate': None,
'slug': 'environmental-ten',
}),
}),
})
# ---
# name: test_save_date_question[True-date-2022-01-31-2022-12-31-True]
dict({
'saveDateQuestion': dict({
'clientMutationId': 'testid',
'question': dict({
'__typename': 'DateQuestion',
'defaultAnswer': None,
'hintText': '',
'id': 'RGF0ZVF1ZXN0aW9uOmVudmlyb25tZW50YWwtdGVu',
'label': 'Bonnie Moreno',
'maxDate': '2022-12-31',
'meta': dict({
}),
'minDate': '2022-01-31',
'slug': 'environmental-ten',
}),
}),
})
# ---
# name: test_save_date_question[True-date-2022-01-31-None-True]
dict({
'saveDateQuestion': dict({
'clientMutationId': 'testid',
'question': dict({
'__typename': 'DateQuestion',
'defaultAnswer': None,
'hintText': '',
'id': 'RGF0ZVF1ZXN0aW9uOmVudmlyb25tZW50YWwtdGVu',
'label': 'Bonnie Moreno',
'maxDate': None,
'meta': dict({
}),
'minDate': '2022-01-31',
'slug': 'environmental-ten',
}),
}),
})
# ---
# name: test_save_date_question[True-date-None-2022-12-31-True]
dict({
'saveDateQuestion': dict({
'clientMutationId': 'testid',
'question': dict({
'__typename': 'DateQuestion',
'defaultAnswer': None,
'hintText': '',
'id': 'RGF0ZVF1ZXN0aW9uOmVudmlyb25tZW50YWwtdGVu',
'label': 'Bonnie Moreno',
'maxDate': '2022-12-31',
'meta': dict({
}),
'minDate': None,
'slug': 'environmental-ten',
}),
}),
})
# ---
# name: test_save_dynamic_choice_question[dynamic_choice-False]
dict({
'saveDynamicChoiceQuestion': dict({
Expand Down
78 changes: 77 additions & 1 deletion caluma/caluma_form/tests/test_question.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import pytest
from django.utils.timezone import datetime

from caluma.caluma_core.relay import extract_global_id
from caluma.caluma_core.tests import (
Expand Down Expand Up @@ -369,7 +370,6 @@ def test_save_textarea_question(db, question, answer, schema_executor):
}
}
"""

question.default_answer = answer
question.hint_text = "test"
question.save()
Expand All @@ -389,6 +389,82 @@ def test_save_textarea_question(db, question, answer, schema_executor):
assert result.data["saveTextareaQuestion"]["question"]["hintText"] == "test"


@pytest.mark.parametrize(
"question__type, min_date, max_date, success",
[
(models.Question.TYPE_DATE, "2022-01-31", "2022-12-31", True),
(models.Question.TYPE_DATE, "2022-01-31", None, True),
(models.Question.TYPE_DATE, None, "2022-12-31", True),
(models.Question.TYPE_DATE, "2022-12-01", "2022-01-31", False),
],
)
@pytest.mark.parametrize("existing_values", [False, True])
def test_save_date_question(
db,
snapshot,
question,
success,
schema_executor,
min_date,
max_date,
existing_values,
):
query = """
mutation SaveDateQuestion($input: SaveDateQuestionInput!) {
saveDateQuestion(input: $input) {
question {
id
slug
label
meta
__typename
... on DateQuestion {
minDate
maxDate
hintText
defaultAnswer {
value
}
}
}
clientMutationId
}
}
"""

if max_date:
question.max_date = datetime.strptime(max_date, "%Y-%m-%d").date()
if min_date:
question.min_date = datetime.strptime(min_date, "%Y-%m-%d").date()

inp = {
"input": extract_serializer_input_fields(
serializers.SaveDateQuestionSerializer, question
)
}

if existing_values:
question.save()

result = schema_executor(query, variable_values=inp)
assert not bool(result.errors) == success
if not success:
return

snapshot.assert_match(result.data)

question.refresh_from_db()
if not min_date:
assert question.min_date is None
else:
assert question.min_date == datetime.strptime(min_date, "%Y-%m-%d").date()

if not max_date:
assert question.max_date is None
else:
assert question.max_date == datetime.strptime(max_date, "%Y-%m-%d").date()


@pytest.mark.parametrize(
"question__type,question__configuration,answer__value,success",
[
Expand Down
4 changes: 4 additions & 0 deletions caluma/tests/__snapshots__/test_schema.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -818,6 +818,8 @@

"""The ID of the object"""
id: ID!
minDate: Date
maxDate: Date
}

"""
Expand Down Expand Up @@ -2376,6 +2378,8 @@
meta: JSONString
isArchived: Boolean
hintText: String
minDate: Date
maxDate: Date
clientMutationId: String
}

Expand Down
Loading