Skip to content

Commit e5c7909

Browse files
committed
Fixes to ch7 per previous chpater reviews
1 parent d300ff4 commit e5c7909

File tree

4 files changed

+130
-110
lines changed

4 files changed

+130
-110
lines changed

Chapter07/ABQ_Data_Entry/abq_data_entry/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def save_record(self, data):
6464
"""Save a dict of data to the CSV file"""
6565
newfile = not self.file.exists()
6666

67-
with open(self.file, 'a') as fh:
67+
with open(self.file, 'a', newline='') as fh:
6868
csvwriter = csv.DictWriter(fh, fieldnames=self.fields.keys())
6969
if newfile:
7070
csvwriter.writeheader()

Chapter07/ABQ_Data_Entry/abq_data_entry/views.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,21 +58,21 @@ def __init__(self, parent, model, settings, *args, **kwargs):
5858
field_spec=fields['Time'],
5959
var=self._vars['Time'],
6060
).grid(row=0, column=1)
61+
w.LabelInput(
62+
r_info, "Technician",
63+
field_spec=fields['Technician'],
64+
var=self._vars['Technician'],
65+
).grid(row=0, column=2)
66+
# line 2
6167
w.LabelInput(
6268
r_info, "Lab",
6369
field_spec=fields['Lab'],
6470
var=self._vars['Lab'],
65-
).grid(row=0, column=2)
66-
# line 2
71+
).grid(row=1, column=0)
6772
w.LabelInput(
6873
r_info, "Plot",
6974
field_spec=fields['Plot'],
7075
var=self._vars['Plot'],
71-
).grid(row=1, column=0)
72-
w.LabelInput(
73-
r_info, "Technician",
74-
field_spec=fields['Technician'],
75-
var=self._vars['Technician'],
7676
).grid(row=1, column=1)
7777
w.LabelInput(
7878
r_info, "Seed Sample",

Chapter07/ABQ_Data_Entry/abq_data_entry/widgets.py

Lines changed: 44 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ def _set_focus_update_var(self, event):
190190
if self.focus_update_var and not self.error.get():
191191
self.focus_update_var.set(value)
192192

193-
def _set_minimum(self, *args):
193+
def _set_minimum(self, *_):
194194
current = self.get()
195195
try:
196196
new_min = self.min_var.get()
@@ -203,7 +203,7 @@ def _set_minimum(self, *args):
203203
self.variable.set(current)
204204
self.trigger_focusout_validation()
205205

206-
def _set_maximum(self, *args):
206+
def _set_maximum(self, *_):
207207
current = self.get()
208208
try:
209209
new_max = self.max_var.get()
@@ -219,13 +219,13 @@ def _set_maximum(self, *args):
219219
def _key_validate(
220220
self, char, index, current, proposed, action, **kwargs
221221
):
222+
if action == '0':
223+
return True
222224
valid = True
223225
min_val = self.cget('from')
224226
max_val = self.cget('to')
225227
no_negative = min_val >= 0
226228
no_decimal = self.precision >= 0
227-
if action == '0':
228-
return True
229229

230230
# First, filter out obviously invalid keystrokes
231231
if any([
@@ -260,55 +260,61 @@ def _focusout_validate(self, **kwargs):
260260
max_val = self.cget('to')
261261

262262
try:
263-
value = Decimal(value)
263+
d_value = Decimal(value)
264264
except InvalidOperation:
265-
self.error.set('Invalid number string: {}'.format(value))
265+
self.error.set(f'Invalid number string: {value}')
266266
return False
267267

268-
if value < min_val:
269-
self.error.set('Value is too low (min {})'.format(min_val))
268+
if d_value < min_val:
269+
self.error.set(f'Value is too low (min {min_val})')
270+
valid = False
271+
if d_value > max_val:
272+
self.error.set(f'Value is too high (max {max_val})')
270273
valid = False
271-
if value > max_val:
272-
self.error.set('Value is too high (max {})'.format(max_val))
273274

274275
return valid
275276

277+
class ValidatedRadio(ttk.Radiobutton):
278+
"""A validated radio button"""
279+
280+
def __init__(self, *args, error_var=None, **kwargs):
281+
super().__init__(*args, **kwargs)
282+
self.error = error_var or tk.StringVar()
283+
self.variable = kwargs.get("variable")
284+
self.bind('<FocusOut>', self._focusout_validate)
285+
286+
def _focusout_validate(self, *_):
287+
self.error.set('')
288+
if not self.variable.get():
289+
self.error.set('A value is required')
290+
291+
def trigger_focusout_validation(self):
292+
self._focusout_validate()
293+
294+
276295
class BoundText(tk.Text):
277296
"""A Text widget with a bound variable."""
278297

279298
def __init__(self, *args, textvariable=None, **kwargs):
280299
super().__init__(*args, **kwargs)
281300
self._variable = textvariable
282-
self._modifying = False
283301
if self._variable:
284302
# insert any default value
285303
self.insert('1.0', self._variable.get())
286304
self._variable.trace_add('write', self._set_content)
287305
self.bind('<<Modified>>', self._set_var)
288306

289-
def _clear_modified_flag(self):
290-
# This also triggers a '<<Modified>>' Event
291-
self.tk.call(self._w, 'edit', 'modified', 0)
292-
293307
def _set_var(self, *_):
294308
"""Set the variable to the text contents"""
295-
if self._modifying:
296-
return
297-
self._modifying = True
298-
# remove trailing newline from content
299-
content = self.get('1.0', tk.END)[:-1]
300-
self._variable.set(content)
301-
self._clear_modified_flag()
302-
self._modifying = False
309+
if self.edit_modified():
310+
content = self.get('1.0', 'end-1chars')
311+
self._variable.set(content)
312+
self.edit_modified(False)
303313

304314
def _set_content(self, *_):
305315
"""Set the text contents to the variable"""
306-
if self._modifying:
307-
return
308-
self._modifying = True
309316
self.delete('1.0', tk.END)
310317
self.insert('1.0', self._variable.get())
311-
self._modifying = False
312318

313319

314320
###########################
@@ -322,7 +328,7 @@ class LabelInput(ttk.Frame):
322328
field_types = {
323329
FT.string: RequiredEntry,
324330
FT.string_list: ValidatedCombobox,
325-
FT.short_string_list: ttk.Radiobutton,
331+
FT.short_string_list: ValidatedRadio,
326332
FT.iso_date_string: DateEntry,
327333
FT.long_string: BoundText,
328334
FT.decimal: ValidatedSpinbox,
@@ -365,22 +371,25 @@ def __init__(
365371
self.label.grid(row=0, column=0, sticky=(tk.W + tk.E))
366372

367373
# setup the variable
368-
if input_class in (ttk.Checkbutton, ttk.Button, ttk.Radiobutton):
374+
if input_class in (
375+
ttk.Checkbutton, ttk.Button, ttk.Radiobutton, ValidatedRadio
376+
):
369377
input_args["variable"] = self.variable
370378
else:
371379
input_args["textvariable"] = self.variable
372380

373381
# Setup the input
374-
if input_class == ttk.Radiobutton:
382+
if input_class in (ttk.Radiobutton, ValidatedRadio):
375383
# for Radiobutton, create one input per value
376384
self.input = tk.Frame(self)
377385
for v in input_args.pop('values', []):
378-
button = ttk.Radiobutton(
379-
self.input, value=v, text=v, **input_args)
386+
button = input_class(
387+
self.input, value=v, text=v, **input_args
388+
)
380389
button.pack(side=tk.LEFT, ipadx=10, ipady=2, expand=True, fill='x')
381-
else:
382-
self.input = input_class(self, **input_args)
383-
390+
self.input.error = getattr(button, 'error')
391+
self.input.trigger_focusout_validation = \
392+
button._focusout_validate
384393
self.input.grid(row=1, column=0, sticky=(tk.W + tk.E))
385394
self.columnconfigure(0, weight=1)
386395

Chapter07/ABQ_Data_Entry/docs/abq_data_entry_spec.rst

Lines changed: 78 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -2,95 +2,106 @@
22
ABQ Data Entry Program specification
33
======================================
44

5-
65
Description
76
-----------
8-
The program is being created to minimize data entry errors for laboratory measurements.
7+
This program facilitates entry of laboratory observations
8+
into a CSV file.
99

10-
Functionality Required
11-
----------------------
10+
Requirements
11+
------------
1212

13-
The program must:
13+
Functional Requirements:
1414

15-
* allow all relevant, valid data to be entered, as per the field chart
16-
* append entered data to a CSV file
17-
- The CSV file must have a filename of abq_data_record_CURRENTDATE.csv,
18-
where CURRENTDATE is the date of the checks in ISO format (Year-month-day)
19-
- The CSV file must have all the fields as per the chart
20-
* enforce correct datatypes per field
21-
* have inputs that:
15+
* Allow all relevant, valid data to be entered,
16+
as per the data dictionary.
17+
* Append entered data to a CSV file:
18+
- The CSV file must have a filename of
19+
abq_data_record_CURRENTDATE.csv, where CURRENTDATE is the date
20+
of the laboratory observations in ISO format (Year-month-day).
21+
- The CSV file must include all fields.
22+
listed in the data dictionary.
23+
- The CSV headers will avoid cryptic abbreviations.
24+
* Enforce correct datatypes per field.
25+
* have inputs that:
2226
- ignore meaningless keystrokes
2327
- display an error if the value is invalid on focusout
2428
- display an error if a required field is empty on focusout
25-
* prevent saving the record when errors are present
29+
* prevent saving the record when errors are present
2630

27-
The program should try, whenever possible, to:
31+
Non-functional Requirements:
2832

29-
* enforce reasonable limits on data entered
30-
* Auto-fill data
31-
* Suggest likely correct values
32-
* Provide a smooth and efficient workflow
33+
* Enforce reasonable limits on data entered, per the data dict.
34+
* Auto-fill data to save time.
35+
* Suggest likely correct values.
36+
* Provide a smooth and efficient workflow.
37+
* Store data in a format easily understandable by Python.
3338

3439
Functionality Not Required
3540
--------------------------
3641

3742
The program does not need to:
3843

39-
* Allow editing of data. This can be done in LibreOffice if necessary.
40-
* Allow deletion of data.
44+
* Allow editing of data.
45+
* Allow deletion of data.
46+
47+
Users can perform both actions in LibreOffice if needed.
48+
4149

4250
Limitations
4351
-----------
4452

4553
The program must:
4654

47-
* Be efficiently operable by keyboard-only users.
48-
* Be accessible to color blind users.
49-
* Run on Debian Linux.
50-
* Run acceptably on a low-end PC.
55+
* Be efficiently operable by keyboard-only users.
56+
* Be accessible to color blind users.
57+
* Run on Debian GNU/Linux.
58+
* Run acceptably on a low-end PC.
5159

5260
Data Dictionary
5361
---------------
54-
+------------+----------+------+------------------+--------------------------+
55-
|Field | Datatype | Units| Range |Descripton |
56-
+============+==========+======+==================+==========================+
57-
|Date |Date | | |Date of record |
58-
+------------+----------+------+------------------+--------------------------+
59-
|Time |Time | |8:00, 12:00, |Time period |
60-
| | | |16:00, or 20:00 | |
61-
+------------+----------+------+------------------+--------------------------+
62-
|Lab |String | | A - C |Lab ID |
63-
+------------+----------+------+------------------+--------------------------+
64-
|Technician |String | | |Technician name |
65-
+------------+----------+------+------------------+--------------------------+
66-
|Plot |Int | | 1 - 20 |Plot ID |
67-
+------------+----------+------+------------------+--------------------------+
68-
|Seed |String | | |Seed sample ID |
69-
|sample | | | | |
70-
+------------+----------+------+------------------+--------------------------+
71-
|Fault |Bool | | |Fault on environmental |
72-
| | | | |sensor |
73-
+------------+----------+------+------------------+--------------------------+
74-
|Light |Decimal |klx | 0 - 100 |Light at plot |
75-
+------------+----------+------+------------------+--------------------------+
76-
|Humidity |Decimal |g/m³ | 0.5 - 52.0 |Abs humidity at plot |
77-
+------------+----------+------+------------------+--------------------------+
78-
|Temperature |Decimal |°C | 4 - 40 |Temperature at plot |
79-
+------------+----------+------+------------------+--------------------------+
80-
|Blossoms |Int | | 0 - 1000 |Number of blossoms in plot|
81-
+------------+----------+------+------------------+--------------------------+
82-
|Fruit |Int | | 0 - 1000 |Number of fruits in plot |
83-
+------------+----------+------+------------------+--------------------------+
84-
|Plants |Int | | 0 - 20 |Number of plants in plot |
85-
+------------+----------+------+------------------+--------------------------+
86-
|Max height |Decimal |cm | 0 - 1000 |Height of tallest plant in|
87-
| | | | |plot |
88-
+------------+----------+------+------------------+--------------------------+
89-
|Min height |Decimal |cm | 0 - 1000 |Height of shortest plant |
90-
| | | | |in plot |
91-
+------------+----------+------+------------------+--------------------------+
92-
|Median |Decimal |cm | 0 - 1000 |Median height of plants in|
93-
|height | | | |plot |
94-
+------------+----------+------+------------------+--------------------------+
95-
|Notes |String | | |Miscellaneous notes |
96-
+------------+----------+------+------------------+--------------------------+
62+
+------------+--------+----+---------------+--------------------+
63+
|Field | Type |Unit| Valid Values |Description |
64+
+============+========+====+===============+====================+
65+
|Date |Date | | |Date of record |
66+
+------------+--------+----+---------------+--------------------+
67+
|Time |Time | |8:00, 12:00, |Time period |
68+
| | | |16:00, or 20:00| |
69+
+------------+--------+----+---------------+--------------------+
70+
|Lab |String | | A - C |Lab ID |
71+
+------------+--------+----+---------------+--------------------+
72+
|Technician |String | | |Technician name |
73+
+------------+--------+----+---------------+--------------------+
74+
|Plot |Int | | 1 - 20 |Plot ID |
75+
+------------+--------+----+---------------+--------------------+
76+
|Seed |String | | 6-character |Seed sample ID |
77+
|Sample | | | string | |
78+
+------------+--------+----+---------------+--------------------+
79+
|Fault |Bool | | True, False |Environmental |
80+
| | | | |sensor fault |
81+
+------------+--------+----+---------------+--------------------+
82+
|Light |Decimal |klx | 0 - 100 |Light at plot |
83+
| | | | |blank on fault |
84+
+------------+--------+----+---------------+--------------------+
85+
|Humidity |Decimal |g/m³| 0.5 - 52.0 |Abs humidity at plot|
86+
| | | | |blank on fault |
87+
+------------+--------+----+---------------+--------------------+
88+
|Temperature |Decimal |°C | 4 - 40 |Temperature at plot |
89+
| | | | |blank on fault |
90+
+------------+--------+----+---------------+--------------------+
91+
|Blossoms |Int | | 0 - 1000 |No. blossoms in plot|
92+
+------------+--------+----+---------------+--------------------+
93+
|Fruit |Int | | 0 - 1000 |No. fruits in plot |
94+
+------------+--------+----+---------------+--------------------+
95+
|Plants |Int | | 0 - 20 |No. plants in plot |
96+
+------------+--------+----+---------------+--------------------+
97+
|Max height |Decimal |cm | 0 - 1000 |Height of tallest |
98+
| | | | |plant in plot |
99+
+------------+--------+----+---------------+--------------------+
100+
|Min height |Decimal |cm | 0 - 1000 |Height of shortest |
101+
| | | | |plant in plot |
102+
+------------+--------+----+---------------+--------------------+
103+
|Median |Decimal |cm | 0 - 1000 |Median height of |
104+
|height | | | |plants in plot |
105+
+------------+--------+----+---------------+--------------------+
106+
|Notes |String | | |Miscellaneous notes |
107+
+------------+--------+----+---------------+--------------------+

0 commit comments

Comments
 (0)