Skip to content
This repository has been archived by the owner on Feb 13, 2022. It is now read-only.

Commit

Permalink
Merge branch 'release/6.7.3'
Browse files Browse the repository at this point in the history
  • Loading branch information
Kaitlyn Crawford committed Mar 8, 2018
2 parents 7359a03 + 8b8698a commit f27b2ec
Show file tree
Hide file tree
Showing 8 changed files with 268 additions and 1 deletion.
5 changes: 5 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
CHANGE LOG
==========
6.7.3
-----
- Accept a wide range of date formats for date and datetime survey fields
- Run validation on default values for date and datetime form fields

6.7.2
-----
- Upgrade wagtail-personalisation-molo to 0.11.1
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
6.7.2
6.7.3
11 changes: 11 additions & 0 deletions molo/surveys/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from wagtailsurveys.forms import FormBuilder

from .blocks import SkipState, VALID_SKIP_LOGIC, VALID_SKIP_SELECTORS
from .widgets import NaturalDateInput


class CharacterCountWidget(forms.TextInput):
Expand Down Expand Up @@ -49,6 +50,16 @@ def create_multiline_field(self, field, options):
options['widget'] = MultiLineWidget
return forms.CharField(**options)

def create_date_field(self, field, options):
options['widget'] = NaturalDateInput
return super(
SurveysFormBuilder, self).create_date_field(field, options)

def create_datetime_field(self, field, options):
options['widget'] = NaturalDateInput
return super(
SurveysFormBuilder, self).create_datetime_field(field, options)

def create_positive_number_field(self, field, options):
return forms.DecimalField(min_value=0, **options)

Expand Down
24 changes: 24 additions & 0 deletions molo/surveys/models.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
import datetime

from django.conf import settings
from django.core.exceptions import ValidationError
Expand Down Expand Up @@ -56,6 +57,7 @@
SurveyResponseRule
)
from .utils import SkipLogicPaginator
from .widgets import NaturalDateInput


SKIP = 'NA (Skipped)'
Expand Down Expand Up @@ -535,6 +537,17 @@ class MoloSurveyFormField(SkipLogicMixin, AdminLabelMixin,
class Meta(AbstractFormField.Meta):
pass

def clean(self):
if self.field_type == 'date' or self.field_type == 'datetime':
if self.default_value:
widget = NaturalDateInput()
parsed_date = widget.value_from_datadict({
'default_value': self.default_value
}, None, 'default_value')
if not isinstance(parsed_date, datetime.datetime):
raise ValidationError(
{'default_value': ["Must be a valid date", ]})


surveys_models.AbstractFormField.panels[4] = SkipLogicStreamPanel('skip_logic')

Expand Down Expand Up @@ -684,6 +697,17 @@ def __str__(self):
class Meta(AbstractFormField.Meta):
verbose_name = _('personalisable form field')

def clean(self):
if self.field_type == 'date' or self.field_type == 'datetime':
if self.default_value:
widget = NaturalDateInput()
parsed_date = widget.value_from_datadict({
'default_value': self.default_value
}, None, 'default_value')
if not isinstance(parsed_date, datetime.datetime):
raise ValidationError(
{'default_value': ["Must be a valid date", ]})


class SegmentUserGroup(models.Model):
name = models.CharField(max_length=254)
Expand Down
136 changes: 136 additions & 0 deletions molo/surveys/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
MoloSurveyPage,
MoloSurveySubmission,
SurveysIndexPage,
PersonalisableSurvey,
PersonalisableSurveyFormField
)

from .utils import skip_logic_block_data, skip_logic_data
Expand Down Expand Up @@ -402,3 +404,137 @@ def test_two_questions_in_last_step_when_one_required(self):
field_3.clean_name: 'because ;)',
}, follow=True)
self.assertContains(response, survey.thank_you_text)


class TestFormFieldDefaultDateValidation(TestCase, MoloTestCaseMixin):
def setUp(self):
self.mk_main()
self.login()

def create_molo_survey_form_field(self, field_type):
survey = MoloSurveyPage(
title='Test Survey',
introduction='Introduction to Test Survey ...',
)
SurveysIndexPage.objects.first().add_child(instance=survey)
survey.save_revision().publish()

return MoloSurveyFormField.objects.create(
page=survey,
label="When is your birthday",
field_type=field_type,
admin_label="birthday",
)

def create_personalisable_survey_form_field(self, field_type):
survey = PersonalisableSurvey(
title='Test Survey',
introduction='Introduction to Test Survey ...',
)

SurveysIndexPage.objects.first().add_child(instance=survey)
survey.save_revision().publish()

return PersonalisableSurveyFormField.objects.create(
page=survey,
label="When is your birthday",
field_type=field_type,
admin_label="birthday",
)

def test_date_molo_form_fields_clean_if_blank(self):
field = self.create_molo_survey_form_field('date')
field.default_value = ""
try:
field.clean()
except ValidationError:
self.fail("clean() raised ValidationError with valid content!")

def test_date_molo_form_fields_clean_with_valid_default(self):
field = self.create_molo_survey_form_field('date')
field.default_value = "2008-05-05"
try:
field.clean()
except ValidationError:
self.fail("clean() raised ValidationError with valid content!")

def test_date_molo_form_fields_not_clean_with_invalid_default(self):
field = self.create_molo_survey_form_field('date')
field.default_value = "something that isn't a date"
with self.assertRaises(ValidationError) as e:
field.clean()

self.assertEqual(e.exception.messages, ['Must be a valid date'])

def test_datetime_molo_form_fields_clean_if_blank(self):
field = self.create_molo_survey_form_field('datetime')
field.default_value = ""
try:
field.clean()
except ValidationError:
self.fail("clean() raised ValidationError with valid content!")

def test_datetime_molo_form_fields_clean_with_valid_default(self):
field = self.create_molo_survey_form_field('datetime')
field.default_value = "2008-05-05"
try:
field.clean()
except ValidationError:
self.fail("clean() raised ValidationError with valid content!")

def test_datetime_molo_form_fields_not_clean_with_invalid_default(self):
field = self.create_molo_survey_form_field('datetime')
field.default_value = "something that isn't a date"
with self.assertRaises(ValidationError) as e:
field.clean()

self.assertEqual(e.exception.messages, ['Must be a valid date'])

def test_date_personalisabe_form_fields_clean_if_blank(self):
field = self.create_personalisable_survey_form_field('date')
field.default_value = ""
try:
field.clean()
except ValidationError:
self.fail("clean() raised ValidationError with valid content!")

def test_date_personalisabe_form_fields_clean_with_valid_default(self):
field = self.create_personalisable_survey_form_field('date')
field.default_value = "2008-05-05"
try:
field.clean()
except ValidationError:
self.fail("clean() raised ValidationError with valid content!")

def test_date_personalisable_fields_not_clean_with_invalid_default(self):
field = self.create_personalisable_survey_form_field('date')
field.default_value = "something that isn't a date"
with self.assertRaises(ValidationError) as e:
field.clean()

self.assertEqual(e.exception.messages, ['Must be a valid date'])

def test_datetime_personalisabe_form_fields_clean_if_blank(self):
field = self.create_personalisable_survey_form_field('datetime')
field.default_value = ""
try:
field.clean()
except ValidationError:
self.fail("clean() raised ValidationError with valid content!")

def test_datetime_personalisabe_form_fields_clean_with_valid_default(self):
field = self.create_personalisable_survey_form_field('datetime')
field.default_value = "2008-05-05"
try:
field.clean()
except ValidationError:
self.fail("clean() raised ValidationError with valid content!")

def test_datetime_personalisable_fields_not_clean_with_invalid_default(
self):
field = self.create_personalisable_survey_form_field('datetime')
field.default_value = "something that isn't a date"
with self.assertRaises(ValidationError) as e:
field.clean()

self.assertEqual(e.exception.messages, ['Must be a valid date'])
58 changes: 58 additions & 0 deletions molo/surveys/tests/test_widgets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from datetime import datetime

from django.test import TestCase

from mock import patch

from molo.surveys.widgets import NaturalDateInput


class TestNaturalDateInputWidget(TestCase):
def setUp(self):
self.widget = NaturalDateInput()

def test_widget_returns_value_if_falsey(self):
data = {'name': ''}

self.assertEqual(
self.widget.value_from_datadict(data, None, 'name'),
'',
)

def test_uses_dateparser_to_parse_dates(self):
data = {'name': '1 January 2018'}

self.assertEqual(
self.widget.value_from_datadict(data, None, 'name'),
datetime(2018, 1, 1),
)

def test_uses_dateparser_to_parse_datetimes(self):
data = {'name': '1 January 2018 5pm'}

self.assertEqual(
self.widget.value_from_datadict(data, None, 'name'),
datetime(2018, 1, 1, 17, 0),
)

@patch('molo.surveys.widgets.NaturalDateInput._parse_date')
def test_caches_expensive_result_of_dateparser(self, parse_date_mock):
parse_date_mock.return_value = datetime(2018, 1, 1)
data = {'name': '1 January 2018'}

self.widget.value_from_datadict(data, None, 'name')
self.widget.value_from_datadict(data, None, 'name')

self.assertEqual(parse_date_mock.call_count, 1)

@patch('molo.surveys.widgets.NaturalDateInput._parse_date')
def test_return_original_if_dateparser_returns_none(self, parse_date_mock):
parse_date_mock.return_value = None
data = {'name': "rubbish value which can't be parsed as a date"}

self.widget.value_from_datadict(data, None, 'name')

self.assertEqual(
self.widget.value_from_datadict(data, None, 'name'),
"rubbish value which can't be parsed as a date",
)
32 changes: 32 additions & 0 deletions molo/surveys/widgets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from dateparser import parse as date_parse

from django.core.cache import cache
from django.forms import DateInput

from hashlib import md5


class NaturalDateInput(DateInput):
def _parse_date(self, date_string):
return date_parse(date_string)

def value_from_datadict(self, data, files, name):
date = data.get(name)

if not date:
return date

hasher = md5()
hasher.update(date.encode('utf-8'))
cache_key = 'date_parse_{0}'.format(hasher.hexdigest())

parsed_date = cache.get(cache_key)

if not parsed_date:
parsed_date = self._parse_date(date)
cache.set(cache_key, parsed_date, None)

if parsed_date is None:
return date
else:
return parsed_date
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
molo.core<7.0.0,>=6.5.0
celery<4.0
dateparser>=0.7.0,<0.8.0
django-celery
wagtailsurveys==0.1.1
wagtail-personalisation-molo==0.11.1
Expand Down

0 comments on commit f27b2ec

Please sign in to comment.