labdata 0.1.16__tar.gz → 0.1.17__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {labdata-0.1.16 → labdata-0.1.17}/PKG-INFO +1 -1
- {labdata-0.1.16 → labdata-0.1.17}/labdata/__init__.py +1 -1
- {labdata-0.1.16 → labdata-0.1.17}/labdata/dashboard/compute.py +49 -20
- {labdata-0.1.16 → labdata-0.1.17}/labdata/dashboard/index.py +81 -36
- {labdata-0.1.16 → labdata-0.1.17}/labdata/dashboard/notes.py +21 -11
- labdata-0.1.17/labdata/dashboard/procedures.py +818 -0
- {labdata-0.1.16 → labdata-0.1.17}/labdata/dashboard/segmentation.py +46 -28
- {labdata-0.1.16 → labdata-0.1.17}/labdata/dashboard/sorting.py +123 -60
- {labdata-0.1.16 → labdata-0.1.17}/labdata/dashboard/video.py +120 -64
- {labdata-0.1.16 → labdata-0.1.17}/labdata/schema/ephys.py +54 -20
- {labdata-0.1.16 → labdata-0.1.17}/labdata/schema/procedures.py +5 -3
- {labdata-0.1.16 → labdata-0.1.17}/labdata/schema/video.py +46 -13
- {labdata-0.1.16 → labdata-0.1.17}/labdata.egg-info/PKG-INFO +1 -1
- labdata-0.1.16/labdata/dashboard/procedures.py +0 -318
- {labdata-0.1.16 → labdata-0.1.17}/LICENSE +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/README.md +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/labdata/cli.py +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/labdata/compute/__init__.py +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/labdata/compute/caiman.py +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/labdata/compute/ec2.py +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/labdata/compute/ephys.py +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/labdata/compute/pose.py +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/labdata/compute/schedulers.py +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/labdata/compute/singularity.py +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/labdata/compute/suite2p.py +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/labdata/compute/utils.py +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/labdata/copy.py +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/labdata/dashboard/__init__.py +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/labdata/dashboard/admin.py +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/labdata/dashboard/ai_utils.py +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/labdata/dashboard/console.py +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/labdata/dashboard/decisiontask.py +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/labdata/dashboard/docstrings.py +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/labdata/dashboard/schema_graph.py +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/labdata/export.py +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/labdata/rules/__init__.py +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/labdata/rules/ephys.py +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/labdata/rules/imaging.py +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/labdata/rules/utils.py +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/labdata/s3.py +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/labdata/schema/__init__.py +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/labdata/schema/general.py +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/labdata/schema/histology.py +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/labdata/schema/onephoton.py +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/labdata/schema/tasks.py +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/labdata/schema/twophoton.py +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/labdata/schema/utils.py +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/labdata/stacks.py +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/labdata/utils.py +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/labdata/widgets.py +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/labdata.egg-info/SOURCES.txt +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/labdata.egg-info/dependency_links.txt +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/labdata.egg-info/entry_points.txt +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/labdata.egg-info/requires.txt +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/labdata.egg-info/top_level.txt +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/pyproject.toml +0 -0
- {labdata-0.1.16 → labdata-0.1.17}/setup.cfg +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import pandas as pd
|
|
2
2
|
import numpy as np
|
|
3
3
|
import streamlit as st
|
|
4
|
+
from writes import write, show_flash
|
|
4
5
|
|
|
5
6
|
STATES = ['COMPLETED', 'WORKING', 'WAITING', 'FAILED', 'CANCELED','ON SERVER']
|
|
6
7
|
STATE_COLORS = ['#01D186', '#48CAE4', '#F3A21C', '#FF5733', '#F3A21C','#F3A21C']
|
|
@@ -50,6 +51,21 @@ def _resubmit_jobs(schema, job_ids, project, target_override=None):
|
|
|
50
51
|
return errors
|
|
51
52
|
|
|
52
53
|
|
|
54
|
+
def _fetch_rows(table):
|
|
55
|
+
'''Fetch all rows of a job table, reconnecting once if the first attempt fails.
|
|
56
|
+
|
|
57
|
+
The dashboard shares one database connection between browser sessions, and
|
|
58
|
+
the client can be left half closed (a stale socket, a packet sequence
|
|
59
|
+
error, or ``'NoneType' object has no attribute 'read'`` from pymysql).
|
|
60
|
+
Such failures clear on a fresh connection, so retry once before giving up.
|
|
61
|
+
'''
|
|
62
|
+
try:
|
|
63
|
+
return table.fetch(as_dict=True)
|
|
64
|
+
except Exception:
|
|
65
|
+
table.connection.connect()
|
|
66
|
+
return table.fetch(as_dict=True)
|
|
67
|
+
|
|
68
|
+
|
|
53
69
|
def _jobs_panel(table, status_field, target_field, cols, delete_key,
|
|
54
70
|
resubmit_fn=None, allow_delete_completed=False, on_server_fn=None):
|
|
55
71
|
|
|
@@ -59,20 +75,13 @@ def _jobs_panel(table, status_field, target_field, cols, delete_key,
|
|
|
59
75
|
if st.session_state.get(f'{delete_key}_resubmit_errors'):
|
|
60
76
|
for e in st.session_state.pop(f'{delete_key}_resubmit_errors'):
|
|
61
77
|
st.error(e)
|
|
78
|
+
show_flash(f'{delete_key}_msg')
|
|
62
79
|
|
|
63
80
|
try:
|
|
64
|
-
rows = table
|
|
81
|
+
rows = _fetch_rows(table)
|
|
65
82
|
except Exception as e:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
table.connection.connect()
|
|
69
|
-
rows = table.fetch(as_dict=True)
|
|
70
|
-
except Exception as e2:
|
|
71
|
-
st.error(f'Database connection error: {e2}')
|
|
72
|
-
return
|
|
73
|
-
else:
|
|
74
|
-
st.error(f'Error fetching jobs: {e}')
|
|
75
|
-
return
|
|
83
|
+
st.error(f'Could not fetch the jobs: {e}')
|
|
84
|
+
return
|
|
76
85
|
if not rows:
|
|
77
86
|
st.info('There are no jobs in the **ComputeTask** table.')
|
|
78
87
|
return
|
|
@@ -95,9 +104,14 @@ def _jobs_panel(table, status_field, target_field, cols, delete_key,
|
|
|
95
104
|
st.warning(f'Delete all {n} COMPLETED job(s)? This cannot be undone.')
|
|
96
105
|
col_yes, col_no, _ = st.columns([1, 1, 4])
|
|
97
106
|
if col_yes.button('Yes, delete', type='primary', key=f'{delete_key}_del_completed_yes'):
|
|
98
|
-
(table
|
|
107
|
+
ok, _ = write(table, 'delete the completed jobs',
|
|
108
|
+
lambda: (table & f'{status_field} = "COMPLETED"').delete(
|
|
109
|
+
safemode=False, transaction=False),
|
|
110
|
+
success=f'Deleted {n} completed job(s).',
|
|
111
|
+
flash_key=f'{delete_key}_msg')
|
|
99
112
|
st.session_state.pop(f'{delete_key}_del_completed_pending', None)
|
|
100
|
-
|
|
113
|
+
if ok:
|
|
114
|
+
st.rerun()
|
|
101
115
|
if col_no.button('Cancel', key=f'{delete_key}_del_completed_no'):
|
|
102
116
|
st.session_state.pop(f'{delete_key}_del_completed_pending', None)
|
|
103
117
|
st.rerun()
|
|
@@ -175,9 +189,14 @@ def _jobs_panel(table, status_field, target_field, cols, delete_key,
|
|
|
175
189
|
st.warning(f'Delete all {len(jobs_to_delete)} filtered job(s)? This cannot be undone.')
|
|
176
190
|
col_yes, col_no, _ = st.columns([1, 1, 4])
|
|
177
191
|
if col_yes.button('Yes, delete', type='primary', key=f'{delete_key}_del_filtered_yes'):
|
|
178
|
-
|
|
192
|
+
ok, _ = write(table, 'delete the filtered jobs',
|
|
193
|
+
lambda: (table & [dict(job_id=j) for j in jobs_to_delete]).delete(
|
|
194
|
+
safemode=False, transaction=False),
|
|
195
|
+
success=f'Deleted {len(jobs_to_delete)} job(s).',
|
|
196
|
+
flash_key=f'{delete_key}_msg')
|
|
179
197
|
st.session_state.pop(f'{delete_key}_del_filtered_pending', None)
|
|
180
|
-
|
|
198
|
+
if ok:
|
|
199
|
+
st.rerun()
|
|
181
200
|
if col_no.button('Cancel', key=f'{delete_key}_del_filtered_no'):
|
|
182
201
|
st.session_state.pop(f'{delete_key}_del_filtered_pending', None)
|
|
183
202
|
st.rerun()
|
|
@@ -221,10 +240,14 @@ def _jobs_panel(table, status_field, target_field, cols, delete_key,
|
|
|
221
240
|
st.warning(f'Are you sure you want to delete {len(jobs_to_delete)} job(s)? This cannot be undone and scheduled jobs might fail.')
|
|
222
241
|
col_yes, col_no, _ = st.columns([1, 1, 4])
|
|
223
242
|
if col_yes.button('Yes, delete', type='primary', key=f'{delete_key}_yes'):
|
|
224
|
-
|
|
243
|
+
ok, _ = write(table, 'delete the selected jobs',
|
|
244
|
+
lambda: (table & [dict(job_id=j) for j in jobs_to_delete]).delete(
|
|
245
|
+
safemode=False, transaction=False),
|
|
246
|
+
success=f'Deleted {len(jobs_to_delete)} job(s).',
|
|
247
|
+
flash_key=f'{delete_key}_msg')
|
|
225
248
|
st.session_state.pop(f'{delete_key}_pending', None)
|
|
226
|
-
|
|
227
|
-
|
|
249
|
+
if ok:
|
|
250
|
+
st.rerun()
|
|
228
251
|
if col_no.button('Cancel', key=f'{delete_key}_no'):
|
|
229
252
|
st.session_state.pop(f'{delete_key}_pending', None)
|
|
230
253
|
st.rerun()
|
|
@@ -245,6 +268,9 @@ def _jobs_panel(table, status_field, target_field, cols, delete_key,
|
|
|
245
268
|
st.session_state.pop(f'{delete_key}_resubmit_pending', None)
|
|
246
269
|
if errors:
|
|
247
270
|
st.session_state[f'{delete_key}_resubmit_errors'] = errors
|
|
271
|
+
else:
|
|
272
|
+
st.session_state[f'{delete_key}_msg'] = (
|
|
273
|
+
f'Re-submitted {len(jobs_to_resubmit)} job(s).')
|
|
248
274
|
st.rerun()
|
|
249
275
|
if col_no.button('Cancel', key=f'{delete_key}_resubmit_no'):
|
|
250
276
|
st.session_state.pop(f'{delete_key}_resubmit_pending', None)
|
|
@@ -255,9 +281,12 @@ def _jobs_panel(table, status_field, target_field, cols, delete_key,
|
|
|
255
281
|
st.warning(f'Reset {len(jobs_to_reset)} job(s) to **ON SERVER** / waiting?')
|
|
256
282
|
col_yes, col_no, _ = st.columns([1, 1, 4])
|
|
257
283
|
if col_yes.button('Yes, reset', type='primary', key=f'{delete_key}_on_server_yes'):
|
|
258
|
-
on_server_fn(jobs_to_reset)
|
|
284
|
+
ok, _ = write(table, 'reset the jobs', lambda: on_server_fn(jobs_to_reset),
|
|
285
|
+
success=f'Reset {len(jobs_to_reset)} job(s) to ON SERVER.',
|
|
286
|
+
flash_key=f'{delete_key}_msg')
|
|
259
287
|
st.session_state.pop(f'{delete_key}_on_server_pending', None)
|
|
260
|
-
|
|
288
|
+
if ok:
|
|
289
|
+
st.rerun()
|
|
261
290
|
if col_no.button('Cancel', key=f'{delete_key}_on_server_no'):
|
|
262
291
|
st.session_state.pop(f'{delete_key}_on_server_pending', None)
|
|
263
292
|
st.rerun()
|
|
@@ -60,6 +60,8 @@ def intro_tab(user_name=None, schema=None):
|
|
|
60
60
|
'''Fetch all strain names.'''
|
|
61
61
|
return list(schema.Strain().fetch('strain_name'))
|
|
62
62
|
|
|
63
|
+
from writes import write, show_flash
|
|
64
|
+
show_flash('subjects_msg')
|
|
63
65
|
list_tab, add_tab, edit_tab, weighing_tab = st.tabs(['Subjects/Sessions', 'New', 'Update', 'Weighing'])
|
|
64
66
|
|
|
65
67
|
with list_tab:
|
|
@@ -164,10 +166,18 @@ def intro_tab(user_name=None, schema=None):
|
|
|
164
166
|
insert_dict['subject_sex'] = 'U' if sex == 'Unknown' else sex
|
|
165
167
|
insert_dict['strain_name'] = st.selectbox('Strain', available_strains)
|
|
166
168
|
if st.form_submit_button('Add Subject', type='primary'):
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
169
|
+
if not (insert_dict['subject_name'] or '').strip():
|
|
170
|
+
st.error('Give the subject an id.')
|
|
171
|
+
elif not insert_dict['user_name']:
|
|
172
|
+
st.error('Pick a user name.')
|
|
173
|
+
else:
|
|
174
|
+
ok, _ = write(schema.Subject, f'add subject {insert_dict["subject_name"]}',
|
|
175
|
+
lambda: schema.Subject().insert1(insert_dict),
|
|
176
|
+
success=f'Added subject {insert_dict["subject_name"]}.',
|
|
177
|
+
flash_key='subjects_msg')
|
|
178
|
+
if ok:
|
|
179
|
+
get_subjects.clear()
|
|
180
|
+
st.rerun()
|
|
171
181
|
|
|
172
182
|
with edit_tab:
|
|
173
183
|
users = get_users()
|
|
@@ -186,10 +196,13 @@ def intro_tab(user_name=None, schema=None):
|
|
|
186
196
|
edit_dict['strain_name'] = st.selectbox('Strain', available_strains,
|
|
187
197
|
index=available_strains.index(edit_dict['strain_name']))
|
|
188
198
|
if st.form_submit_button('Save changes', type='primary'):
|
|
189
|
-
schema.Subject
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
199
|
+
ok, _ = write(schema.Subject, f'update {selected_subject}',
|
|
200
|
+
lambda: schema.Subject().update1(edit_dict),
|
|
201
|
+
success=f'Updated {selected_subject}.',
|
|
202
|
+
flash_key='subjects_msg')
|
|
203
|
+
if ok:
|
|
204
|
+
get_subjects.clear()
|
|
205
|
+
st.rerun()
|
|
193
206
|
|
|
194
207
|
with weighing_tab:
|
|
195
208
|
@st.cache_data
|
|
@@ -222,13 +235,14 @@ def intro_tab(user_name=None, schema=None):
|
|
|
222
235
|
w_weight = st.number_input('Weight (g)', min_value=0.0, step=0.1, format='%.1f')
|
|
223
236
|
if st.form_submit_button('Add', type='primary'):
|
|
224
237
|
w_dt = datetime.datetime.combine(w_date, w_time)
|
|
225
|
-
|
|
226
|
-
|
|
238
|
+
ok, _ = write(schema.Weighing, f'add the weight of {w_subject}',
|
|
239
|
+
lambda: schema.Weighing().insert1(dict(
|
|
240
|
+
subject_name=w_subject, weighing_datetime=w_dt, weight=w_weight)),
|
|
241
|
+
success=f'Added {w_weight:.1f} g for {w_subject} at {w_dt}.',
|
|
242
|
+
flash_key='subjects_msg')
|
|
243
|
+
if ok:
|
|
227
244
|
get_weighings.clear()
|
|
228
|
-
st.success(f'Added {w_weight:.1f} g for {w_subject} at {w_dt}.')
|
|
229
245
|
st.rerun()
|
|
230
|
-
except Exception as e:
|
|
231
|
-
st.error(str(e))
|
|
232
246
|
|
|
233
247
|
with plot_col:
|
|
234
248
|
weights_df = get_weighings(w_subject)
|
|
@@ -267,16 +281,17 @@ def intro_tab(user_name=None, schema=None):
|
|
|
267
281
|
new_weight = st.number_input('New weight (g)', value=float(sel_row['weight']),
|
|
268
282
|
min_value=0.0, step=0.1, format='%.1f')
|
|
269
283
|
if st.form_submit_button('Update', type='primary'):
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
284
|
+
ok, _ = write(schema.Weighing, f'update the weight of {w_subject}',
|
|
285
|
+
lambda: schema.Weighing().update1(dict(
|
|
286
|
+
subject_name=w_subject,
|
|
287
|
+
weighing_datetime=sel_dt_exact.to_pydatetime(),
|
|
288
|
+
weight=new_weight)),
|
|
289
|
+
success=(f'Updated the weight of {w_subject} at '
|
|
290
|
+
f'{sel_dt_exact:%Y-%m-%d %H:%M} to {new_weight:.1f} g.'),
|
|
291
|
+
flash_key='subjects_msg')
|
|
292
|
+
if ok:
|
|
275
293
|
get_weighings.clear()
|
|
276
|
-
st.success(f'Updated to {new_weight:.1f} g.')
|
|
277
294
|
st.rerun()
|
|
278
|
-
except Exception as e:
|
|
279
|
-
st.error(str(e))
|
|
280
295
|
except Exception:
|
|
281
296
|
pass
|
|
282
297
|
|
|
@@ -329,6 +344,36 @@ if project is None:
|
|
|
329
344
|
project = k.replace('project=','')
|
|
330
345
|
|
|
331
346
|
import time as _time
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def _is_db_client_error(exc):
|
|
350
|
+
'''True for a pymysql error or any exception raised from inside pymysql.
|
|
351
|
+
|
|
352
|
+
The dashboard shares one client connection between browser sessions; a half
|
|
353
|
+
closed client raises AttributeError from its socket reader rather than a
|
|
354
|
+
database error, and a fresh connection clears it.'''
|
|
355
|
+
import traceback
|
|
356
|
+
import pymysql
|
|
357
|
+
if isinstance(exc, pymysql.err.Error) or isinstance(getattr(exc, '__cause__', None), pymysql.err.Error):
|
|
358
|
+
return True
|
|
359
|
+
return any('pymysql' in f.filename for f in traceback.extract_tb(exc.__traceback__))
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def _reconnect():
|
|
363
|
+
'''Drop the shared client connection and open a new one.'''
|
|
364
|
+
import datajoint as dj
|
|
365
|
+
try:
|
|
366
|
+
conn = dj.conn()
|
|
367
|
+
if conn._conn is not None:
|
|
368
|
+
try:
|
|
369
|
+
conn._conn.close()
|
|
370
|
+
except Exception:
|
|
371
|
+
pass
|
|
372
|
+
conn.connect()
|
|
373
|
+
except Exception:
|
|
374
|
+
pass
|
|
375
|
+
|
|
376
|
+
|
|
332
377
|
_schema_err = None
|
|
333
378
|
for _attempt in range(5):
|
|
334
379
|
try:
|
|
@@ -342,6 +387,13 @@ for _attempt in range(5):
|
|
|
342
387
|
_schema_err = _e
|
|
343
388
|
load_schema.clear()
|
|
344
389
|
_time.sleep(0.3 * (_attempt + 1))
|
|
390
|
+
except Exception as _e:
|
|
391
|
+
if not _is_db_client_error(_e):
|
|
392
|
+
raise
|
|
393
|
+
_schema_err = _e
|
|
394
|
+
load_schema.clear()
|
|
395
|
+
_reconnect()
|
|
396
|
+
_time.sleep(0.3 * (_attempt + 1))
|
|
345
397
|
if _schema_err is not None:
|
|
346
398
|
st.error(f'Database connection error after 5 attempts: {_schema_err}. Please refresh the page.')
|
|
347
399
|
st.stop()
|
|
@@ -433,20 +485,13 @@ tab_name = st.session_state['active_tab']
|
|
|
433
485
|
try:
|
|
434
486
|
page_names_to_funcs[tab_name]()
|
|
435
487
|
except Exception as _e:
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
try:
|
|
442
|
-
conn = dj.conn()
|
|
443
|
-
if conn._conn is not None:
|
|
444
|
-
try:
|
|
445
|
-
conn._conn.close()
|
|
446
|
-
except Exception:
|
|
447
|
-
pass
|
|
448
|
-
conn.connect()
|
|
449
|
-
except Exception:
|
|
450
|
-
pass
|
|
488
|
+
# a stale client connection: reconnect and draw again, at most twice in a row
|
|
489
|
+
_retries = st.session_state.get('_db_reconnects', 0)
|
|
490
|
+
if _is_db_client_error(_e) and _retries < 2:
|
|
491
|
+
st.session_state['_db_reconnects'] = _retries + 1
|
|
492
|
+
_reconnect()
|
|
451
493
|
st.rerun()
|
|
494
|
+
st.session_state.pop('_db_reconnects', None)
|
|
452
495
|
raise
|
|
496
|
+
else:
|
|
497
|
+
st.session_state.pop('_db_reconnects', None)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import pandas as pd
|
|
2
2
|
import streamlit as st
|
|
3
3
|
from datetime import datetime, date, time as dtime
|
|
4
|
+
from writes import write
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
def notes_tab(schema=None):
|
|
@@ -161,11 +162,17 @@ def notes_tab(schema=None):
|
|
|
161
162
|
note_dt = datetime.combine(note_date, note_time)
|
|
162
163
|
dataset_key = dict(subject_name=subject_name, session_name=session_name,
|
|
163
164
|
dataset_name=dataset_name)
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
165
|
+
|
|
166
|
+
def _add_note():
|
|
167
|
+
# the note and the link from the dataset land together
|
|
168
|
+
schema.Note.insert1(dict(notetaker=notetaker, note_datetime=note_dt, notes=note_text))
|
|
169
|
+
schema.Dataset.update1({**dataset_key, 'notetaker': notetaker, 'note_datetime': note_dt})
|
|
170
|
+
|
|
171
|
+
ok, _ = write(schema.Note, 'add the note', _add_note,
|
|
172
|
+
success=f'Added the note to {subject_name} / {session_name} / {dataset_name}.')
|
|
173
|
+
if ok:
|
|
174
|
+
get_note_counts.clear()
|
|
175
|
+
get_subject_notes.clear()
|
|
169
176
|
|
|
170
177
|
with update_tab:
|
|
171
178
|
selected = st.session_state.get('selected_note', {})
|
|
@@ -188,9 +195,12 @@ def notes_tab(schema=None):
|
|
|
188
195
|
if not new_text.strip():
|
|
189
196
|
st.error('Note text cannot be empty.')
|
|
190
197
|
else:
|
|
191
|
-
schema.Note
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
198
|
+
ok, _ = write(schema.Note, 'update the note',
|
|
199
|
+
lambda: schema.Note.update1(dict(notetaker=notetaker_upd,
|
|
200
|
+
note_datetime=note_dt_upd,
|
|
201
|
+
notes=new_text)),
|
|
202
|
+
success=f'Updated the note by {notetaker_upd} from {note_dt_upd}.')
|
|
203
|
+
if ok:
|
|
204
|
+
get_note_counts.clear()
|
|
205
|
+
get_subject_notes.clear()
|
|
206
|
+
st.session_state.pop('selected_note', None)
|