Skip to content

Commit 3d02948

Browse files
committed
Added Chapter 16, updates to ch14 & 15
1 parent cfab3b7 commit 3d02948

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+5840
-111
lines changed

Chapter14/ABQ_Data_Entry/abq_data_entry/application.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ def __init__(self, *args, **kwargs):
3535
return
3636

3737
# show the window
38-
self.deiconify()
38+
# chapter14: use after() here to eliminate the small window
39+
# that flashes up breifly
40+
self.after(250, self.deiconify)
3941

4042
# Create model
4143
# remove for ch12
@@ -176,7 +178,7 @@ def _simple_login(username, password):
176178

177179
# new ch12
178180
def _database_login(self, username, password):
179-
"""Try to login to the database and create self.data_model"""
181+
"""Try to login to the database and create self.model"""
180182
db_host = self.settings['db_host'].get()
181183
db_name = self.settings['db_name'].get()
182184
try:

Chapter14/ABQ_Data_Entry/abq_data_entry/mainmenu.py

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -67,25 +67,18 @@ def _add_weather_download(self, menu):
6767
menu.add_command(
6868
label="Update Weather Data",
6969
command=self._event('<<UpdateWeatherData>>'),
70-
accelerator=self.accelerators.get('weather'),
71-
image=self.icons.get('weather'),
72-
compound=tk.LEFT
7370
)
7471

7572
def _add_rest_upload(self, menu):
7673
menu.add_command(
7774
label="Upload CSV to corporate REST",
7875
command=self._event('<<UploadToCorporateREST>>'),
79-
image=self.icons.get('upload_rest'),
80-
compound=tk.LEFT
8176
)
8277

83-
def _add_ftp_upload(self, menu):
78+
def _add_sftp_upload(self, menu):
8479
menu.add_command(
85-
label="Upload CSV to corporate FTP",
86-
command=self._event('<<UploadToCorporateFTP>>'),
87-
image=self.icons.get('upload_ftp'),
88-
compound=tk.LEFT
80+
label="Upload CSV to corporate SFTP",
81+
command=self._event('<<UploadToCorporateSFTP>>'),
8982
)
9083

9184
def _add_autofill_date(self, menu):
@@ -157,7 +150,7 @@ def _build_menu(self):
157150
self._menus['Tools'] = tk.Menu(self, tearoff=False, **self.styles)
158151
self._add_weather_download(self._menus['Tools'])
159152
self._add_rest_upload(self._menus['Tools'])
160-
self._add_ftp_upload(self._menus['Tools'])
153+
self._add_sftp_upload(self._menus['Tools'])
161154

162155
# The options menu
163156
self._menus['Options'] = tk.Menu(self, tearoff=False, **self.styles)
@@ -265,7 +258,7 @@ def _build_menu(self):
265258
self._add_themes_menu(self._menus['Tools'])
266259
self._add_weather_download(self._menus['Tools'])
267260
self._add_rest_upload(self._menus['Tools'])
268-
self._add_ftp_upload(self._menus['Tools'])
261+
self._add_sftp_upload(self._menus['Tools'])
269262

270263
# The help menu
271264
self._menus['Help'] = tk.Menu(self, tearoff=False)
@@ -310,7 +303,7 @@ def _build_menu(self):
310303
self._menus['Tools'] = tk.Menu(self, tearoff=False, **self.styles)
311304
self._add_weather_download(self._menus['Tools'])
312305
self._add_rest_upload(self._menus['Tools'])
313-
self._add_ftp_upload(self._menus['Tools'])
306+
self._add_sftp_upload(self._menus['Tools'])
314307

315308
# The View menu
316309
self._menus['View'] = tk.Menu(self, tearoff=False, **self.styles)
@@ -379,7 +372,7 @@ def _build_menu(self):
379372
self._menus['Tools'] = tk.Menu(self, tearoff=False)
380373
self._add_weather_download(self._menus['Tools'])
381374
self._add_rest_upload(self._menus['Tools'])
382-
self._add_ftp_upload(self._menus['Tools'])
375+
self._add_sftp_upload(self._menus['Tools'])
383376

384377
# View menu
385378
self._menus['View'] = tk.Menu(self, tearoff=False)

Chapter14/ABQ_Data_Entry/abq_data_entry/models.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -88,21 +88,16 @@ def __init__(self, host, database, user, password):
8888
self.connection = pg.connect(host=host, database=database,
8989
user=user, password=password, cursor_factory=DictCursor)
9090

91-
techs = self.query("SELECT * FROM lab_techs ORDER BY name")
91+
techs = self.query("SELECT name FROM lab_techs ORDER BY name")
9292
labs = self.query("SELECT id FROM labs ORDER BY id")
9393
plots = self.query("SELECT DISTINCT plot FROM plots ORDER BY plot")
9494
self.fields['Technician']['values'] = [x['name'] for x in techs]
9595
self.fields['Lab']['values'] = [x['id'] for x in labs]
9696
self.fields['Plot']['values'] = [str(x['plot']) for x in plots]
9797

9898
def query(self, query, parameters=None):
99-
cursor = self.connection.cursor()
100-
try:
99+
with self.connection.cursor() as cursor:
101100
cursor.execute(query, parameters)
102-
except (pg.Error) as e:
103-
self.connection.rollback()
104-
raise e
105-
else:
106101
self.connection.commit()
107102
# cursor.description is None when
108103
# no rows are returned
@@ -134,7 +129,7 @@ def get_record(self, rowkey):
134129
query,
135130
{"date": date, "time": time, "lab": lab, "plot": plot}
136131
)
137-
return result[0] if result else {}
132+
return result[0] if result else dict()
138133

139134
def save_record(self, record, rowkey):
140135
"""Save a record to the database
@@ -175,7 +170,7 @@ def get_lab_check(self, date, time, lab):
175170
'lab_id = %(lab)s AND date = %(date)s AND time = %(time)s')
176171
results = self.query(
177172
query, {'date': date, 'time': time, 'lab': lab})
178-
return results[0] if results else {}
173+
return results[0] if results else dict()
179174

180175
def get_current_seed_sample(self, lab, plot):
181176
"""Get the seed sample currently planted in the given lab and plot"""

Chapter15/ABQ_Data_Entry/abq_data_entry/application.py

Lines changed: 173 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from tkinter import filedialog
77
from tkinter import font
88
import platform
9+
from queue import Queue
910

1011
from . import views as v
1112
from . import models as m
@@ -65,7 +66,7 @@ def __init__(self, *args, **kwargs):
6566
'<<NewRecord>>': self._new_record,
6667
'<<UpdateWeatherData>>': self._update_weather_data,
6768
'<<UploadToCorporateREST>>': self._upload_to_corporate_rest,
68-
'<<UploadToCorporateFTP>>': self._upload_to_corporate_ftp,
69+
'<<UploadToCorporateSFTP>>': self._upload_to_corporate_sftp,
6970
'<<ShowGrowthChart>>': self.show_growth_chart,
7071
'<<ShowYieldChart>>': self.show_yield_chart
7172
}
@@ -99,9 +100,8 @@ def __init__(self, *args, **kwargs):
99100

100101
# The data record list
101102
self.recordlist_icon = tk.PhotoImage(file=images.LIST_ICON)
102-
self.recordlist = v.RecordList(
103-
self, self.inserted_rows, self.updated_rows
104-
)
103+
self.recordlist = v.RecordList(self)
104+
105105
self.notebook.insert(
106106
0, self.recordlist, text='Records',
107107
image=self.recordlist_icon, compound=tk.LEFT
@@ -288,12 +288,13 @@ def _set_font(self, *_):
288288
tk_font.config(size=font_size, family=font_family)
289289

290290
# new chapter 13
291-
def _update_weather_data(self):
291+
def _update_weather_data(self, *_):
292292
"""Initiate retrieval and storage of weather data"""
293+
weather_data_model = m.WeatherDataModel(
294+
self.settings['weather_station'].get()
295+
)
293296
try:
294-
weather_data = n.get_local_weather(
295-
self.settings['weather_station'].get()
296-
)
297+
weather_data = weather_data_model.get_weather_data()
297298
except Exception as e:
298299
messagebox.showerror(
299300
title='Error',
@@ -306,58 +307,186 @@ def _update_weather_data(self):
306307
time = weather_data['observation_time_rfc822']
307308
self.status.set(f"Weather data recorded for {time}")
308309

309-
def _upload_to_corporate_ftp(self, *_):
310+
def _create_csv_extract(self):
311+
csvmodel = m.CSVModel()
312+
records = self.model.get_all_records()
313+
if not records:
314+
raise Exception('No records were found to build a CSV file.')
315+
for record in records:
316+
csvmodel.save_record(record)
317+
return csvmodel.file
310318

311-
csvfile = self._create_csv_extract()
312-
d = v.LoginDialog(self, 'Login to ABQ Corporate FTP')
313-
if d.result is not None:
314-
username, password = d.result
315-
try:
316-
n.upload_to_corporate_ftp(
317-
csvfile,
318-
self.settings['abq_ftp_host'].get(),
319-
self.settings['abq_ftp_port'].get(),
320-
username, password
321-
)
322-
except n.ftp.all_errors as e:
323-
messagebox.showerror('Error connecting to ftp', str(e))
324-
else:
325-
messagebox.showinfo(
326-
'Success', f'{csvfile} successfully uploaded to FTP'
319+
320+
def _upload_to_corporate_sftp(self, *_):
321+
322+
# create csv file
323+
try:
324+
csvfile = self._create_csv_extract()
325+
except Exception as e:
326+
messagebox.showwarning(
327+
title='Error', message=str(e)
328+
)
329+
return
330+
331+
# authenticate
332+
d = v.LoginDialog(self, 'Login to ABQ Corporate SFTP')
333+
if d.result is None:
334+
return
335+
username, password = d.result
336+
337+
# create model
338+
host = self.settings['abq_sftp_host'].get()
339+
port = self.settings['abq_sftp_port'].get()
340+
sftp_model = m.SFTPModel(host, port)
341+
try:
342+
sftp_model.authenticate(username, password)
343+
except Exception as e:
344+
messagebox.showerror('Error Authenticating', str(e))
345+
return
346+
347+
# check destination file
348+
destination_dir = self.settings['abq_sftp_path'].get()
349+
destination_path = f'{destination_dir}/{csvfile.name}'
350+
351+
try:
352+
exists = sftp_model.check_file(destination_path)
353+
except Exception as e:
354+
messagebox.showerror(
355+
f'Error checking file {destination_path}',
356+
str(e)
357+
)
358+
return
359+
if exists:
360+
# ask if we should overwrite
361+
overwrite = messagebox.askyesno(
362+
'File exists',
363+
f'The file {destination_path} already exists on the server, '
364+
'do you want to overwrite it?'
365+
)
366+
if not overwrite:
367+
# ask if we should download it
368+
download = messagebox.askyesno(
369+
'Download file',
370+
'Do you want to download the file to inspect it?'
327371
)
372+
if download:
373+
# get a destination filename and save
374+
filename = filedialog.asksaveasfilename()
375+
if not filename:
376+
return
377+
try:
378+
sftp_model.get_file(destination_path, filename)
379+
except Exception as e:
380+
messagebox.showerror('Error downloading', str(e))
381+
return
382+
messagebox.showinfo(
383+
'Download Complete', 'Download Complete.'
384+
)
385+
return
386+
# if we haven't returned, the user wants to upload
387+
try:
388+
sftp_model.upload_file(csvfile, destination_path)
389+
except Exception as e:
390+
messagebox.showerror('Error uploading', str(e))
391+
else:
392+
messagebox.showinfo(
393+
'Success',
394+
f'{csvfile} successfully uploaded to SFTP server.'
395+
)
328396

329-
def _upload_to_corporate_rest(self):
330-
csvfile = self._create_csv_extract()
331-
if csvfile is None:
397+
def _upload_to_corporate_rest(self, *_):
398+
399+
# create csv file
400+
try:
401+
csvfile = self._create_csv_extract()
402+
except Exception as e:
332403
messagebox.showwarning(
333-
title='No records',
334-
message='There are no records to upload'
404+
title='Error', message=str(e)
335405
)
336406
return
407+
408+
# Authenticate to the rest server
337409
d = v.LoginDialog(
338410
self, 'Login to ABQ Corporate REST API'
339411
)
340412
if d.result is not None:
341413
username, password = d.result
342414
else:
343415
return
416+
417+
# create REST model
418+
rest_model = m.CorporateRestModel(
419+
self.settings['abq_rest_url'].get()
420+
)
344421
try:
345-
n.upload_to_corporate_rest(
346-
csvfile,
347-
self.settings['abq_upload_url'].get(),
348-
self.settings['abq_auth_url'].get(),
349-
username,
350-
password
351-
)
352-
except n.requests.ConnectionError as e:
353-
messagebox.showerror('Error connecting', str(e))
422+
rest_model.authenticate(username, password)
354423
except Exception as e:
355-
messagebox.showerror('General Exception', str(e))
356-
else:
357-
messagebox.showinfo(
358-
'Success',
359-
f'{csvfile} successfully uploaded to REST API.'
424+
messagebox.showerror('Error authenticating', str(e))
425+
return
426+
427+
# Check if the file exists
428+
try:
429+
exists = rest_model.check_file(csvfile.name)
430+
except Exception as e:
431+
messagebox.showerror('Error checking for file', str(e))
432+
return
433+
434+
if exists:
435+
# ask if we should overwrite
436+
overwrite = messagebox.askyesno(
437+
'File exists',
438+
f'The file {csvfile.name} already exists on the server, '
439+
'do you want to overwrite it?'
360440
)
441+
if not overwrite:
442+
# ask if we should download it
443+
download = messagebox.askyesno(
444+
'Download file',
445+
'Do you want to download the file to inspect it?'
446+
)
447+
if download:
448+
# get a destination filename and save
449+
filename = filedialog.asksaveasfilename()
450+
if not filename:
451+
return
452+
try:
453+
data = rest_model.get_file(csvfile.name)
454+
except Exception as e:
455+
messagebox.showerror('Error downloading', str(e))
456+
return
457+
with open(filename, 'w', encoding='utf-8') as fh:
458+
fh.write(data)
459+
messagebox.showinfo(
460+
'Download Complete', 'Download Complete.'
461+
)
462+
return
463+
# if we haven't returned, the user wants to upload
464+
rest_model.upload_file(csvfile)
465+
self._check_queue(rest_model.queue)
466+
467+
468+
def _check_queue(self, queue):
469+
while not queue.empty():
470+
item = queue.get()
471+
if item.status == 'done':
472+
messagebox.showinfo(
473+
item.status,
474+
message=item.subject,
475+
detail=item.body
476+
)
477+
self.status.set(item.subject)
478+
return
479+
elif item.status == 'error':
480+
messagebox.showerror(
481+
item.status,
482+
message=item.subject,
483+
detail=item.body
484+
)
485+
self.status.set(item.subject)
486+
return
487+
else:
488+
self.status.set(f'{item.subject}: {item.body}')
489+
self.after(100, self._check_queue, queue)
361490

362491
#New for ch15
363492
def show_growth_chart(self, *_):

0 commit comments

Comments
 (0)