Skip to content

Commit e982c7e

Browse files
authored
Merge pull request #183 from kobotoolbox/182-shifted-value-with-deleted-multiple-options
Exporting data with multiple response options previously deleted shifts values - fix
2 parents d4e4ce0 + 4896f59 commit e982c7e

File tree

7 files changed

+214
-13
lines changed

7 files changed

+214
-13
lines changed

src/formpack/pack.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323

2424
class FormPack(object):
2525

26-
2726
def __init__(self, versions=None, title='Submissions', id_string=None,
2827
default_version_id_key='__version__',
2928
strict_schema=False,
@@ -193,19 +192,24 @@ def summr(v):
193192

194193
@staticmethod
195194
def _combine_field_choices(old_field, new_field):
196-
""" Update `new_field.choice` so that it contains everything from
197-
`old_field.choice`. In the event of a conflict, `new_field.choice`
198-
wins. If either field does not have a `choice` attribute, do
199-
nothing
195+
"""
196+
Update `new_field.choice` so that it contains everything from
197+
`old_field.choice`. In the event of a conflict, `new_field.choice`
198+
wins. If either field does not have a `choice` attribute, do
199+
nothing
200+
201+
:param old_field: FormField
202+
:param new_field: FormField
203+
:return: FormField. Updated new_field
200204
"""
201205
try:
202206
old_choice = old_field.choice
203207
new_choice = new_field.choice
208+
new_field.merge_choice(old_choice)
204209
except AttributeError:
205-
return
206-
combined_options = old_choice.options.copy()
207-
combined_options.update(new_choice.options)
208-
new_choice.options = combined_options
210+
pass
211+
212+
return new_field
209213

210214
def get_fields_for_versions(self, versions=-1, data_types=None):
211215

@@ -274,7 +278,9 @@ def get_fields_for_versions(self, versions=-1, data_types=None):
274278
# Because versions_desc are ordered from latest to oldest,
275279
# we use current field object as the old one and the one already
276280
# in position as the latest one.
277-
self._combine_field_choices(field_object, latest_field_object)
281+
new_object = self._combine_field_choices(
282+
field_object, latest_field_object)
283+
tmp2d[position[0]][position[1]] = new_object
278284
else:
279285
try:
280286
current_index_list = tmp2d[index]

src/formpack/reporting/export.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,6 @@ def parse_submissions(self, submissions):
113113
except KeyError:
114114
pass
115115

116-
117116
def reset(self):
118117
""" Reset sections and indexes to initial values """
119118

@@ -222,7 +221,6 @@ def get_fields_labels_tags_for_all_versions(self,
222221
len(field.value_names)
223222
)
224223

225-
226224
names = [name for name_list in name_lists for name in name_list]
227225

228226
# add auto fields:

src/formpack/schema/fields.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,12 +704,37 @@ def sum_frequencies(element):
704704

705705
return stats
706706

707+
def merge_choice(self, choice):
708+
"""
709+
Update `new_field.choice` so that it contains everything from
710+
`old_field.choice`. In the event of a conflict, `new_field.choice`
711+
wins. If either field does not have a `choice` attribute, do
712+
nothing
713+
714+
:param choice: formpack.schema.datadef.FormChoice
715+
"""
716+
combined_options = choice.options.copy()
717+
combined_options.update(self.choice.options)
718+
self.choice.options = combined_options
719+
720+
self._empty_result()
721+
self.value_names = self.get_value_names()
722+
723+
def _empty_result(self):
724+
"""
725+
Nothing to do here
726+
"""
727+
pass
728+
707729

708730
class FormChoiceFieldWithMultipleSelect(FormChoiceField):
709731
""" Same as FormChoiceField, but you can select several answer """
710732

711733
def __init__(self, *args, **kwargs):
712734
super(FormChoiceFieldWithMultipleSelect, self).__init__(*args, **kwargs)
735+
self._empty_result()
736+
737+
def _empty_result(self):
713738
# reset empty result so it doesn't contain '0'
714739
self.empty_result = dict.fromkeys(self.empty_result, '')
715740

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# coding: utf-8
2+
3+
from __future__ import (unicode_literals, print_function,
4+
absolute_import, division)
5+
6+
7+
from ..load_fixture_json import load_fixture_json
8+
9+
DATA = {
10+
u'title': 'Favorite coffee',
11+
u'id_string': 'favorite_coffee',
12+
u'versions': [
13+
load_fixture_json('favorite_coffee/v1'),
14+
load_fixture_json('favorite_coffee/v2')
15+
]
16+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{
2+
"id_string": "favorite_coffee",
3+
"version": "fcv1",
4+
"content": {
5+
"choices": [
6+
{
7+
"name": "french",
8+
"label": [
9+
"French"
10+
],
11+
"list_name": "al1hv46",
12+
"order": 0
13+
},
14+
{
15+
"name": "italian",
16+
"label": [
17+
"Italian"
18+
],
19+
"list_name": "al1hv46",
20+
"order": 1
21+
},
22+
{
23+
"name": "american",
24+
"label": [
25+
"American"
26+
],
27+
"list_name": "al1hv46",
28+
"order": 2
29+
}
30+
],
31+
"survey": [
32+
{
33+
"select_from_list_name": "al1hv46",
34+
"required": false,
35+
"label": [
36+
"Favorite coffee type"
37+
],
38+
"name": "favorite_coffee_type",
39+
"type": "select_multiple"
40+
},
41+
{
42+
"required": false,
43+
"type": "text",
44+
"label": [
45+
"Brand of coffee machine"
46+
],
47+
"name": "brand_of_coffee_machine"
48+
}
49+
]
50+
},
51+
"submissions": [
52+
{
53+
"brand_of_coffee_machine": "Breville",
54+
"favorite_coffee_type": "french italian"
55+
},
56+
{
57+
"brand_of_coffee_machine": "DeLonghi",
58+
"favorite_coffee_type": "italian"
59+
},
60+
{
61+
"brand_of_coffee_machine": "Nespresso",
62+
"favorite_coffee_type": "american"
63+
}
64+
]
65+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
{
2+
"id_string": "favorite_coffee",
3+
"version": "fcv2",
4+
"content": {
5+
"choices": [
6+
{
7+
"name": "french",
8+
"label": [
9+
"French"
10+
],
11+
"list_name": "al1hv46",
12+
"order": 0
13+
},
14+
{
15+
"name": "american",
16+
"label": [
17+
"American"
18+
],
19+
"list_name": "al1hv46",
20+
"order": 1
21+
},
22+
{
23+
"name": "british",
24+
"label": [
25+
"British"
26+
],
27+
"list_name": "al1hv46",
28+
"order": 2
29+
}
30+
],
31+
"survey": [
32+
{
33+
"select_from_list_name": "al1hv46",
34+
"required": false,
35+
"label": [
36+
"Favorite coffee type"
37+
],
38+
"name": "favorite_coffee_type",
39+
"type": "select_multiple"
40+
},
41+
{
42+
"required": false,
43+
"type": "text",
44+
"label": [
45+
"Brand of coffee machine"
46+
],
47+
"name": "brand_of_coffee_machine"
48+
}
49+
]
50+
},
51+
"submissions": [
52+
{
53+
"brand_of_coffee_machine": "Saico",
54+
"favorite_coffee_type": "french"
55+
},
56+
{
57+
"brand_of_coffee_machine": "Keurig",
58+
"favorite_coffee_type": "american british"
59+
}
60+
]
61+
}

tests/test_exports.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,6 @@ def test_export_with_choice_lists(self):
191191
'',
192192
'traditionnel']])
193193

194-
195194
def test_headers_of_group_exports(self):
196195
title, schemas, submissions = build_fixture('grouped_questions')
197196
fp = FormPack(schemas, title)
@@ -1746,3 +1745,34 @@ def test_untranslated_spss_labels(self):
17461745
assert actual.read() == expected.read()
17471746
zipped.close()
17481747
raw_zip.close()
1748+
1749+
def test_select_multiple_with_different_options_in_multiple_versions(self):
1750+
title, schemas, submissions = build_fixture('favorite_coffee')
1751+
fp = FormPack(schemas, title)
1752+
self.assertEqual(len(fp.versions), 2)
1753+
1754+
export = fp.export(versions=fp.versions.keys()).to_dict(submissions)
1755+
1756+
headers = export['Favorite coffee']['fields']
1757+
self.assertListEqual(headers, [
1758+
'favorite_coffee_type',
1759+
'favorite_coffee_type/french',
1760+
'favorite_coffee_type/italian',
1761+
'favorite_coffee_type/american',
1762+
'favorite_coffee_type/british',
1763+
'brand_of_coffee_machine'
1764+
])
1765+
1766+
# Check length of each row
1767+
for row in export['Favorite coffee']['data']:
1768+
self.assertEqual(len(headers), len(row))
1769+
1770+
# Ensure latest submissions is not shifted
1771+
self.assertListEqual(export['Favorite coffee']['data'][-1], [
1772+
'american british',
1773+
'0',
1774+
'0',
1775+
'1',
1776+
'1',
1777+
'Keurig'
1778+
])

0 commit comments

Comments
 (0)