Skip to content

Commit

Permalink
right: Add RIGHT option, like DOWN but a different direction
Browse files Browse the repository at this point in the history
  • Loading branch information
odscjames committed May 28, 2020
1 parent f764c20 commit ffc0640
Show file tree
Hide file tree
Showing 11 changed files with 237 additions and 4 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

### Added

- added right option as well as down

## [0.3.0] - 2020-05-27

### Added
Expand Down
2 changes: 1 addition & 1 deletion docs/guideform/down.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Definition
----------

Sometimes you want to allow people to put in multiple rows with the same set of headings.
People can put in as few or as many as they want.
People can put in as few or as many rows as they want.
Each row will be converted to one JSON dictionary.
In the final data, there will be a list of JSON dictionaries.

Expand Down
1 change: 1 addition & 0 deletions docs/guideform/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ You then put special values in certain cells to indicate that these cells should

single.rst
down.rst
right.rst
jsonkey.rst
67 changes: 67 additions & 0 deletions docs/guideform/right.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
Right field
===========


Definition
----------

Sometimes you want to allow people to put in multiple columns with the same set of headings in rows.
People can put in as few or as many columns as they want.
Each column will be converted to one JSON dictionary.
In the final data, there will be a list of JSON dictionaries.


To do this, in your cell put

.. code-block:: none
SPREADSHEETFORM:RIGHT:listkey:itemkey
The `listkey` is the JSON key that the list of items will appear in.

All Down configurations for the same `listkey` should appear on the same row.

You should probably only have one set of right configurations per set of rows and to the right of that there should be nothing.
This is because the user can put as many data columns as they want in; if you try and put something else there it may be overwritten.

The order of the columns and the order of the items in the JSON list will be the same.

The `itemkey` is the JSON key that the data will appear in in each dictionary.

See :doc:`JSON Key for information on how to structure those<jsonkey>`.

Example
-------

A guide of:

+-------------------------------------+------------------------------------------+
| Title | SPREADSHEETFORM:RIGHT:toys:title |
+-------------------------------------+------------------------------------------+
| Does it squeak? | SPREADSHEETFORM:RIGHT:toys:squeak |
+-------------------------------------+------------------------------------------+

And a spreadsheet of:

+--------------------------+-----------------+------------------------+
| Title | Plastic bone | Tennis Ball |
+--------------------------+-----------------+------------------------+
| Does it squeak? | Oh Yes | No |
+--------------------------+-----------------+------------------------+

Will map to the data:


.. code-block:: json
{
"toys": [
{"title": "Plastic bone", "squeak": "Oh Yes"},
{"title": "Tennis Ball", "squeak": "No"},
]
}
73 changes: 70 additions & 3 deletions spreadsheetforms/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ def _get_guide_field_spec_from_content(content):
"item_path": bits[3],
}

if isinstance(content, str) and content.startswith("SPREADSHEETFORM:RIGHT:"):
bits = content.split(":")
return {
"mode": "right",
"list_path": bits[2],
"item_path": bits[3],
}

return None


Expand Down Expand Up @@ -50,13 +58,15 @@ def make_empty_form(guide_filename, out_filename):
def _build_all_configs_in_excel_sheet(worksheet):
single_configs = {}
down_configs = {}
right_configs = {}

for row_idx in range(1, worksheet.max_row + 1):
for cell in worksheet[row_idx]:
guide_field_spec = _get_guide_field_spec_from_content(cell.value)
if guide_field_spec:
guide_field_spec["coordinate"] = cell.coordinate
guide_field_spec["column_letter"] = cell.column_letter
guide_field_spec["column"] = cell.column
guide_field_spec["row"] = cell.row
if guide_field_spec["mode"] == "single":
single_configs[guide_field_spec["path"]] = guide_field_spec
Expand All @@ -67,10 +77,19 @@ def _build_all_configs_in_excel_sheet(worksheet):
)
else:
down_configs[guide_field_spec["list_path"]] = [guide_field_spec]
elif guide_field_spec["mode"] == "right":
if guide_field_spec["list_path"] in right_configs:
right_configs[guide_field_spec["list_path"]].append(
guide_field_spec
)
else:
right_configs[guide_field_spec["list_path"]] = [
guide_field_spec
]

# TODO could check that every down config for each list_path is on the same row. Things will get odd if they are not.

return single_configs, down_configs
return single_configs, down_configs, right_configs


def get_data_from_form(guide_filename, in_filename, date_format=None):
Expand All @@ -81,7 +100,9 @@ def get_data_from_form(guide_filename, in_filename, date_format=None):
for worksheet in guide_workbook.worksheets:

# Step 1: build details of all configs on this sheet
single_configs, down_configs = _build_all_configs_in_excel_sheet(worksheet)
single_configs, down_configs, right_configs = _build_all_configs_in_excel_sheet(
worksheet
)

# Step 2: Process single configs (easy ones)
for single_config in single_configs.values():
Expand Down Expand Up @@ -116,6 +137,29 @@ def get_data_from_form(guide_filename, in_filename, date_format=None):
if found_anything:
json_append_deep_value(data, down_config[0]["list_path"], item)

# Step 4: Process Right Configs
for right_config in right_configs.values():
start_column = right_config[0]["column"]
max_column = in_workbook[worksheet.title].max_column + 1
json_set_deep_value(data, right_config[0]["list_path"], [])
for column in range(start_column, max_column + 1):
item = {}
found_anything = False
for this_right_config in right_config:
cell = in_workbook[worksheet.title][
openpyxl.utils.get_column_letter(column)
+ str(this_right_config["row"])
]
json_set_deep_value(
item,
this_right_config["item_path"],
_get_cell_value(cell, date_format),
)
if json_get_deep_value(item, this_right_config["item_path"]):
found_anything = True
if found_anything:
json_append_deep_value(data, right_config[0]["list_path"], item)

return data


Expand All @@ -126,7 +170,9 @@ def put_data_in_form(guide_filename, data, out_filename):
for worksheet in workbook.worksheets:

# Step 1: build details of all configs on this sheet
single_configs, down_configs = _build_all_configs_in_excel_sheet(worksheet)
single_configs, down_configs, right_configs = _build_all_configs_in_excel_sheet(
worksheet
)

# Step 2: Process single configs (easy ones)
for single_config in single_configs.values():
Expand All @@ -153,4 +199,25 @@ def put_data_in_form(guide_filename, data, out_filename):
for this_down_config in down_config:
worksheet[this_down_config["coordinate"]] = ""

# Step 4: Process Right Configs
for right_config in right_configs.values():
datas_to_insert = json_get_deep_value(data, right_config[0]["list_path"])
if isinstance(datas_to_insert, list) and len(datas_to_insert) > 0:
extra_column = 0
for data_to_insert in datas_to_insert:
for this_right_config in right_config:
worksheet[
openpyxl.utils.get_column_letter(
this_right_config["column"] + extra_column
)
+ str(this_right_config["row"])
] = json_get_deep_value(
data_to_insert, this_right_config["item_path"]
)
extra_column += 1
else:
# no data, but we still want to remove the special values from the output spreadsheet
for this_right_config in right_config:
worksheet[this_right_config["coordinate"]] = ""

workbook.save(out_filename)
Binary file modified tests/data/cat1.xlsx
Binary file not shown.
Binary file modified tests/data/pet1-deep.xlsx
Binary file not shown.
Binary file modified tests/data/pet1.xlsx
Binary file not shown.
20 changes: 20 additions & 0 deletions tests/test_get_data_from_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ def test_1():
assert {
"noise": "Miaow Miaow Purr Purr Hiss",
"pet": "Cat",
"hungry": [
{"state": "Yes", "wants": "Food"},
{"state": "Always", "wants": "Food"},
{"state": "Right Now", "wants": "Food"},
],
"sleepy": [
{"state": "A lot", "wants": "Sleep"},
{"state": "Also Right Now", "wants": "A Nap"},
],
"toys": [
{"title": "Bit of string", "squeak": "No"},
{"title": "Marble", "squeak": "No"},
Expand All @@ -36,6 +45,17 @@ def test_deep():
assert {
"emits": {"noise": "Miaow Miaow Purr Purr Hiss"},
"pet": {"kind": "Cat"},
"mood": {
"hungry": [
{"current": {"state": "Yes", "wants": "Food"}},
{"current": {"state": "Always", "wants": "Food"}},
{"current": {"state": "Right Now", "wants": "Food"}},
],
"sleepy": [
{"current": {"state": "A lot", "wants": "Sleep"}},
{"current": {"state": "Also Right Now", "wants": "A Nap"}},
],
},
"likes": {
"toys": [
{
Expand Down
3 changes: 3 additions & 0 deletions tests/test_make_empy_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ def test_1():
workbook = openpyxl.load_workbook(outfile, read_only=True)

assert "Pet" == workbook["Info"]["A5"].value
assert None == workbook["Info"]["B9"].value
assert None == workbook["Info"]["B5"].value
assert None == workbook["Info"]["B13"].value
assert None == workbook["Info"]["B14"].value
assert None == workbook["Toys"]["A7"].value
assert None == workbook["Toys"]["B7"].value
71 changes: 71 additions & 0 deletions tests/test_put_data_in_form.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ def test_1():
data = {
"noise": "Woof Woof",
"pet": "Dog",
"hungry": [
{"state": "Yes", "wants": "Food"},
{"state": "Always", "wants": "Food"},
{"state": "Right Now", "wants": "Food"},
],
"sleepy": [
{"state": "A lot", "wants": "Sleep"},
{"state": "Also Right Now", "wants": "A Nap"},
],
"toys": [
{"title": "Plastic bone", "squeak": "Oh Yes"},
{"title": "Tennis Ball", "squeak": "No"},
Expand All @@ -27,8 +36,25 @@ def test_1():

workbook = openpyxl.load_workbook(outfile, read_only=True)

# Test - General Info
assert "Dog" == workbook["Info"]["B5"].value
assert "Woof Woof" == workbook["Info"]["B6"].value

# Test - Hungry
assert "Yes" == workbook["Info"]["B9"].value
assert "Food" == workbook["Info"]["B10"].value
assert "Always" == workbook["Info"]["C9"].value
assert "Food" == workbook["Info"]["C10"].value
assert "Right Now" == workbook["Info"]["D9"].value
assert "Food" == workbook["Info"]["D10"].value

# Test - Sleepy
assert "A lot" == workbook["Info"]["B13"].value
assert "Sleep" == workbook["Info"]["B14"].value
assert "Also Right Now" == workbook["Info"]["C13"].value
assert "A Nap" == workbook["Info"]["C14"].value

# Test - Toys
assert "Plastic bone" == workbook["Toys"]["A7"].value
assert "Oh Yes" == workbook["Toys"]["B7"].value
assert "Tennis Ball" == workbook["Toys"]["A8"].value
Expand All @@ -45,8 +71,25 @@ def test_no_list():

workbook = openpyxl.load_workbook(outfile, read_only=True)

# Test - General Info
assert "Dog" == workbook["Info"]["B5"].value
assert "Woof Woof" == workbook["Info"]["B6"].value

# Test - Hungry
assert None == workbook["Info"]["B9"].value
assert None == workbook["Info"]["B10"].value
assert None == workbook["Info"]["C9"].value
assert None == workbook["Info"]["C10"].value
assert None == workbook["Info"]["D9"].value
assert None == workbook["Info"]["D10"].value

# Test - Sleepy
assert None == workbook["Info"]["B13"].value
assert None == workbook["Info"]["B14"].value
assert None == workbook["Info"]["C13"].value
assert None == workbook["Info"]["C14"].value

# Test - Toys
assert None == workbook["Toys"]["A7"].value
assert None == workbook["Toys"]["B7"].value
assert None == workbook["Toys"]["A8"].value
Expand All @@ -60,6 +103,17 @@ def test_deep():
data = {
"emits": {"noise": "Woof Woof"},
"pet": {"kind": "Dog"},
"mood": {
"hungry": [
{"current": {"state": "Yes", "wants": "Food"}},
{"current": {"state": "Always", "wants": "Food"}},
{"current": {"state": "Right Now", "wants": "Food"}},
],
"sleepy": [
{"current": {"state": "A lot", "wants": "Sleep"}},
{"current": {"state": "Also Right Now", "wants": "A Nap"}},
],
},
"likes": {
"toys": [
{
Expand All @@ -78,8 +132,25 @@ def test_deep():

workbook = openpyxl.load_workbook(outfile, read_only=True)

# Test - General Info
assert "Dog" == workbook["Info"]["B5"].value
assert "Woof Woof" == workbook["Info"]["B6"].value

# Test - Hungry
assert "Yes" == workbook["Info"]["B9"].value
assert "Food" == workbook["Info"]["B10"].value
assert "Always" == workbook["Info"]["C9"].value
assert "Food" == workbook["Info"]["C10"].value
assert "Right Now" == workbook["Info"]["D9"].value
assert "Food" == workbook["Info"]["D10"].value

# Test - Sleepy
assert "A lot" == workbook["Info"]["B13"].value
assert "Sleep" == workbook["Info"]["B14"].value
assert "Also Right Now" == workbook["Info"]["C13"].value
assert "A Nap" == workbook["Info"]["C14"].value

# Test - Toys
assert "Plastic bone" == workbook["Toys"]["A7"].value
assert "Oh Yes" == workbook["Toys"]["B7"].value
assert "Tennis Ball" == workbook["Toys"]["A8"].value
Expand Down

0 comments on commit ffc0640

Please sign in to comment.