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

141882981-Add-question-type-to-content-loader-and-summary-display #36

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
8c7825a
141882981-Add-question-type-to-content-loader-and-summary-display
benvand Mar 23, 2017
fc68dab
Add test for date summary formatting
idavidmcdonald Mar 28, 2017
3ba2e4e
wip date tests
idavidmcdonald Mar 28, 2017
aead0f9
Remove redundant tests
benvand Mar 29, 2017
2c1aa52
Avoid redundant checks for None
benvand Mar 29, 2017
7753666
wip date tests
idavidmcdonald Mar 28, 2017
dcefe3f
Remove redundant tests
benvand Mar 29, 2017
e9d4db2
Return None from `unformat_data` when any field is missing
benvand Apr 2, 2017
a5bc981
Bring datefield display inline with dm convention
benvand Apr 2, 2017
ea3ac84
Ignore all venvs in pep, update tests
benvand Apr 4, 2017
c9e96d2
Return incorrect values on error to repopulate field.
benvand Apr 5, 2017
7d7502b
Use dmutils date formats, add dmutils as dependency.
benvand Apr 5, 2017
f687cd5
Refactor Section.unformat_data to be part of question
benvand Apr 5, 2017
43a8424
Remove redundant `is_date` methods on Question and Section
benvand Apr 10, 2017
19f0a52
Extrapolate value processing into static method
benvand Apr 10, 2017
958d24a
More tests, pass key to unformat value for assurance qs
benvand Apr 10, 2017
ebb7e95
Linter caught redifined test
benvand Apr 10, 2017
fc377ed
Update doco line on changed unformat method
benvand Apr 10, 2017
9294be4
Remove extranous apostrophies
benvand Apr 10, 2017
1ac9929
Replace setup.py dep with requirements dep
benvand Apr 10, 2017
359618c
Version bump to 3.7.0
benvand Apr 10, 2017
08191f9
BUGFIX-failing-YAML-test
benvand Apr 10, 2017
85a23f2
Fix test for python3, assert correct dates
benvand Apr 10, 2017
26da03e
Retail standard `unformat_data` signature for list questions.
benvand Apr 11, 2017
d221162
Remove extraneous space.
benvand Apr 11, 2017
9165547
Fix comment.
benvand Apr 11, 2017
1ed993c
Remove extraneous space
benvand Apr 11, 2017
666e986
Better comments and cleanup of `Pricing` `get_data` and `unformat_data`
benvand Apr 18, 2017
c2f8b4d
Update `DynamicList.unformat_data` and test
benvand Apr 18, 2017
c5dae54
Version bump to 4.0.0 and changelog description.
benvand Apr 19, 2017
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
24 changes: 23 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,29 @@

Records breaking changes from major version bumps

## 4.0.0

PR: [#36](https://github.com/alphagov/digitalmarketplace-content-loader/pull/36/files)

### What changed

New question type `Date` and non-backwards compatible change to `Question.unformat_data` which now returns only data
relevant to the given question.

### Example Change

#### Old
```
>>> question.unformat_data({"thisQuestion": 'some data', "notThisQuestion": 'other data'})
{"thisQuestion": 'some data changed by unformat method', "notThisQuestion": 'other data'}
```
#### New
```
>>> question.unformat_data({"thisQuestion": 'some data', "notThisQuestion": 'other data'})
{"thisQuestion": 'some data changed by unformat method'}
```


## 3.0.0

PR: [#25](https://github.com/alphagov/digitalmarketplace-content-loader/pull/25)
Expand All @@ -13,7 +36,6 @@ and multiple followups for a single question. This requires changing the questio
syntax for listing followups, so the content loader is modified to support the new format
and will only work with an updated frameworks repo.


### Example change

#### Old
Expand Down
2 changes: 1 addition & 1 deletion dmcontent/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
from .errors import ContentTemplateError, QuestionNotFoundError
from .questions import ContentQuestion

__version__ = '3.6.0'
__version__ = '4.0.0'
21 changes: 17 additions & 4 deletions dmcontent/content_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,8 +353,15 @@ def get_error_messages(self, errors, question_descriptor_from="label"):

return errors_map

@staticmethod
def unformat_assurance(key, data):
return {
key + '--assurance': data[key].get('assurance', None),
key: data[key].get('value', None)
}

def unformat_data(self, data):
"""Unpack assurance information to be used in a form
"""Method to process data into format for form, special assurance case or individual question level unformat.

:param data: the service data as returned from the data API
:type data: dict
Expand All @@ -372,10 +379,16 @@ def unformat_data(self, data):
result = {}
for key in data:
if self._has_assurance(key):
result[key + '--assurance'] = data[key].get('assurance', None)
result[key] = data[key].get('value', None)
# If it's an assurance question do the unformatting here.
result.update(self.unformat_assurance(key, data))
else:
result[key] = data[key]
question = self.get_question(key)
if question:
# Otherwise if it is a legitimate question use the unformat method on the question.
result.update(question.unformat_data(data))
else:
# Otherwise it's not a question, default to returning the k: v pair.
result[key] = data[key]
return result

def get_question(self, field_name):
Expand Down
99 changes: 82 additions & 17 deletions dmcontent/questions.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
from collections import OrderedDict, defaultdict
from datetime import datetime

from dmutils.formats import DATE_FORMAT, DISPLAY_DATE_FORMAT

from .converters import convert_to_boolean, convert_to_number
from .errors import ContentNotFoundError
from .formats import format_price
Expand Down Expand Up @@ -96,7 +100,6 @@ def get_error_messages(self, errors, question_descriptor_from="label"):
for field_name in sorted(error_fields):
question = self.get_question(field_name)
message_key = errors[field_name]

validation_message = question.get_error_message(message_key, field_name)

error_key = question.id
Expand All @@ -112,7 +115,7 @@ def get_error_messages(self, errors, question_descriptor_from="label"):
return question_errors

def unformat_data(self, data):
return data
return {self.id: data.get(self.id, None)}

def get_error_message(self, message_key, field_name=None):
"""Return a single error message.
Expand Down Expand Up @@ -392,19 +395,19 @@ def unformat_data(self, data):
"yesno-0": True,
"yesno-1": False,
"evidence-0": "Yes, I did."
"nonDynamicListKey": 'other data'
}
"""
result = {}
for key in data:
if key == self.id:
for question in self.questions:
# For each question e.g. evidence-0, find if data exists for it and insert it into our result
root, index = question.id.split('-')
if root in data[self.id][int(index)]:
result[question.id] = data[self.id][int(index)].get(root)
else:
result[key] = data[key]
dynamic_list_data = data.get(self.id, None)
if not dynamic_list_data:
return result
for question in self.questions:
# For each question e.g. evidence-0, find if data exists for it and insert it into our result
root, index = question.id.split('-')
question_data = dynamic_list_data[int(index)]
if root in question_data:
result.update({question.id: question_data.get(root)})

return result

def get_error_messages(self, errors, question_descriptor_from="label"):
Expand Down Expand Up @@ -464,12 +467,17 @@ def get_question(self, field_name):
if self.id == field_name or field_name in self.fields.values():
return self

def unformat_data(self, data):
"""Get values from api data whose keys are in self.fields; this indicates they are related to this question."""
return {key: data[key] for key in data if key in self.fields.values()}

def get_data(self, form_data):
return {
key: form_data[key] if form_data[key] else None
for key in self.fields.values()
if key in form_data
}
"""
Return a subset of form_data containing only those key: value pairs whose key appears in the self.fields of
this question (therefore only those pairs relevant to this question).
Filter 0/ False values here and replace with None as they are handled with the optional flag.
"""
return {key: form_data[key] or None for key in self.fields.values() if key in form_data}

@property
def form_fields(self):
Expand Down Expand Up @@ -544,6 +552,46 @@ def update_expected_values(options, parents, expected_set):
return expected_values - selected_values_set


class Date(Question):
"""Class used as an interface for date data between forms, backend and summary pages."""

FIELDS = ('year', 'month', 'day')

def summary(self, service_data):
return DateSummary(self, service_data)

@staticmethod
def process_value(value):
"""If there are any hyphens in the value then it does not validate."""
value = value.strip() if value else ''
if not value or '-' in value:
return ''
return value

def get_data(self, form_data):
"""Retreive the fields from the POST data (form_data).

The d, m, y should be in the post as 'questionName-day', questionName-month ...
Extract them and format as '\d\d\d\d-\d{1,2}-\d{1,2}'.
https://code.tutsplus.com/tutorials/validating-data-with-json-schema-part-1--cms-25343
"""
parts = []
for key in self.FIELDS:
identifier = '-'.join([self.id, key])
value = form_data.get(identifier, '')
parts.append(self.process_value(value))

return {self.id: '-'.join(parts) if any(parts) else None}

def unformat_data(self, data):
result = {}
value = data[self.id]
if value:
for partial_value, field in zip(value.split('-'), self.FIELDS):
result['-'.join([self.id, field])] = partial_value
return result


class QuestionSummary(Question):
def __init__(self, question, service_data):
self.number = question.number
Expand Down Expand Up @@ -626,6 +674,22 @@ def answer_required(self):
return self.is_empty


class DateSummary(QuestionSummary):

def __init__(self, question, service_data):
super(DateSummary, self).__init__(question, service_data)
self._value = self._service_data.get(self.id, '')

@property
def value(self):
try:
return datetime.strptime(self._value, DATE_FORMAT).strftime(DISPLAY_DATE_FORMAT)
except ValueError:
# We may need to fall back to displaying a plain string value in the case of briefs in draft before
# the date field was introduced.
return self._value


class MultiquestionSummary(QuestionSummary, Multiquestion):
def __init__(self, question, service_data):
super(MultiquestionSummary, self).__init__(question, service_data)
Expand Down Expand Up @@ -758,6 +822,7 @@ def _get_options_recursive(options):
'list': List,
'checkboxes': List,
'checkbox_tree': Hierarchy,
'date': Date
}


Expand Down
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
-e .
-e .
git+https://github.com/alphagov/[email protected]#egg=digitalmarketplace-utils==25.0.1
4 changes: 2 additions & 2 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[pep8]
exclude = ./venv
exclude = ./venv*
max-line-length = 120

[pytest]
norecursedirs = venv
norecursedirs = venv*
4 changes: 2 additions & 2 deletions tests/test_content_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -1699,11 +1699,11 @@ def test_inject_messages_into_section_and_section_summary(self):


class TestReadYaml(object):
@mock.patch.object(builtins, 'open', return_value=io.StringIO(u'foo: bar'))
@mock.patch('dmcontent.content_loader.open', return_value=io.StringIO(u'foo: bar'))
def test_loading_existant_file(self, mocked_open):
assert read_yaml('anything.yml') == {'foo': 'bar'}

@mock.patch.object(builtins, 'open', side_effect=IOError)
@mock.patch('dmcontent.content_loader.open', side_effect=IOError)
def test_file_not_found(self, mocked_open):
with pytest.raises(IOError):
assert read_yaml('something.yml')
Expand Down
Loading