From 0ff4f5cf9b7a9956a6a18e65aaf528cdd4cc82dc Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Morin Date: Thu, 4 Jun 2015 12:04:22 -0400 Subject: [PATCH 01/89] Create main.py --- scheduler/main.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 scheduler/main.py diff --git a/scheduler/main.py b/scheduler/main.py new file mode 100644 index 0000000..9daeafb --- /dev/null +++ b/scheduler/main.py @@ -0,0 +1 @@ +test From 472eb7419908874ecec3593afcc645773a55c1cd Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Morin Date: Thu, 4 Jun 2015 12:05:03 -0400 Subject: [PATCH 02/89] Update main.py --- scheduler/main.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/scheduler/main.py b/scheduler/main.py index 9daeafb..2ebd80e 100644 --- a/scheduler/main.py +++ b/scheduler/main.py @@ -1 +1,10 @@ -test +#!/usr/bin/env python + +from Tkinter import Tk +import userinterface + + +root = Tk() +app = userinterface.ApplicationGUI(root) +root.protocol("WM_DELETE_WINDOW", app.quitapplication) +app.mainloop() From 692c6d3e5d0afbf9070e3b89e7583f513a959e57 Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Morin Date: Thu, 4 Jun 2015 12:07:11 -0400 Subject: [PATCH 03/89] Create candidate.py --- scheduler/candidate.py | 113 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 scheduler/candidate.py diff --git a/scheduler/candidate.py b/scheduler/candidate.py new file mode 100644 index 0000000..725e09b --- /dev/null +++ b/scheduler/candidate.py @@ -0,0 +1,113 @@ +import datetime +import visit +import lib.datamanagement as datamanagement +import lib.multilanguage as multilanguage +import lib.utilities as utilities +from uuid import uuid1 + +class Candidate(): + def __init__(self, firstname, lastname, phone, uid=None, visitset = None, status = None, pscid=None, **kwargs): #TODO *kwarg + self.uid = utilities.generateUniqueID() + self.firstname = firstname + self.lastname = lastname + self.visitset = visitset + self.phone = phone + self.status = status + self.pscid = pscid + #...many other attributes + if kwargs is not None: + for key, value in kwargs.iteritems(): + setattr(self, key, value) + + + def setupvisitset(self): + #open studysetup.db and 'parse' the dict to a sorted list (dict cannot be sorted)... + visitlist =[] + visitlabel_templist = [] + try: + studysetup = dict(datamanagement.readstudydata()) #TODO replace datafile name + except Exception as e: + print str(e) #TODO add error login + for key, value in studysetup.iteritems(): + visitlist.append(studysetup[key]) + visitlist = sorted(visitlist, key=lambda visit: visit.rank) + #...and create a temporary list of visitlabels that will serve a key within the Candidate.visitset dictionary + for each in visitlist: + visitlabel_templist.append(each.visitlabel) #we now have a list with all visit labels included in the study + #instantiate individual visit based on each instance of StudySetup() + self.visitset = {} #setup a dict to receive a set of Visit() + count = 0 + #set values of : uid, rank, visitlabel, previousvisit, visitwindow, visitmargin, + for key in visitlist: + mainkey = str(visitlabel_templist[count]) + rank =key.rank + visitlabel = key.visitlabel + previousvisit = key.previousvisit + visitwindow = key.visitwindow + visitmargin = key.visitmargin + visitdata = visit.Visit(rank, visitlabel, previousvisit, visitwindow, visitmargin,) + self.visitset[mainkey] = visitdata + count += 1 + self.status = multilanguage.status_active + + + def setvisitdate(self, visitlabel, visitdate, visittime, visitwhere, visitwhom): + #check to see if visitset == None before trying to create a new date instance + if self.visitset is None: + self.setupvisitset() + #get current visit within visitset + current = self.visitset.get(visitlabel) + #check to see if this visit already has a date + if current.when is not None: + print "date already exists" #TODO add confirmation of change log??? + pass + #concatenate visitdate and visittime and parse into a datetime object + visitwhen = visitdate + ' ' + visittime + when = datetime.datetime.strptime(visitwhen, '%Y-%m-%d %H:%M') + #set 'current' values of : when, where, withwhom, status + current.when = when + current.where = visitwhere + current.withwhom = visitwhom + current.status = "active" + return current + + + def setnextvisitwindow(self, candidate, currentvisit): + #get the current visit object as argument. Will search and look for the next visit (visit where previousvisit == currentvisitlabel) + nextvisitsearchset = candidate.visitset + currentvisitlabel = currentvisit.visitlabel + nextvisit = "" + for key in nextvisitsearchset: + visitdata = nextvisitsearchset[key] + if visitdata.previousvisit == currentvisitlabel: + nextvisit = candidate.visitset.get(visitdata.visitlabel) #TODO debug when current is last visit + #gather info about currentvisit (mostly for my own internal computer!) + currentvisitdate = currentvisit.when + currentvisityear = currentvisitdate.year #get the year of the current visit date + nextvisitwindow = nextvisit.visitwindow + nextvisitmargin = nextvisit.visitmargin + #set dates for the next visit + nextvisitearly = int(currentvisitdate.strftime('%j')) + (nextvisitwindow - nextvisitmargin) #this properly handle change of year + nextvisitearlydate = datetime.datetime(currentvisityear, 1, 1) + datetime.timedelta(nextvisitearly - 1) + nextvisitlate = int(currentvisitdate.strftime('%j')) + (nextvisitwindow + nextvisitmargin) + nextvisitlatedate = datetime.datetime(currentvisityear, 1, 1) + datetime.timedelta(nextvisitlate - 1) + nextvisit.whenearliest = nextvisitearlydate + nextvisit.whenlatest = nextvisitlatedate + nextvisit.status = "tentative" + + + def getactivevisit(self, candidate): + candidatefullname = str(candidate.firstname + ' ' + candidate.lastname) + currentvisitset = candidate.visitset + if currentvisitset is None: + return + elif currentvisitset is not None: + for key in currentvisitset: + if currentvisitset[key].status == multilanguage.status_active: + visitlabel = currentvisitset[key].visitlabel + when = currentvisitset[key].when.strftime('%Y-%m-%d %Hh%m') + where = currentvisitset[key].where + who = currentvisitset[key].withwhom + activevisit = [candidatefullname, visitlabel, when, where, who] + return activevisit + From 8a13bd98a284d901bb0a00152f2af0af9fe6c4c9 Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Morin Date: Thu, 4 Jun 2015 12:08:07 -0400 Subject: [PATCH 04/89] Create dialogbox.py --- scheduler/dialogbox.py | 80 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 scheduler/dialogbox.py diff --git a/scheduler/dialogbox.py b/scheduler/dialogbox.py new file mode 100644 index 0000000..86f3535 --- /dev/null +++ b/scheduler/dialogbox.py @@ -0,0 +1,80 @@ +#import packages +from Tkinter import * +import functools +import lib.utilities as utilities +import lib.multilanguage as multilanguage + +class DialogBox(Toplevel): + """ + This class was created mainly because the native dialog box don't work as expected when called from a top-level window. + This class (although it could be improved in many aspects) insure that the parent window cannot get focus while this dialog box is still active. + """ + def __init__(self,parent, title, message, button1, button2): + Toplevel.__init__(self,parent) + self.transient(parent) + self.parent = parent + self.title(title) + + body = Frame(self) + self.initial_focus = self.body(body, message) + body.pack(padx=4, pady=4) + + self.buttonbox(button1, button2) + self.grab_set() + + if not self.initial_focus: + self.initial_focus = self + + self.protocol("WM_DELETE_WINDOW", self.button2) + utilities.centerwindow(self) + self.initial_focus.focus_set() + self.deiconify() + self.wait_window(self) + + def body(self, parent, message): + label = Label(self, text=message) + label.pack(padx=4, pady=4) + pass + + def buttonbox(self, button1, button2): + #add a standard button box + box = Frame(self) + b1 = Button(box, text=button1, width=12, command=self.button1, default=ACTIVE) + b1.pack(side=LEFT, padx=4, pady=4) + b2 = Button(box, text=button2, width=12, command=self.button2, default=ACTIVE) + b2.pack(side=LEFT, padx=4, pady=4) + self.bind("", self.button1) + self.bind("", self.button2) + box.pack() + + def button1(self, event=None): + if not self.validate(): + self.initial_focus.focus_set() #put focus on Button + return + self.buttonvalue = 1 + self.closedialog() + + def button2(self, event=None): + self.buttonvalue = 2 + self.closedialog() + + + def closedialog(self, event=None): + #put focus back to parent window before destroying the window + self.parent.focus_set() + self.destroy() + + def validate(self): + return 1 + +######################################################################################### +class ConfirmYesNo(DialogBox): + def __init__(self, parent, message): + title = multilanguage.dialogtitle_confirm + button1 = multilanguage.dialog_yes + button2 = multilanguage.dialog_no + DialogBox.__init__(self, parent, title, message, button1, button2) + + + + From 110b159398b9c26fb30f8ffb41b8e8ff51104dd8 Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Morin Date: Thu, 4 Jun 2015 12:08:42 -0400 Subject: [PATCH 05/89] Create newwindow.py --- scheduler/newwindow.py | 110 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 scheduler/newwindow.py diff --git a/scheduler/newwindow.py b/scheduler/newwindow.py new file mode 100644 index 0000000..4a74adb --- /dev/null +++ b/scheduler/newwindow.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +#import packages +from Tkinter import * +#import functools +import dialogbox +import lib.utilities as utilities +import lib.multilanguage as multilanguage +import lib.datamanagement as datamanagement + + + + +#ref: http://effbot.org/tkinterbook/tkinter-newDialog-windows.htm +#TODO this class needs a major clean-up +class NewWindow(Toplevel): + def __init__(self, parent, candidate, function, visitlabel): #TODO change for multilanguage variable + Toplevel.__init__(self, parent) + #create a transient window on top of parent window + print "running NewWindow(Toplevel) " + str(candidate) + str(function) + str(visitlabel) #TODO remove when done + self.transient(parent) + self.parent = parent + if function == 'schedule': + self.title(multilanguage.schedulewindow_title) + elif function == 'candidate': + self.title(multilanguage.candidatewindow_title) + body = Frame(self) + self.initial_focus = self.body(body, candidate, function, visitlabel) + body.pack(padx=5, pady=5) + + self.buttonbox() + self.grab_set() + + if not self.initial_focus: + self.initial_focus = self + + self.protocol("WM_DELETE_WINDOW", self.closedialog) + + utilities.centerwindow(self) + self.initial_focus.focus_set() + + self.deiconify() + self.wait_window(self) + + + def body(self, master, candidate, function, visitlabel): + db = dict(datamanagement.readcandidatedata()) + candidate = db.get(candidate) + name = str(candidate.firstname) + " " + str(candidate.lastname) + namelabel = Label(self, text = name) + namelabel.pack(side=TOP, expand=YES, fill=BOTH) + if function == "schedule": + currentvisitset = candidate.visitset + currentvisit = currentvisitset.get(visitlabel) + when = currentvisit.when + where = currentvisit.where + whenlabel = Label(self, text = when) + whenlabel.pack(side=TOP, expand=YES, fill=BOTH) + wherelabel = Label(self, text = where) + wherelabel.pack(side=TOP, expand=YES, fill=BOTH) + elif function == "candidate": + phone = candidate.phone + pscid = candidate.pscid + phonelabel = Label(self, text=phone) + phonelabel.pack(side=TOP, expand=YES, fill=BOTH) + pscidlabel = Label(self, text=pscid) + pscidlabel.pack(side=TOP, expand=YES, fill=BOTH) + + else: + pass + + + + + def buttonbox(self): + # add standard button box + box = Frame(self) + w = Button(box, text="OK", width=10, command=self.okbutton, default=ACTIVE) + w.pack(side=LEFT, padx=5, pady=5) + w = Button(box, text="Cancel", width=10, command=self.cancelbutton) + w.pack(side=LEFT, padx=5, pady=5) + self.bind("", self.okbutton) + self.bind("", self.closedialog) + box.pack() + + def okbutton(self, event=None): + print "saving data and closing" #TODO remove when done + if not self.validate(): + self.initial_focus.focus_set() # put focus back + return + self.withdraw() + self.closedialog() + + def cancelbutton(self, event=None): + print "close without saving" + parent = Frame(self) + newwin = dialogbox.ConfirmYesNo(parent, multilanguage.dialogclose) + if newwin.buttonvalue == 1: + self.closedialog() + else: + return + + def closedialog(self, event=None): + #put focus back to parent window before destroying the window + self.parent.focus_set() + self.destroy() + + def validate(self): + return 1 From 7a5e5cc12257d72ec9d882984d6c4dbd55c1e16e Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Morin Date: Thu, 4 Jun 2015 12:09:40 -0400 Subject: [PATCH 06/89] Create userinterface.py --- scheduler/userinterface.py | 256 +++++++++++++++++++++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 scheduler/userinterface.py diff --git a/scheduler/userinterface.py b/scheduler/userinterface.py new file mode 100644 index 0000000..825d295 --- /dev/null +++ b/scheduler/userinterface.py @@ -0,0 +1,256 @@ +#!/usr/bin/env python + +#Python Tkinter imports +from Tkinter import * +from ttk import * + +#local imports +import lib.multilanguage as multilanguage +import lib.datamanagement as datamanagement +import newwindow + + +class ApplicationGUI(Frame): + def __init__(self, parent): + Frame.__init__(self, parent) + self.parent = parent + self.parent.title(multilanguage.apptitle) #TODO modify + self.pack(side=TOP, expand=YES, fill=BOTH, padx=10, pady=10) + + self.create_menubar() + self.projectinfopane =Labelframe(self, text=multilanguage.project_infopane, width=250, height=350, borderwidth=10) #TODO add dynamic resize + self.projectinfopane.pack(side=LEFT, expand=NO, fill=BOTH) + #datapane is composed of one PanedWindow containing two Labelframe + self.datapane = Panedwindow(self, width = 1000, height = 500, orient=HORIZONTAL) #TODO add dynamic resize + self.datapane.pack(side=RIGHT, expand=YES, fill=BOTH) + #self.projectinfopane =Labelframe(self.datapane, text='Project Information', width=100, height=350, borderwidth=10) #TODO add dynamic resize #TODO clean + self.candidatepane = Labelframe(self.datapane, text=multilanguage.candidate_pane, width=100, height=350, borderwidth=10) #TODO add dynamic resize + self.visitpane = Labelframe(self.datapane, text=multilanguage.calendar_pane, width=100, height=350, borderwidth=10) #TODO add dynamic resize + #self.datapane.add(self.projectinfopane) #TODO clean + + self.datapane.add(self.candidatepane) + self.datapane.add(self.visitpane) + + #get data from *.db files + data = dict(datamanagement.readcandidatedata())#TODO place data management elsewhere + #create data tables (treeview) + visitcolheaders = ('candidate', 'visitlabel', 'when', 'where', 'status') + self.visittable = DataTable(self.visitpane, data, visitcolheaders) + self.visittable.pack(side=BOTTOM, expand=YES, fill=BOTH) + colheaders = ('firstname', 'lastname', 'phone', 'status') + datatable = DataTable(self.candidatepane, data, colheaders) + datatable.pack(side=BOTTOM, expand=YES, fill=BOTH) + + + + def create_menubar(self): #TODO try to replace by class + #create toplevel menubar + self.menubar = Menu(self) + #create an APPLICATION pulldown menu + self.applicationmenu = Menu(self.menubar, tearoff = 0) + self.applicationmenu.add_command(label = multilanguage.settingapplication, command=self.appsettings) + self.applicationmenu.add_separator() + self.applicationmenu.add_command(label = multilanguage.quitapplication, command=self.quitapplication) + #create a CANDIDATE pulldown menu + self.candidatemenu = Menu(self.menubar, tearoff = 0) + self.candidatemenu.add_command(label = multilanguage.addcandidate, command = self.addcandidate) + self.candidatemenu.add_command(label = multilanguage.findcandidate, command = self.findcandidate) + self.candidatemenu.add_command(label = multilanguage.updatecandidate, command = self.updatecandidate) + self.candidatemenu.add_separator() + self.candidatemenu.add_command(label = multilanguage.getcandidateid, command = self.getcandidateid) + self.candidatemenu.add_separator() + self.candidatemenu.add_command(label= multilanguage.excludecandidate, command = self.excludecandidate) + #create a CALENDAR pulldown menu + self.calendarmenu = Menu(self.menubar, tearoff = 0) + self.calendarmenu.add_command(label = multilanguage.newappointment, command=self.opencalendar) + #create a DICOM_anonymizer pulldown men + self.anonymizermenu = Menu(self.menubar, tearoff = 0) #TODO add relevant menu + self.anonymizermenu.add_command(label = "menu") + #create a HELP pulldown menu + self.helpmenu = Menu(self.menubar, tearoff = 0) + self.helpmenu.add_command(label = multilanguage.gethelp, command=self.openhelp) + self.helpmenu.add_command(label = multilanguage.aboutwindow, command=self.aboutapplication) + #add all menu to the menubar + self.menubar.add_cascade(label = multilanguage.menuapplication, menu = self.applicationmenu) + self.menubar.add_cascade(label = multilanguage.menucandidate, menu = self.candidatemenu) + self.menubar.add_cascade(label = multilanguage.menucalendar, menu = self.calendarmenu) + self.menubar.add_cascade(label = multilanguage.menuanonymizer, menu = self.anonymizermenu) + #self.menubar.add_cascade(label = multilanguage.menuhelp, menu = self.helpmenu) #TODO add help menu + #add the menubar to the parent frame + self.parent.config(menu = self.menubar) + + + def appsettings(self): + print 'running appsettings' + pass + + def quitapplication(self): + print 'running quitapplication' + self.quit() + pass + + def opencalendar(self): + print 'running opencalendar' + pass + + def addcandidate(self): + print 'running addcandidate' + pass + + def findcandidate(self): + print 'running findcandidate' + pass + + def updatecandidate(self): + print 'running updatecandidate' + pass + + def getcandidateid(self): + print 'running getcandidateid' + pass + + def excludecandidate(self): + print 'running incativatecandidate' + pass + + def openhelp(self): + print 'running openhelp' + pass + + def aboutapplication(self): + print 'running aboutapplication' + pass + + + + +############################################################################################################################# +class DataTable(Frame): + def __init__(self, parent, dataset, colheaders): #expected is dataset + Frame.__init__(self) + self.parent = parent + colheaders = colheaders + dataset = dataset + datatable = self.initdatatable(parent, colheaders) + self.updatedata(datatable,dataset, colheaders) + + def initdatatable(self, parent, colheaders): + self.datatable = Treeview(parent, selectmode='browse', columns=colheaders, show="headings") + for col in colheaders: + self.datatable.heading(col, text=col.title(), command=lambda c=col: self.treeview_sortby(self.datatable, c, 0)) + self.datatable.column(col, width=100, stretch="Yes", anchor="center") + self.verticalscroll = Scrollbar(parent, orient="vertical", command=self.datatable.yview) + self.horizontalscroll = Scrollbar(parent, orient="horizontal", command=self.datatable.xview) + self.datatable.configure(yscrollcommand=self.verticalscroll.set, xscroll=self.horizontalscroll.set) + self.verticalscroll.pack(side=RIGHT, expand=NO, fill=BOTH) + self.horizontalscroll.pack(side=BOTTOM, expand=NO, fill=BOTH) + self.datatable.pack(side=LEFT, expand=YES, fill=BOTH) + + + def treeview_sortby(self, tree, column, descending): + """Taken from Dave's IDmapper""" + """Sort tree contents when a column is clicked on.""" + """From: https://code.google.com/p/python-ttk/source/browse/trunk/pyttk-samples/treeview_multicolumn.py?r=21""" + # grab values to sort + data = [(tree.set(child, column), child) for child in tree.get_children('')] + # reorder data + data.sort(reverse=descending) + for index, item in enumerate(data): + tree.move(item[1], '', index) + # switch the heading so that it will sort in the opposite direction + tree.heading(column, command=lambda column=column: self.treeview_sortby(tree, column, int(not descending))) + + + #TODO move to dataManagement + def updatedata(self, datatable, dataset, colheaders): + if 'firstname' in colheaders: + # This method will add cantidates information to cantidatetable + try: + for key in dataset: + if dataset[key].status is None: + status = '' + else: + status = dataset[key].status + self.datatable.insert('', 'end', values=[dataset[key].firstname, dataset[key].lastname, dataset[key].phone, status], tags=(status, dataset[key].uid, 'candidate')) + except Exception as e: + print "Exception AddIdentifierAction(self, dataset, save=True): " + str(e) + #utilities.errorlog(message) + pass #TODO add some error handling + self.datatable.tag_configure('active', background='#ccffcc') #TODO set color in application settings and preferences + self.datatable.bind('', self.ondoubleclick) + + elif 'candidate' in colheaders: + currentvisitset = {} + for key, value in dataset.iteritems(): + if dataset[key].visitset is not None: #skip the search if visitset = None + currentvisitset = dataset[key].visitset #set this candidate.visitset for the next step + #gather information about the candidate + candidatekey = dataset[key].uid #not printed on screen but saved with the new Scheduler object (after all it is the candidate unique id^^) + candidatefirstname = dataset[key].firstname + candidatelastname = dataset[key].lastname + candidatefullname = str(candidatefirstname + ' ' + candidatelastname) + for key, value in currentvisitset.iteritems(): + if currentvisitset[key].status is not None: + visitlabel = currentvisitset[key].visitlabel + if currentvisitset[key].when is None: + when = currentvisitset[key].whenearliest + else: + when = currentvisitset[key].when + if currentvisitset[key].where is None: + where = '' + else: + where = currentvisitset[key].where + #whom = currentvisitset[key].withwhom + if currentvisitset[key].status is None: + status = '' + else: + status = currentvisitset[key].status + #print candidatefullname, visitlabel, when, where, whom #TODO remove + #TODO ? create a new scheduler object with these informations + + # This method will add planned visits information to the visit datatable + try: + self.datatable.insert('', 'end', values= [candidatefullname, visitlabel, when, where, status], tags=(status, candidatekey, 'schedule', visitlabel)) + except Exception as e: + print "Exception AddIdentifierAction(self, dataset, save=True): " + str(e) #TODO remove + #utilities.errorlog(message) + pass #TODO add some error handling + self.datatable.tag_configure('active', background='#ccffcc') #TODO set in application settings and preferences + self.datatable.tag_configure('tentative', background='#f0f0f1') #TODO set in application settings and preferences + self.datatable.bind('', self.ondoubleclick) + + def ondoubleclick(self, event): + # double clicking on blank space of the treeview will generate an IndexOutOfRange error + try: + item_id = self.datatable.selection()[0] + item = self.datatable.item(item_id)['tags'] + candidate = item[1] + function = item[2] + if function == "schedule": + visitlabel = item[3] + else: + visitlabel = None + except Exception as e: + print str(e) #TODO error management + return + parent = self.parent + if function == "schedule": + newwin = newwindow.NewWindow(parent, candidate, function, visitlabel) + elif function == "candidate": + newwin = newwindow.NewWindow(parent, candidate, function, visitlabel) + else: + return + + +############################################################################################################################# + +#self-test "module" TODO remove +if __name__ == '__main__': + root = Tk() + + #root.geometry() #TODO add dynamic resize + app = ApplicationGUI(root) + + root.protocol("WM_DELETE_WINDOW", app.quitapplication) + app.mainloop() + From 9181e25cd39615c58aaae5d317a5f07a433c69d2 Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Morin Date: Thu, 4 Jun 2015 12:09:59 -0400 Subject: [PATCH 07/89] Create visit.py --- scheduler/visit.py | 75 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 scheduler/visit.py diff --git a/scheduler/visit.py b/scheduler/visit.py new file mode 100644 index 0000000..868238d --- /dev/null +++ b/scheduler/visit.py @@ -0,0 +1,75 @@ +import lib.utilities as utilities + +class StudySetup(): + """ + The StudySetup(object) class define a study in terms of sequence of visits. + A study can have as many visits as required and each visit (instance) has its own 'definition'. + + This is the parent class of Visit(StudySetup), furthermore the Visit(StudySetup) objects will be 'instanciated' from each instance of StudySetup(object). + Both StudySetup(object) class and instances are used to create individual instances of Visit(StudySetup) + + uid: unique identifier. Used to store and retrieve object in shelve() + rank: The rank of the visit in the sequence (int). Useful to sort the visits in order of occurrence. + visitlabel: The label of the visit (string) such as V1, V2 or anything else the user may come up with + previousvisit: The visitlabel (string) of the visit occurring before this one. Used to plan this visit based on the date of the previous visit. (default to None) + visitwindow: The number of days (int) between this visit and the previous visit. (default to None) + visitmargin: The margin (in number of days (int)) that is an allowed deviation. Basically, this allow the 'calculation' of a date window when this visit should occur. (default to None) + actions: A list of action points (or simply reminders) for specific to that visit (i.e.'reserve room 101'). (default to None) + """ + + def __init__(self, rank, visitlabel, previousvisit = None, visitwindow = None, visitmargin = None, actions = None, uid=None): + self.uid = utilities.generateUniqueID() + self.rank = rank + self.visitlabel = visitlabel + self.previousvisit = previousvisit + self.visitwindow = visitwindow + self.visitmargin = visitmargin + self.actions = actions #not implemented yet! + + + + +class Visit(StudySetup): + """ + The Visit(StudySetup) class help define individual visit of the candidate using StudySetup(object) instances as 'templates'. + Upon creation of the first meeting with a candidate, the Candidate(object) instance will get a full set of Visit(StudySetup) instances. + Each time a visit is being setup, a 'time window' is calculated to define the earliest and latest date at which the 'nextVisit' should occur. + + In addition of parent class attributes. + uid: Not used. + when: Date at which this visit is occurring. (default to None) + whenearliest: Earliest date when this visit should occur. Set to value when previous visit is activated. (default to None) + whenlatest: Latest date when this visit should occur. Set to value when previous visit is activated. (default to None) + where: Place where this meeting is taking place. (default to None) + whitwhom: Professional meeting the study candidate at the reception. (default to None) + status: Status of this visit. Set to active when 'when' is set (default to None) + """ + def __init__(self, rank, visitlabel, previousvisit, visitwindow, visitmargin, actions=None, uid=None, when = None, whenearliest = None, whenlatest = None, where = None, withwhom = None, status = None): + StudySetup.__init__(self, rank, visitlabel, previousvisit, visitwindow, visitmargin, actions, uid) + self.when = when + self.whenearliest = whenearliest + self.whenlatest = whenlatest + self.where = where + self.withwhom = withwhom + self.status = status + + + #def setvisitdate(self, visitdate, visittime, where, whom): + # #parse the strings 'visitdate' and 'visittime' to a datetime object before assigning it to Visit instance + # #TODO add invalid data/format check + # visitdatetimestr = visitdate, ' ', visittime + # visitdatetime = datetime.datetime.strptime(visitdatetimestr,"%Y-%m-%d %H:%m") #parse the string to a datetime object + # self.when = visitdatetime + # self.status = 'active' + + + + + + + + + + + + From e5727ae169cb48258d3af746fc67e800201480d3 Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Morin Date: Thu, 4 Jun 2015 12:10:55 -0400 Subject: [PATCH 08/89] Create candidate --- scheduler/candidate | 611 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 611 insertions(+) create mode 100644 scheduler/candidate diff --git a/scheduler/candidate b/scheduler/candidate new file mode 100644 index 0000000..4cb3c4c --- /dev/null +++ b/scheduler/candidate @@ -0,0 +1,611 @@ +  a   îÍ  f—{+   Ñ�h^     < +ÛûÖ`;Ѭ< (icandidate +Candidate +p1 +(dp2 +S'status' +p3 +S'active' +p4 +sS'pscid' +p5 +NsS'uid' +p6 +S'57d5f5d4-a8be-11e4-aed0-fc4dd4d3c3f3' +p7 +sS'firstname' +p8 +S'Marc' +p9 +sS'lastname' +p10 +S'St-Pierre' +p11 +sS'visitset' +p12 +(dp13 +S'V0' +p14 +(ivisit +Visit +p15 +(dp16 +g3 +g4 +sS'visitmargin' +p17 +Nsg6 +S'57f2cc9f-a8be-11e4-812e-fc4dd4d3c3f3' +p18 +sS'visitwindow' +p19 +NsS'visitlabel' +p20 +S'V0' +p21 +sS'when' +p22 +cdatetime +datetime +p23 +(S'\x07\xdf\x02\x18\x0f\x1e\x00\x00\x00\x00' +tRp24 +sS'rank' +p25 +I1 +sS'actions' +p26 +NsS'whenearliest' +p27 +NsS'previousvisit' +p28 +NsS'whenlatest' +p29 +NsS'where' +p30 +S'IGA' +p31 +sS'withwhom' +p32 +S'Charlie' +p33 +sbsS'V1' +p34 +(ivisit +Visit +p35 +(dp36 +g3 +S'tentative' +p37 +sg17 +I2 +sg6 +S'57f2cca1-a8be-11e4-b363-fc4dd4d3c3f3' +p38 +sg19 +I10 +sg20 +S'V1' +p39 +sg22 +Nsg25 +I2 +sg26 +Nsg27 +g23 +(S'\x07\xdf\x03\x04\x00\x00\x00\x00\x00\x00' +tRp40 +sg28 +S'V0' +p41 +sg29 +g23 +(S'\x07\xdf\x03\x08\x00\x00\x00\x00\x00\x00' +tRp42 +sg30 +Nsg32 +NsbsS'V2' +p43 +(ivisit +Visit +p44 +(dp45 +g3 +Nsg17 +I10 +sg6 +S'57f2cca3-a8be-11e4-a958-fc4dd4d3c3f3' +p46 +sg19 +I20 +sg20 +S'V2' +p47 +sg22 +Nsg25 +I3 +sg26 +Nsg27 +Nsg28 +S'V1' +p48 +sg29 +Nsg30 +Nsg32 +NsbssS'phone' +p49 +S'412-897-9874' +p50 +sb.57d5f5d4-a8be-11e4-aed0-fc4dd4d3c3f3(icandidate +Candidate +p1 +(dp2 +S'status' +p3 +S'active' +p4 +sS'pscid' +p5 +NsS'uid' +p6 +S'57d5f5d3-a8be-11e4-b850-fc4dd4d3c3f3' +p7 +sS'firstname' +p8 +S'Alain' +p9 +sS'lastname' +p10 +S'Jeanson' +p11 +sS'visitset' +p12 +(dp13 +S'V0' +p14 +(ivisit +Visit +p15 +(dp16 +g3 +NsS'visitmargin' +p17 +Nsg6 +S'57f2a590-a8be-11e4-ac32-fc4dd4d3c3f3' +p18 +sS'visitwindow' +p19 +NsS'visitlabel' +p20 +S'V0' +p21 +sS'when' +p22 +NsS'rank' +p23 +I1 +sS'actions' +p24 +NsS'whenearliest' +p25 +NsS'previousvisit' +p26 +NsS'whenlatest' +p27 +NsS'where' +p28 +NsS'withwhom' +p29 +NsbsS'V1' +p30 +(ivisit +Visit +p31 +(dp32 +g3 +g4 +sg17 +I2 +sg6 +S'57f2a592-a8be-11e4-a2c9-fc4dd4d3c3f3' +p33 +sg19 +I10 +sg20 +S'V1' +p34 +sg22 +cdatetime +datetime +p35 +(S'\x07\xdf\x01\r\t\x0f\x00\x00\x00\x00' +tRp36 +sg23 +I2 +sg24 +Nsg25 +Nsg26 +S'V0' +p37 +sg27 +Nsg28 +S'McDo' +p38 +sg29 +S'Scott' +p39 +sbsS'V2' +p40 +(ivisit +Visit +p41 +(dp42 +g3 +S'tentative' +p43 +sg17 +I10 +sg6 +S'57f2a594-a8be-11e4-87a9-fc4dd4d3c3f3' +p44 +sg19 +I20 +sg20 +S'V2' +p45 +sg22 +Nsg23 +I3 +sg24 +Nsg25 +g35 +(S'\x07\xdf\x01\x17\x00\x00\x00\x00\x00\x00' +tRp46 +sg26 +S'V1' +p47 +sg27 +g35 +(S'\x07\xdf\x02\x0c\x00\x00\x00\x00\x00\x00' +tRp48 +sg28 +Nsg29 +NsbssS'phone' +p49 +S'245-874-6321' +p50 +sb.57d5f5d3-a8be-11e4-b850-fc4dd4d3c3f3(icandidate +Candidate +p1 +(dp2 +S'status' +p3 +S'active' +p4 +sS'pscid' +p5 +NsS'uid' +p6 +S'57d5f5d2-a8be-11e4-b9c6-fc4dd4d3c3f3' +p7 +sS'firstname' +p8 +S'Pierre' +p9 +sS'lastname' +p10 +S'Tremblay' +p11 +sS'visitset' +p12 +(dp13 +S'V0' +p14 +(ivisit +Visit +p15 +(dp16 +g3 +g4 +sS'visitmargin' +p17 +Nsg6 +S'57ebeed0-a8be-11e4-9741-fc4dd4d3c3f3' +p18 +sS'visitwindow' +p19 +NsS'visitlabel' +p20 +S'V0' +p21 +sS'when' +p22 +cdatetime +datetime +p23 +(S'\x07\xde\x0c\x19\r\x0f\x00\x00\x00\x00' +tRp24 +sS'rank' +p25 +I1 +sS'actions' +p26 +NsS'whenearliest' +p27 +NsS'previousvisit' +p28 +NsS'whenlatest' +p29 +NsS'where' +p30 +S'CRIUGM lobby' +p31 +sS'withwhom' +p32 +S'Annie' +p33 +sbsS'V1' +p34 +(ivisit +Visit +p35 +(dp36 +g3 +S'tentative' +p37 +sg17 +I2 +sg6 +S'57ebeed2-a8be-11e4-a4cf-fc4dd4d3c3f3' +p38 +sg19 +I10 +sg20 +S'V1' +p39 +sg22 +Nsg25 +I2 +sg26 +Nsg27 +g23 +(S'\x07\xdf\x01\x02\x00\x00\x00\x00\x00\x00' +tRp40 +sg28 +S'V0' +p41 +sg29 +g23 +(S'\x07\xdf\x01\x06\x00\x00\x00\x00\x00\x00' +tRp42 +sg30 +Nsg32 +NsbsS'V2' +p43 +(ivisit +Visit +p44 +(dp45 +g3 +Nsg17 +I10 +sg6 +S'57ebeed4-a8be-11e4-967c-fc4dd4d3c3f3' +p46 +sg19 +I20 +sg20 +S'V2' +p47 +sg22 +Nsg25 +I3 +sg26 +Nsg27 +Nsg28 +S'V1' +p48 +sg29 +Nsg30 +Nsg32 +NsbssS'phone' +p49 +S'547-852-9745' +p50 +sb.57d5f5d2-a8be-11e4-b9c6-fc4dd4d3c3f3(icandidate +Candidate +p1 +(dp2 +S'status' +p3 +NsS'pscid' +p4 +NsS'uid' +p5 +S'57d5f5d0-a8be-11e4-885c-fc4dd4d3c3f3' +p6 +sS'firstname' +p7 +S'Sue' +p8 +sS'lastname' +p9 +S'Allen' +p10 +sS'visitset' +p11 +NsS'phone' +p12 +S'451-874-9632' +p13 +sb.57d5f5d0-a8be-11e4-885c-fc4dd4d3c3f3    8 +ÛЫ8 îÍ  f—{+   Ñ�h^  (icandidate +Candidate +p1 +(dp2 +S'status' +p3 +S'active' +p4 +sS'pscid' +p5 +NsS'uid' +p6 +S'57d5f5d1-a8be-11e4-8507-fc4dd4d3c3f3' +p7 +sS'firstname' +p8 +S'Alan' +p9 +sS'lastname' +p10 +S'Parson' +p11 +sS'visitset' +p12 +(dp13 +S'V0' +p14 +(ivisit +Visit +p15 +(dp16 +g3 +NsS'visitmargin' +p17 +Nsg6 +S'57f25771-a8be-11e4-9e84-fc4dd4d3c3f3' +p18 +sS'visitwindow' +p19 +NsS'visitlabel' +p20 +S'V0' +p21 +sS'when' +p22 +NsS'rank' +p23 +I1 +sS'actions' +p24 +NsS'whenearliest' +p25 +NsS'previousvisit' +p26 +NsS'whenlatest' +p27 +NsS'where' +p28 +NsS'withwhom' +p29 +NsbsS'V1' +p30 +(ivisit +Visit +p31 +(dp32 +g3 +g4 +sg17 +I2 +sg6 +S'57f25773-a8be-11e4-9a1e-fc4dd4d3c3f3' +p33 +sg19 +I10 +sg20 +S'V1' +p34 +sg22 +cdatetime +datetime +p35 +(S'\x07\xde\x0c\x1b\x0e\x1e\x00\x00\x00\x00' +tRp36 +sg23 +I2 +sg24 +Nsg25 +Nsg26 +S'V0' +p37 +sg27 +Nsg28 +S'CRIUGM M-124' +p38 +sg29 +S'Jean' +p39 +sbsS'V2' +p40 +(ivisit +Visit +p41 +(dp42 +g3 +S'tentative' +p43 +sg17 +I10 +sg6 +S'57f25775-a8be-11e4-935e-fc4dd4d3c3f3' +p44 +sg19 +I20 +sg20 +S'V2' +p45 +sg22 +Nsg23 +I3 +sg24 +Nsg25 +g35 +(S'\x07\xdf\x01\x06\x00\x00\x00\x00\x00\x00' +tRp46 +sg26 +S'V1' +p47 +sg27 +g35 +(S'\x07\xdf\x01\x1a\x00\x00\x00\x00\x00\x00' +tRp48 +sg28 +Nsg29 +NsbssS'phone' +p49 +S'451-874-8965' +p50 +sb.57d5f5d1-a8be-11e4-8507-fc4dd4d3c3f3(icandidate +Candidate +p1 +(dp2 +S'status' +p3 +NsS'pscid' +p4 +NsS'uid' +p5 +S'57d5f5cf-a8be-11e4-a5a9-fc4dd4d3c3f3' +p6 +sS'firstname' +p7 +S'Billy' +p8 +sS'lastname' +p9 +S'Roberts' +p10 +sS'visitset' +p11 +NsS'phone' +p12 +S'451-784-9856' +p13 +sS'otherphone' +p14 +S'514-874-9658' +p15 +sb.57d5f5cf-a8be-11e4-a5a9-fc4dd4d3c3f3 From e0965247c0f109b0b3ac7f8d261d697507067ef8 Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Morin Date: Thu, 4 Jun 2015 12:12:03 -0400 Subject: [PATCH 09/89] Create __init__.py --- scheduler/lib/__init__.py | 1 + 1 file changed, 1 insertion(+) create mode 100644 scheduler/lib/__init__.py diff --git a/scheduler/lib/__init__.py b/scheduler/lib/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/scheduler/lib/__init__.py @@ -0,0 +1 @@ + From f9965fe9619aea8ab655f3ed0ecbaf19d6a10ceb Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Morin Date: Thu, 4 Jun 2015 12:12:37 -0400 Subject: [PATCH 10/89] Create classtool.py --- scheduler/lib/classtool.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 scheduler/lib/classtool.py diff --git a/scheduler/lib/classtool.py b/scheduler/lib/classtool.py new file mode 100644 index 0000000..143f894 --- /dev/null +++ b/scheduler/lib/classtool.py @@ -0,0 +1,20 @@ +class ClassTool(): + """ + Not used! Useful for during development as inheritable class MyClass(ClassTool). + """ + #no constructor + def __repr__(self): + attributes =[] + for key in sorted(self.__dict__): + attributes.append('%s=%s' %(key, getattr(self, key))) + return ', '.join(attributes) + + def gatherattributes(self): + attributes =[] + for key in sorted(self.__dict__): + attributes.append('%s' %(key)) + return ', '.join(attributes) + + def getframesize(self): + print str(self.winfo_width()) + From 114be86b01c0dc66d606f9f76bd303eccbb7560f Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Morin Date: Thu, 4 Jun 2015 12:13:07 -0400 Subject: [PATCH 11/89] Create datamanagement.py --- scheduler/lib/datamanagement.py | 44 +++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 scheduler/lib/datamanagement.py diff --git a/scheduler/lib/datamanagement.py b/scheduler/lib/datamanagement.py new file mode 100644 index 0000000..443c1a6 --- /dev/null +++ b/scheduler/lib/datamanagement.py @@ -0,0 +1,44 @@ +import shelve +""" +The data_management.py file contains functions related to data management only. +Generic functions: savedata(data, datafilename) and readdata(datafile) + +Specific functions readcandidatedata(), savecandidatedata(), readstudydata() and savestudydata() are used to get/save candidate data and study setup data respectively. +""" +""" +#Generic functions +def savedata(data, datafilename): + db = shelve.open(datafilename) + for key in data: + db[data[key].uid] = data[key] + db.close() + + +def readdata(datafile): + db = shelve.open(datafile) #TODO check is db.close() is needed (may be automatic) + return db +""" + +#Specific functions +def readcandidatedata(): + db = shelve.open("candidate") + return db + + +def savecandidatedata(data): + db = shelve.open("candidate") + for key in data: + db[data[key].uid] = data[key] + db.close() + + +def readstudydata(): + db = shelve.open("studysetup.db") + return db + + +def savestudydata(data): + db = shelve.open("studysetup.db") + for key in data: + db[data[key].uid] = data[key] + db.close() From b91607230b68efec7e5ec2dbde1e6aa0dd62965f Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Morin Date: Thu, 4 Jun 2015 12:13:31 -0400 Subject: [PATCH 12/89] Create multilanguage.py --- scheduler/lib/multilanguage.py | 204 +++++++++++++++++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100644 scheduler/lib/multilanguage.py diff --git a/scheduler/lib/multilanguage.py b/scheduler/lib/multilanguage.py new file mode 100644 index 0000000..721e558 --- /dev/null +++ b/scheduler/lib/multilanguage.py @@ -0,0 +1,204 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# read language preference from appdata file +#from utilities import readappdata #TODO replace and remove +#language = readappdata()[0] + +language = "en" + +if language == "fr": #TODO make dynamic + + ###################### TOP LEVEL MENU BAR ###################### + apptitle = u"Outils LORIS" + #APPLICATION menu + menuapplication = u"Application" + settingapplication = u"Préferences" + quitapplication = u"Quitter" + #PROJECT menu + #menuproject = u"Projet" #TODO remove? + #openproject = u"Ouvrir un projet" + #modifyproject = u"Modifier le projet ouvert" + #newproject = u"Créer un nouveau projet" + #CANDIDATE menu + menucandidate = "Candidat" + addcandidate = u"Nouveau candidat" + findcandidate = u"Trouver candidat" + updatecandidate = u"Mettre à jour" + excludecandidate = u"Exclure un candidat" + getcandidateid = u"Obtenir l'identifiant d'un candidat" + clearallfield = u"Effacer" + #ANONYMIZER menu + menuanonymizer = u"DICOM" + #CALENDAR menu + menucalendar = u"Calendrier" + newappointment = u"Nouveau Rendez-vous" + #HELP menu + menuhelp = u"Aide" + gethelp = u"Obtenir de l'aide" + aboutwindow = u"A propos de ..." + ###################### PROJECT INFO PANE ####################### + project_infopane = u"Informations" + project_detailpane = u"Détails du Projet" + visit_detailpane = u"Détails des Visites" + projectname = u"Projet" + projectstart = u"Début" + projectend = u"Fin" + targetrecruitment = u"Cible de recrutement" + currentrecruitment = u"Recrutement actuel" + totalvisit = u"Nombre de Visites" + #################### MULTI-TAB DATA SECTION ##################### + calendar_pane = u"Calendrier" + candidate_pane = u"Candidats" + + + labelcandidatetable = u"Faites un double-clic sur l'une des lignes pour remplir les champs ci-dessus" + datatable_id = u"ID" + datatable_firstname = u"Prénom" + datatable_lastname = "Nom" + datatable_dob = u"Date de Naissance" + datatable_phone = u"Téléphone" + datatable_address = u"Adresse" + datatable_city = u"Ville" + datatable_province = u"Province" + datatable_country = u"Pays" + datatable_postalcode = u"Code Postal" + + + calendar_monday = u"Lundi" + calendar_tuesday = u"Mardi" + calendar_wednesday = u"Mercredi" + calendar_thursday = u"Jeudi" + calendar_friday = u"Vendredi" + calendar_saturday = u"Samedi" + calendar_sunday = u"Dimanche" + calendar_january = u"Janvier" + calendar_february = u"Février" + calendar_march = u"Mars" + calendar_april = u"Avril" + calendar_may = u"Mai" + calendar_june = u"Juin" + calendar_jully = u"Juillet" + calendar_august = u"Août" + calendar_september = u"Septembre" + calendar_october = u"Octobre" + calendar_november = u"Novembre" + calendar_december = u"Décembre" + ################ COLUMN HEADER ################## + col_candidate = u"Candidat" + col_visitlabel = u"Visite" + col_when = u"Date/Heure" + col_where = u"Endroit" + col_status = u"Statut" + + #################### STATUS ##################### + status_active = "actif" + status_tentative = "provisoire" + + ################# DATA WINDOWS ################## + schedulewindow_title ="Calendrier" + candidatewindow_title = "Information du candidat" + + + ################## DIALOGBOX #################### + dialog_yes = "Oui" + dialog_no = "Non" + dialogtitle_confirm = "Veuillez confirmer!" + dialogclose = "Vous êtes sur le point de fermer cette fenêtre sans sauvegarder! Voulez-vous continuer?" + + candidate_firstname = "prénom" + +elif language == "en": + apptitle = "LORIS tools" + #APPLICATION menu + menuapplication = u"Application" + settingapplication = u"Preferences" + quitapplication = u"Quit" + #PROJECT menu + #menuproject = u"Project" #TODO remove? + #openproject = u"Open project" + #modifyproject = u"Modify open project" + #newproject = u"New project" + #CANDIDATE menu + menucandidate = u"Candidate" + addcandidate = u"New candidate" + findcandidate = u"Find a candidate" + updatecandidate = u"Update" + excludecandidate = u"Exclude a candidate" + getcandidateid = u"Get a canditate ID" + clearallfield = u"Clear" + #CALENDAR menu + menucalendar = u"Calendar" + newappointment = u"New appointment" + #ANONYMIZER menu + menuanonymizer = u"DICOM" + #HELP menu + menuhelp = u"Help" + gethelp = u"Get some help" + aboutwindow = u"About this..." + ###################### PROJECT INFO PANE ####################### + project_infopane = u"Informations" + project_detailpane = u"Project Details" + visit_detailpane = u"Visit Details" + projectname = u"Project" + projectstart = u"Start" + projectend = u"End" + targetrecruitment = u"Recruitment target" + currentrecruitment = u"Current recruitment" + totalvisit = u"Total number of Visits" + #################### MULTI-TAB DATA SECTION ##################### + calendar_pane = u"Calendar" + candidate_pane = u"Candidates" + labelcandidatetable = u"Double click on row to populate fields above" + datatable_id = u"ID" + datatable_firstname = u"First Name" + datatable_lastname = "Last Name" + datatable_dob = u"Date of Birth" + datatable_phone = u"Phone" + datatable_address = u"Address" + datatable_city = u"City" + datatable_province = u"Province" + datatable_country = u"Country" + datatable_postalcode = u"Postal Code" + + + calendar_monday = u"Monday" + calendar_tuesday = u"Tuesday" + calendar_wednesday = u"Wednesday" + calendar_thursday = u"Thursday" + calendar_friday = u"Friday" + calendar_saturday = u"Saturday" + calendar_sunday = u"Sunday" + calendar_january = u"January" + calendar_february = u"February" + calendar_march = u"Marc" + calendar_april = u"April" + calendar_may = u"May" + calendar_june = u"June" + calendar_jully = u"Jully" + calendar_august = u"August" + calendar_september = u"September" + calendar_october = u"October" + calendar_november = u"November" + calendar_december = u"December" + + ################ COLUMN HEADER ################## + col_candidate = u"Candidate" + col_visitlabel = u"Visit" + col_when = u"Date/Time" + col_where = u"Place" + col_status = u"Status" + #################### STATUS ##################### + status_active = "active" + status_tentative = "tentative" + + ################# DATA WINDOWS ################## + schedulewindow_title ="Scheduler" + candidatewindow_title = "Candidate information" + + + ################## DIALOGBOX #################### + dialog_yes = "Yes" + dialog_no = "No" + dialogtitle_confirm = "Please confirm!" + dialogclose = "You are about to close this window without saving! \n\nDo you want to continue?" From 771f56176342e3bb6a7e3e5a2d75241edd438801 Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Morin Date: Thu, 4 Jun 2015 12:14:08 -0400 Subject: [PATCH 13/89] Create utilities.py --- scheduler/lib/utilities.py | 92 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 scheduler/lib/utilities.py diff --git a/scheduler/lib/utilities.py b/scheduler/lib/utilities.py new file mode 100644 index 0000000..317effd --- /dev/null +++ b/scheduler/lib/utilities.py @@ -0,0 +1,92 @@ +from uuid import uuid1 +import time + +def generateUniqueID(): + """ + will generate a random 14-bit sequence number is chosen. + see python documentation https://docs.python.org/2/library/uuid.html + """ + ui = str(uuid1()) + return ui + +def isUnique(visitdata): + """ + will verify if the visitdata passed as argument is unique in a set + """ + seen = set() + return not any(value in seen or seen.add(value) for value in visitdata) + + +def describe(something): + """ + this will return the object(something) class name and type as well as all attributes excluding those starting with a double underscore + """ + #will return the object type and class (Type:Class) as text + objectclass = str(something.__class__.__name__) + objecttype = str(type(something)) + #will return a list of all non"__" attributes, including use defined ones (**kwargs) + attributes = str(filter(lambda a: not a.startswith('__'), dir(something))) + returnvalue = objectclass, " (", objecttype, "): ", attributes + return returnvalue + + + + +def errorlog(message): + # append/save timestamp and exception to errorlog.txt + # object Exception e is sent as is and this method is taking care of parsing it to string + # and adding a timestamp + console = 1 #TODO use this value to send error message to the console instead of the file + if console == 0: + timestamp = time.strftime("%c") # date and time representation using this format: 11/07/14 12:50:35 + fullmessage = timestamp + ": " + str(message) + "\n" + anyfile = open("errorlog.txt", "a") #this is required since a file object is later expected + anyfile.write(fullmessage) + anyfile.close() + elif console == 1: + print message + "\n" #TODO remove when done + + + +def centerwindow(win): + win.update_idletasks() + width = win.winfo_width() + height = win.winfo_height() + x = (win.winfo_screenwidth() // 2) - (width // 2) + y = (win.winfo_screenheight() // 2) - (height // 2)-200 + win.geometry('{}x{}+{}+{}'.format(width, height, x, y)) + + + + + +######################################################################################## +def gatherattributes(something): + """ + Receive an object and return an array containing the object attributes and value + """ + attributes =[] + for key in sorted(something.__dict__): + attributes.append('%s' %(key)) + return attributes + + +def searchuid(db, value): + return filter(lambda candidate: candidate['uid'] == value, db) + + +def printobject(something): + #for dev only! + #will print key, attributes, value in the console. + #most likely will be an dict or a class instance. + #so this is for the dict + if type(something) is dict: + for key, value in something.iteritems(): + c = something.get(key) + for attr, value in c.__dict__.iteritems(): + print attr,": ", value + print "\n" + else: #and this for the class instance + for attr, value in something.__dict__.iteritems(): + print attr, value + print "\n\n" From a9817e28afd3cf06ac2ba25810855eceb77079b9 Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Morin Date: Thu, 4 Jun 2015 12:14:53 -0400 Subject: [PATCH 14/89] Create test.py --- scheduler/tests/test.py | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 scheduler/tests/test.py diff --git a/scheduler/tests/test.py b/scheduler/tests/test.py new file mode 100644 index 0000000..24ec26f --- /dev/null +++ b/scheduler/tests/test.py @@ -0,0 +1,6 @@ +import lib.utilities as utilities +import lib.datamanagement as datamanagement + +db = dict(datamanagement.readcandidatedata()) + +utilities.printobject(db) From f2c0fcb2be76a13bf6e4bb598ac470a8748a370b Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Morin Date: Thu, 4 Jun 2015 12:15:12 -0400 Subject: [PATCH 15/89] Create test1.py --- scheduler/tests/test1.py | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 scheduler/tests/test1.py diff --git a/scheduler/tests/test1.py b/scheduler/tests/test1.py new file mode 100644 index 0000000..555c0de --- /dev/null +++ b/scheduler/tests/test1.py @@ -0,0 +1,7 @@ +import candidate + +happycandidate = candidate.Candidate('Billy', 'Roberts', '451-784-9856', otherphone='514-874-9658') + + +for attr, value in happycandidate.__dict__.iteritems(): + print attr, " = ", value From 2261805e8c64fec0af1fee7fd3838567c890837a Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Morin Date: Thu, 4 Jun 2015 12:15:34 -0400 Subject: [PATCH 16/89] Create test5.py --- scheduler/tests/test5.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 scheduler/tests/test5.py diff --git a/scheduler/tests/test5.py b/scheduler/tests/test5.py new file mode 100644 index 0000000..f26d399 --- /dev/null +++ b/scheduler/tests/test5.py @@ -0,0 +1,19 @@ +import Tkinter as tk +import ttk + +class App: + def __init__(self): + self.root = tk.Tk() + self.tree = ttk.Treeview() + self.tree.pack() + for i in range(10): + self.tree.insert("", "end", text="Item %s" % i) + self.tree.bind("", self.OnDoubleClick) + self.root.mainloop() + + def OnDoubleClick(self, event): + item = self.tree.selection()[0] + print "you clicked on", self.tree.item(item,"text") + +if __name__ == "__main__": + app=App() From dae09a0d8efa629a3da669b397fa0ab2860be398 Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Morin Date: Thu, 4 Jun 2015 12:16:06 -0400 Subject: [PATCH 17/89] Create create1_StudySetup.py --- scheduler/tests/create1_StudySetup.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 scheduler/tests/create1_StudySetup.py diff --git a/scheduler/tests/create1_StudySetup.py b/scheduler/tests/create1_StudySetup.py new file mode 100644 index 0000000..8443b39 --- /dev/null +++ b/scheduler/tests/create1_StudySetup.py @@ -0,0 +1,23 @@ +import visit +import lib.datamanagement as datamanagement + +#GUI: Setting up the sequence of visit - must be done prior to anything else +#setup the StudySetup instances to match the study visit sequence +#the visitdata of each instance will serve to populate individual Visit() instances +#TODO visitlabels MUST start with a letter - ADD regex +#TODO visitlabels MUST be unique + +studydb = {} +studyvisit = visit.StudySetup(1, 'V0') +studydb[studyvisit.uid] = studyvisit + +studyvisit = visit.StudySetup(2, 'V1', 'V0', 10, 2) +studydb[studyvisit.uid] = studyvisit + +studyvisit = visit.StudySetup(3, 'V2', 'V1', 20, 10) +studydb[studyvisit.uid] = studyvisit + + +datamanagement.savestudydata(studydb) + +#TESTED From 9725f2e705574100922599ac6ff29932e9465fb8 Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Morin Date: Thu, 4 Jun 2015 12:16:59 -0400 Subject: [PATCH 18/89] Create create2_CandidateList_test.py --- scheduler/tests/create2_CandidateList_test.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 scheduler/tests/create2_CandidateList_test.py diff --git a/scheduler/tests/create2_CandidateList_test.py b/scheduler/tests/create2_CandidateList_test.py new file mode 100644 index 0000000..057ad3d --- /dev/null +++ b/scheduler/tests/create2_CandidateList_test.py @@ -0,0 +1,30 @@ +import candidate +import lib.datamanagement as datamanagement + + +#GUI: Setting up a list of candidate +#print '\nGUI: ENTERING DATA FOR MULTIPLE CANDIDATES' +#TODO add duplicate control by checking for name+phone number +candidatedb = {} + +candidatedata = candidate.Candidate('Billy', 'Roberts', '451-784-9856', otherphone='514-874-9658') +candidatedb[candidatedata.uid] = candidatedata + +candidatedata = candidate.Candidate('Sue', 'Allen', '451-874-9632') +candidatedb[candidatedata.uid] = candidatedata + +candidatedata = candidate.Candidate('Alan', 'Parson', '451-874-8965') +candidatedb[candidatedata.uid] = candidatedata + +candidatedata = candidate.Candidate('Pierre', 'Tremblay', '547-852-9745') +candidatedb[candidatedata.uid] = candidatedata + +candidatedata = candidate.Candidate('Alain', 'Jeanson', '245-874-6321') +candidatedb[candidatedata.uid] = candidatedata + +candidatedata = candidate.Candidate('Marc', 'St-Pierre', '412-897-9874') +candidatedb[candidatedata.uid] = candidatedata + +datamanagement.savecandidatedata(candidatedb) + +#TESTED From 4db0a288f658ccd183cacea06f53a1cb4e5b0fbe Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Morin Date: Thu, 4 Jun 2015 12:17:24 -0400 Subject: [PATCH 19/89] Create create3_CandidateVisit.py --- scheduler/tests/create3_CandidateVisit.py | 28 +++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 scheduler/tests/create3_CandidateVisit.py diff --git a/scheduler/tests/create3_CandidateVisit.py b/scheduler/tests/create3_CandidateVisit.py new file mode 100644 index 0000000..dd3a2b8 --- /dev/null +++ b/scheduler/tests/create3_CandidateVisit.py @@ -0,0 +1,28 @@ +import candidate +import visit +import datetime +import lib.datamanagement as datamanagement + +#loading data +candidatedb = dict(datamanagement.readcandidatedata()) +#GUI: selecting a candidate from db +#print '\nGUI: SELECTING ONE CANDIDATE,...' +happycandidate = candidatedb.get("a045a530-a31f-11e4-9c66-fc4dd4d3c3f3") +#Upon setting the first visit with a candidate, we will dump a complete visitset into candidate.visitset +#set values of : visitlabel, visitdate, visittime, where, whom +#print '\nGUI: ...AND SETTING UP THE FIRST VISIT (date, time...)' +#print 'system: collecting information from application' +visitlabel = 'V0' #TODO selection from droplist +visitdate = '2014-12-22' #TODO add regex controls +visittime = '13:15' #TODO add regex controls +visitwhere = 'CRIUGM lobby' +visitwhom = 'me' +#print 'system: create visitset instance if necessary and add collected information to proper visit in Candidate.visitset' +thisvisit = happycandidate.setvisitdate(visitlabel, visitdate, visittime, visitwhere, visitwhom) +happycandidate.setnextvisitwindow(happycandidate, thisvisit) +#print'\nGUI: NOW THIS CANDIDATE HAS A DATE FOR HIS/HER FIRST VISIT + A RANGE FOR THE FOLLOWING VISIT' +#print '\nGUI: put on screen all active visits (sorted by datetime) => see test3.py' +datamanagement.savecandidatedata(candidatedb) + + +#TESTED From da9575a93b196998931745b9919dac4322892b26 Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Morin Date: Thu, 4 Jun 2015 12:18:03 -0400 Subject: [PATCH 20/89] Create create4_PopulateVisits.py --- scheduler/tests/create4_PopulateVisits.py | 87 +++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 scheduler/tests/create4_PopulateVisits.py diff --git a/scheduler/tests/create4_PopulateVisits.py b/scheduler/tests/create4_PopulateVisits.py new file mode 100644 index 0000000..fc4fa4e --- /dev/null +++ b/scheduler/tests/create4_PopulateVisits.py @@ -0,0 +1,87 @@ +""" +a045a534-a31f-11e4-bc02-fc4dd4d3c3f3 +a04493c0-a31f-11e4-9414-fc4dd4d3c3f3 +a045a533-a31f-11e4-aba9-fc4dd4d3c3f3 +a045a532-a31f-11e4-96a6-fc4dd4d3c3f3 +a045a531-a31f-11e4-a1c4-fc4dd4d3c3f3 +""" + +import visit +import candidate +import lib.datamanagement as datamanagement +import lib.utilities as utilities + +db = dict(datamanagement.readcandidatedata()) + +#get all key values +keylist = [] +for key in db: + keylist.append(key) + +candidate1 = db.get(keylist[0]) +candidate2 = db.get(keylist[1]) +candidate3 = db.get(keylist[2]) +candidate4 = db.get(keylist[3]) +candidate5 = db.get(keylist[4]) + +visitlabel = 'V0' #TODO selection from droplist +visitdate = '2014-12-25' #TODO add regex controls +visittime = '13:15' #TODO add regex controls +visitwhere = 'CRIUGM lobby' +visitwhom = 'Annie' +thisvisit = candidate1.setvisitdate(visitlabel, visitdate, visittime, visitwhere, visitwhom) +candidate1.setnextvisitwindow(candidate1, thisvisit) + +visitlabel = 'V1' #TODO selection from droplist +visitdate = '2014-12-27' #TODO add regex controls +visittime = '14:30' #TODO add regex controls +visitwhere = 'CRIUGM M-124' +visitwhom = 'Jean' +thisvisit = candidate2.setvisitdate(visitlabel, visitdate, visittime, visitwhere, visitwhom) +candidate2.setnextvisitwindow(candidate2, thisvisit) + +visitlabel = 'V1' #TODO selection from droplist +visitdate = '2015-01-13' #TODO add regex controls +visittime = '09:15' #TODO add regex controls +visitwhere = 'McDo' +visitwhom = 'Scott' +thisvisit = candidate3.setvisitdate(visitlabel, visitdate, visittime, visitwhere, visitwhom) +candidate3.setnextvisitwindow(candidate3, thisvisit) + +visitlabel = 'V0' #TODO selection from droplist +visitdate = '2015-02-24' #TODO add regex controls +visittime = '15:30' #TODO add regex controls +visitwhere = 'IGA' +visitwhom = 'Charlie' +thisvisit = candidate4.setvisitdate(visitlabel, visitdate, visittime, visitwhere, visitwhom) +candidate4.setnextvisitwindow(candidate4, thisvisit) + +datamanagement.savecandidatedata(db) + + +""" +######################################################################### +######################################################################### + +print '\nGUI: print on screen all active visits (sorted by datetime)' +currentvisitset = {} +for key, value in candidatedb.iteritems(): + if candidatedb[key].visitset is not None: #skip the search if visitset = None + currentvisitset = candidatedb[key].visitset #set this candidate.visitset for the next step + #gather information about the candidate + candidatekey = candidatedb[key].uid #not printed on screen but saved with the new Scheduler object (after all it is the candidate unique id^^) + candidatefirstname = candidatedb[key].firstname + candidatelastname = candidatedb[key].lastname + for key, value in currentvisitset.iteritems(): + if currentvisitset[key].status is not None: + visitlabel = currentvisitset[key].visitlabel + when = currentvisitset[key].when + where = currentvisitset[key].where + whom = currentvisitset[key].withwhom + status = currentvisitset[key].status + print candidatefirstname, candidatelastname, visitlabel, when, where, whom, status + #create a new scheduler object with these informations + + + +""" From 98eda7ca42018f075fa37d4dbfc631d853dfa400 Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Morin Date: Thu, 4 Jun 2015 12:18:32 -0400 Subject: [PATCH 21/89] Create create_alldata.py --- scheduler/tests/create_alldata.py | 76 +++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 scheduler/tests/create_alldata.py diff --git a/scheduler/tests/create_alldata.py b/scheduler/tests/create_alldata.py new file mode 100644 index 0000000..b92ca8b --- /dev/null +++ b/scheduler/tests/create_alldata.py @@ -0,0 +1,76 @@ +import visit +import candidate +import lib.datamanagement as datamanagement + +#create studysetup +studydb = {} +studyvisit = visit.StudySetup(1, 'V0') +studydb[studyvisit.uid] = studyvisit +studyvisit = visit.StudySetup(2, 'V1', 'V0', 10, 2) +studydb[studyvisit.uid] = studyvisit +studyvisit = visit.StudySetup(3, 'V2', 'V1', 20, 10) +studydb[studyvisit.uid] = studyvisit +datamanagement.savestudydata(studydb) + +#create a list of candidate +candidatedb = {} +candidatedata = candidate.Candidate('Billy', 'Roberts', '451-784-9856', otherphone='514-874-9658') +candidatedb[candidatedata.uid] = candidatedata +candidatedata = candidate.Candidate('Sue', 'Allen', '451-874-9632') +candidatedb[candidatedata.uid] = candidatedata +candidatedata = candidate.Candidate('Alan', 'Parson', '451-874-8965') +candidatedb[candidatedata.uid] = candidatedata +candidatedata = candidate.Candidate('Pierre', 'Tremblay', '547-852-9745') +candidatedb[candidatedata.uid] = candidatedata +candidatedata = candidate.Candidate('Alain', 'Jeanson', '245-874-6321') +candidatedb[candidatedata.uid] = candidatedata +candidatedata = candidate.Candidate('Marc', 'St-Pierre', '412-897-9874') +candidatedb[candidatedata.uid] = candidatedata +datamanagement.savecandidatedata(candidatedb) + +#add visit data to candidates +db = dict(datamanagement.readcandidatedata()) +#get all key values +keylist = [] +for key in db: + keylist.append(key) + +candidate1 = db.get(keylist[0]) +candidate2 = db.get(keylist[1]) +candidate3 = db.get(keylist[2]) +candidate4 = db.get(keylist[3]) +candidate5 = db.get(keylist[4]) + +visitlabel = 'V0' #TODO selection from droplist +visitdate = '2014-12-25' #TODO add regex controls +visittime = '13:15' #TODO add regex controls +visitwhere = 'CRIUGM lobby' +visitwhom = 'Annie' +thisvisit = candidate1.setvisitdate(visitlabel, visitdate, visittime, visitwhere, visitwhom) +candidate1.setnextvisitwindow(candidate1, thisvisit) + +visitlabel = 'V1' #TODO selection from droplist +visitdate = '2014-12-27' #TODO add regex controls +visittime = '14:30' #TODO add regex controls +visitwhere = 'CRIUGM M-124' +visitwhom = 'Jean' +thisvisit = candidate2.setvisitdate(visitlabel, visitdate, visittime, visitwhere, visitwhom) +candidate2.setnextvisitwindow(candidate2, thisvisit) + +visitlabel = 'V1' #TODO selection from droplist +visitdate = '2015-01-13' #TODO add regex controls +visittime = '09:15' #TODO add regex controls +visitwhere = 'McDo' +visitwhom = 'Scott' +thisvisit = candidate3.setvisitdate(visitlabel, visitdate, visittime, visitwhere, visitwhom) +candidate3.setnextvisitwindow(candidate3, thisvisit) + +visitlabel = 'V0' #TODO selection from droplist +visitdate = '2015-02-24' #TODO add regex controls +visittime = '15:30' #TODO add regex controls +visitwhere = 'IGA' +visitwhom = 'Charlie' +thisvisit = candidate4.setvisitdate(visitlabel, visitdate, visittime, visitwhere, visitwhom) +candidate4.setnextvisitwindow(candidate4, thisvisit) + +datamanagement.savecandidatedata(db) From 2a3beeac2af455ebe98b0795231126d2e6b64b56 Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Morin Date: Thu, 4 Jun 2015 12:35:04 -0400 Subject: [PATCH 22/89] Update classtool.py --- scheduler/lib/classtool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scheduler/lib/classtool.py b/scheduler/lib/classtool.py index 143f894..3a3083e 100644 --- a/scheduler/lib/classtool.py +++ b/scheduler/lib/classtool.py @@ -1,6 +1,6 @@ class ClassTool(): """ - Not used! Useful for during development as inheritable class MyClass(ClassTool). + Not used! Useful for development as inheritable class MyClass(ClassTool). """ #no constructor def __repr__(self): From e8fdb557a51bb691066d0c6e6c752151f948831c Mon Sep 17 00:00:00 2001 From: pemorin Date: Mon, 13 Jul 2015 16:52:29 -0400 Subject: [PATCH 23/89] Removed data file extension and renamed candidate data file to candidatedata to avoid confusion with candidate.py class file. --- scheduler/candidate.py | 2 ++ scheduler/lib/datamanagement.py | 14 ++++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/scheduler/candidate.py b/scheduler/candidate.py index 725e09b..8010318 100644 --- a/scheduler/candidate.py +++ b/scheduler/candidate.py @@ -111,3 +111,5 @@ def getactivevisit(self, candidate): activevisit = [candidatefullname, visitlabel, when, where, who] return activevisit + + diff --git a/scheduler/lib/datamanagement.py b/scheduler/lib/datamanagement.py index 443c1a6..850f36d 100644 --- a/scheduler/lib/datamanagement.py +++ b/scheduler/lib/datamanagement.py @@ -21,24 +21,30 @@ def readdata(datafile): #Specific functions def readcandidatedata(): - db = shelve.open("candidate") + db = shelve.open("candidatedata") return db def savecandidatedata(data): - db = shelve.open("candidate") + db = shelve.open("candidatedata") for key in data: db[data[key].uid] = data[key] db.close() def readstudydata(): - db = shelve.open("studysetup.db") + db = shelve.open("studydata") return db def savestudydata(data): - db = shelve.open("studysetup.db") + db = shelve.open("studydata") for key in data: db[data[key].uid] = data[key] db.close() + +#self-test "module" TODO remove +if __name__ == '__main__': + print 'testing module: datamanagement.py' + data=dict(readcandidatedata()); + print data; \ No newline at end of file From 2a1e94323b1c2c2f0e39be1ee469506a3926b44b Mon Sep 17 00:00:00 2001 From: PierreEMorin Date: Tue, 14 Jul 2015 14:16:09 -0400 Subject: [PATCH 24/89] Changed names for data files (candidate for candidatedata and studysetup.db for studydata) for consistency and avoid confusion between candidate and candidate.py class file --- scheduler/candidate | Bin 24599 -> 24576 bytes scheduler/lib/datamanagement.py | 8 ++++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scheduler/candidate b/scheduler/candidate index 4cb3c4ccb4fc2d273fdd3750591e5d72dc49e7b5..1a40bc33f7cbca576221ec0724b082953170ed67 100644 GIT binary patch literal 24576 zcmeI(F$w}f3@qAa9>C%a%p@d3iq|Jey5ZJ2 zA2!)l(hqlANwS*4*ZaEGuXUdH+p#rjMw`=W@5klztKRw|0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zATSHmj{aiLzt4W^FY05_{{L(zBS3%v0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N I0{H;TO^@e((7M=l=osf|&#$cw2yrbYui zyRR29PD8pk(W_f_ANPhu?{Stc7F5@^sm5uRXGNYCbitt>+tka0K9xOdC*%9p0oB44 z#We=j*tNzy0KR>!7vpS7Pxu(exE#R9iQ{l&3?Q(bv_1ekSoPs7n*pt>O6PK;a;B|rSv|>v0}nrWXGwsI+;7JwKMDcu2Wb0X%@cwxmwdYa8na_%x+4px;X z=v`U092FX1_%01-@8K?LyXA5@(6D^u&L0niOV)|*(Sqo zkxnt&${FTjdW>O`JqO3NQM7FsC2|LD-6$@MGKLlQFlF*_iJsq$DK!Rin3xDrtBhp~ z4C4lK7mPiVvw6_;!7N#u-|DUw!U-?VM)I(6ILT#={~m5{hiA@N}KVYeVC>ka_DfdjxzE=0T`5uoIll$ODJE)7LPQvk?1L)1uh=wI?wRty$6*5J~&_Tx`xq>&fih2<`u6{OQHrX3Su zFKrNk@{lAjkxdDOe6&ae0GTUu;Ohn;s7$*=s1gv317l-}sKz#KA#`qI4cnwsl}q7c zN=$@>g%t>L+-M3*U_Y)Ntsl##7#l5Jr8Gr(U5*@2^`Jz(f$PxAHs$D2S$KtVWKPv6 zj0g3p+8QIRf}mDC`fQONM-%-u#`ubZq%=w?tXoHUq3_xC5!_so+T3wt!KR=Hv%?PI zlFxIK#KHc> zCtJCWm!E73E7C<&W;$~as0I<}_2ws=k7tJ`<;NRt8DDG^0q#yF{G~%7>gLCq&2dB( zF5=q_;i9rxpF;>VX%|+Pb+~wG$LOP-k^s!V`G2bu+$)m+zTbZK^EL{NANlV98c5`V z5D)@FKnMr{As_^VfDjM@LO=)z0U;m+gn$qb0zyCt2mv7=1pX@ouCtAH%`8YWUk&+2 zetna%b%!4bN0d<1y1)NeFJ4Q!k@9`B?_nBv^}gSHZ~tGPZUjnZ?ntQZMw;l2(~YY9 zh6sD&IDbwO=u4y=UF0Y1_iTqU^wz0Il+#8RZa4D?tdyB8;;II#TgyCpQ6j-rGmnsy zVC#A=LRG$E=25k?9fX7C_P*(34Sl$dF1>hrA1NT~Tpg{Nd0(yD`yXeM31zP!!K$&r z|6~@Wi-J;&ue`!24OwTaOn}SBVEqkf CA6Tyd diff --git a/scheduler/lib/datamanagement.py b/scheduler/lib/datamanagement.py index 443c1a6..df6d6bd 100644 --- a/scheduler/lib/datamanagement.py +++ b/scheduler/lib/datamanagement.py @@ -21,24 +21,24 @@ def readdata(datafile): #Specific functions def readcandidatedata(): - db = shelve.open("candidate") + db = shelve.open("candidatedata") return db def savecandidatedata(data): - db = shelve.open("candidate") + db = shelve.open("candidatedata") for key in data: db[data[key].uid] = data[key] db.close() def readstudydata(): - db = shelve.open("studysetup.db") + db = shelve.open("studydata") return db def savestudydata(data): - db = shelve.open("studysetup.db") + db = shelve.open("studydata") for key in data: db[data[key].uid] = data[key] db.close() From 311f457f92b1fa583c90838155635a2ca2dac85a Mon Sep 17 00:00:00 2001 From: PierreEMorin Date: Mon, 27 Jul 2015 08:30:34 -0400 Subject: [PATCH 25/89] 2015-07-27: Update --- scheduler/.idea/.name | 1 + scheduler/.idea/codeStyleSettings.xml | 13 + scheduler/.idea/dictionaries/PierreEMorin.xml | 3 + scheduler/.idea/dictionaries/pemorin.xml | 7 + scheduler/.idea/misc.xml | 14 + scheduler/.idea/modules.xml | 8 + scheduler/.idea/scheduler.iml | 8 + scheduler/.idea/vcs.xml | 6 + scheduler/.idea/workspace.xml | 914 ++++++++++++++++++ scheduler/application.py | 66 ++ scheduler/candidate.py | 220 +++-- scheduler/candidatedata | Bin 0 -> 24576 bytes scheduler/create_alldata.py | 103 ++ scheduler/documentation/namingConvention | 17 + scheduler/documentation/someBurningQuestions | 1 + scheduler/lib/__init__.py | 7 + scheduler/lib/datamanagement.py | 22 +- scheduler/lib/multilanguage.py | 198 ++-- scheduler/{candidate => lib/studydata} | Bin 24576 -> 24576 bytes scheduler/lib/utilities.py | 47 +- scheduler/main.py | 10 - scheduler/newwindow.py | 110 --- scheduler/studydata | Bin 0 -> 24576 bytes scheduler/tests/create1_StudySetup.py | 10 +- scheduler/tests/create2_CandidateList_test.py | 2 +- scheduler/tests/create3_CandidateVisit.py | 8 +- scheduler/tests/create4_PopulateVisits.py | 20 +- scheduler/tests/create_alldata.py | 32 +- scheduler/tests/test.py | 2 +- scheduler/ui/__init__.py | 8 + scheduler/ui/datatable.py | 134 +++ scheduler/ui/datawindow.py | 143 +++ scheduler/{ => ui}/dialogbox.py | 22 +- scheduler/ui/menubar.py | 93 ++ scheduler/ui/projectpane.py | 6 + scheduler/userinterface.py | 256 ----- scheduler/visit.py | 107 +- 37 files changed, 1960 insertions(+), 658 deletions(-) create mode 100644 scheduler/.idea/.name create mode 100644 scheduler/.idea/codeStyleSettings.xml create mode 100644 scheduler/.idea/dictionaries/PierreEMorin.xml create mode 100644 scheduler/.idea/dictionaries/pemorin.xml create mode 100644 scheduler/.idea/misc.xml create mode 100644 scheduler/.idea/modules.xml create mode 100644 scheduler/.idea/scheduler.iml create mode 100644 scheduler/.idea/vcs.xml create mode 100644 scheduler/.idea/workspace.xml create mode 100644 scheduler/application.py create mode 100644 scheduler/candidatedata create mode 100644 scheduler/create_alldata.py create mode 100644 scheduler/documentation/namingConvention create mode 100644 scheduler/documentation/someBurningQuestions rename scheduler/{candidate => lib/studydata} (95%) delete mode 100644 scheduler/main.py delete mode 100644 scheduler/newwindow.py create mode 100644 scheduler/studydata create mode 100644 scheduler/ui/__init__.py create mode 100644 scheduler/ui/datatable.py create mode 100644 scheduler/ui/datawindow.py rename scheduler/{ => ui}/dialogbox.py (85%) create mode 100644 scheduler/ui/menubar.py create mode 100644 scheduler/ui/projectpane.py delete mode 100644 scheduler/userinterface.py diff --git a/scheduler/.idea/.name b/scheduler/.idea/.name new file mode 100644 index 0000000..ec7c259 --- /dev/null +++ b/scheduler/.idea/.name @@ -0,0 +1 @@ +scheduler \ No newline at end of file diff --git a/scheduler/.idea/codeStyleSettings.xml b/scheduler/.idea/codeStyleSettings.xml new file mode 100644 index 0000000..7fb8ed0 --- /dev/null +++ b/scheduler/.idea/codeStyleSettings.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/scheduler/.idea/dictionaries/PierreEMorin.xml b/scheduler/.idea/dictionaries/PierreEMorin.xml new file mode 100644 index 0000000..c82ff06 --- /dev/null +++ b/scheduler/.idea/dictionaries/PierreEMorin.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/scheduler/.idea/dictionaries/pemorin.xml b/scheduler/.idea/dictionaries/pemorin.xml new file mode 100644 index 0000000..877122b --- /dev/null +++ b/scheduler/.idea/dictionaries/pemorin.xml @@ -0,0 +1,7 @@ + + + + dictionnary + + + \ No newline at end of file diff --git a/scheduler/.idea/misc.xml b/scheduler/.idea/misc.xml new file mode 100644 index 0000000..27b342e --- /dev/null +++ b/scheduler/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/scheduler/.idea/modules.xml b/scheduler/.idea/modules.xml new file mode 100644 index 0000000..7e8c9ec --- /dev/null +++ b/scheduler/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/scheduler/.idea/scheduler.iml b/scheduler/.idea/scheduler.iml new file mode 100644 index 0000000..9ea38e6 --- /dev/null +++ b/scheduler/.idea/scheduler.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/scheduler/.idea/vcs.xml b/scheduler/.idea/vcs.xml new file mode 100644 index 0000000..6564d52 --- /dev/null +++ b/scheduler/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/scheduler/.idea/workspace.xml b/scheduler/.idea/workspace.xml new file mode 100644 index 0000000..9222d27 --- /dev/null +++ b/scheduler/.idea/workspace.xml @@ -0,0 +1,914 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Buildout + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1437155263605 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/scheduler/application.py b/scheduler/application.py new file mode 100644 index 0000000..c1daa7e --- /dev/null +++ b/scheduler/application.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python + +#import standard packages +from Tkinter import * +from ttk import * +#import internal packages +import ui.menubar as MenuBar +import ui.datatable as DataTable +import lib.multilanguage as MultiLanguage +import lib.datamanagement as DataManagement +import ui.projectpane as ProjectPane #TODO create classe for project info pane + +class UserInterface(Frame): + def __init__(self, parent): + Frame.__init__(self) + self.parent = parent + self.parent.title(MultiLanguage.app_title) + self.pack(side=TOP, expand=YES, fill=BOTH, padx=10, pady=10) + # TODO create classe for project info pane + self.project_infopane = Labelframe(self, text=MultiLanguage.project_info_pane, width=250, height=350, + borderwidth=10) # TODO add dynamic resize + self.project_infopane.pack(side=LEFT, expand=NO, fill=BOTH) + # This area (datapane) is composed of one Panedwindow containing two Labelframe + self.data_pane = Panedwindow(self, width=1000, height=500, orient=HORIZONTAL) # TODO add dynamic resize + self.data_pane.pack(side=RIGHT, expand=YES, fill=BOTH) + self.candidate_pane = Labelframe(self.data_pane, text=MultiLanguage.candidate_pane, width=100, height=450, + borderwidth=10) # TODO add dynamic resize + self.visit_pane = Labelframe(self.data_pane, text=MultiLanguage.calendar_pane, width=100, height=350, + borderwidth=10) # TODO add dynamic resize + self.data_pane.add(self.candidate_pane) + self.data_pane.add(self.visit_pane) + + #create a filter section in each data_pane(not implemented yet) + #TODO This whole section needs to be replaced by REAL CODE actively filtering the data + self.filter_candidate = Labelframe(self.candidate_pane, text='Filters', width=220, height=50, borderwidth=10) + self.filter_candidate.pack(side=TOP, expand=NO, fill=BOTH, pady=5) + self.filter_candidate_label = Label(self.filter_candidate, text='Filter for Non-Active / Active / Excluded / Group...') + self.filter_candidate_label.pack(side=TOP, expand=NO, fill=BOTH) + self.filter_visit = Labelframe(self.visit_pane, text='Filters', width=220, height=50, borderwidth=10) + self.filter_visit.pack(side=TOP, expand=NO, fill=BOTH, pady=5) + self.filter_candidate_label = Label(self.filter_visit, text='Filters for Active / Tentative / Closed ...') + self.filter_candidate_label.pack(side=TOP, expand=NO, fill=BOTH) + + + # get data from shelve files + data = dict(DataManagement.read_candidate_data()) # TODO place data management elsewhere + + # create data tables (treeview) + visit_column_headers = ('candidate', 'visitlabel', 'when', 'where', 'status') + self.visit_table = DataTable.DataTable(self.visit_pane, data, visit_column_headers) + self.visit_table.pack(side=BOTTOM, expand=YES, fill=BOTH) + column_header = ('firstname', 'lastname', 'phone', 'status') + data_table = DataTable.DataTable(self.candidate_pane, data, column_header) + data_table.pack(side=BOTTOM, expand=YES, fill=BOTH) + +class Application(Tk): + def __init__(self): + Tk.__init__(self) + menu = MenuBar.MenuBar(self) + self.config(menu=menu) + frame = UserInterface(self) + +#Application main loop +if __name__ == "__main__": + app=Application() + app.mainloop() \ No newline at end of file diff --git a/scheduler/candidate.py b/scheduler/candidate.py index 725e09b..8d0591e 100644 --- a/scheduler/candidate.py +++ b/scheduler/candidate.py @@ -1,13 +1,35 @@ +#import standard packages import datetime import visit -import lib.datamanagement as datamanagement -import lib.multilanguage as multilanguage -import lib.utilities as utilities -from uuid import uuid1 +#import internal packages +import lib.datamanagement as DataManagement +import lib.multilanguage as MultiLanguage +import lib.utilities as Utilities class Candidate(): + """ + The Candidate() class defines the candidates/participants of the study + + Attributes: + uid: A unique identifier using python's uuid1 method. Used as key to store and retrieve objects from + files and/or dictionaries. + firstname: First name of the candidate + lastname: Last name of the candidate + visitset: A dictionnary containing all visits (planed or not) + phone: A primary phone number + status: Status of this candidate + pscid: Loris (clinical results database) specific ID + + kwargs: Not implemented yet! + + Code example: + candidatedb = {} #setup a dict to receive the candidates + candidatedata = candidate.Candidate('Billy', 'Roberts', '451-784-9856', otherphone='514-874-9658') #instanciate one candidate + candidatedb[candidatedata.uid] = candidatedata #add candidate to dict + DataManagement.save_candidate_data(candidatedb) #save data to file + """ def __init__(self, firstname, lastname, phone, uid=None, visitset = None, status = None, pscid=None, **kwargs): #TODO *kwarg - self.uid = utilities.generateUniqueID() + self.uid = Utilities.generate_uid() self.firstname = firstname self.lastname = lastname self.visitset = visitset @@ -18,96 +40,154 @@ def __init__(self, firstname, lastname, phone, uid=None, visitset = None, status if kwargs is not None: for key, value in kwargs.iteritems(): setattr(self, key, value) - - - def setupvisitset(self): - #open studysetup.db and 'parse' the dict to a sorted list (dict cannot be sorted)... - visitlist =[] - visitlabel_templist = [] - try: - studysetup = dict(datamanagement.readstudydata()) #TODO replace datafile name + + def setup_visitset(self): + """ + When creating the first visit for a candidate, a complete set of visits is added based on a study/project visit list + There are no parameters passed to this method since it will simply create a new 'empty' + This method will: + 1-open studydata (the study visit list) and 'parse' the dict to a sorted list (dict cannot be sorted) + 2-create a temporary list of visit_labels that will serve as a key within the Candidate.visit_set dictionary + 3-instantiate individual visits based on the study visit list + Usage: + Called by Candidate.set_visit_date() + """ + visit_list =[] + visit_label_templist = [] + study_setup = dict() + try: + #1-open studydata + study_setup = dict(DataManagement.read_studydata()) except Exception as e: - print str(e) #TODO add error login - for key, value in studysetup.iteritems(): - visitlist.append(studysetup[key]) - visitlist = sorted(visitlist, key=lambda visit: visit.rank) - #...and create a temporary list of visitlabels that will serve a key within the Candidate.visitset dictionary - for each in visitlist: - visitlabel_templist.append(each.visitlabel) #we now have a list with all visit labels included in the study - #instantiate individual visit based on each instance of StudySetup() + print str(e) #TODO add error login (in case a study data file does not exist) + for key, value in study_setup.iteritems(): + #2-parse into a sorted list + visit_list.append(study_setup[key]) + visit_list = sorted(visit_list, key=lambda visit: visit.rank) + #create a temporary list of visitlabels + for each in visit_list: + visit_label_templist.append(each.visitlabel) + #3-instantiate individual visit based on each instance of VisitSetup() self.visitset = {} #setup a dict to receive a set of Visit() count = 0 - #set values of : uid, rank, visitlabel, previousvisit, visitwindow, visitmargin, - for key in visitlist: - mainkey = str(visitlabel_templist[count]) + #set values of : uid, rank, visit_label, previous_visit, visit_window, visitmargin, + for key in visit_list: + mainkey = str(visit_label_templist[count]) rank =key.rank - visitlabel = key.visitlabel - previousvisit = key.previousvisit - visitwindow = key.visitwindow - visitmargin = key.visitmargin - visitdata = visit.Visit(rank, visitlabel, previousvisit, visitwindow, visitmargin,) - self.visitset[mainkey] = visitdata + visit_label = key.visitlabel + previous_visit = key.previousvisit + visit_window = key.visitwindow + visit_margin = key.visitmargin + visit_data = visit.Visit(rank, visit_label, previous_visit, visit_window, visit_margin,) + self.visitset[mainkey] = visit_data count += 1 - self.status = multilanguage.status_active - - def setvisitdate(self, visitlabel, visitdate, visittime, visitwhere, visitwhom): - #check to see if visitset == None before trying to create a new date instance + def set_visit_date(self, visitlabel, visitdate, visittime, visitwhere, visitwhom): + """ + This method update the visit information (according to visitlabel) + This method will: + 1-Check if visitset==None. If so, then Candidate.setup_visitset() is called to setup Candidate.visitset + 2-Check if a date is already set for this visitlabel + 3-Set values of Visit.when, 'Visit.where, Visit.whithwhom and Visit.status for current visit + Usage: Called by GUI methods + Return: current_visit as current Visit(Visit(VisitSetup) instance + """ + #1-Check to see if visitset == None before trying to create a new date instance if self.visitset is None: - self.setupvisitset() + self.setup_visitset() + self.set_candidate_status_active() #Candidate.status='active' since we're setting up a first visit #get current visit within visitset - current = self.visitset.get(visitlabel) - #check to see if this visit already has a date - if current.when is not None: - print "date already exists" #TODO add confirmation of change log??? + current_visit = self.visitset.get(visitlabel) + #2-Check to see if this visit already has a date + if current_visit.when is not None: + print "date already exists" #TODO add confirmation of change log??? pass #concatenate visitdate and visittime and parse into a datetime object visitwhen = visitdate + ' ' + visittime when = datetime.datetime.strptime(visitwhen, '%Y-%m-%d %H:%M') - #set 'current' values of : when, where, withwhom, status - current.when = when - current.where = visitwhere - current.withwhom = visitwhom - current.status = "active" - return current + #3-Set values of Visit.when, 'Visit.where, Visit.whithwhom and Visit.status for current visit + current_visit.when = when + current_visit.where = visitwhere + current_visit.withwhom = visitwhom + current_visit.status = "active" #that is the status of the visit + return current_visit + - - def setnextvisitwindow(self, candidate, currentvisit): - #get the current visit object as argument. Will search and look for the next visit (visit where previousvisit == currentvisitlabel) - nextvisitsearchset = candidate.visitset - currentvisitlabel = currentvisit.visitlabel - nextvisit = "" - for key in nextvisitsearchset: - visitdata = nextvisitsearchset[key] - if visitdata.previousvisit == currentvisitlabel: - nextvisit = candidate.visitset.get(visitdata.visitlabel) #TODO debug when current is last visit - #gather info about currentvisit (mostly for my own internal computer!) - currentvisitdate = currentvisit.when - currentvisityear = currentvisitdate.year #get the year of the current visit date - nextvisitwindow = nextvisit.visitwindow - nextvisitmargin = nextvisit.visitmargin + """ + def set_next_visit_window(self, candidate, current_visit): + #get the current visit object as argument. Will search and look for the next visit (visit where previousvisit == current_visit_label) + next_visit_searchset = candidate.visitset + current_visit_label = current_visit.visitlabel + next_visit = "" + for key in next_visit_searchset: + visit_data = next_visit_searchset[key] + if visit_data.previousvisit == current_visit_label: + next_visit = candidate.visitset.get(visit_data.visitlabel) #TODO debug when current is last visit + #gather info about current_visit (mostly for my own biological computer! else I get lost) + current_visit_date = current_visit.when + current_visit_year = current_visit_date.year #get the year of the current visit date + next_visit_window = next_visit.visitwindow + next_visit_margin = next_visit.visitmargin #set dates for the next visit - nextvisitearly = int(currentvisitdate.strftime('%j')) + (nextvisitwindow - nextvisitmargin) #this properly handle change of year - nextvisitearlydate = datetime.datetime(currentvisityear, 1, 1) + datetime.timedelta(nextvisitearly - 1) - nextvisitlate = int(currentvisitdate.strftime('%j')) + (nextvisitwindow + nextvisitmargin) - nextvisitlatedate = datetime.datetime(currentvisityear, 1, 1) + datetime.timedelta(nextvisitlate - 1) - nextvisit.whenearliest = nextvisitearlydate - nextvisit.whenlatest = nextvisitlatedate - nextvisit.status = "tentative" + next_visit_early = int(current_visit_date.strftime('%j')) + (next_visit_window - next_visit_margin) #this properly handle change of year + next_visit_early_date = datetime.datetime(current_visit_year, 1, 1) + datetime.timedelta(next_visit_early - 1) + next_visit_late = int(current_visit_date.strftime('%j')) + (next_visit_window + next_visit_margin) + next_visit_late_date = datetime.datetime(current_visit_year, 1, 1) + datetime.timedelta(next_visit_late - 1) + next_visit.when_earliest = next_visit_early_date + next_visit.when_latest = next_visit_late_date + next_visit.status = "tentative" + Utilities.print_object((next_visit)) + """ + def set_next_visit_window(self, candidate, current_visit): + """ + This method will 'calculate' a min and max date when the next visit should occur + 1-Get candidate.visitset (as visit_searchset) and current_visit.visitlabel + 2-Identify which visit (in visitset) has previousvisit == current_visit.visitlabel + 3-Get + Usage: Currently called by GUI function (TODO RETHINK THIS LOGIC maybe it should be called when running Candidate.set_visit_date()) + """ - def getactivevisit(self, candidate): + #get the current visit object as argument. Will search and look for the next visit (visit where previousvisit == current_visitlabel) + + #1- Get Candidate.visitset and current_visit / next_visit will == Visit(VisitSetup) of the next visit (relative to current_visit) + visit_searchset = candidate.visitset + current_visitlabel = current_visit.visitlabel + next_visit = "" + #2-Identify which visit (in visitset) has previousvisit == current_visit.visitlabel + for key in visit_searchset: + visit_data = visit_searchset[key] + if visit_data.previousvisit == current_visitlabel: + next_visit = candidate.visitset.get(visit_data.visitlabel) #TODO debug when current is last visit + #3-Calculate a min and max date for the next visit to occur based on Visit.visitwindow and Visit.visitmargin + current_visitdate = current_visit.when + current_visityear = current_visitdate.year #get the year of the current visit date + next_visitwindow = next_visit.visitwindow + nextvisitmargin = next_visit.visitmargin + #set dates for the next visit + nextvisitearly = int(current_visitdate.strftime('%j')) + (next_visitwindow - nextvisitmargin) #this properly handle change of year + nextvisitearlydate = datetime.datetime(current_visityear, 1, 1) + datetime.timedelta(nextvisitearly - 1) + nextvisitlate = int(current_visitdate.strftime('%j')) + (next_visitwindow + nextvisitmargin) + nextvisitlatedate = datetime.datetime(current_visityear, 1, 1) + datetime.timedelta(nextvisitlate - 1) + next_visit.whenearliest = nextvisitearlydate + next_visit.whenlatest = nextvisitlatedate + next_visit.status = "tentative" #set this visit.status + + def get_active_visit(self, candidate): candidatefullname = str(candidate.firstname + ' ' + candidate.lastname) currentvisitset = candidate.visitset + activevisit = [] if currentvisitset is None: return elif currentvisitset is not None: for key in currentvisitset: - if currentvisitset[key].status == multilanguage.status_active: + if currentvisitset[key].status == MultiLanguage.status_active: visitlabel = currentvisitset[key].visitlabel when = currentvisitset[key].when.strftime('%Y-%m-%d %Hh%m') where = currentvisitset[key].where who = currentvisitset[key].withwhom activevisit = [candidatefullname, visitlabel, when, where, who] return activevisit - + + def set_candidate_status_active(self): + self.status = MultiLanguage.status_active #set the Candidate.status to 'active' \ No newline at end of file diff --git a/scheduler/candidatedata b/scheduler/candidatedata new file mode 100644 index 0000000000000000000000000000000000000000..7d7604dc92efe997137745c31e7e7854086effc9 GIT binary patch literal 24576 zcmeI3TW{k;6oAu}SirE-zU;oN1pAf>Dadp2?XeWG3Zko31AVHH#+Nu2sgq6I3qN8X z_$&Mqp7|xban5m^_>z)V&2kZ^QtBCdJRW~D^UX=(^m@JC0le_fJbT>x7!xny^${j| zy+`Gy@&EBJSn5eH_~sK)9l$!Oda8ZDy_@{FZGUx~w@iQuFaajO1egF5U;<2l2`~XB zzyz286JP>NfC(@GCcp%k025#WOn?b60Vco%n85xBw7;XbdLMq;d-D6|U%dF>#oOoK zJs&?idiLwncTZt2|1kk3zyz286JP>NfC(@GCh*@UaB!GKCXch&6p3}bPR)gN7|*3O z>MsjZTrT_8TtUV}MRt`yMqA5Kf4+>e7&4Ca8giGEcA=CuV<97dJWx{F10fP;;K$y0 zFphK_>sUolkN?Ws#c;tkj2dPs}2MWPrz}W_?K5aBNfz-ek#Qfn|lj5?9$W zE0#%tJ0!MPU@OmT%xmi~qvO^YmV!MFm2j<8QG@B=g;}Ioj^#brnNssZ+c%N2Yt1*Y z6SbQ!d~Ct>Bq`eyZ0fwu@_2R)bxM1)b85n5iu;6>Lfgty@xoG4p@qqR!kn^(bZdxu zmLqtnsQ_FlPt0PPB}=?tSyG=blB;ZXxul!H-R`>Z6uPu5inliE&*oU8p++F;*E1|n zq_4ALay^+{zz&6N4x#v&z#NQ&CPhV@l|qUs+(!!Fh7z_lbfSjNcb%|}PSJS*c6anO zwn8~j0ni#kKSKf#!G$3qtq2EZkzFL#Ix$zSbvWvu-`L*yO+1FJIA5F>u*I}pfAy{6 zbgp2arN|XqNib7_mjbKRU{TPQsiYAgeJe#MDS&_>7tIQ!tzZv9k&w}L8;Yjsq9sBZ z^@}8jNwbYbt7&RpNBs-vkPbI=TGyXxD-J~gpp!P3T9cx^!vTf>6jn3V-VYH;zo1#uo9 z<#~qV7In2TBCH}UBf?CH(K4OMPg-gCvFxt8AC2SN?e0R`qyp~&r~FEaC>NTmxO$DXAk%8O*8K{Sr;+7vvMs+_LM|S;)qS`Pj{RoUS8F9^saup-wMt4i5LtD|H=(HQg zLc>{7Lj>dLfvY5)SvhOBJMQBwT%)Uys$BBRkjg=8n#2iB-0+l+{Pw^BCe4t_gLBt0 z69p_aQ6Y-&7s*99HK+hwp_(C8IsYpZ5Mhn_j`jw=BL{(}9Xtp4lD>C0?>;DQk2Hru zD($!=%<*fH9oq;IlfimN7HGt2ZdwD{zb4U?9i^q%7L+EyJI^7 zU)Q;@_5U!~1azXl_64@xS-UsE=4#1183>7?#{+`SYC#LZg^(7QZ)ldB_>jO8;|FXb zss@J7*NfC(@GCcp%k025#WOn?b60Vco%{&52Ld7Nj*BboR+ wk;a1+N!WR4VCy59@aQ-`-L>t3%r|DSdT#tKk7MFv0{CRAAGpoOF?TlYPf_sqWB>pF literal 0 HcmV?d00001 diff --git a/scheduler/create_alldata.py b/scheduler/create_alldata.py new file mode 100644 index 0000000..a45555d --- /dev/null +++ b/scheduler/create_alldata.py @@ -0,0 +1,103 @@ +import visit +import candidate +import lib.datamanagement as DataManagement +import lib.utilities as Utilities + +#create studysetup +#saving (DataManagement.save_study_data(studydb)) after each visit is really not necessary +#but this mimics the way the application will work +# rank, visitlabel, previousvisit=None, visitwindow = None, visitmargin = None, optional = 'No', actions = None, uid=None +# +studydb = {} +studyvisit = visit.VisitSetup(1, 'V0', None) +studydb[studyvisit.uid] = studyvisit +DataManagement.save_study_data(studydb) +studyvisit = visit.VisitSetup(2, 'V1', 'V0', 10, 2) +studydb[studyvisit.uid] = studyvisit +DataManagement.save_study_data(studydb) +studyvisit = visit.VisitSetup(3, 'V2', 'V1', 20, 10) +studydb[studyvisit.uid] = studyvisit +DataManagement.save_study_data(studydb) + +#create a list of candidate +#saving (DataManagement.save_candidate_data(candidatedb)) after each candidate is really not necessary +#firstname, lastname, phone, uid=None, visitset = None, status = None, pscid=None, **kwargs +#but this mimics the way the application wil work +candidatedb = {} +candidatedata = candidate.Candidate('Billy', 'Roberts', '451-784-9856', otherphone='514-874-9658') +candidatedb[candidatedata.uid] = candidatedata +DataManagement.save_candidate_data(candidatedb) +Utilities.print_object(candidatedata) + +candidatedata = candidate.Candidate('Sue', 'Allen', '451-874-9632', None, None, None, 1234567) +candidatedb[candidatedata.uid] = candidatedata +DataManagement.save_candidate_data(candidatedb) + +candidatedata = candidate.Candidate('Alan', 'Parson', '451-874-8965') +candidatedb[candidatedata.uid] = candidatedata +DataManagement.save_candidate_data(candidatedb) + +candidatedata = candidate.Candidate('Pierre', 'Tremblay', '547-852-9745') +candidatedb[candidatedata.uid] = candidatedata +DataManagement.save_candidate_data(candidatedb) + +candidatedata = candidate.Candidate('Alain', 'Jeanson', '245-874-6321') +candidatedb[candidatedata.uid] = candidatedata +DataManagement.save_candidate_data(candidatedb) + +candidatedata = candidate.Candidate('Marc', 'St-Pierre', '412-897-9874') +candidatedb[candidatedata.uid] = candidatedata +DataManagement.save_candidate_data(candidatedb) + + +#add visit data to candidates +db = dict(DataManagement.read_candidate_data()) +#get all key values +keylist = [] +for key in db: + keylist.append(key) + +candidate1 = db.get(keylist[0]) +visitlabel = 'V0' #TODO selection from droplist +visitdate = '2014-12-25' #TODO add regex controls +visittime = '13:15' #TODO add regex controls +visitwhere = 'CRIUGM lobby' +visitwhom = 'Annie' +thisvisit = candidate1.set_visit_date(visitlabel, visitdate, visittime, visitwhere, visitwhom) +candidate1.set_next_visit_window(candidate1, thisvisit) +DataManagement.save_candidate_data(db) + + +candidate2 = db.get(keylist[1]) +visitlabel = 'V1' #TODO selection from droplist +visitdate = '2014-12-27' #TODO add regex controls +visittime = '14:30' #TODO add regex controls +visitwhere = 'CRIUGM M-124' +visitwhom = 'Jean' +thisvisit = candidate2.set_visit_date(visitlabel, visitdate, visittime, visitwhere, visitwhom) +candidate2.set_next_visit_window(candidate2, thisvisit) +DataManagement.save_candidate_data(db) + +candidate3 = db.get(keylist[2]) +visitlabel = 'V1' #TODO selection from droplist +visitdate = '2015-01-13' #TODO add regex controls +visittime = '09:15' #TODO add regex controls +visitwhere = 'McDo' +visitwhom = 'Scott' +thisvisit = candidate3.set_visit_date(visitlabel, visitdate, visittime, visitwhere, visitwhom) +candidate3.set_next_visit_window(candidate3, thisvisit) +DataManagement.save_candidate_data(db) +Utilities.print_object(thisvisit) + +candidate4 = db.get(keylist[3]) +visitlabel = 'V0' #TODO selection from droplist +visitdate = '2015-02-24' #TODO add regex controls +visittime = '15:30' #TODO add regex controls +visitwhere = 'IGA' +visitwhom = 'Charlie' +thisvisit = candidate4.set_visit_date(visitlabel, visitdate, visittime, visitwhere, visitwhom) +candidate4.set_next_visit_window(candidate4, thisvisit) + +Utilities.print_object(thisvisit) + +DataManagement.save_candidate_data(db) \ No newline at end of file diff --git a/scheduler/documentation/namingConvention b/scheduler/documentation/namingConvention new file mode 100644 index 0000000..6e15538 --- /dev/null +++ b/scheduler/documentation/namingConvention @@ -0,0 +1,17 @@ +Naming convention according to https://www.python.org/dev/peps/pep-0008/ + +Indentation: Use 4 spaces per indentation level + +Package and Module Names: Should be called one per line (yes: import os; no: import os, tkinter, ttk) + +Class names: Use the CapWords convention + +Function names: Use lowercase with words separated by underscores + +Method names: Use lowercase with words separated by underscores + Use one leading underscore only for non-public methods and instance variables + To avoid name clashes with subclasses, use two leading underscores to invoke Python's name mangling rules. + +Instance variable names: Use lowercase with words separated by underscores + +Constant names: Use uppercase with words separated by underscores. Constant should only be defined at the module level. \ No newline at end of file diff --git a/scheduler/documentation/someBurningQuestions b/scheduler/documentation/someBurningQuestions new file mode 100644 index 0000000..e1ebd72 --- /dev/null +++ b/scheduler/documentation/someBurningQuestions @@ -0,0 +1 @@ +How do deal with the shebang line (#!/usr/bin/env python) for Window and Mac? \ No newline at end of file diff --git a/scheduler/lib/__init__.py b/scheduler/lib/__init__.py index 8b13789..5c9d53e 100644 --- a/scheduler/lib/__init__.py +++ b/scheduler/lib/__init__.py @@ -1 +1,8 @@ +""" +From the documentation at https://docs.python.org/2/tutorial/modules.html#packages +The __init__.py files are required to make Python treat the directories as containing packages; this is done to prevent +directories with a common name, such as string, from unintentionally hiding valid modules that occur later on the module +search path. In the simplest case, __init__.py can just be an empty file, but it can also execute initialization code +for the package or set the __all__ variable, described later. +""" diff --git a/scheduler/lib/datamanagement.py b/scheduler/lib/datamanagement.py index df6d6bd..713a387 100644 --- a/scheduler/lib/datamanagement.py +++ b/scheduler/lib/datamanagement.py @@ -1,9 +1,10 @@ +#imports from standard packages import shelve """ The data_management.py file contains functions related to data management only. -Generic functions: savedata(data, datafilename) and readdata(datafile) +Generic functions: savedata(data, datafilename) and readdata(datafile). Currently, these are not being used. -Specific functions readcandidatedata(), savecandidatedata(), readstudydata() and savestudydata() are used to get/save candidate data and study setup data respectively. +Specific functions read_candidate_data(), save_candidate_data(), read_studydata() and save_study_data() are used to get/save candidate data and study setup data respectively. """ """ #Generic functions @@ -20,25 +21,28 @@ def readdata(datafile): """ #Specific functions -def readcandidatedata(): +def read_candidate_data(): db = shelve.open("candidatedata") return db - -def savecandidatedata(data): +def save_candidate_data(data): db = shelve.open("candidatedata") for key in data: db[data[key].uid] = data[key] db.close() - -def readstudydata(): +def read_studydata(): db = shelve.open("studydata") return db - -def savestudydata(data): +def save_study_data(data): db = shelve.open("studydata") for key in data: db[data[key].uid] = data[key] db.close() + +#self-test "module" TODO remove +if __name__ == '__main__': + print 'testing module: datamanagement.py' + data=dict(read_candidate_data()); + print data; diff --git a/scheduler/lib/multilanguage.py b/scheduler/lib/multilanguage.py index 721e558..2e27cf4 100644 --- a/scheduler/lib/multilanguage.py +++ b/scheduler/lib/multilanguage.py @@ -1,58 +1,57 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# read language preference from appdata file +#read language preference from appdata file #from utilities import readappdata #TODO replace and remove #language = readappdata()[0] -language = "en" - -if language == "fr": #TODO make dynamic +language = "fr" #TODO make dynamic +if language == "fr": ###################### TOP LEVEL MENU BAR ###################### - apptitle = u"Outils LORIS" + app_title = u"Outils LORIS" #APPLICATION menu - menuapplication = u"Application" - settingapplication = u"Préferences" - quitapplication = u"Quitter" + application_menu = u"Application" + application_setting = u"Préferences" + application_quit = u"Quitter" #PROJECT menu #menuproject = u"Projet" #TODO remove? #openproject = u"Ouvrir un projet" #modifyproject = u"Modifier le projet ouvert" #newproject = u"Créer un nouveau projet" #CANDIDATE menu - menucandidate = "Candidat" - addcandidate = u"Nouveau candidat" - findcandidate = u"Trouver candidat" - updatecandidate = u"Mettre à jour" - excludecandidate = u"Exclure un candidat" - getcandidateid = u"Obtenir l'identifiant d'un candidat" - clearallfield = u"Effacer" + candidate_menu = u"Candidat" + candidate_add = u"Nouveau candidat" + candidate_find = u"Trouver candidat" + candidate_update = u"Mettre à jour" + candidate_exclude_include_toggle = u"Inclure/Exclure un candidat" + candidate_get_id = u"Obtenir l'identifiant d'un candidat" + #clear_all_field = u"Effacer" #ANONYMIZER menu - menuanonymizer = u"DICOM" + anonymizer_menu = u"DICOM" + anonymizer_run = u"Anonymizer" #CALENDAR menu - menucalendar = u"Calendrier" - newappointment = u"Nouveau Rendez-vous" + calendar_menu = u"Calendrier" + calendar_new_appointment = u"Nouveau Rendez-vous" #HELP menu - menuhelp = u"Aide" - gethelp = u"Obtenir de l'aide" - aboutwindow = u"A propos de ..." + help_menu = u"Aide" + help_get_help = u"Obtenir de l'aide" + help_about_window = u"A propos de ..." ###################### PROJECT INFO PANE ####################### - project_infopane = u"Informations" - project_detailpane = u"Détails du Projet" - visit_detailpane = u"Détails des Visites" - projectname = u"Projet" - projectstart = u"Début" - projectend = u"Fin" - targetrecruitment = u"Cible de recrutement" - currentrecruitment = u"Recrutement actuel" - totalvisit = u"Nombre de Visites" + project_info_pane = u"Projet" + project_detail_pane = u"Détails du Projet" + visit_detail_pane = u"Détails des Visites" + project_name = u"Projet" + project_start = u"Début" + project_end = u"Fin" + target_recruitment = u"Cible de recrutement" + current_recruitment = u"Recrutement actuel" + total_visit = u"Nombre de Visites" #################### MULTI-TAB DATA SECTION ##################### calendar_pane = u"Calendrier" candidate_pane = u"Candidats" - - - labelcandidatetable = u"Faites un double-clic sur l'une des lignes pour remplir les champs ci-dessus" + + label_candidate_table = u"Faites un double-clic sur l'une des lignes pour remplir les champs ci-dessus" datatable_id = u"ID" datatable_firstname = u"Prénom" datatable_lastname = "Nom" @@ -62,9 +61,8 @@ datatable_city = u"Ville" datatable_province = u"Province" datatable_country = u"Pays" - datatable_postalcode = u"Code Postal" - - + datatable_postal_code = u"Code Postal" + calendar_monday = u"Lundi" calendar_tuesday = u"Mardi" calendar_wednesday = u"Mercredi" @@ -84,83 +82,91 @@ calendar_october = u"Octobre" calendar_november = u"Novembre" calendar_december = u"Décembre" + ################ COLUMN HEADER ################## col_candidate = u"Candidat" col_visitlabel = u"Visite" col_when = u"Date/Heure" col_where = u"Endroit" col_status = u"Statut" - #################### STATUS ##################### - status_active = "actif" - status_tentative = "provisoire" - + status_active = u"actif" + status_tentative = u"provisoire" ################# DATA WINDOWS ################## - schedulewindow_title ="Calendrier" - candidatewindow_title = "Information du candidat" - - + data_window_title = u"DATA WINDOW" #TODO trouver un titre français ################## DIALOGBOX #################### - dialog_yes = "Oui" - dialog_no = "Non" - dialogtitle_confirm = "Veuillez confirmer!" - dialogclose = "Vous êtes sur le point de fermer cette fenêtre sans sauvegarder! Voulez-vous continuer?" - - candidate_firstname = "prénom" + # very not sure what to do about that section + dialog_yes = u"Oui" + dialog_no = u"Non" + dialog_title_confirm = u"Veuillez confirmer!" + dialog_close = u"Vous êtes sur le point de fermer cette fenêtre sans sauvegarder!\n\nVoulez-vous continuer?" + ################ DATA WINDOW ################### + schedule_pane = u"Calendrier" + candidate_pane = u"Candidat" + candidate_firstname = u"Prénom" + candidate_lastname = u"Nom de famille" + candidate_phone = u"Téléphone" + candidate_pscid = u"ID" + candidate_status = u"Status" + schedule_visit_label = u"Visite" + schedule_visit_rank = u"#" + schedule_visit_status = u"Status" + schedule_visit_when = u"Date" + schedule_optional =u"Optionnel" elif language == "en": - apptitle = "LORIS tools" + app_title = u"LORIS tools" #APPLICATION menu - menuapplication = u"Application" - settingapplication = u"Preferences" - quitapplication = u"Quit" + application_menu = u"Application" + application_setting = u"Preferences" + application_quit = u"Quit" #PROJECT menu #menuproject = u"Project" #TODO remove? #openproject = u"Open project" #modifyproject = u"Modify open project" #newproject = u"New project" #CANDIDATE menu - menucandidate = u"Candidate" - addcandidate = u"New candidate" - findcandidate = u"Find a candidate" - updatecandidate = u"Update" - excludecandidate = u"Exclude a candidate" - getcandidateid = u"Get a canditate ID" - clearallfield = u"Clear" + candidate_menu = u"Candidate" + candidate_add = u"New candidate" + candidate_find = u"Find a candidate" + candidate_update = u"Update" + candidate_exclude_include_toggle = u"Include/Exclude a candidate" + candidate_get_id = u"Get a canditate ID" + #clear_all_field = u"Clear" #CALENDAR menu - menucalendar = u"Calendar" - newappointment = u"New appointment" + calendar_menu = u"Calendar" + calendar_new_appointment = u"New appointment" #ANONYMIZER menu - menuanonymizer = u"DICOM" + anonymizer_menu = u"DICOM" + anonymizer_run = u"Anonymizer" #HELP menu - menuhelp = u"Help" - gethelp = u"Get some help" - aboutwindow = u"About this..." + help_menu = u"Help" + help_get_help = u"Get some help" + help_about_window = u"About this..." ###################### PROJECT INFO PANE ####################### - project_infopane = u"Informations" - project_detailpane = u"Project Details" - visit_detailpane = u"Visit Details" - projectname = u"Project" - projectstart = u"Start" - projectend = u"End" - targetrecruitment = u"Recruitment target" - currentrecruitment = u"Current recruitment" - totalvisit = u"Total number of Visits" + project_info_pane = u"Project Informations" + project_detail_pane = u"Project Details" + visit_detail_pane = u"Visit Details" + project_name = u"Project" + project_start = u"Start" + project_end = u"End" + target_recruitment = u"Recruitment target" + current_recruitment = u"Current recruitment" + total_visit = u"Total number of Visits" #################### MULTI-TAB DATA SECTION ##################### calendar_pane = u"Calendar" candidate_pane = u"Candidates" - labelcandidatetable = u"Double click on row to populate fields above" + label_candidate_table = u"Double click on row to populate fields above" datatable_id = u"ID" datatable_firstname = u"First Name" - datatable_lastname = "Last Name" + datatable_lastname = u"Last Name" datatable_dob = u"Date of Birth" datatable_phone = u"Phone" datatable_address = u"Address" datatable_city = u"City" datatable_province = u"Province" datatable_country = u"Country" - datatable_postalcode = u"Postal Code" - + datatable_postal_code = u"Postal Code" calendar_monday = u"Monday" calendar_tuesday = u"Tuesday" @@ -189,16 +195,26 @@ col_where = u"Place" col_status = u"Status" #################### STATUS ##################### - status_active = "active" - status_tentative = "tentative" - + status_active = u"active" + status_tentative = u"tentative" ################# DATA WINDOWS ################## - schedulewindow_title ="Scheduler" - candidatewindow_title = "Candidate information" - - + data_window_title =u"Data Window" ################## DIALOGBOX #################### - dialog_yes = "Yes" - dialog_no = "No" - dialogtitle_confirm = "Please confirm!" - dialogclose = "You are about to close this window without saving! \n\nDo you want to continue?" + # very not sure what to do about that section + dialog_yes = u"Yes" + dialog_no = u"No" + dialog_title_confirm = u"Please confirm!" + dialog_close = u"You are about to close this window without saving! \n\nDo you want to continue?" + ################ DATA WINDOW ################### + schedule_pane = u"Calendrier" + candidate_pane = u"Candidat" + candidate_firstname = u"Firstname" + candidate_lastname = u"Lastname" + candidate_phone = u"Phone" + candidate_pscid = u"ID" + candidate_status = u"Status" + schedule_visit_label = u"Visit" + schedule_visit_rank = u"#" + schedule_visit_status = u"Status" + schedule_visit_when = u"Date" + schedule_optional =u"Optional" \ No newline at end of file diff --git a/scheduler/candidate b/scheduler/lib/studydata similarity index 95% rename from scheduler/candidate rename to scheduler/lib/studydata index 1a40bc33f7cbca576221ec0724b082953170ed67..5f8b5ddf3534264988aff6e41533f7f1dccfaf1d 100644 GIT binary patch literal 24576 zcmeI(%}&BV5Ww+;7(do<^eW!a1E$+jfk)uN1?6bcO3_AzhJrqY59ist@hxATK#3;w%8Cr009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1pb#m z@@l|kj$dHc)CclAtY?%aU(5Pr{Qs>%=CUHAg^6n zzUz0p(bDq6aoa7md>Ltd?V?p!yD7W&{>PHPosxW_lK)@+&%df(v;4n3uEg`tng9QD z3C;*0fB*srAbrXo5&ZxF diff --git a/scheduler/lib/utilities.py b/scheduler/lib/utilities.py index 317effd..74f7c59 100644 --- a/scheduler/lib/utilities.py +++ b/scheduler/lib/utilities.py @@ -1,20 +1,25 @@ +#imports from standard packages from uuid import uuid1 import time -def generateUniqueID(): +""" +This file contains utility functions used throughout the application +""" + +def generate_uid(): """ - will generate a random 14-bit sequence number is chosen. + will generate a random UUID. see python documentation https://docs.python.org/2/library/uuid.html """ ui = str(uuid1()) return ui -def isUnique(visitdata): +def is_unique(data, dataset): """ - will verify if the visitdata passed as argument is unique in a set + will verify if 'data' passed as argument is unique in a dataset """ seen = set() - return not any(value in seen or seen.add(value) for value in visitdata) + return not any(value in seen or seen.add(value) for value in data) def describe(something): @@ -29,10 +34,25 @@ def describe(something): returnvalue = objectclass, " (", objecttype, "): ", attributes return returnvalue +def get_current_date(): + """ + will return today's date as a string of format yyyymmdd + """ + return time.strftime("%Y%m%d") +def get_current_time(option): + """ + will return current time as a string of format hhmmss + """ + if option == 1: + return time.strftime("%H%M%S") + elif option == 2: + return time.strftime("%H:%M:%S") + else: + pass -def errorlog(message): +def error_log(message): # append/save timestamp and exception to errorlog.txt # object Exception e is sent as is and this method is taking care of parsing it to string # and adding a timestamp @@ -48,7 +68,7 @@ def errorlog(message): -def centerwindow(win): +def center_window(win): win.update_idletasks() width = win.winfo_width() height = win.winfo_height() @@ -61,7 +81,7 @@ def centerwindow(win): ######################################################################################## -def gatherattributes(something): +def gather_attributes(something): """ Receive an object and return an array containing the object attributes and value """ @@ -71,11 +91,11 @@ def gatherattributes(something): return attributes -def searchuid(db, value): +def search_uid(db, value): return filter(lambda candidate: candidate['uid'] == value, db) -def printobject(something): +def print_object(something): #for dev only! #will print key, attributes, value in the console. #most likely will be an dict or a class instance. @@ -90,3 +110,10 @@ def printobject(something): for attr, value in something.__dict__.iteritems(): print attr, value print "\n\n" + + +# self-test "module" TODO remove before release +if __name__ == '__main__': + import lib.datamanagement as DataManagement + data=dict(DataManagement.read_studydata()) + print_object(data) \ No newline at end of file diff --git a/scheduler/main.py b/scheduler/main.py deleted file mode 100644 index 2ebd80e..0000000 --- a/scheduler/main.py +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env python - -from Tkinter import Tk -import userinterface - - -root = Tk() -app = userinterface.ApplicationGUI(root) -root.protocol("WM_DELETE_WINDOW", app.quitapplication) -app.mainloop() diff --git a/scheduler/newwindow.py b/scheduler/newwindow.py deleted file mode 100644 index 4a74adb..0000000 --- a/scheduler/newwindow.py +++ /dev/null @@ -1,110 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -#import packages -from Tkinter import * -#import functools -import dialogbox -import lib.utilities as utilities -import lib.multilanguage as multilanguage -import lib.datamanagement as datamanagement - - - - -#ref: http://effbot.org/tkinterbook/tkinter-newDialog-windows.htm -#TODO this class needs a major clean-up -class NewWindow(Toplevel): - def __init__(self, parent, candidate, function, visitlabel): #TODO change for multilanguage variable - Toplevel.__init__(self, parent) - #create a transient window on top of parent window - print "running NewWindow(Toplevel) " + str(candidate) + str(function) + str(visitlabel) #TODO remove when done - self.transient(parent) - self.parent = parent - if function == 'schedule': - self.title(multilanguage.schedulewindow_title) - elif function == 'candidate': - self.title(multilanguage.candidatewindow_title) - body = Frame(self) - self.initial_focus = self.body(body, candidate, function, visitlabel) - body.pack(padx=5, pady=5) - - self.buttonbox() - self.grab_set() - - if not self.initial_focus: - self.initial_focus = self - - self.protocol("WM_DELETE_WINDOW", self.closedialog) - - utilities.centerwindow(self) - self.initial_focus.focus_set() - - self.deiconify() - self.wait_window(self) - - - def body(self, master, candidate, function, visitlabel): - db = dict(datamanagement.readcandidatedata()) - candidate = db.get(candidate) - name = str(candidate.firstname) + " " + str(candidate.lastname) - namelabel = Label(self, text = name) - namelabel.pack(side=TOP, expand=YES, fill=BOTH) - if function == "schedule": - currentvisitset = candidate.visitset - currentvisit = currentvisitset.get(visitlabel) - when = currentvisit.when - where = currentvisit.where - whenlabel = Label(self, text = when) - whenlabel.pack(side=TOP, expand=YES, fill=BOTH) - wherelabel = Label(self, text = where) - wherelabel.pack(side=TOP, expand=YES, fill=BOTH) - elif function == "candidate": - phone = candidate.phone - pscid = candidate.pscid - phonelabel = Label(self, text=phone) - phonelabel.pack(side=TOP, expand=YES, fill=BOTH) - pscidlabel = Label(self, text=pscid) - pscidlabel.pack(side=TOP, expand=YES, fill=BOTH) - - else: - pass - - - - - def buttonbox(self): - # add standard button box - box = Frame(self) - w = Button(box, text="OK", width=10, command=self.okbutton, default=ACTIVE) - w.pack(side=LEFT, padx=5, pady=5) - w = Button(box, text="Cancel", width=10, command=self.cancelbutton) - w.pack(side=LEFT, padx=5, pady=5) - self.bind("", self.okbutton) - self.bind("", self.closedialog) - box.pack() - - def okbutton(self, event=None): - print "saving data and closing" #TODO remove when done - if not self.validate(): - self.initial_focus.focus_set() # put focus back - return - self.withdraw() - self.closedialog() - - def cancelbutton(self, event=None): - print "close without saving" - parent = Frame(self) - newwin = dialogbox.ConfirmYesNo(parent, multilanguage.dialogclose) - if newwin.buttonvalue == 1: - self.closedialog() - else: - return - - def closedialog(self, event=None): - #put focus back to parent window before destroying the window - self.parent.focus_set() - self.destroy() - - def validate(self): - return 1 diff --git a/scheduler/studydata b/scheduler/studydata new file mode 100644 index 0000000000000000000000000000000000000000..e22a6477b450adc7c8c25045809169297e4a2ffa GIT binary patch literal 24576 zcmeI(ziyjA9Ki8&6-Dhu8M`%UrZ|fu-+}Yz5xiuvlrhp_AVp0LGIsI|9Xj+;I`$o! zp-Y#(MV}z|J7mcwkwThMJZj^A-Vjt@U3gi=cN{3?rkyRF_xZTX1B53 z`YLDbsL%Gt;i^M&tn#d4KfipqdMdu^XO92^2q1s}0tg_000IagfB*srAbSCcUWjo0h%Su;9AElMmlYZ#>x#K(>P7zG@3?vSWgln$bB>DL$9i!s>UX+lZ<3~%p&*IVliDyMl!BfiWGiRDZc7Lr6kYJ zR$fr~{@?n~|3K|FuWl see test3.py' -datamanagement.savecandidatedata(candidatedb) +datamanagement.save_candidate_data(candidatedb) #TESTED diff --git a/scheduler/tests/create4_PopulateVisits.py b/scheduler/tests/create4_PopulateVisits.py index fc4fa4e..58555b0 100644 --- a/scheduler/tests/create4_PopulateVisits.py +++ b/scheduler/tests/create4_PopulateVisits.py @@ -11,7 +11,7 @@ import lib.datamanagement as datamanagement import lib.utilities as utilities -db = dict(datamanagement.readcandidatedata()) +db = dict(datamanagement.read_candidate_data()) #get all key values keylist = [] @@ -29,34 +29,34 @@ visittime = '13:15' #TODO add regex controls visitwhere = 'CRIUGM lobby' visitwhom = 'Annie' -thisvisit = candidate1.setvisitdate(visitlabel, visitdate, visittime, visitwhere, visitwhom) -candidate1.setnextvisitwindow(candidate1, thisvisit) +thisvisit = candidate1.set_visit_date(visitlabel, visitdate, visittime, visitwhere, visitwhom) +candidate1.set_next_visit_window(candidate1, thisvisit) visitlabel = 'V1' #TODO selection from droplist visitdate = '2014-12-27' #TODO add regex controls visittime = '14:30' #TODO add regex controls visitwhere = 'CRIUGM M-124' visitwhom = 'Jean' -thisvisit = candidate2.setvisitdate(visitlabel, visitdate, visittime, visitwhere, visitwhom) -candidate2.setnextvisitwindow(candidate2, thisvisit) +thisvisit = candidate2.set_visit_date(visitlabel, visitdate, visittime, visitwhere, visitwhom) +candidate2.set_next_visit_window(candidate2, thisvisit) visitlabel = 'V1' #TODO selection from droplist visitdate = '2015-01-13' #TODO add regex controls visittime = '09:15' #TODO add regex controls visitwhere = 'McDo' visitwhom = 'Scott' -thisvisit = candidate3.setvisitdate(visitlabel, visitdate, visittime, visitwhere, visitwhom) -candidate3.setnextvisitwindow(candidate3, thisvisit) +thisvisit = candidate3.set_visit_date(visitlabel, visitdate, visittime, visitwhere, visitwhom) +candidate3.set_next_visit_window(candidate3, thisvisit) visitlabel = 'V0' #TODO selection from droplist visitdate = '2015-02-24' #TODO add regex controls visittime = '15:30' #TODO add regex controls visitwhere = 'IGA' visitwhom = 'Charlie' -thisvisit = candidate4.setvisitdate(visitlabel, visitdate, visittime, visitwhere, visitwhom) -candidate4.setnextvisitwindow(candidate4, thisvisit) +thisvisit = candidate4.set_visit_date(visitlabel, visitdate, visittime, visitwhere, visitwhom) +candidate4.set_next_visit_window(candidate4, thisvisit) -datamanagement.savecandidatedata(db) +datamanagement.save_candidate_data(db) """ diff --git a/scheduler/tests/create_alldata.py b/scheduler/tests/create_alldata.py index b92ca8b..772cc8b 100644 --- a/scheduler/tests/create_alldata.py +++ b/scheduler/tests/create_alldata.py @@ -1,16 +1,16 @@ import visit import candidate -import lib.datamanagement as datamanagement +import lib.datamanagement as DataManagement #create studysetup studydb = {} -studyvisit = visit.StudySetup(1, 'V0') +studyvisit = visit.VisitSetup(1, 'V0') studydb[studyvisit.uid] = studyvisit -studyvisit = visit.StudySetup(2, 'V1', 'V0', 10, 2) +studyvisit = visit.VisitSetup(2, 'V1', 'V0', 10, 2) studydb[studyvisit.uid] = studyvisit -studyvisit = visit.StudySetup(3, 'V2', 'V1', 20, 10) +studyvisit = visit.VisitSetup(3, 'V2', 'V1', 20, 10) studydb[studyvisit.uid] = studyvisit -datamanagement.savestudydata(studydb) +DataManagement.save_study_data(studydb) #create a list of candidate candidatedb = {} @@ -26,10 +26,10 @@ candidatedb[candidatedata.uid] = candidatedata candidatedata = candidate.Candidate('Marc', 'St-Pierre', '412-897-9874') candidatedb[candidatedata.uid] = candidatedata -datamanagement.savecandidatedata(candidatedb) +DataManagement.save_candidate_data(candidatedb) #add visit data to candidates -db = dict(datamanagement.readcandidatedata()) +db = dict(DataManagement.read_candidate_data()) #get all key values keylist = [] for key in db: @@ -46,31 +46,31 @@ visittime = '13:15' #TODO add regex controls visitwhere = 'CRIUGM lobby' visitwhom = 'Annie' -thisvisit = candidate1.setvisitdate(visitlabel, visitdate, visittime, visitwhere, visitwhom) -candidate1.setnextvisitwindow(candidate1, thisvisit) +thisvisit = candidate1.set_visit_date(visitlabel, visitdate, visittime, visitwhere, visitwhom) +candidate1.set_next_visit_window(candidate1, thisvisit) visitlabel = 'V1' #TODO selection from droplist visitdate = '2014-12-27' #TODO add regex controls visittime = '14:30' #TODO add regex controls visitwhere = 'CRIUGM M-124' visitwhom = 'Jean' -thisvisit = candidate2.setvisitdate(visitlabel, visitdate, visittime, visitwhere, visitwhom) -candidate2.setnextvisitwindow(candidate2, thisvisit) +thisvisit = candidate2.set_visit_date(visitlabel, visitdate, visittime, visitwhere, visitwhom) +candidate2.set_next_visit_window(candidate2, thisvisit) visitlabel = 'V1' #TODO selection from droplist visitdate = '2015-01-13' #TODO add regex controls visittime = '09:15' #TODO add regex controls visitwhere = 'McDo' visitwhom = 'Scott' -thisvisit = candidate3.setvisitdate(visitlabel, visitdate, visittime, visitwhere, visitwhom) -candidate3.setnextvisitwindow(candidate3, thisvisit) +thisvisit = candidate3.set_visit_date(visitlabel, visitdate, visittime, visitwhere, visitwhom) +candidate3.set_next_visit_window(candidate3, thisvisit) visitlabel = 'V0' #TODO selection from droplist visitdate = '2015-02-24' #TODO add regex controls visittime = '15:30' #TODO add regex controls visitwhere = 'IGA' visitwhom = 'Charlie' -thisvisit = candidate4.setvisitdate(visitlabel, visitdate, visittime, visitwhere, visitwhom) -candidate4.setnextvisitwindow(candidate4, thisvisit) +thisvisit = candidate4.set_visit_date(visitlabel, visitdate, visittime, visitwhere, visitwhom) +candidate4.set_next_visit_window(candidate4, thisvisit) -datamanagement.savecandidatedata(db) +DataManagement.save_candidate_data(db) diff --git a/scheduler/tests/test.py b/scheduler/tests/test.py index 24ec26f..3da2c01 100644 --- a/scheduler/tests/test.py +++ b/scheduler/tests/test.py @@ -1,6 +1,6 @@ import lib.utilities as utilities import lib.datamanagement as datamanagement -db = dict(datamanagement.readcandidatedata()) +db = dict(datamanagement.read_candidate_data()) utilities.printobject(db) diff --git a/scheduler/ui/__init__.py b/scheduler/ui/__init__.py new file mode 100644 index 0000000..5c9d53e --- /dev/null +++ b/scheduler/ui/__init__.py @@ -0,0 +1,8 @@ +""" +From the documentation at https://docs.python.org/2/tutorial/modules.html#packages + +The __init__.py files are required to make Python treat the directories as containing packages; this is done to prevent +directories with a common name, such as string, from unintentionally hiding valid modules that occur later on the module +search path. In the simplest case, __init__.py can just be an empty file, but it can also execute initialization code +for the package or set the __all__ variable, described later. +""" diff --git a/scheduler/ui/datatable.py b/scheduler/ui/datatable.py new file mode 100644 index 0000000..597dda5 --- /dev/null +++ b/scheduler/ui/datatable.py @@ -0,0 +1,134 @@ +#import standard packages +from Tkinter import * +from ttk import * +#import internal packages +import ui.datawindow as DataWindow + +class DataTable(Frame): + def __init__(self, parent, dataset, colheaders): # expected is dataset + Frame.__init__(self) + self.parent = parent + colheaders = colheaders + dataset = dataset + datatable = self.init_datatable(parent, colheaders) + self.load_data(datatable, dataset, colheaders) + + def init_datatable(self, parent, colheaders): + self.datatable = Treeview(parent, selectmode='browse', columns=colheaders, show="headings") + for col in colheaders: + self.datatable.heading(col, text=col.title(), + command=lambda c=col: self.treeview_sortby(self.datatable, c, 0)) + self.datatable.column(col, width=100, stretch="Yes", anchor="center") + #add vertical and horizontal scroll + self.verticalscroll = Scrollbar(parent, orient="vertical", command=self.datatable.yview) + self.horizontalscroll = Scrollbar(parent, orient="horizontal", command=self.datatable.xview) + self.datatable.configure(yscrollcommand=self.verticalscroll.set, xscroll=self.horizontalscroll.set) + self.verticalscroll.pack(side=RIGHT, expand=NO, fill=BOTH) + self.horizontalscroll.pack(side=BOTTOM, expand=NO, fill=BOTH) + self.datatable.pack(side=LEFT, expand=YES, fill=BOTH) + + def treeview_sortby(self, tree, column, descending): + """Taken from Dave's IDmapper""" + """Sort tree contents when a column is clicked on.""" + """From: https://code.google.com/p/python-ttk/source/browse/trunk/pyttk-samples/treeview_multicolumn.py?r=21""" + # grab values to sort + data = [(tree.set(child, column), child) for child in tree.get_children('')] + # reorder data + data.sort(reverse=descending) + for index, item in enumerate(data): + tree.move(item[1], '', index) + # switch the heading so that it will sort in the opposite direction + tree.heading(column, command=lambda column=column: self.treeview_sortby(tree, column, int(not descending))) + + # TODO move to dataManagement + # Need to re-write this section + def load_data(self, datatable, dataset, colheaders): + if 'firstname' in colheaders: + # This method will add cantidates information to cantidatetable + try: + for key in dataset: + if dataset[key].status is None: + status = '' + else: + status = dataset[key].status + self.datatable.insert('', 'end', + values=[dataset[key].firstname, dataset[key].lastname, dataset[key].phone, + status], tags=(status, dataset[key].uid)) + except Exception as e: + print str(e) + # utilities.errorlog(message) + pass # TODO add some error handling + + # TODO add these color settings in a 'settings and preferences section of the app' + self.datatable.tag_configure('active', background='#F1F8FF') + # Behavior of datatable on single and double click + self.datatable.bind('', self.ondoubleclick) + self.datatable.bind("<>", self.onrowclick) + self.datatable.bind('', self.onrightclik) + + elif 'candidate' in colheaders: + for key, value in dataset.iteritems(): + if dataset[key].visitset is not None: # skip the search if visitset = None + currentvisitset = dataset[key].visitset # set this candidate.visitset for the next step + # gather information about the candidate + # this candidatekey is not printed on screen but saved with the new Scheduler object + # (after all it is the candidate unique id^^) + candidatekey = dataset[key].uid + + candidatefirstname = dataset[key].firstname + candidatelastname = dataset[key].lastname + candidatefullname = str(candidatefirstname + ' ' + candidatelastname) + for key, value in currentvisitset.iteritems(): + if currentvisitset[key].status is not None: + visitlabel = currentvisitset[key].visitlabel + if currentvisitset[key].when is None: + when = currentvisitset[key].whenearliest + else: + when = currentvisitset[key].when + if currentvisitset[key].where is None: + where = '' + else: + where = currentvisitset[key].where + if currentvisitset[key].status is None: + status = '' + else: + status = currentvisitset[key].status + # TODO ? create a new scheduler object with these informations + # This method will add planned visits information to the visit datatable + try: + self.datatable.insert('', 'end', + values=[candidatefullname, visitlabel, when, where, status], + tags=(status, candidatekey, visitlabel)) + except Exception as e: + print "Exception AddIdentifierAction(self, dataset, save=True): " + str( + e) # TODO dd some error handling + pass + + # TODO add these color settings in a 'settings and preferences section of the app' + self.datatable.tag_configure('active',background='#F1F8FF') + self.datatable.tag_configure('tentative',background='#F0F0F0') + # Behavior of datatable on single and double click + self.datatable.bind('', self.ondoubleclick) + self.datatable.bind("<>", self.onrowclick) + self.datatable.bind('', self.onrightclik) + + def ondoubleclick(self, event): + # double clicking on blank space of the treeview when no valide line is selected generates an IndexOutOfRange + # error which is taken care of by this try:except block + try: + itemID = self.datatable.selection()[0] + item =self.datatable.item(itemID)['tags'] + parent = self.parent + candidate_uuid = item[1] + DataWindow.DataWindow(parent, candidate_uuid) + except Exception as e: + print str(e) #TODO deal with exception or not!?! + + def onrightclik(self, event): + print 'contextual menu for this item' + pass + + def onrowclick(self,event): + item_id = str(self.datatable.focus()) + item = self.datatable.item(item_id)['values'] + print item_id, item \ No newline at end of file diff --git a/scheduler/ui/datawindow.py b/scheduler/ui/datawindow.py new file mode 100644 index 0000000..d0cdf39 --- /dev/null +++ b/scheduler/ui/datawindow.py @@ -0,0 +1,143 @@ +#import standard packages +from Tkinter import * +from ttk import * +#import internal packages +import ui.dialogbox as DialogBox +import lib.utilities as Utilities +import lib.multilanguage as MultiLanguage +import lib.datamanagement as DataManagement + +#ref: http://effbot.org/tkinterbook/tkinter-newDialog-windows.htm +#TODO this class needs a major clean-up +class DataWindow(Toplevel): + def __init__(self, parent, candidate_uuid): + Toplevel.__init__(self, parent) + #create a transient window on top of parent window + print "running DataWindow(Toplevel) " + str(candidate_uuid) #TODO remove when done + self.transient(parent) + self.parent = parent + self.title(MultiLanguage.data_window_title) #TODO find a better title for the thing + body = Frame(self) + self.initial_focus = self.body(body, candidate_uuid) + body.pack(padx=5, pady=5) + + self.button_box() + self.grab_set() + if not self.initial_focus: + self.initial_focus = self + self.protocol("WM_DELETE_WINDOW", self.closedialog) + Utilities.center_window(self) + self.initial_focus.focus_set() + #self.deiconify() + self.wait_window(self) + + def body(self, master, candidate): + try: + data = dict(DataManagement.read_candidate_data()) #TODO better way to do this + candidate = data.get(candidate) + except Exception as e: + print str(e) #TODO manage exceptions + #Candidate section + self.candidate_pane = Labelframe(self, text=MultiLanguage.candidate_pane, width=250, height=350, borderwidth=10) + self.candidate_pane.pack(side=TOP, expand=YES, fill=BOTH, padx=5, pady=5) + #PSCID + self.label_pscid = Label(self.candidate_pane, text=MultiLanguage.candidate_pscid) + self.label_pscid.grid(column=0, row=0, padx=10, pady=5, sticky=N+S+E+W) + self.text_pscid_var = StringVar() + self.text_pscid_var.set(candidate.pscid) + self.text_pscid = Entry(self.candidate_pane, textvariable=self.text_pscid_var) + self.text_pscid.grid(column=0, row=1, padx=10, pady=5, sticky=N+S+E+W) + #status + self.label_status = Label(self.candidate_pane, text=MultiLanguage.candidate_status) + self.label_status.grid(column=1, row=0, padx=10, pady=5, sticky=N+S+E+W) + self.text_status_var = StringVar() + self.text_status_var.set(candidate.status) + self.text_status = Entry(self.candidate_pane, textvariable=self.text_status_var) + self.text_status.grid(column=1, row=1, padx=10, pady=5, sticky=N+S+E+W) + #firstname + self.label_firstname = Label(self.candidate_pane, text=MultiLanguage.candidate_firstname) + self.label_firstname.grid(column=0, row=2, padx=10, pady=5, sticky=N+S+E+W) + self.text_firstname_var = StringVar() + self.text_firstname_var.set(candidate.firstname) + self.text_firstname = Entry(self.candidate_pane, textvariable=self.text_firstname_var) + self.text_firstname.grid(column=0, row=3, padx=10, pady=5, sticky=N+S+E+W) + #lastname + self.label_lastname = Label(self.candidate_pane, text=MultiLanguage.candidate_lastname) + self.label_lastname.grid(column=1, row=2, padx=10, pady=5, sticky=N+S+E+W) + self.text_lastname_var = StringVar() + self.text_lastname_var.set(candidate.lastname) + self.text_lastname = Entry(self.candidate_pane, textvariable=self.text_lastname_var) + self.text_lastname.grid(column=1, row=3, padx=10, pady=5, sticky=N+S+E+W) + #phone number + self.label_phone = Label(self.candidate_pane, text=MultiLanguage.candidate_phone) + self.label_phone.grid(column=2, row=2, padx=10, pady=5, sticky=N+S+E+W) + self.text_phone_var = StringVar() + self.text_phone_var.set(candidate.phone) + self.text_pone = Entry(self.candidate_pane, textvariable=self.text_phone_var) + self.text_pone.grid(column=2, row=3, padx=10, pady=5, sticky=N+S+E+W) + #Schedule Section - displayed as a table + self.schedule_pane = Labelframe(self, text=MultiLanguage.schedule_pane, width=250, height=350, borderwidth=10) + self.schedule_pane.pack(side=TOP, expand=YES, fill=BOTH, padx=5, pady=5) + #top row + self.label_visit_rank = Label(self.schedule_pane, text=MultiLanguage.schedule_visit_rank) + self.label_visit_rank.grid(column=0, row=0, padx=5, pady=5, sticky=N+S+E+W) + self.label_visit_label = Label(self.schedule_pane, text=MultiLanguage.schedule_visit_label) + self.label_visit_label.grid(column=1, row=0, padx=5, pady=5, sticky=N+S+E+W) + self.label_visit_optional = Label(self.schedule_pane, text=MultiLanguage.schedule_optional) + self.label_visit_optional.grid(column=2, row=0, padx=5, pady=5, sticky=N+S+E+W) + self.label_visit_status = Label(self.schedule_pane, text=MultiLanguage.schedule_visit_status) + self.label_visit_status.grid(column=3, row=0, padx=5, pady=5, sticky=N+S+E+W) + self.label_visit_when = Label(self.schedule_pane, text=MultiLanguage.schedule_visit_when) + self.label_visit_when.grid(column=4, row=0, padx=5, pady=5, sticky=N+S+E+W) + #TODO add logic "foreach" to create a table showing each visit + """ + PSEUDOCODE + 1. Get candidate.visitset + 2. Parse into a sorted (on visit.rank) list + 3. Print data on scree + + + visit_set = candidate.visitset + for key, value in study_setup.iteritems(): + visit_list.append(study_setup[key]) + visit_list = sorted(visit_list, key=lambda visit: visit.rank) + + for key, value in visit_list.iteritems(): + + """ + + + def button_box(self): + # add standard button box + box = Frame(self) + w = Button(box, text="OK", width=10, command=self.ok_button, default=ACTIVE) + w.pack(side=LEFT, padx=5, pady=5) + w = Button(box, text="Cancel", width=10, command=self.cancel_button) + w.pack(side=LEFT, padx=5, pady=5) + self.bind("", self.ok_button) + self.bind("", self.closedialog) + box.pack() + + def ok_button(self, event=None): + print "saving data and closing" #TODO remove when done + if not self.validate(): + self.initial_focus.focus_set() # put focus back + return + self.withdraw() + self.closedialog() + + def cancel_button(self, event=None): + print "close without saving" + parent = Frame(self) + newwin = DialogBox.ConfirmYesNo(parent, MultiLanguage.dialog_close) + if newwin.buttonvalue == 1: + self.closedialog() + else: + return + + def closedialog(self, event=None): + self.parent.focus_set() #put focus back to parent window before destroying the window + self.destroy() + + def validate(self): + return 1 diff --git a/scheduler/dialogbox.py b/scheduler/ui/dialogbox.py similarity index 85% rename from scheduler/dialogbox.py rename to scheduler/ui/dialogbox.py index 86f3535..182e830 100644 --- a/scheduler/dialogbox.py +++ b/scheduler/ui/dialogbox.py @@ -1,32 +1,30 @@ -#import packages +#import standard packages from Tkinter import * -import functools -import lib.utilities as utilities -import lib.multilanguage as multilanguage +#import internal packages +import lib.utilities as Utilities +import lib.multilanguage as MultiLanguage class DialogBox(Toplevel): """ This class was created mainly because the native dialog box don't work as expected when called from a top-level window. - This class (although it could be improved in many aspects) insure that the parent window cannot get focus while this dialog box is still active. + This class (although it could be improved in many aspects) insure that the parent window cannot get focus while a dialog box is still active. """ def __init__(self,parent, title, message, button1, button2): Toplevel.__init__(self,parent) self.transient(parent) self.parent = parent self.title(title) - body = Frame(self) self.initial_focus = self.body(body, message) body.pack(padx=4, pady=4) - self.buttonbox(button1, button2) self.grab_set() - + if not self.initial_focus: self.initial_focus = self self.protocol("WM_DELETE_WINDOW", self.button2) - utilities.centerwindow(self) + Utilities.center_window(self) self.initial_focus.focus_set() self.deiconify() self.wait_window(self) @@ -70,9 +68,9 @@ def validate(self): ######################################################################################### class ConfirmYesNo(DialogBox): def __init__(self, parent, message): - title = multilanguage.dialogtitle_confirm - button1 = multilanguage.dialog_yes - button2 = multilanguage.dialog_no + title = MultiLanguage.dialog_title_confirm + button1 = MultiLanguage.dialog_yes + button2 = MultiLanguage.dialog_no DialogBox.__init__(self, parent, title, message, button1, button2) diff --git a/scheduler/ui/menubar.py b/scheduler/ui/menubar.py new file mode 100644 index 0000000..6e918df --- /dev/null +++ b/scheduler/ui/menubar.py @@ -0,0 +1,93 @@ +#import standard packages +import Tkinter +#import internal packages +import lib.multilanguage as MultiLanguage + +class MenuBar(Tkinter.Menu): + def __init__(self, parent): + Tkinter.Menu.__init__(self, parent) + # create an APPLICATION pulldown menu + application_menu = Tkinter.Menu(self, tearoff=False) + self.add_cascade(label=MultiLanguage.application_menu,underline=0, menu=application_menu) + application_menu.add_command(label=MultiLanguage.application_setting, underline=1, command=self.app_settings) + application_menu.add_separator() + application_menu.add_command(label=MultiLanguage.application_quit, underline=1, command=self.quit_application) + # create a CANDIDATE pulldown menu + candidate_menu = Tkinter.Menu(self, tearoff=False) + self.add_cascade(label=MultiLanguage.candidate_menu, underline=0, menu=candidate_menu) + candidate_menu.add_command(label=MultiLanguage.candidate_add, command=self.add_candidate) + candidate_menu.add_command(label=MultiLanguage.candidate_find, command=self.find_candidate) + candidate_menu.add_command(label=MultiLanguage.candidate_update, command=self.update_candidate) + candidate_menu.add_separator() + candidate_menu.add_command(label=MultiLanguage.candidate_get_id, command=self.get_candidate_id) + candidate_menu.add_separator() + candidate_menu.add_command(label=MultiLanguage.candidate_exclude_include_toggle, command=self.exclude_candidate) + # create a CALENDAR pulldown menu + calendar_menu = Tkinter.Menu(self, tearoff=False) + self.add_cascade(label=MultiLanguage.calendar_menu, underline=0, menu=calendar_menu) + calendar_menu.add_command(label=MultiLanguage.calendar_new_appointment, command=self.open_calendar) + # create a DICOM_anonymizer pulldown men + anonymizer_menu = Tkinter.Menu(self, tearoff=0) # TODO add relevant menu + self.add_cascade(label=MultiLanguage.anonymizer_menu, underline=0, menu=anonymizer_menu) + anonymizer_menu.add_command(label=MultiLanguage.anonymizer_run, command=self.dicom_anonymizer) + # create a HELP pulldown menu + help_menu = Tkinter.Menu(self, tearoff=0) + self.add_cascade(label=MultiLanguage.help_menu, underline=0, menu=help_menu) + help_menu.add_command(label=MultiLanguage.help_get_help, command=self.open_help) + help_menu.add_command(label=MultiLanguage.help_about_window, command=self.about_application) + + def app_settings(self): + #TODO implement app_settings() + print 'running appsettings' + pass + + def quit_application(self): + #TODO implement quit_application() + print 'running quit_application' + self.quit() + pass + + def open_calendar(self): + #TODO implement open_calendar() + print 'running open_calendar' + pass + + def dicom_anonymizer(self): + #TODO implement dicom_anonymizer() + print 'running dicom anonymizer' + pass + + def add_candidate(self): + #TODO implement add_candidate() + print 'running add_candidate' + pass + + def find_candidate(self): + #TODO implement find_candidate() + print 'running find_candidate' + pass + + def update_candidate(self): + #TODO implement update_candidate() + print 'running update_candidate' + pass + + def get_candidate_id(self): + #TODO get_candidate_id() + print 'running get_candidate_id' + pass + + def exclude_candidate(self): #need renaming + #TODO exclude_candidate() + print 'running incativate_candidate' + pass + + def open_help(self): + #TODO open_help() + print 'running open_help' + pass + + def about_application(self): + #TODO about_application() + print 'running about_application' + pass \ No newline at end of file diff --git a/scheduler/ui/projectpane.py b/scheduler/ui/projectpane.py new file mode 100644 index 0000000..bed5d89 --- /dev/null +++ b/scheduler/ui/projectpane.py @@ -0,0 +1,6 @@ +from Tkinter import * +import lib.multilanguage as multilanguage + +class ProjectPane(LabelFrame): + def __init__(self, parent): + LabelFrame.__init__(self, parent) \ No newline at end of file diff --git a/scheduler/userinterface.py b/scheduler/userinterface.py deleted file mode 100644 index 825d295..0000000 --- a/scheduler/userinterface.py +++ /dev/null @@ -1,256 +0,0 @@ -#!/usr/bin/env python - -#Python Tkinter imports -from Tkinter import * -from ttk import * - -#local imports -import lib.multilanguage as multilanguage -import lib.datamanagement as datamanagement -import newwindow - - -class ApplicationGUI(Frame): - def __init__(self, parent): - Frame.__init__(self, parent) - self.parent = parent - self.parent.title(multilanguage.apptitle) #TODO modify - self.pack(side=TOP, expand=YES, fill=BOTH, padx=10, pady=10) - - self.create_menubar() - self.projectinfopane =Labelframe(self, text=multilanguage.project_infopane, width=250, height=350, borderwidth=10) #TODO add dynamic resize - self.projectinfopane.pack(side=LEFT, expand=NO, fill=BOTH) - #datapane is composed of one PanedWindow containing two Labelframe - self.datapane = Panedwindow(self, width = 1000, height = 500, orient=HORIZONTAL) #TODO add dynamic resize - self.datapane.pack(side=RIGHT, expand=YES, fill=BOTH) - #self.projectinfopane =Labelframe(self.datapane, text='Project Information', width=100, height=350, borderwidth=10) #TODO add dynamic resize #TODO clean - self.candidatepane = Labelframe(self.datapane, text=multilanguage.candidate_pane, width=100, height=350, borderwidth=10) #TODO add dynamic resize - self.visitpane = Labelframe(self.datapane, text=multilanguage.calendar_pane, width=100, height=350, borderwidth=10) #TODO add dynamic resize - #self.datapane.add(self.projectinfopane) #TODO clean - - self.datapane.add(self.candidatepane) - self.datapane.add(self.visitpane) - - #get data from *.db files - data = dict(datamanagement.readcandidatedata())#TODO place data management elsewhere - #create data tables (treeview) - visitcolheaders = ('candidate', 'visitlabel', 'when', 'where', 'status') - self.visittable = DataTable(self.visitpane, data, visitcolheaders) - self.visittable.pack(side=BOTTOM, expand=YES, fill=BOTH) - colheaders = ('firstname', 'lastname', 'phone', 'status') - datatable = DataTable(self.candidatepane, data, colheaders) - datatable.pack(side=BOTTOM, expand=YES, fill=BOTH) - - - - def create_menubar(self): #TODO try to replace by class - #create toplevel menubar - self.menubar = Menu(self) - #create an APPLICATION pulldown menu - self.applicationmenu = Menu(self.menubar, tearoff = 0) - self.applicationmenu.add_command(label = multilanguage.settingapplication, command=self.appsettings) - self.applicationmenu.add_separator() - self.applicationmenu.add_command(label = multilanguage.quitapplication, command=self.quitapplication) - #create a CANDIDATE pulldown menu - self.candidatemenu = Menu(self.menubar, tearoff = 0) - self.candidatemenu.add_command(label = multilanguage.addcandidate, command = self.addcandidate) - self.candidatemenu.add_command(label = multilanguage.findcandidate, command = self.findcandidate) - self.candidatemenu.add_command(label = multilanguage.updatecandidate, command = self.updatecandidate) - self.candidatemenu.add_separator() - self.candidatemenu.add_command(label = multilanguage.getcandidateid, command = self.getcandidateid) - self.candidatemenu.add_separator() - self.candidatemenu.add_command(label= multilanguage.excludecandidate, command = self.excludecandidate) - #create a CALENDAR pulldown menu - self.calendarmenu = Menu(self.menubar, tearoff = 0) - self.calendarmenu.add_command(label = multilanguage.newappointment, command=self.opencalendar) - #create a DICOM_anonymizer pulldown men - self.anonymizermenu = Menu(self.menubar, tearoff = 0) #TODO add relevant menu - self.anonymizermenu.add_command(label = "menu") - #create a HELP pulldown menu - self.helpmenu = Menu(self.menubar, tearoff = 0) - self.helpmenu.add_command(label = multilanguage.gethelp, command=self.openhelp) - self.helpmenu.add_command(label = multilanguage.aboutwindow, command=self.aboutapplication) - #add all menu to the menubar - self.menubar.add_cascade(label = multilanguage.menuapplication, menu = self.applicationmenu) - self.menubar.add_cascade(label = multilanguage.menucandidate, menu = self.candidatemenu) - self.menubar.add_cascade(label = multilanguage.menucalendar, menu = self.calendarmenu) - self.menubar.add_cascade(label = multilanguage.menuanonymizer, menu = self.anonymizermenu) - #self.menubar.add_cascade(label = multilanguage.menuhelp, menu = self.helpmenu) #TODO add help menu - #add the menubar to the parent frame - self.parent.config(menu = self.menubar) - - - def appsettings(self): - print 'running appsettings' - pass - - def quitapplication(self): - print 'running quitapplication' - self.quit() - pass - - def opencalendar(self): - print 'running opencalendar' - pass - - def addcandidate(self): - print 'running addcandidate' - pass - - def findcandidate(self): - print 'running findcandidate' - pass - - def updatecandidate(self): - print 'running updatecandidate' - pass - - def getcandidateid(self): - print 'running getcandidateid' - pass - - def excludecandidate(self): - print 'running incativatecandidate' - pass - - def openhelp(self): - print 'running openhelp' - pass - - def aboutapplication(self): - print 'running aboutapplication' - pass - - - - -############################################################################################################################# -class DataTable(Frame): - def __init__(self, parent, dataset, colheaders): #expected is dataset - Frame.__init__(self) - self.parent = parent - colheaders = colheaders - dataset = dataset - datatable = self.initdatatable(parent, colheaders) - self.updatedata(datatable,dataset, colheaders) - - def initdatatable(self, parent, colheaders): - self.datatable = Treeview(parent, selectmode='browse', columns=colheaders, show="headings") - for col in colheaders: - self.datatable.heading(col, text=col.title(), command=lambda c=col: self.treeview_sortby(self.datatable, c, 0)) - self.datatable.column(col, width=100, stretch="Yes", anchor="center") - self.verticalscroll = Scrollbar(parent, orient="vertical", command=self.datatable.yview) - self.horizontalscroll = Scrollbar(parent, orient="horizontal", command=self.datatable.xview) - self.datatable.configure(yscrollcommand=self.verticalscroll.set, xscroll=self.horizontalscroll.set) - self.verticalscroll.pack(side=RIGHT, expand=NO, fill=BOTH) - self.horizontalscroll.pack(side=BOTTOM, expand=NO, fill=BOTH) - self.datatable.pack(side=LEFT, expand=YES, fill=BOTH) - - - def treeview_sortby(self, tree, column, descending): - """Taken from Dave's IDmapper""" - """Sort tree contents when a column is clicked on.""" - """From: https://code.google.com/p/python-ttk/source/browse/trunk/pyttk-samples/treeview_multicolumn.py?r=21""" - # grab values to sort - data = [(tree.set(child, column), child) for child in tree.get_children('')] - # reorder data - data.sort(reverse=descending) - for index, item in enumerate(data): - tree.move(item[1], '', index) - # switch the heading so that it will sort in the opposite direction - tree.heading(column, command=lambda column=column: self.treeview_sortby(tree, column, int(not descending))) - - - #TODO move to dataManagement - def updatedata(self, datatable, dataset, colheaders): - if 'firstname' in colheaders: - # This method will add cantidates information to cantidatetable - try: - for key in dataset: - if dataset[key].status is None: - status = '' - else: - status = dataset[key].status - self.datatable.insert('', 'end', values=[dataset[key].firstname, dataset[key].lastname, dataset[key].phone, status], tags=(status, dataset[key].uid, 'candidate')) - except Exception as e: - print "Exception AddIdentifierAction(self, dataset, save=True): " + str(e) - #utilities.errorlog(message) - pass #TODO add some error handling - self.datatable.tag_configure('active', background='#ccffcc') #TODO set color in application settings and preferences - self.datatable.bind('', self.ondoubleclick) - - elif 'candidate' in colheaders: - currentvisitset = {} - for key, value in dataset.iteritems(): - if dataset[key].visitset is not None: #skip the search if visitset = None - currentvisitset = dataset[key].visitset #set this candidate.visitset for the next step - #gather information about the candidate - candidatekey = dataset[key].uid #not printed on screen but saved with the new Scheduler object (after all it is the candidate unique id^^) - candidatefirstname = dataset[key].firstname - candidatelastname = dataset[key].lastname - candidatefullname = str(candidatefirstname + ' ' + candidatelastname) - for key, value in currentvisitset.iteritems(): - if currentvisitset[key].status is not None: - visitlabel = currentvisitset[key].visitlabel - if currentvisitset[key].when is None: - when = currentvisitset[key].whenearliest - else: - when = currentvisitset[key].when - if currentvisitset[key].where is None: - where = '' - else: - where = currentvisitset[key].where - #whom = currentvisitset[key].withwhom - if currentvisitset[key].status is None: - status = '' - else: - status = currentvisitset[key].status - #print candidatefullname, visitlabel, when, where, whom #TODO remove - #TODO ? create a new scheduler object with these informations - - # This method will add planned visits information to the visit datatable - try: - self.datatable.insert('', 'end', values= [candidatefullname, visitlabel, when, where, status], tags=(status, candidatekey, 'schedule', visitlabel)) - except Exception as e: - print "Exception AddIdentifierAction(self, dataset, save=True): " + str(e) #TODO remove - #utilities.errorlog(message) - pass #TODO add some error handling - self.datatable.tag_configure('active', background='#ccffcc') #TODO set in application settings and preferences - self.datatable.tag_configure('tentative', background='#f0f0f1') #TODO set in application settings and preferences - self.datatable.bind('', self.ondoubleclick) - - def ondoubleclick(self, event): - # double clicking on blank space of the treeview will generate an IndexOutOfRange error - try: - item_id = self.datatable.selection()[0] - item = self.datatable.item(item_id)['tags'] - candidate = item[1] - function = item[2] - if function == "schedule": - visitlabel = item[3] - else: - visitlabel = None - except Exception as e: - print str(e) #TODO error management - return - parent = self.parent - if function == "schedule": - newwin = newwindow.NewWindow(parent, candidate, function, visitlabel) - elif function == "candidate": - newwin = newwindow.NewWindow(parent, candidate, function, visitlabel) - else: - return - - -############################################################################################################################# - -#self-test "module" TODO remove -if __name__ == '__main__': - root = Tk() - - #root.geometry() #TODO add dynamic resize - app = ApplicationGUI(root) - - root.protocol("WM_DELETE_WINDOW", app.quitapplication) - app.mainloop() - diff --git a/scheduler/visit.py b/scheduler/visit.py index 868238d..5057350 100644 --- a/scheduler/visit.py +++ b/scheduler/visit.py @@ -1,75 +1,76 @@ -import lib.utilities as utilities +#import standard packages +#import internal packages +import lib.utilities as Utilities -class StudySetup(): +class VisitSetup(): """ - The StudySetup(object) class define a study in terms of sequence of visits. - A study can have as many visits as required and each visit (instance) has its own 'definition'. - - This is the parent class of Visit(StudySetup), furthermore the Visit(StudySetup) objects will be 'instanciated' from each instance of StudySetup(object). - Both StudySetup(object) class and instances are used to create individual instances of Visit(StudySetup) + The VisitSetup() class is used to define a study (or project) in terms of sequence of visits and also serves as a + base class for Visit(VisitSetup). A study (or project) can have as many visits as required. There is no class for + study as it is merely a simple dictionnary. + + Code example: This study contains 3 visits. Since V0 is the first visit, it doesn't have any values for + 'previousvisit', 'visitwindow' and ' visitmargin' + study = {} + visit = visit.VisitSetup(1, 'V0') #a uid (uuid1) is automatically generated + study[visit.uid] = visit #VisitSetup.uid is a unique ID used as key + visit = visit.VisitSetup(2, 'V1', 'V0', 10, 2) + study[visit.uid] = visit + visit = visit.VisitSetup(3, 'V2', 'V1', 20, 10) + study[visit.uid] = visit - uid: unique identifier. Used to store and retrieve object in shelve() - rank: The rank of the visit in the sequence (int). Useful to sort the visits in order of occurrence. - visitlabel: The label of the visit (string) such as V1, V2 or anything else the user may come up with - previousvisit: The visitlabel (string) of the visit occurring before this one. Used to plan this visit based on the date of the previous visit. (default to None) - visitwindow: The number of days (int) between this visit and the previous visit. (default to None) - visitmargin: The margin (in number of days (int)) that is an allowed deviation. Basically, this allow the 'calculation' of a date window when this visit should occur. (default to None) - actions: A list of action points (or simply reminders) for specific to that visit (i.e.'reserve room 101'). (default to None) + This is the parent class of Visit(VisitSetup), furthermore Visit(VisitSetup) objects will be 'instanciated' from + each instance of VisitSetup(object). + Both VisitSetup(object) class and instances are used to create individual instances of Visit(VisitSetup) + + Attributes: + uid: A unique identifier using python's uuid1 method. Used as key to store and retrieve objects from + files and/or dictionaries. + rank: The rank of the visit in the sequence (int). Useful to sort the visits in order of occurrence. + visitlabel: The label of the visit (string) such as V1, V2 or anything else the user may come up with + previousvisit: The visitlabel (string) of the visit occurring before this one. Used to plan this visit based on + the date of the previous visit. (default to None) + visitwindow: The number of days (int) between this visit and the previous visit. (default to None) + visitmargin: The margin (in number of days (int)) that is an allowed deviation (a +/- few days ). Basically, + this allow the 'calculation' of a date window when this visit should occur. (default to None) + actions: A list of action points (or simply reminders) specific to that visit (i.e.'reserve room 101'). + This is not implemented yet (default to None) """ - def __init__(self, rank, visitlabel, previousvisit = None, visitwindow = None, visitmargin = None, actions = None, uid=None): - self.uid = utilities.generateUniqueID() + def __init__(self, rank, visitlabel, previousvisit = None, visitwindow = None, visitmargin = None, + optional='No', actions = None, uid=None): + self.uid = Utilities.generate_uid() self.rank = rank self.visitlabel = visitlabel self.previousvisit = previousvisit self.visitwindow = visitwindow self.visitmargin = visitmargin - self.actions = actions #not implemented yet! - - + self.optional = optional + self.actions = actions #not implemented yet! -class Visit(StudySetup): +class Visit(VisitSetup): """ - The Visit(StudySetup) class help define individual visit of the candidate using StudySetup(object) instances as 'templates'. - Upon creation of the first meeting with a candidate, the Candidate(object) instance will get a full set of Visit(StudySetup) instances. - Each time a visit is being setup, a 'time window' is calculated to define the earliest and latest date at which the 'nextVisit' should occur. + The Visit(VisitSetup) class help define individual visit of the candidate using VisitSetup(object) instances as 'templates'. + Upon creation of the first meeting with a candidate, the Candidate(object) instance will get a full set of Visit(VisitSetup) instances. + This set of visits is contained in Candidate.visitset + Each time a visit is being setup, a 'time window' is calculated to define the earliest and latest date at which + the 'nextVisit' should occur. - In addition of parent class attributes. - uid: Not used. - when: Date at which this visit is occurring. (default to None) - whenearliest: Earliest date when this visit should occur. Set to value when previous visit is activated. (default to None) - whenlatest: Latest date when this visit should occur. Set to value when previous visit is activated. (default to None) - where: Place where this meeting is taking place. (default to None) - whitwhom: Professional meeting the study candidate at the reception. (default to None) - status: Status of this visit. Set to active when 'when' is set (default to None) + Attributes: (In addition of parent class attributes.) + when: Date at which this visit is occurring. (default to None) + when_earliest: Earliest date when this visit should occur. Set to value when previous visit is activated. (default to None) + when_latest: Latest date when this visit should occur. Set to value when previous visit is activated. (default to None) + where: Place where this meeting is taking place. (default to None) + whitwhom: Professional meeting the study candidate at the reception. (default to None) + status: Status of this visit. Set to active when 'when' is set (default to None) """ - def __init__(self, rank, visitlabel, previousvisit, visitwindow, visitmargin, actions=None, uid=None, when = None, whenearliest = None, whenlatest = None, where = None, withwhom = None, status = None): - StudySetup.__init__(self, rank, visitlabel, previousvisit, visitwindow, visitmargin, actions, uid) + def __init__(self, rank, visitlabel, previousvisit, visitwindow, visitmargin, optional='No', actions=None, uid=None, when = None, + whenearliest = None, whenlatest = None, where = None, withwhom = None, status = None): + VisitSetup.__init__(self, rank, visitlabel, previousvisit, visitwindow, visitmargin, optional, actions, uid) self.when = when self.whenearliest = whenearliest self.whenlatest = whenlatest self.where = where self.withwhom = withwhom self.status = status - - - #def setvisitdate(self, visitdate, visittime, where, whom): - # #parse the strings 'visitdate' and 'visittime' to a datetime object before assigning it to Visit instance - # #TODO add invalid data/format check - # visitdatetimestr = visitdate, ' ', visittime - # visitdatetime = datetime.datetime.strptime(visitdatetimestr,"%Y-%m-%d %H:%m") #parse the string to a datetime object - # self.when = visitdatetime - # self.status = 'active' - - - - - - - - - - - From fa3518581c0274bb9f27c9588f3b63cf489e7fd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Mon, 27 Jul 2015 16:17:19 -0400 Subject: [PATCH 26/89] Implemented close function under Application/Quitter. Also replaced button1 and button2 by button_ok and button_cancel in DialogBox class. --- scheduler/lib/multilanguage.py | 5 ++++- scheduler/ui/dialogbox.py | 26 +++++++++++++------------- scheduler/ui/menubar.py | 16 ++++++++++++---- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/scheduler/lib/multilanguage.py b/scheduler/lib/multilanguage.py index 2e27cf4..f6b2aa8 100644 --- a/scheduler/lib/multilanguage.py +++ b/scheduler/lib/multilanguage.py @@ -98,8 +98,11 @@ # very not sure what to do about that section dialog_yes = u"Oui" dialog_no = u"Non" + dialog_ok = u"OK" + dialog_cancel = u"Cancel" dialog_title_confirm = u"Veuillez confirmer!" dialog_close = u"Vous êtes sur le point de fermer cette fenêtre sans sauvegarder!\n\nVoulez-vous continuer?" + dialog_quit = u"Vous êtes sur le point de fermer l'application sans sauvegarder!\n\nVoulez-vous quitter?" ################ DATA WINDOW ################### schedule_pane = u"Calendrier" candidate_pane = u"Candidat" @@ -217,4 +220,4 @@ schedule_visit_rank = u"#" schedule_visit_status = u"Status" schedule_visit_when = u"Date" - schedule_optional =u"Optional" \ No newline at end of file + schedule_optional =u"Optional" diff --git a/scheduler/ui/dialogbox.py b/scheduler/ui/dialogbox.py index 182e830..2c60c97 100644 --- a/scheduler/ui/dialogbox.py +++ b/scheduler/ui/dialogbox.py @@ -9,7 +9,7 @@ class DialogBox(Toplevel): This class was created mainly because the native dialog box don't work as expected when called from a top-level window. This class (although it could be improved in many aspects) insure that the parent window cannot get focus while a dialog box is still active. """ - def __init__(self,parent, title, message, button1, button2): + def __init__(self,parent, title, message, button_ok, button_cancel): Toplevel.__init__(self,parent) self.transient(parent) self.parent = parent @@ -17,13 +17,13 @@ def __init__(self,parent, title, message, button1, button2): body = Frame(self) self.initial_focus = self.body(body, message) body.pack(padx=4, pady=4) - self.buttonbox(button1, button2) + self.buttonbox(button_ok, button_cancel) self.grab_set() if not self.initial_focus: self.initial_focus = self - self.protocol("WM_DELETE_WINDOW", self.button2) + self.protocol("WM_DELETE_WINDOW", self.button_cancel) Utilities.center_window(self) self.initial_focus.focus_set() self.deiconify() @@ -34,25 +34,25 @@ def body(self, parent, message): label.pack(padx=4, pady=4) pass - def buttonbox(self, button1, button2): + def buttonbox(self, button_ok, button_cancel): #add a standard button box box = Frame(self) - b1 = Button(box, text=button1, width=12, command=self.button1, default=ACTIVE) + b1 = Button(box, text=button_ok, width=12, command=self.button_ok, default=ACTIVE) b1.pack(side=LEFT, padx=4, pady=4) - b2 = Button(box, text=button2, width=12, command=self.button2, default=ACTIVE) + b2 = Button(box, text=button_cancel, width=12, command=self.button_cancel, default=ACTIVE) b2.pack(side=LEFT, padx=4, pady=4) - self.bind("", self.button1) - self.bind("", self.button2) + self.bind("", self.button_ok) + self.bind("", self.button_cancel) box.pack() - def button1(self, event=None): + def button_ok(self, event=None): if not self.validate(): self.initial_focus.focus_set() #put focus on Button return self.buttonvalue = 1 self.closedialog() - def button2(self, event=None): + def button_cancel(self, event=None): self.buttonvalue = 2 self.closedialog() @@ -69,9 +69,9 @@ def validate(self): class ConfirmYesNo(DialogBox): def __init__(self, parent, message): title = MultiLanguage.dialog_title_confirm - button1 = MultiLanguage.dialog_yes - button2 = MultiLanguage.dialog_no - DialogBox.__init__(self, parent, title, message, button1, button2) + button_yes = MultiLanguage.dialog_yes + button_no = MultiLanguage.dialog_no + DialogBox.__init__(self, parent, title, message, button_yes, button_no) diff --git a/scheduler/ui/menubar.py b/scheduler/ui/menubar.py index 6e918df..be13c23 100644 --- a/scheduler/ui/menubar.py +++ b/scheduler/ui/menubar.py @@ -2,6 +2,7 @@ import Tkinter #import internal packages import lib.multilanguage as MultiLanguage +import ui.dialogbox as DialogBox class MenuBar(Tkinter.Menu): def __init__(self, parent): @@ -42,9 +43,16 @@ def app_settings(self): pass def quit_application(self): - #TODO implement quit_application() - print 'running quit_application' - self.quit() + # TODO Mac instance has a Python->quit menu on top of Application->Quitter + parent = Tkinter.Frame(self) + button_ok = MultiLanguage.dialog_ok + button_cancel = MultiLanguage.dialog_cancel + newwin = DialogBox.ConfirmYesNo(parent, MultiLanguage.dialog_quit) + if newwin.buttonvalue == 1: + self.quit() + print 'running quit_application' + else: + return pass def open_calendar(self): @@ -90,4 +98,4 @@ def open_help(self): def about_application(self): #TODO about_application() print 'running about_application' - pass \ No newline at end of file + pass From e1e01d39ca2167187c251719d0c52083bf376746 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Mon, 27 Jul 2015 16:28:55 -0400 Subject: [PATCH 27/89] Added dialog_quit text in the english section. --- scheduler/lib/multilanguage.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scheduler/lib/multilanguage.py b/scheduler/lib/multilanguage.py index f6b2aa8..0fda9a4 100644 --- a/scheduler/lib/multilanguage.py +++ b/scheduler/lib/multilanguage.py @@ -98,8 +98,6 @@ # very not sure what to do about that section dialog_yes = u"Oui" dialog_no = u"Non" - dialog_ok = u"OK" - dialog_cancel = u"Cancel" dialog_title_confirm = u"Veuillez confirmer!" dialog_close = u"Vous êtes sur le point de fermer cette fenêtre sans sauvegarder!\n\nVoulez-vous continuer?" dialog_quit = u"Vous êtes sur le point de fermer l'application sans sauvegarder!\n\nVoulez-vous quitter?" @@ -116,6 +114,8 @@ schedule_visit_status = u"Status" schedule_visit_when = u"Date" schedule_optional =u"Optionnel" + ################ NEW CANDIDATE ################## + newcandidate_title = u"Création d'un nouveau candidat" elif language == "en": app_title = u"LORIS tools" @@ -208,6 +208,7 @@ dialog_no = u"No" dialog_title_confirm = u"Please confirm!" dialog_close = u"You are about to close this window without saving! \n\nDo you want to continue?" + dialog_quit = u"You are about to close the application without saving!\n\nDo you want to quit?" ################ DATA WINDOW ################### schedule_pane = u"Calendrier" candidate_pane = u"Candidat" @@ -221,3 +222,5 @@ schedule_visit_status = u"Status" schedule_visit_when = u"Date" schedule_optional =u"Optional" + ################ NEW CANDIDATE ################## + newcandidate_title = u"Create a new candidate" From 5fbdee3e08f3ac82c472692611a695fa21a26bfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Mon, 27 Jul 2015 16:43:16 -0400 Subject: [PATCH 28/89] Replaced button OK and Cancel by Yes and No in dialog box to quit. --- scheduler/lib/multilanguage.py | 326 +++++++++++++++++---------------- scheduler/ui/menubar.py | 9 +- 2 files changed, 175 insertions(+), 160 deletions(-) diff --git a/scheduler/lib/multilanguage.py b/scheduler/lib/multilanguage.py index 0fda9a4..f47b452 100644 --- a/scheduler/lib/multilanguage.py +++ b/scheduler/lib/multilanguage.py @@ -8,219 +8,235 @@ language = "fr" #TODO make dynamic if language == "fr": + ###################### TOP LEVEL MENU BAR ###################### - app_title = u"Outils LORIS" + app_title = u"Outils LORIS" #APPLICATION menu - application_menu = u"Application" + application_menu = u"Application" application_setting = u"Préferences" - application_quit = u"Quitter" + application_quit = u"Quitter" #PROJECT menu - #menuproject = u"Projet" #TODO remove? - #openproject = u"Ouvrir un projet" - #modifyproject = u"Modifier le projet ouvert" - #newproject = u"Créer un nouveau projet" + #menuproject = u"Projet" #TODO remove? + #openproject = u"Ouvrir un projet" + #modifyproject = u"Modifier le projet ouvert" + #newproject = u"Créer un nouveau projet" #CANDIDATE menu - candidate_menu = u"Candidat" - candidate_add = u"Nouveau candidat" - candidate_find = u"Trouver candidat" - candidate_update = u"Mettre à jour" + candidate_menu = u"Candidat" + candidate_add = u"Nouveau candidat" + candidate_find = u"Trouver candidat" + candidate_update = u"Mettre à jour" + candidate_get_id = u"Obtenir l'identifiant d'un candidat" candidate_exclude_include_toggle = u"Inclure/Exclure un candidat" - candidate_get_id = u"Obtenir l'identifiant d'un candidat" #clear_all_field = u"Effacer" #ANONYMIZER menu anonymizer_menu = u"DICOM" - anonymizer_run = u"Anonymizer" + anonymizer_run = u"Anonymizer" #CALENDAR menu - calendar_menu = u"Calendrier" + calendar_menu = u"Calendrier" calendar_new_appointment = u"Nouveau Rendez-vous" #HELP menu - help_menu = u"Aide" - help_get_help = u"Obtenir de l'aide" - help_about_window = u"A propos de ..." + help_menu = u"Aide" + help_get_help = u"Obtenir de l'aide" + help_about_window = u"A propos de ..." + ###################### PROJECT INFO PANE ####################### - project_info_pane = u"Projet" + project_info_pane = u"Projet" project_detail_pane = u"Détails du Projet" - visit_detail_pane = u"Détails des Visites" - project_name = u"Projet" - project_start = u"Début" - project_end = u"Fin" - target_recruitment = u"Cible de recrutement" + visit_detail_pane = u"Détails des Visites" + project_name = u"Projet" + project_start = u"Début" + project_end = u"Fin" + target_recruitment = u"Cible de recrutement" current_recruitment = u"Recrutement actuel" - total_visit = u"Nombre de Visites" + total_visit = u"Nombre de Visites" + #################### MULTI-TAB DATA SECTION ##################### - calendar_pane = u"Calendrier" - candidate_pane = u"Candidats" + calendar_pane = u"Calendrier" + candidate_pane = u"Candidats" label_candidate_table = u"Faites un double-clic sur l'une des lignes pour remplir les champs ci-dessus" - datatable_id = u"ID" - datatable_firstname = u"Prénom" - datatable_lastname = "Nom" - datatable_dob = u"Date de Naissance" - datatable_phone = u"Téléphone" - datatable_address = u"Adresse" - datatable_city = u"Ville" - datatable_province = u"Province" - datatable_country = u"Pays" + datatable_id = u"ID" + datatable_firstname = u"Prénom" + datatable_lastname = "Nom" + datatable_dob = u"Date de Naissance" + datatable_phone = u"Téléphone" + datatable_address = u"Adresse" + datatable_city = u"Ville" + datatable_province = u"Province" + datatable_country = u"Pays" datatable_postal_code = u"Code Postal" - calendar_monday = u"Lundi" - calendar_tuesday = u"Mardi" - calendar_wednesday = u"Mercredi" - calendar_thursday = u"Jeudi" - calendar_friday = u"Vendredi" - calendar_saturday = u"Samedi" - calendar_sunday = u"Dimanche" - calendar_january = u"Janvier" - calendar_february = u"Février" - calendar_march = u"Mars" - calendar_april = u"Avril" - calendar_may = u"Mai" - calendar_june = u"Juin" - calendar_jully = u"Juillet" - calendar_august = u"Août" - calendar_september = u"Septembre" - calendar_october = u"Octobre" - calendar_november = u"Novembre" - calendar_december = u"Décembre" + calendar_monday = u"Lundi" + calendar_tuesday = u"Mardi" + calendar_wednesday = u"Mercredi" + calendar_thursday = u"Jeudi" + calendar_friday = u"Vendredi" + calendar_saturday = u"Samedi" + calendar_sunday = u"Dimanche" + calendar_january = u"Janvier" + calendar_february = u"Février" + calendar_march = u"Mars" + calendar_april = u"Avril" + calendar_may = u"Mai" + calendar_june = u"Juin" + calendar_jully = u"Juillet" + calendar_august = u"Août" + calendar_september = u"Septembre" + calendar_october = u"Octobre" + calendar_november = u"Novembre" + calendar_december = u"Décembre" ################ COLUMN HEADER ################## - col_candidate = u"Candidat" - col_visitlabel = u"Visite" - col_when = u"Date/Heure" - col_where = u"Endroit" - col_status = u"Statut" + col_candidate = u"Candidat" + col_visitlabel = u"Visite" + col_when = u"Date/Heure" + col_where = u"Endroit" + col_status = u"Statut" + #################### STATUS ##################### - status_active = u"actif" - status_tentative = u"provisoire" + status_active = u"actif" + status_tentative = u"provisoire" + ################# DATA WINDOWS ################## data_window_title = u"DATA WINDOW" #TODO trouver un titre français + ################## DIALOGBOX #################### # very not sure what to do about that section - dialog_yes = u"Oui" - dialog_no = u"Non" + dialog_yes = u"Oui" + dialog_no = u"Non" dialog_title_confirm = u"Veuillez confirmer!" - dialog_close = u"Vous êtes sur le point de fermer cette fenêtre sans sauvegarder!\n\nVoulez-vous continuer?" - dialog_quit = u"Vous êtes sur le point de fermer l'application sans sauvegarder!\n\nVoulez-vous quitter?" + dialog_close = u"Vous êtes sur le point de fermer cette fenêtre sans sauvegarder!\n\nVoulez-vous continuer?" + dialog_quit = u"Vous êtes sur le point de fermer l'application sans sauvegarder!\n\nVoulez-vous quitter?" + ################ DATA WINDOW ################### - schedule_pane = u"Calendrier" - candidate_pane = u"Candidat" - candidate_firstname = u"Prénom" - candidate_lastname = u"Nom de famille" - candidate_phone = u"Téléphone" - candidate_pscid = u"ID" - candidate_status = u"Status" - schedule_visit_label = u"Visite" - schedule_visit_rank = u"#" + schedule_pane = u"Calendrier" + candidate_pane = u"Candidat" + candidate_firstname = u"Prénom" + candidate_lastname = u"Nom de famille" + candidate_phone = u"Téléphone" + candidate_pscid = u"ID" + candidate_status = u"Status" + schedule_visit_label = u"Visite" + schedule_visit_rank = u"#" schedule_visit_status = u"Status" - schedule_visit_when = u"Date" - schedule_optional =u"Optionnel" + schedule_visit_when = u"Date" + schedule_optional = u"Optionnel" + ################ NEW CANDIDATE ################## - newcandidate_title = u"Création d'un nouveau candidat" + new_candidate_title = u"Création d'un nouveau candidat" elif language == "en": - app_title = u"LORIS tools" + + ###################### TOP LEVEL MENU BAR ###################### + app_title = u"LORIS tools" #APPLICATION menu - application_menu = u"Application" + application_menu = u"Application" application_setting = u"Preferences" - application_quit = u"Quit" + application_quit = u"Quit" #PROJECT menu - #menuproject = u"Project" #TODO remove? - #openproject = u"Open project" - #modifyproject = u"Modify open project" - #newproject = u"New project" + #menuproject = u"Project" #TODO remove? + #openproject = u"Open project" + #modifyproject = u"Modify open project" + #newproject = u"New project" #CANDIDATE menu - candidate_menu = u"Candidate" - candidate_add = u"New candidate" - candidate_find = u"Find a candidate" - candidate_update = u"Update" + candidate_menu = u"Candidate" + candidate_add = u"New candidate" + candidate_find = u"Find a candidate" + candidate_update = u"Update" + candidate_get_id = u"Get a canditate ID" candidate_exclude_include_toggle = u"Include/Exclude a candidate" - candidate_get_id = u"Get a canditate ID" #clear_all_field = u"Clear" #CALENDAR menu - calendar_menu = u"Calendar" + calendar_menu = u"Calendar" calendar_new_appointment = u"New appointment" #ANONYMIZER menu - anonymizer_menu = u"DICOM" - anonymizer_run = u"Anonymizer" + anonymizer_menu = u"DICOM" + anonymizer_run = u"Anonymizer" #HELP menu - help_menu = u"Help" - help_get_help = u"Get some help" - help_about_window = u"About this..." + help_menu = u"Help" + help_get_help = u"Get some help" + help_about_window = u"About this..." + ###################### PROJECT INFO PANE ####################### - project_info_pane = u"Project Informations" + project_info_pane = u"Project Informations" project_detail_pane = u"Project Details" - visit_detail_pane = u"Visit Details" - project_name = u"Project" - project_start = u"Start" - project_end = u"End" - target_recruitment = u"Recruitment target" + visit_detail_pane = u"Visit Details" + project_name = u"Project" + project_start = u"Start" + project_end = u"End" + target_recruitment = u"Recruitment target" current_recruitment = u"Current recruitment" - total_visit = u"Total number of Visits" + total_visit = u"Total number of Visits" + #################### MULTI-TAB DATA SECTION ##################### - calendar_pane = u"Calendar" - candidate_pane = u"Candidates" + calendar_pane = u"Calendar" + candidate_pane = u"Candidates" label_candidate_table = u"Double click on row to populate fields above" - datatable_id = u"ID" - datatable_firstname = u"First Name" - datatable_lastname = u"Last Name" - datatable_dob = u"Date of Birth" - datatable_phone = u"Phone" - datatable_address = u"Address" - datatable_city = u"City" - datatable_province = u"Province" - datatable_country = u"Country" + datatable_id = u"ID" + datatable_firstname = u"First Name" + datatable_lastname = u"Last Name" + datatable_dob = u"Date of Birth" + datatable_phone = u"Phone" + datatable_address = u"Address" + datatable_city = u"City" + datatable_province = u"Province" + datatable_country = u"Country" datatable_postal_code = u"Postal Code" - calendar_monday = u"Monday" - calendar_tuesday = u"Tuesday" - calendar_wednesday = u"Wednesday" - calendar_thursday = u"Thursday" - calendar_friday = u"Friday" - calendar_saturday = u"Saturday" - calendar_sunday = u"Sunday" - calendar_january = u"January" - calendar_february = u"February" - calendar_march = u"Marc" - calendar_april = u"April" - calendar_may = u"May" - calendar_june = u"June" - calendar_jully = u"Jully" - calendar_august = u"August" - calendar_september = u"September" - calendar_october = u"October" - calendar_november = u"November" - calendar_december = u"December" + calendar_monday = u"Monday" + calendar_tuesday = u"Tuesday" + calendar_wednesday = u"Wednesday" + calendar_thursday = u"Thursday" + calendar_friday = u"Friday" + calendar_saturday = u"Saturday" + calendar_sunday = u"Sunday" + calendar_january = u"January" + calendar_february = u"February" + calendar_march = u"Marc" + calendar_april = u"April" + calendar_may = u"May" + calendar_june = u"June" + calendar_jully = u"Jully" + calendar_august = u"August" + calendar_september = u"September" + calendar_october = u"October" + calendar_november = u"November" + calendar_december = u"December" ################ COLUMN HEADER ################## - col_candidate = u"Candidate" - col_visitlabel = u"Visit" - col_when = u"Date/Time" - col_where = u"Place" - col_status = u"Status" + col_candidate = u"Candidate" + col_visitlabel = u"Visit" + col_when = u"Date/Time" + col_where = u"Place" + col_status = u"Status" + #################### STATUS ##################### - status_active = u"active" + status_active = u"active" status_tentative = u"tentative" + ################# DATA WINDOWS ################## - data_window_title =u"Data Window" + data_window_title = u"Data Window" + ################## DIALOGBOX #################### # very not sure what to do about that section - dialog_yes = u"Yes" - dialog_no = u"No" + dialog_yes = u"Yes" + dialog_no = u"No" dialog_title_confirm = u"Please confirm!" - dialog_close = u"You are about to close this window without saving! \n\nDo you want to continue?" - dialog_quit = u"You are about to close the application without saving!\n\nDo you want to quit?" + dialog_close = u"You are about to close this window without saving! \n\nDo you want to continue?" + dialog_quit = u"You are about to close the application without saving!\n\nDo you want to quit?" ################ DATA WINDOW ################### - schedule_pane = u"Calendrier" - candidate_pane = u"Candidat" - candidate_firstname = u"Firstname" - candidate_lastname = u"Lastname" - candidate_phone = u"Phone" - candidate_pscid = u"ID" - candidate_status = u"Status" - schedule_visit_label = u"Visit" - schedule_visit_rank = u"#" + schedule_pane = u"Calendrier" + candidate_pane = u"Candidat" + candidate_firstname = u"Firstname" + candidate_lastname = u"Lastname" + candidate_phone = u"Phone" + candidate_pscid = u"ID" + candidate_status = u"Status" + schedule_visit_label = u"Visit" + schedule_visit_rank = u"#" schedule_visit_status = u"Status" - schedule_visit_when = u"Date" - schedule_optional =u"Optional" + schedule_visit_when = u"Date" + schedule_optional = u"Optional" + ################ NEW CANDIDATE ################## - newcandidate_title = u"Create a new candidate" + new_candidate_title = u"Create a new candidate" diff --git a/scheduler/ui/menubar.py b/scheduler/ui/menubar.py index be13c23..8f3e688 100644 --- a/scheduler/ui/menubar.py +++ b/scheduler/ui/menubar.py @@ -44,13 +44,12 @@ def app_settings(self): def quit_application(self): # TODO Mac instance has a Python->quit menu on top of Application->Quitter - parent = Tkinter.Frame(self) - button_ok = MultiLanguage.dialog_ok - button_cancel = MultiLanguage.dialog_cancel - newwin = DialogBox.ConfirmYesNo(parent, MultiLanguage.dialog_quit) + parent = Tkinter.Frame(self) + button_yes = MultiLanguage.dialog_yes + button_no = MultiLanguage.dialog_no + newwin = DialogBox.ConfirmYesNo(parent, MultiLanguage.dialog_quit) if newwin.buttonvalue == 1: self.quit() - print 'running quit_application' else: return pass From 43837a7481fa03e19010cefd29061aa4560da57c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Mon, 27 Jul 2015 16:47:10 -0400 Subject: [PATCH 29/89] Renamed Data Window to Candidate information (information sur le candidat in French). --- scheduler/lib/multilanguage.py | 4 ++-- scheduler/ui/datawindow.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scheduler/lib/multilanguage.py b/scheduler/lib/multilanguage.py index f47b452..859bff8 100644 --- a/scheduler/lib/multilanguage.py +++ b/scheduler/lib/multilanguage.py @@ -98,7 +98,7 @@ status_tentative = u"provisoire" ################# DATA WINDOWS ################## - data_window_title = u"DATA WINDOW" #TODO trouver un titre français + data_window_title = u"Information sur le candidat" ################## DIALOGBOX #################### # very not sure what to do about that section @@ -215,7 +215,7 @@ status_tentative = u"tentative" ################# DATA WINDOWS ################## - data_window_title = u"Data Window" + data_window_title = u"Candidate information" ################## DIALOGBOX #################### # very not sure what to do about that section diff --git a/scheduler/ui/datawindow.py b/scheduler/ui/datawindow.py index d0cdf39..92b1f40 100644 --- a/scheduler/ui/datawindow.py +++ b/scheduler/ui/datawindow.py @@ -16,7 +16,7 @@ def __init__(self, parent, candidate_uuid): print "running DataWindow(Toplevel) " + str(candidate_uuid) #TODO remove when done self.transient(parent) self.parent = parent - self.title(MultiLanguage.data_window_title) #TODO find a better title for the thing + self.title(MultiLanguage.data_window_title) body = Frame(self) self.initial_focus = self.body(body, candidate_uuid) body.pack(padx=5, pady=5) From 9c080a77978e170086881585afb093bae00fcad8 Mon Sep 17 00:00:00 2001 From: pemorin Date: Tue, 28 Jul 2015 15:50:21 -0400 Subject: [PATCH 30/89] Hackathon - 20150728 datawindow.Datawindow class to now include schedule info got ride of old userinterface.Userinterface class --- scheduler/application.py | 5 +- scheduler/candidate.py | 220 ++++++++++++++++++++---------- scheduler/candidatedata | Bin 24576 -> 0 bytes scheduler/lib/datamanagement.py | 1 + scheduler/lib/multilanguage.py | 7 + scheduler/studydata | Bin 24576 -> 0 bytes scheduler/tests/create_alldata.py | 50 +++++-- scheduler/ui/datawindow.py | 68 +++++++-- scheduler/visit.py | 29 ++-- 9 files changed, 277 insertions(+), 103 deletions(-) delete mode 100644 scheduler/candidatedata delete mode 100644 scheduler/studydata diff --git a/scheduler/application.py b/scheduler/application.py index c1daa7e..b16658f 100644 --- a/scheduler/application.py +++ b/scheduler/application.py @@ -41,7 +41,6 @@ def __init__(self, parent): self.filter_candidate_label = Label(self.filter_visit, text='Filters for Active / Tentative / Closed ...') self.filter_candidate_label.pack(side=TOP, expand=NO, fill=BOTH) - # get data from shelve files data = dict(DataManagement.read_candidate_data()) # TODO place data management elsewhere @@ -50,8 +49,8 @@ def __init__(self, parent): self.visit_table = DataTable.DataTable(self.visit_pane, data, visit_column_headers) self.visit_table.pack(side=BOTTOM, expand=YES, fill=BOTH) column_header = ('firstname', 'lastname', 'phone', 'status') - data_table = DataTable.DataTable(self.candidate_pane, data, column_header) - data_table.pack(side=BOTTOM, expand=YES, fill=BOTH) + self.data_table = DataTable.DataTable(self.candidate_pane, data, column_header) + self.data_table.pack(side=BOTTOM, expand=YES, fill=BOTH) class Application(Tk): def __init__(self): diff --git a/scheduler/candidate.py b/scheduler/candidate.py index 8010318..8d0591e 100644 --- a/scheduler/candidate.py +++ b/scheduler/candidate.py @@ -1,13 +1,35 @@ +#import standard packages import datetime import visit -import lib.datamanagement as datamanagement -import lib.multilanguage as multilanguage -import lib.utilities as utilities -from uuid import uuid1 +#import internal packages +import lib.datamanagement as DataManagement +import lib.multilanguage as MultiLanguage +import lib.utilities as Utilities class Candidate(): + """ + The Candidate() class defines the candidates/participants of the study + + Attributes: + uid: A unique identifier using python's uuid1 method. Used as key to store and retrieve objects from + files and/or dictionaries. + firstname: First name of the candidate + lastname: Last name of the candidate + visitset: A dictionnary containing all visits (planed or not) + phone: A primary phone number + status: Status of this candidate + pscid: Loris (clinical results database) specific ID + + kwargs: Not implemented yet! + + Code example: + candidatedb = {} #setup a dict to receive the candidates + candidatedata = candidate.Candidate('Billy', 'Roberts', '451-784-9856', otherphone='514-874-9658') #instanciate one candidate + candidatedb[candidatedata.uid] = candidatedata #add candidate to dict + DataManagement.save_candidate_data(candidatedb) #save data to file + """ def __init__(self, firstname, lastname, phone, uid=None, visitset = None, status = None, pscid=None, **kwargs): #TODO *kwarg - self.uid = utilities.generateUniqueID() + self.uid = Utilities.generate_uid() self.firstname = firstname self.lastname = lastname self.visitset = visitset @@ -18,98 +40,154 @@ def __init__(self, firstname, lastname, phone, uid=None, visitset = None, status if kwargs is not None: for key, value in kwargs.iteritems(): setattr(self, key, value) - - - def setupvisitset(self): - #open studysetup.db and 'parse' the dict to a sorted list (dict cannot be sorted)... - visitlist =[] - visitlabel_templist = [] - try: - studysetup = dict(datamanagement.readstudydata()) #TODO replace datafile name + + def setup_visitset(self): + """ + When creating the first visit for a candidate, a complete set of visits is added based on a study/project visit list + There are no parameters passed to this method since it will simply create a new 'empty' + This method will: + 1-open studydata (the study visit list) and 'parse' the dict to a sorted list (dict cannot be sorted) + 2-create a temporary list of visit_labels that will serve as a key within the Candidate.visit_set dictionary + 3-instantiate individual visits based on the study visit list + Usage: + Called by Candidate.set_visit_date() + """ + visit_list =[] + visit_label_templist = [] + study_setup = dict() + try: + #1-open studydata + study_setup = dict(DataManagement.read_studydata()) except Exception as e: - print str(e) #TODO add error login - for key, value in studysetup.iteritems(): - visitlist.append(studysetup[key]) - visitlist = sorted(visitlist, key=lambda visit: visit.rank) - #...and create a temporary list of visitlabels that will serve a key within the Candidate.visitset dictionary - for each in visitlist: - visitlabel_templist.append(each.visitlabel) #we now have a list with all visit labels included in the study - #instantiate individual visit based on each instance of StudySetup() + print str(e) #TODO add error login (in case a study data file does not exist) + for key, value in study_setup.iteritems(): + #2-parse into a sorted list + visit_list.append(study_setup[key]) + visit_list = sorted(visit_list, key=lambda visit: visit.rank) + #create a temporary list of visitlabels + for each in visit_list: + visit_label_templist.append(each.visitlabel) + #3-instantiate individual visit based on each instance of VisitSetup() self.visitset = {} #setup a dict to receive a set of Visit() count = 0 - #set values of : uid, rank, visitlabel, previousvisit, visitwindow, visitmargin, - for key in visitlist: - mainkey = str(visitlabel_templist[count]) + #set values of : uid, rank, visit_label, previous_visit, visit_window, visitmargin, + for key in visit_list: + mainkey = str(visit_label_templist[count]) rank =key.rank - visitlabel = key.visitlabel - previousvisit = key.previousvisit - visitwindow = key.visitwindow - visitmargin = key.visitmargin - visitdata = visit.Visit(rank, visitlabel, previousvisit, visitwindow, visitmargin,) - self.visitset[mainkey] = visitdata + visit_label = key.visitlabel + previous_visit = key.previousvisit + visit_window = key.visitwindow + visit_margin = key.visitmargin + visit_data = visit.Visit(rank, visit_label, previous_visit, visit_window, visit_margin,) + self.visitset[mainkey] = visit_data count += 1 - self.status = multilanguage.status_active - - def setvisitdate(self, visitlabel, visitdate, visittime, visitwhere, visitwhom): - #check to see if visitset == None before trying to create a new date instance + def set_visit_date(self, visitlabel, visitdate, visittime, visitwhere, visitwhom): + """ + This method update the visit information (according to visitlabel) + This method will: + 1-Check if visitset==None. If so, then Candidate.setup_visitset() is called to setup Candidate.visitset + 2-Check if a date is already set for this visitlabel + 3-Set values of Visit.when, 'Visit.where, Visit.whithwhom and Visit.status for current visit + Usage: Called by GUI methods + Return: current_visit as current Visit(Visit(VisitSetup) instance + """ + #1-Check to see if visitset == None before trying to create a new date instance if self.visitset is None: - self.setupvisitset() + self.setup_visitset() + self.set_candidate_status_active() #Candidate.status='active' since we're setting up a first visit #get current visit within visitset - current = self.visitset.get(visitlabel) - #check to see if this visit already has a date - if current.when is not None: - print "date already exists" #TODO add confirmation of change log??? + current_visit = self.visitset.get(visitlabel) + #2-Check to see if this visit already has a date + if current_visit.when is not None: + print "date already exists" #TODO add confirmation of change log??? pass #concatenate visitdate and visittime and parse into a datetime object visitwhen = visitdate + ' ' + visittime when = datetime.datetime.strptime(visitwhen, '%Y-%m-%d %H:%M') - #set 'current' values of : when, where, withwhom, status - current.when = when - current.where = visitwhere - current.withwhom = visitwhom - current.status = "active" - return current + #3-Set values of Visit.when, 'Visit.where, Visit.whithwhom and Visit.status for current visit + current_visit.when = when + current_visit.where = visitwhere + current_visit.withwhom = visitwhom + current_visit.status = "active" #that is the status of the visit + return current_visit + - - def setnextvisitwindow(self, candidate, currentvisit): - #get the current visit object as argument. Will search and look for the next visit (visit where previousvisit == currentvisitlabel) - nextvisitsearchset = candidate.visitset - currentvisitlabel = currentvisit.visitlabel - nextvisit = "" - for key in nextvisitsearchset: - visitdata = nextvisitsearchset[key] - if visitdata.previousvisit == currentvisitlabel: - nextvisit = candidate.visitset.get(visitdata.visitlabel) #TODO debug when current is last visit - #gather info about currentvisit (mostly for my own internal computer!) - currentvisitdate = currentvisit.when - currentvisityear = currentvisitdate.year #get the year of the current visit date - nextvisitwindow = nextvisit.visitwindow - nextvisitmargin = nextvisit.visitmargin + """ + def set_next_visit_window(self, candidate, current_visit): + #get the current visit object as argument. Will search and look for the next visit (visit where previousvisit == current_visit_label) + next_visit_searchset = candidate.visitset + current_visit_label = current_visit.visitlabel + next_visit = "" + for key in next_visit_searchset: + visit_data = next_visit_searchset[key] + if visit_data.previousvisit == current_visit_label: + next_visit = candidate.visitset.get(visit_data.visitlabel) #TODO debug when current is last visit + #gather info about current_visit (mostly for my own biological computer! else I get lost) + current_visit_date = current_visit.when + current_visit_year = current_visit_date.year #get the year of the current visit date + next_visit_window = next_visit.visitwindow + next_visit_margin = next_visit.visitmargin #set dates for the next visit - nextvisitearly = int(currentvisitdate.strftime('%j')) + (nextvisitwindow - nextvisitmargin) #this properly handle change of year - nextvisitearlydate = datetime.datetime(currentvisityear, 1, 1) + datetime.timedelta(nextvisitearly - 1) - nextvisitlate = int(currentvisitdate.strftime('%j')) + (nextvisitwindow + nextvisitmargin) - nextvisitlatedate = datetime.datetime(currentvisityear, 1, 1) + datetime.timedelta(nextvisitlate - 1) - nextvisit.whenearliest = nextvisitearlydate - nextvisit.whenlatest = nextvisitlatedate - nextvisit.status = "tentative" + next_visit_early = int(current_visit_date.strftime('%j')) + (next_visit_window - next_visit_margin) #this properly handle change of year + next_visit_early_date = datetime.datetime(current_visit_year, 1, 1) + datetime.timedelta(next_visit_early - 1) + next_visit_late = int(current_visit_date.strftime('%j')) + (next_visit_window + next_visit_margin) + next_visit_late_date = datetime.datetime(current_visit_year, 1, 1) + datetime.timedelta(next_visit_late - 1) + next_visit.when_earliest = next_visit_early_date + next_visit.when_latest = next_visit_late_date + next_visit.status = "tentative" + Utilities.print_object((next_visit)) + """ + def set_next_visit_window(self, candidate, current_visit): + """ + This method will 'calculate' a min and max date when the next visit should occur + 1-Get candidate.visitset (as visit_searchset) and current_visit.visitlabel + 2-Identify which visit (in visitset) has previousvisit == current_visit.visitlabel + 3-Get + Usage: Currently called by GUI function (TODO RETHINK THIS LOGIC maybe it should be called when running Candidate.set_visit_date()) + """ - def getactivevisit(self, candidate): + #get the current visit object as argument. Will search and look for the next visit (visit where previousvisit == current_visitlabel) + + #1- Get Candidate.visitset and current_visit / next_visit will == Visit(VisitSetup) of the next visit (relative to current_visit) + visit_searchset = candidate.visitset + current_visitlabel = current_visit.visitlabel + next_visit = "" + #2-Identify which visit (in visitset) has previousvisit == current_visit.visitlabel + for key in visit_searchset: + visit_data = visit_searchset[key] + if visit_data.previousvisit == current_visitlabel: + next_visit = candidate.visitset.get(visit_data.visitlabel) #TODO debug when current is last visit + #3-Calculate a min and max date for the next visit to occur based on Visit.visitwindow and Visit.visitmargin + current_visitdate = current_visit.when + current_visityear = current_visitdate.year #get the year of the current visit date + next_visitwindow = next_visit.visitwindow + nextvisitmargin = next_visit.visitmargin + #set dates for the next visit + nextvisitearly = int(current_visitdate.strftime('%j')) + (next_visitwindow - nextvisitmargin) #this properly handle change of year + nextvisitearlydate = datetime.datetime(current_visityear, 1, 1) + datetime.timedelta(nextvisitearly - 1) + nextvisitlate = int(current_visitdate.strftime('%j')) + (next_visitwindow + nextvisitmargin) + nextvisitlatedate = datetime.datetime(current_visityear, 1, 1) + datetime.timedelta(nextvisitlate - 1) + next_visit.whenearliest = nextvisitearlydate + next_visit.whenlatest = nextvisitlatedate + next_visit.status = "tentative" #set this visit.status + + def get_active_visit(self, candidate): candidatefullname = str(candidate.firstname + ' ' + candidate.lastname) currentvisitset = candidate.visitset + activevisit = [] if currentvisitset is None: return elif currentvisitset is not None: for key in currentvisitset: - if currentvisitset[key].status == multilanguage.status_active: + if currentvisitset[key].status == MultiLanguage.status_active: visitlabel = currentvisitset[key].visitlabel when = currentvisitset[key].when.strftime('%Y-%m-%d %Hh%m') where = currentvisitset[key].where who = currentvisitset[key].withwhom activevisit = [candidatefullname, visitlabel, when, where, who] return activevisit - - + def set_candidate_status_active(self): + self.status = MultiLanguage.status_active #set the Candidate.status to 'active' \ No newline at end of file diff --git a/scheduler/candidatedata b/scheduler/candidatedata deleted file mode 100644 index 7d7604dc92efe997137745c31e7e7854086effc9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24576 zcmeI3TW{k;6oAu}SirE-zU;oN1pAf>Dadp2?XeWG3Zko31AVHH#+Nu2sgq6I3qN8X z_$&Mqp7|xban5m^_>z)V&2kZ^QtBCdJRW~D^UX=(^m@JC0le_fJbT>x7!xny^${j| zy+`Gy@&EBJSn5eH_~sK)9l$!Oda8ZDy_@{FZGUx~w@iQuFaajO1egF5U;<2l2`~XB zzyz286JP>NfC(@GCcp%k025#WOn?b60Vco%n85xBw7;XbdLMq;d-D6|U%dF>#oOoK zJs&?idiLwncTZt2|1kk3zyz286JP>NfC(@GCh*@UaB!GKCXch&6p3}bPR)gN7|*3O z>MsjZTrT_8TtUV}MRt`yMqA5Kf4+>e7&4Ca8giGEcA=CuV<97dJWx{F10fP;;K$y0 zFphK_>sUolkN?Ws#c;tkj2dPs}2MWPrz}W_?K5aBNfz-ek#Qfn|lj5?9$W zE0#%tJ0!MPU@OmT%xmi~qvO^YmV!MFm2j<8QG@B=g;}Ioj^#brnNssZ+c%N2Yt1*Y z6SbQ!d~Ct>Bq`eyZ0fwu@_2R)bxM1)b85n5iu;6>Lfgty@xoG4p@qqR!kn^(bZdxu zmLqtnsQ_FlPt0PPB}=?tSyG=blB;ZXxul!H-R`>Z6uPu5inliE&*oU8p++F;*E1|n zq_4ALay^+{zz&6N4x#v&z#NQ&CPhV@l|qUs+(!!Fh7z_lbfSjNcb%|}PSJS*c6anO zwn8~j0ni#kKSKf#!G$3qtq2EZkzFL#Ix$zSbvWvu-`L*yO+1FJIA5F>u*I}pfAy{6 zbgp2arN|XqNib7_mjbKRU{TPQsiYAgeJe#MDS&_>7tIQ!tzZv9k&w}L8;Yjsq9sBZ z^@}8jNwbYbt7&RpNBs-vkPbI=TGyXxD-J~gpp!P3T9cx^!vTf>6jn3V-VYH;zo1#uo9 z<#~qV7In2TBCH}UBf?CH(K4OMPg-gCvFxt8AC2SN?e0R`qyp~&r~FEaC>NTmxO$DXAk%8O*8K{Sr;+7vvMs+_LM|S;)qS`Pj{RoUS8F9^saup-wMt4i5LtD|H=(HQg zLc>{7Lj>dLfvY5)SvhOBJMQBwT%)Uys$BBRkjg=8n#2iB-0+l+{Pw^BCe4t_gLBt0 z69p_aQ6Y-&7s*99HK+hwp_(C8IsYpZ5Mhn_j`jw=BL{(}9Xtp4lD>C0?>;DQk2Hru zD($!=%<*fH9oq;IlfimN7HGt2ZdwD{zb4U?9i^q%7L+EyJI^7 zU)Q;@_5U!~1azXl_64@xS-UsE=4#1183>7?#{+`SYC#LZg^(7QZ)ldB_>jO8;|FXb zss@J7*NfC(@GCcp%k025#WOn?b60Vco%{&52Ld7Nj*BboR+ wk;a1+N!WR4VCy59@aQ-`-L>t3%r|DSdT#tKk7MFv0{CRAAGpoOF?TlYPf_sqWB>pF diff --git a/scheduler/lib/datamanagement.py b/scheduler/lib/datamanagement.py index 713a387..f12470c 100644 --- a/scheduler/lib/datamanagement.py +++ b/scheduler/lib/datamanagement.py @@ -1,5 +1,6 @@ #imports from standard packages import shelve +import sqlite3 """ The data_management.py file contains functions related to data management only. Generic functions: savedata(data, datafilename) and readdata(datafile). Currently, these are not being used. diff --git a/scheduler/lib/multilanguage.py b/scheduler/lib/multilanguage.py index 859bff8..2055287 100644 --- a/scheduler/lib/multilanguage.py +++ b/scheduler/lib/multilanguage.py @@ -101,12 +101,19 @@ data_window_title = u"Information sur le candidat" ################## DIALOGBOX #################### +<<<<<<< Updated upstream # very not sure what to do about that section dialog_yes = u"Oui" dialog_no = u"Non" dialog_title_confirm = u"Veuillez confirmer!" dialog_close = u"Vous êtes sur le point de fermer cette fenêtre sans sauvegarder!\n\nVoulez-vous continuer?" dialog_quit = u"Vous êtes sur le point de fermer l'application sans sauvegarder!\n\nVoulez-vous quitter?" +======= + dialog_yes = "Oui" + dialog_no = "Non" + dialogtitle_confirm = "Veuillez confirmer!" + dialogclose = "Vous êtes sur le point de fermer cette fenêtre sans sauvegarder!\n\nVoulez-vous continuer?" +>>>>>>> Stashed changes ################ DATA WINDOW ################### schedule_pane = u"Calendrier" diff --git a/scheduler/studydata b/scheduler/studydata deleted file mode 100644 index e22a6477b450adc7c8c25045809169297e4a2ffa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24576 zcmeI(ziyjA9Ki8&6-Dhu8M`%UrZ|fu-+}Yz5xiuvlrhp_AVp0LGIsI|9Xj+;I`$o! zp-Y#(MV}z|J7mcwkwThMJZj^A-Vjt@U3gi=cN{3?rkyRF_xZTX1B53 z`YLDbsL%Gt;i^M&tn#d4KfipqdMdu^XO92^2q1s}0tg_000IagfB*srAbSCcUWjo0h%Su;9AElMmlYZ#>x#K(>P7zG@3?vSWgln$bB>DL$9i!s>UX+lZ<3~%p&*IVliDyMl!BfiWGiRDZc7Lr6kYJ zR$fr~{@?n~|3K|FuWl', str(late_date) + return date_range + + + From 26a61dff1b30d00c3895513e6e564f0500ee8f93 Mon Sep 17 00:00:00 2001 From: pemorin Date: Tue, 17 May 2016 13:23:58 -0400 Subject: [PATCH 31/89] 20160517 --- scheduler/.idea/misc.xml | 2 +- scheduler/.idea/scheduler.iml | 2 +- scheduler/.idea/vcs.xml | 6 - scheduler/.idea/workspace.xml | 504 +++++++++++++++++--------------- scheduler/application.py | 31 +- scheduler/candidate.py | 9 +- scheduler/create_alldata.py | 103 ------- scheduler/lib/datamanagement.py | 55 ++-- scheduler/lib/multilanguage.py | 341 ++++++++++----------- scheduler/lib/studydata | Bin 24576 -> 0 bytes scheduler/lib/utilities.py | 5 +- scheduler/ui/datatable.py | 206 +++++++------ scheduler/ui/datawindow.py | 95 +++--- scheduler/ui/dialogbox.py | 26 +- scheduler/ui/menubar.py | 17 +- scheduler/visit.py | 14 +- 16 files changed, 690 insertions(+), 726 deletions(-) delete mode 100644 scheduler/.idea/vcs.xml delete mode 100644 scheduler/create_alldata.py delete mode 100644 scheduler/lib/studydata diff --git a/scheduler/.idea/misc.xml b/scheduler/.idea/misc.xml index 27b342e..df245c4 100644 --- a/scheduler/.idea/misc.xml +++ b/scheduler/.idea/misc.xml @@ -10,5 +10,5 @@ - + \ No newline at end of file diff --git a/scheduler/.idea/scheduler.iml b/scheduler/.idea/scheduler.iml index 9ea38e6..0e18433 100644 --- a/scheduler/.idea/scheduler.iml +++ b/scheduler/.idea/scheduler.iml @@ -2,7 +2,7 @@ - + \ No newline at end of file diff --git a/scheduler/.idea/vcs.xml b/scheduler/.idea/vcs.xml deleted file mode 100644 index 6564d52..0000000 --- a/scheduler/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/scheduler/.idea/workspace.xml b/scheduler/.idea/workspace.xml index 9222d27..8f3b8dd 100644 --- a/scheduler/.idea/workspace.xml +++ b/scheduler/.idea/workspace.xml @@ -20,58 +20,64 @@ - + - - - - - + + + - - + + - - - - - + + + - - + + - - + + - + - - + + - - + + - - + + - - + + + + + + + + + + + + @@ -99,27 +105,31 @@ - - - - + - - + + + - + + + - + - + + + - + - - - - + + + + + - - - - - + + + + + + - + + @@ -454,6 +519,7 @@ 1437155263605 @@ -495,420 +561,384 @@ - - + - - - - - + + - + - + + + + + + + + + + + + + + + + + - - - - + - - - - + - - + - - + - - - - - - - - - - - - - - + - - - - + - - - - + - + - - - - + - - + - + - - - - + - - - - + - - - - - - - - + - - - - - - - - - - - + - - - - + - - - - + - + - - + - + - + - - - - + - + - + - - - - + - + - + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + + - + - - + + + - + - - + + + - + - - + + + - + - - + + + - + - - + + + - + - - + + - + - - - - - + + + - - + + - - - - - + + + - + - - + + - + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/scheduler/application.py b/scheduler/application.py index b16658f..d83e633 100644 --- a/scheduler/application.py +++ b/scheduler/application.py @@ -7,10 +7,10 @@ import ui.menubar as MenuBar import ui.datatable as DataTable import lib.multilanguage as MultiLanguage -import lib.datamanagement as DataManagement -import ui.projectpane as ProjectPane #TODO create classe for project info pane + class UserInterface(Frame): + def __init__(self, parent): Frame.__init__(self) self.parent = parent @@ -30,6 +30,15 @@ def __init__(self, parent): self.data_pane.add(self.candidate_pane) self.data_pane.add(self.visit_pane) + # create data tables (treeview) + visit_column_headers = ('candidate', 'visitlabel', 'when', 'where', 'status') + self.visit_table = DataTable.VisitList(self.visit_pane, visit_column_headers) + self.visit_table.pack(side=BOTTOM, expand=YES, fill=BOTH) + column_header = ('firstname', 'lastname', 'phone', 'status') + self.data_table = DataTable.ParticipantsList(self.candidate_pane, column_header) + self.data_table.pack(side=BOTTOM, expand=YES, fill=BOTH) + + """ #create a filter section in each data_pane(not implemented yet) #TODO This whole section needs to be replaced by REAL CODE actively filtering the data self.filter_candidate = Labelframe(self.candidate_pane, text='Filters', width=220, height=50, borderwidth=10) @@ -40,17 +49,8 @@ def __init__(self, parent): self.filter_visit.pack(side=TOP, expand=NO, fill=BOTH, pady=5) self.filter_candidate_label = Label(self.filter_visit, text='Filters for Active / Tentative / Closed ...') self.filter_candidate_label.pack(side=TOP, expand=NO, fill=BOTH) + """ - # get data from shelve files - data = dict(DataManagement.read_candidate_data()) # TODO place data management elsewhere - - # create data tables (treeview) - visit_column_headers = ('candidate', 'visitlabel', 'when', 'where', 'status') - self.visit_table = DataTable.DataTable(self.visit_pane, data, visit_column_headers) - self.visit_table.pack(side=BOTTOM, expand=YES, fill=BOTH) - column_header = ('firstname', 'lastname', 'phone', 'status') - self.data_table = DataTable.DataTable(self.candidate_pane, data, column_header) - self.data_table.pack(side=BOTTOM, expand=YES, fill=BOTH) class Application(Tk): def __init__(self): @@ -59,7 +59,12 @@ def __init__(self): self.config(menu=menu) frame = UserInterface(self) +""" #Application main loop if __name__ == "__main__": app=Application() - app.mainloop() \ No newline at end of file + app.mainloop() +""" +#Application main loop +app=Application() +app.mainloop() \ No newline at end of file diff --git a/scheduler/candidate.py b/scheduler/candidate.py index 8d0591e..0b9d52f 100644 --- a/scheduler/candidate.py +++ b/scheduler/candidate.py @@ -1,14 +1,15 @@ -#import standard packages +# import standard packages import datetime import visit -#import internal packages +# import internal packages import lib.datamanagement as DataManagement import lib.multilanguage as MultiLanguage import lib.utilities as Utilities -class Candidate(): + +class Candidate: """ - The Candidate() class defines the candidates/participants of the study + The Candidate class defines the candidates/participants of the study Attributes: uid: A unique identifier using python's uuid1 method. Used as key to store and retrieve objects from diff --git a/scheduler/create_alldata.py b/scheduler/create_alldata.py deleted file mode 100644 index a45555d..0000000 --- a/scheduler/create_alldata.py +++ /dev/null @@ -1,103 +0,0 @@ -import visit -import candidate -import lib.datamanagement as DataManagement -import lib.utilities as Utilities - -#create studysetup -#saving (DataManagement.save_study_data(studydb)) after each visit is really not necessary -#but this mimics the way the application will work -# rank, visitlabel, previousvisit=None, visitwindow = None, visitmargin = None, optional = 'No', actions = None, uid=None -# -studydb = {} -studyvisit = visit.VisitSetup(1, 'V0', None) -studydb[studyvisit.uid] = studyvisit -DataManagement.save_study_data(studydb) -studyvisit = visit.VisitSetup(2, 'V1', 'V0', 10, 2) -studydb[studyvisit.uid] = studyvisit -DataManagement.save_study_data(studydb) -studyvisit = visit.VisitSetup(3, 'V2', 'V1', 20, 10) -studydb[studyvisit.uid] = studyvisit -DataManagement.save_study_data(studydb) - -#create a list of candidate -#saving (DataManagement.save_candidate_data(candidatedb)) after each candidate is really not necessary -#firstname, lastname, phone, uid=None, visitset = None, status = None, pscid=None, **kwargs -#but this mimics the way the application wil work -candidatedb = {} -candidatedata = candidate.Candidate('Billy', 'Roberts', '451-784-9856', otherphone='514-874-9658') -candidatedb[candidatedata.uid] = candidatedata -DataManagement.save_candidate_data(candidatedb) -Utilities.print_object(candidatedata) - -candidatedata = candidate.Candidate('Sue', 'Allen', '451-874-9632', None, None, None, 1234567) -candidatedb[candidatedata.uid] = candidatedata -DataManagement.save_candidate_data(candidatedb) - -candidatedata = candidate.Candidate('Alan', 'Parson', '451-874-8965') -candidatedb[candidatedata.uid] = candidatedata -DataManagement.save_candidate_data(candidatedb) - -candidatedata = candidate.Candidate('Pierre', 'Tremblay', '547-852-9745') -candidatedb[candidatedata.uid] = candidatedata -DataManagement.save_candidate_data(candidatedb) - -candidatedata = candidate.Candidate('Alain', 'Jeanson', '245-874-6321') -candidatedb[candidatedata.uid] = candidatedata -DataManagement.save_candidate_data(candidatedb) - -candidatedata = candidate.Candidate('Marc', 'St-Pierre', '412-897-9874') -candidatedb[candidatedata.uid] = candidatedata -DataManagement.save_candidate_data(candidatedb) - - -#add visit data to candidates -db = dict(DataManagement.read_candidate_data()) -#get all key values -keylist = [] -for key in db: - keylist.append(key) - -candidate1 = db.get(keylist[0]) -visitlabel = 'V0' #TODO selection from droplist -visitdate = '2014-12-25' #TODO add regex controls -visittime = '13:15' #TODO add regex controls -visitwhere = 'CRIUGM lobby' -visitwhom = 'Annie' -thisvisit = candidate1.set_visit_date(visitlabel, visitdate, visittime, visitwhere, visitwhom) -candidate1.set_next_visit_window(candidate1, thisvisit) -DataManagement.save_candidate_data(db) - - -candidate2 = db.get(keylist[1]) -visitlabel = 'V1' #TODO selection from droplist -visitdate = '2014-12-27' #TODO add regex controls -visittime = '14:30' #TODO add regex controls -visitwhere = 'CRIUGM M-124' -visitwhom = 'Jean' -thisvisit = candidate2.set_visit_date(visitlabel, visitdate, visittime, visitwhere, visitwhom) -candidate2.set_next_visit_window(candidate2, thisvisit) -DataManagement.save_candidate_data(db) - -candidate3 = db.get(keylist[2]) -visitlabel = 'V1' #TODO selection from droplist -visitdate = '2015-01-13' #TODO add regex controls -visittime = '09:15' #TODO add regex controls -visitwhere = 'McDo' -visitwhom = 'Scott' -thisvisit = candidate3.set_visit_date(visitlabel, visitdate, visittime, visitwhere, visitwhom) -candidate3.set_next_visit_window(candidate3, thisvisit) -DataManagement.save_candidate_data(db) -Utilities.print_object(thisvisit) - -candidate4 = db.get(keylist[3]) -visitlabel = 'V0' #TODO selection from droplist -visitdate = '2015-02-24' #TODO add regex controls -visittime = '15:30' #TODO add regex controls -visitwhere = 'IGA' -visitwhom = 'Charlie' -thisvisit = candidate4.set_visit_date(visitlabel, visitdate, visittime, visitwhere, visitwhom) -candidate4.set_next_visit_window(candidate4, thisvisit) - -Utilities.print_object(thisvisit) - -DataManagement.save_candidate_data(db) \ No newline at end of file diff --git a/scheduler/lib/datamanagement.py b/scheduler/lib/datamanagement.py index f12470c..2dbcac2 100644 --- a/scheduler/lib/datamanagement.py +++ b/scheduler/lib/datamanagement.py @@ -1,46 +1,49 @@ #imports from standard packages -import shelve -import sqlite3 +import os.path +import pickle """ The data_management.py file contains functions related to data management only. Generic functions: savedata(data, datafilename) and readdata(datafile). Currently, these are not being used. Specific functions read_candidate_data(), save_candidate_data(), read_studydata() and save_study_data() are used to get/save candidate data and study setup data respectively. """ -""" -#Generic functions -def savedata(data, datafilename): - db = shelve.open(datafilename) - for key in data: - db[data[key].uid] = data[key] - db.close() -def readdata(datafile): - db = shelve.open(datafile) #TODO check is db.close() is needed (may be automatic) - return db -""" - -#Specific functions def read_candidate_data(): - db = shelve.open("candidatedata") + """Read and return the content of a file called candidatedata. Returns nothing if file doesn't exist""" + + #check to see if file exists before loading it + if os.path.isfile("candidatedata"): + #load file + db = pickle.load(open("candidatedata", "rb")) + else: + db = "" return db + def save_candidate_data(data): - db = shelve.open("candidatedata") - for key in data: - db[data[key].uid] = data[key] - db.close() + """Save data in a pickle file named candididatedata. + Will overwrite any existing file. Will create one if it doesn't exist""" + pickle.dump(data, open("candidatedata", "wb")) + def read_studydata(): - db = shelve.open("studydata") - return db + """Read and return the content of a file called studydata. Returns nothing if file doesn't exist""" + #check to see if file exists before loading it + if os.path.isfile("studydata"): + #load file + db = pickle.load(open("studydata", "rb")) + else: + db = "" + return db + def save_study_data(data): - db = shelve.open("studydata") - for key in data: - db[data[key].uid] = data[key] - db.close() + """Save data in a pickle file named studydata. + Will overwrite any existing file. Will create one if it doesn't exist""" + pickle.dump(data, open("studydata", "wb")) + + #self-test "module" TODO remove if __name__ == '__main__': diff --git a/scheduler/lib/multilanguage.py b/scheduler/lib/multilanguage.py index 2055287..affc9ef 100644 --- a/scheduler/lib/multilanguage.py +++ b/scheduler/lib/multilanguage.py @@ -5,245 +5,218 @@ #from utilities import readappdata #TODO replace and remove #language = readappdata()[0] -language = "fr" #TODO make dynamic +language = "en" #TODO make dynamic if language == "fr": - ###################### TOP LEVEL MENU BAR ###################### - app_title = u"Outils LORIS" + app_title = u"Outils LORIS" #APPLICATION menu - application_menu = u"Application" + application_menu = u"Application" application_setting = u"Préferences" - application_quit = u"Quitter" + application_quit = u"Quitter" #PROJECT menu - #menuproject = u"Projet" #TODO remove? - #openproject = u"Ouvrir un projet" - #modifyproject = u"Modifier le projet ouvert" - #newproject = u"Créer un nouveau projet" + #menuproject = u"Projet" #TODO remove? + #openproject = u"Ouvrir un projet" + #modifyproject = u"Modifier le projet ouvert" + #newproject = u"Créer un nouveau projet" #CANDIDATE menu - candidate_menu = u"Candidat" - candidate_add = u"Nouveau candidat" - candidate_find = u"Trouver candidat" - candidate_update = u"Mettre à jour" - candidate_get_id = u"Obtenir l'identifiant d'un candidat" + candidate_menu = u"Candidat" + candidate_add = u"Nouveau candidat" + candidate_find = u"Trouver candidat" + candidate_update = u"Mettre à jour" candidate_exclude_include_toggle = u"Inclure/Exclure un candidat" + candidate_get_id = u"Obtenir l'identifiant d'un candidat" #clear_all_field = u"Effacer" #ANONYMIZER menu anonymizer_menu = u"DICOM" - anonymizer_run = u"Anonymizer" + anonymizer_run = u"Anonymizer" #CALENDAR menu - calendar_menu = u"Calendrier" + calendar_menu = u"Calendrier" calendar_new_appointment = u"Nouveau Rendez-vous" #HELP menu - help_menu = u"Aide" - help_get_help = u"Obtenir de l'aide" - help_about_window = u"A propos de ..." - + help_menu = u"Aide" + help_get_help = u"Obtenir de l'aide" + help_about_window = u"A propos de ..." ###################### PROJECT INFO PANE ####################### - project_info_pane = u"Projet" + project_info_pane = u"Projet" project_detail_pane = u"Détails du Projet" - visit_detail_pane = u"Détails des Visites" - project_name = u"Projet" - project_start = u"Début" - project_end = u"Fin" - target_recruitment = u"Cible de recrutement" + visit_detail_pane = u"Détails des Visites" + project_name = u"Projet" + project_start = u"Début" + project_end = u"Fin" + target_recruitment = u"Cible de recrutement" current_recruitment = u"Recrutement actuel" - total_visit = u"Nombre de Visites" - + total_visit = u"Nombre de Visites" #################### MULTI-TAB DATA SECTION ##################### - calendar_pane = u"Calendrier" - candidate_pane = u"Candidats" + calendar_pane = u"Calendrier" + candidate_pane = u"Candidats" label_candidate_table = u"Faites un double-clic sur l'une des lignes pour remplir les champs ci-dessus" - datatable_id = u"ID" - datatable_firstname = u"Prénom" - datatable_lastname = "Nom" - datatable_dob = u"Date de Naissance" - datatable_phone = u"Téléphone" - datatable_address = u"Adresse" - datatable_city = u"Ville" - datatable_province = u"Province" - datatable_country = u"Pays" + datatable_id = u"ID" + datatable_firstname = u"Prénom" + datatable_lastname = "Nom" + datatable_dob = u"Date de Naissance" + datatable_phone = u"Téléphone" + datatable_address = u"Adresse" + datatable_city = u"Ville" + datatable_province = u"Province" + datatable_country = u"Pays" datatable_postal_code = u"Code Postal" - calendar_monday = u"Lundi" - calendar_tuesday = u"Mardi" - calendar_wednesday = u"Mercredi" - calendar_thursday = u"Jeudi" - calendar_friday = u"Vendredi" - calendar_saturday = u"Samedi" - calendar_sunday = u"Dimanche" - calendar_january = u"Janvier" - calendar_february = u"Février" - calendar_march = u"Mars" - calendar_april = u"Avril" - calendar_may = u"Mai" - calendar_june = u"Juin" - calendar_jully = u"Juillet" - calendar_august = u"Août" - calendar_september = u"Septembre" - calendar_october = u"Octobre" - calendar_november = u"Novembre" - calendar_december = u"Décembre" + calendar_monday = u"Lundi" + calendar_tuesday = u"Mardi" + calendar_wednesday = u"Mercredi" + calendar_thursday = u"Jeudi" + calendar_friday = u"Vendredi" + calendar_saturday = u"Samedi" + calendar_sunday = u"Dimanche" + calendar_january = u"Janvier" + calendar_february = u"Février" + calendar_march = u"Mars" + calendar_april = u"Avril" + calendar_may = u"Mai" + calendar_june = u"Juin" + calendar_jully = u"Juillet" + calendar_august = u"Août" + calendar_september = u"Septembre" + calendar_october = u"Octobre" + calendar_november = u"Novembre" + calendar_december = u"Décembre" ################ COLUMN HEADER ################## - col_candidate = u"Candidat" - col_visitlabel = u"Visite" - col_when = u"Date/Heure" - col_where = u"Endroit" - col_status = u"Statut" - + col_candidate = u"Candidat" + col_visitlabel = u"Visite" + col_when = u"Date/Heure" + col_where = u"Endroit" + col_status = u"Statut" + col_withwhom = u"Avec qui" #################### STATUS ##################### - status_active = u"actif" - status_tentative = u"provisoire" - + status_active = u"actif" + status_tentative = u"provisoire" ################# DATA WINDOWS ################## - data_window_title = u"Information sur le candidat" - + data_window_title = u"DATA WINDOW" #TODO trouver un titre français ################## DIALOGBOX #################### -<<<<<<< Updated upstream # very not sure what to do about that section - dialog_yes = u"Oui" - dialog_no = u"Non" + dialog_yes = u"Oui" + dialog_no = u"Non" dialog_title_confirm = u"Veuillez confirmer!" - dialog_close = u"Vous êtes sur le point de fermer cette fenêtre sans sauvegarder!\n\nVoulez-vous continuer?" - dialog_quit = u"Vous êtes sur le point de fermer l'application sans sauvegarder!\n\nVoulez-vous quitter?" -======= - dialog_yes = "Oui" - dialog_no = "Non" - dialogtitle_confirm = "Veuillez confirmer!" - dialogclose = "Vous êtes sur le point de fermer cette fenêtre sans sauvegarder!\n\nVoulez-vous continuer?" ->>>>>>> Stashed changes - + dialog_close = u"Vous êtes sur le point de fermer cette fenêtre sans sauvegarder!\n\nVoulez-vous continuer?" ################ DATA WINDOW ################### - schedule_pane = u"Calendrier" - candidate_pane = u"Candidat" - candidate_firstname = u"Prénom" - candidate_lastname = u"Nom de famille" - candidate_phone = u"Téléphone" - candidate_pscid = u"ID" - candidate_status = u"Status" - schedule_visit_label = u"Visite" - schedule_visit_rank = u"#" + schedule_pane = u"Calendrier" + candidate_pane = u"Candidat" + candidate_firstname = u"Prénom" + candidate_lastname = u"Nom de famille" + candidate_phone = u"Téléphone" + candidate_pscid = u"ID" + candidate_status = u"Status" + schedule_visit_label = u"Visite" + schedule_visit_rank = u"#" schedule_visit_status = u"Status" - schedule_visit_when = u"Date" - schedule_optional = u"Optionnel" + schedule_visit_when = u"Date" + schedule_optional =u"Optionnel" - ################ NEW CANDIDATE ################## - new_candidate_title = u"Création d'un nouveau candidat" - elif language == "en": - - ###################### TOP LEVEL MENU BAR ###################### - app_title = u"LORIS tools" + app_title = u"LORIS tools" #APPLICATION menu - application_menu = u"Application" + application_menu = u"Application" application_setting = u"Preferences" - application_quit = u"Quit" + application_quit = u"Quit" #PROJECT menu - #menuproject = u"Project" #TODO remove? - #openproject = u"Open project" - #modifyproject = u"Modify open project" - #newproject = u"New project" + #menuproject = u"Project" #TODO remove? + #openproject = u"Open project" + #modifyproject = u"Modify open project" + #newproject = u"New project" #CANDIDATE menu - candidate_menu = u"Candidate" - candidate_add = u"New candidate" - candidate_find = u"Find a candidate" - candidate_update = u"Update" - candidate_get_id = u"Get a canditate ID" + candidate_menu = u"Candidate" + candidate_add = u"New candidate" + candidate_find = u"Find a candidate" + candidate_update = u"Update" candidate_exclude_include_toggle = u"Include/Exclude a candidate" + candidate_get_id = u"Get a canditate ID" #clear_all_field = u"Clear" #CALENDAR menu - calendar_menu = u"Calendar" + calendar_menu = u"Calendar" calendar_new_appointment = u"New appointment" #ANONYMIZER menu - anonymizer_menu = u"DICOM" - anonymizer_run = u"Anonymizer" + anonymizer_menu = u"DICOM" + anonymizer_run = u"Anonymizer" #HELP menu - help_menu = u"Help" - help_get_help = u"Get some help" - help_about_window = u"About this..." - + help_menu = u"Help" + help_get_help = u"Get some help" + help_about_window = u"About this..." ###################### PROJECT INFO PANE ####################### - project_info_pane = u"Project Informations" + project_info_pane = u"Project Informations" project_detail_pane = u"Project Details" - visit_detail_pane = u"Visit Details" - project_name = u"Project" - project_start = u"Start" - project_end = u"End" - target_recruitment = u"Recruitment target" + visit_detail_pane = u"Visit Details" + project_name = u"Project" + project_start = u"Start" + project_end = u"End" + target_recruitment = u"Recruitment target" current_recruitment = u"Current recruitment" - total_visit = u"Total number of Visits" - + total_visit = u"Total number of Visits" #################### MULTI-TAB DATA SECTION ##################### - calendar_pane = u"Calendar" - candidate_pane = u"Candidates" + calendar_pane = u"Calendar" + candidate_pane = u"Candidates" label_candidate_table = u"Double click on row to populate fields above" - datatable_id = u"ID" - datatable_firstname = u"First Name" - datatable_lastname = u"Last Name" - datatable_dob = u"Date of Birth" - datatable_phone = u"Phone" - datatable_address = u"Address" - datatable_city = u"City" - datatable_province = u"Province" - datatable_country = u"Country" + datatable_id = u"ID" + datatable_firstname = u"First Name" + datatable_lastname = u"Last Name" + datatable_dob = u"Date of Birth" + datatable_phone = u"Phone" + datatable_address = u"Address" + datatable_city = u"City" + datatable_province = u"Province" + datatable_country = u"Country" datatable_postal_code = u"Postal Code" - - calendar_monday = u"Monday" - calendar_tuesday = u"Tuesday" - calendar_wednesday = u"Wednesday" - calendar_thursday = u"Thursday" - calendar_friday = u"Friday" - calendar_saturday = u"Saturday" - calendar_sunday = u"Sunday" - calendar_january = u"January" - calendar_february = u"February" - calendar_march = u"Marc" - calendar_april = u"April" - calendar_may = u"May" - calendar_june = u"June" - calendar_jully = u"Jully" - calendar_august = u"August" - calendar_september = u"September" - calendar_october = u"October" - calendar_november = u"November" - calendar_december = u"December" - + + calendar_monday = u"Monday" + calendar_tuesday = u"Tuesday" + calendar_wednesday = u"Wednesday" + calendar_thursday = u"Thursday" + calendar_friday = u"Friday" + calendar_saturday = u"Saturday" + calendar_sunday = u"Sunday" + calendar_january = u"January" + calendar_february = u"February" + calendar_march = u"Marc" + calendar_april = u"April" + calendar_may = u"May" + calendar_june = u"June" + calendar_jully = u"Jully" + calendar_august = u"August" + calendar_september = u"September" + calendar_october = u"October" + calendar_november = u"November" + calendar_december = u"December" + ################ COLUMN HEADER ################## - col_candidate = u"Candidate" - col_visitlabel = u"Visit" - col_when = u"Date/Time" - col_where = u"Place" - col_status = u"Status" - + col_candidate = u"Candidate" + col_visitlabel = u"Visit" + col_when = u"Date/Time" + col_where = u"Place" + col_status = u"Status" + col_withwhom = u"Whom" #################### STATUS ##################### - status_active = u"active" + status_active = u"active" status_tentative = u"tentative" - ################# DATA WINDOWS ################## - data_window_title = u"Candidate information" - + data_window_title =u"Data Window" ################## DIALOGBOX #################### # very not sure what to do about that section - dialog_yes = u"Yes" - dialog_no = u"No" + dialog_yes = u"Yes" + dialog_no = u"No" dialog_title_confirm = u"Please confirm!" - dialog_close = u"You are about to close this window without saving! \n\nDo you want to continue?" - dialog_quit = u"You are about to close the application without saving!\n\nDo you want to quit?" + dialog_close = u"You are about to close this window without saving! \n\nDo you want to continue?" ################ DATA WINDOW ################### - schedule_pane = u"Calendrier" - candidate_pane = u"Candidat" - candidate_firstname = u"Firstname" - candidate_lastname = u"Lastname" - candidate_phone = u"Phone" - candidate_pscid = u"ID" - candidate_status = u"Status" - schedule_visit_label = u"Visit" - schedule_visit_rank = u"#" + schedule_pane = u"Calendar" + candidate_pane = u"Candidate" + candidate_firstname = u"Firstname" + candidate_lastname = u"Lastname" + candidate_phone = u"Phone" + candidate_pscid = u"ID" + candidate_status = u"Status" + schedule_visit_label = u"Visit" + schedule_visit_rank = u"#" schedule_visit_status = u"Status" - schedule_visit_when = u"Date" - schedule_optional = u"Optional" - - ################ NEW CANDIDATE ################## - new_candidate_title = u"Create a new candidate" + schedule_visit_when = u"Date" + schedule_optional =u"Optional" \ No newline at end of file diff --git a/scheduler/lib/studydata b/scheduler/lib/studydata deleted file mode 100644 index 5f8b5ddf3534264988aff6e41533f7f1dccfaf1d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24576 zcmeI(%}&BV5Ww+;7(do<^eW!a1E$+jfk)uN1?6bcO3_AzhJrqY59ist@hxATK#3;w%8Cr009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1pb#m z@@l|kj$dHc)CclAtY?%aU(5Pr{Qs>%=CUHAg^6n zzUz0p(bDq6aoa7md>Ltd?V?p!yD7W&{>PHPosxW_lK)@+&%df(v;4n3uEg`tng9QD z3C;*0fB*srAb', self.ondoubleclick) + self.datatable.bind("<>", self.onrowclick) + self.datatable.bind('', self.onrightclik) + + def load_data(self): + """Should be overriden in child class""" + pass + + def update_data(self): + for i in self.datatable.get_children(): + self.datatable.delete(i) + self.load_data() def treeview_sortby(self, tree, column, descending): - """Taken from Dave's IDmapper""" - """Sort tree contents when a column is clicked on.""" - """From: https://code.google.com/p/python-ttk/source/browse/trunk/pyttk-samples/treeview_multicolumn.py?r=21""" + """ + Sort treeview contents when a column is clicked on. + Taken from Dave's IDmapper + From: https://code.google.com/p/python-ttk/source/browse/trunk/pyttk-samples/treeview_multicolumn.py?r=21 + """ # grab values to sort data = [(tree.set(child, column), child) for child in tree.get_children('')] # reorder data @@ -40,95 +61,106 @@ def treeview_sortby(self, tree, column, descending): # switch the heading so that it will sort in the opposite direction tree.heading(column, command=lambda column=column: self.treeview_sortby(tree, column, int(not descending))) - # TODO move to dataManagement - # Need to re-write this section - def load_data(self, datatable, dataset, colheaders): - if 'firstname' in colheaders: - # This method will add cantidates information to cantidatetable - try: - for key in dataset: - if dataset[key].status is None: - status = '' - else: - status = dataset[key].status - self.datatable.insert('', 'end', - values=[dataset[key].firstname, dataset[key].lastname, dataset[key].phone, - status], tags=(status, dataset[key].uid)) - except Exception as e: - print str(e) - # utilities.errorlog(message) - pass # TODO add some error handling - - # TODO add these color settings in a 'settings and preferences section of the app' - self.datatable.tag_configure('active', background='#F1F8FF') - # Behavior of datatable on single and double click - self.datatable.bind('', self.ondoubleclick) - self.datatable.bind("<>", self.onrowclick) - self.datatable.bind('', self.onrightclik) - - elif 'candidate' in colheaders: - for key, value in dataset.iteritems(): - if dataset[key].visitset is not None: # skip the search if visitset = None - currentvisitset = dataset[key].visitset # set this candidate.visitset for the next step - # gather information about the candidate - # this candidatekey is not printed on screen but saved with the new Scheduler object - # (after all it is the candidate unique id^^) - candidatekey = dataset[key].uid - - candidatefirstname = dataset[key].firstname - candidatelastname = dataset[key].lastname - candidatefullname = str(candidatefirstname + ' ' + candidatelastname) - for key, value in currentvisitset.iteritems(): - if currentvisitset[key].status is not None: - visitlabel = currentvisitset[key].visitlabel - if currentvisitset[key].when is None: - when = currentvisitset[key].whenearliest - else: - when = currentvisitset[key].when - if currentvisitset[key].where is None: - where = '' - else: - where = currentvisitset[key].where - if currentvisitset[key].status is None: - status = '' - else: - status = currentvisitset[key].status - # TODO ? create a new scheduler object with these informations - # This method will add planned visits information to the visit datatable - try: - self.datatable.insert('', 'end', - values=[candidatefullname, visitlabel, when, where, status], - tags=(status, candidatekey, visitlabel)) - except Exception as e: - print "Exception AddIdentifierAction(self, dataset, save=True): " + str( - e) # TODO dd some error handling - pass - - # TODO add these color settings in a 'settings and preferences section of the app' - self.datatable.tag_configure('active',background='#F1F8FF') - self.datatable.tag_configure('tentative',background='#F0F0F0') - # Behavior of datatable on single and double click - self.datatable.bind('', self.ondoubleclick) - self.datatable.bind("<>", self.onrowclick) - self.datatable.bind('', self.onrightclik) - def ondoubleclick(self, event): - # double clicking on blank space of the treeview when no valide line is selected generates an IndexOutOfRange - # error which is taken care of by this try:except block + """ + Double clicking on a treeview line opens a 'data window' + and refresh the treeview data when the 'data window' is closed + """ + + # double clicking on blank space of the treeview when no valid line is selected generates a + # IndexOutOfRange error which is taken care of by this try:except block try: itemID = self.datatable.selection()[0] - item =self.datatable.item(itemID)['tags'] + item = self.datatable.item(itemID)['tags'] parent = self.parent candidate_uuid = item[1] DataWindow.DataWindow(parent, candidate_uuid) + self.update_data() except Exception as e: - print str(e) #TODO deal with exception or not!?! + print "Datatable ondoubleclick ", str(e) # TODO deal with exception or not!?! def onrightclik(self, event): + """Not used yet""" print 'contextual menu for this item' pass - def onrowclick(self,event): + def onrowclick(self, event): + """Not used yet""" item_id = str(self.datatable.focus()) item = self.datatable.item(item_id)['values'] - print item_id, item \ No newline at end of file + # print item_id, item #TODO remove when done + + +class ParticipantsList(DataTable): + """ + class ParticipantsList(DataTable) takes care of the data table holding the list of participants + That list is comprised of all participants (even those that have not been called once. + """ + + def __init__(self, parent, colheaders): # expected is dataset + DataTable.__init__(self, parent, colheaders) + self.colheaders = colheaders + self.load_data() + # TODO add these color settings in a 'settings and preferences section of the app' + self.datatable.tag_configure('active', background='#F1F8FF') # TODO replace active tag by status variable value + + def load_data(self): + data = dict(DataManagement.read_candidate_data()) + try: + for key in data: + if data[key].status is None: + status = '' + else: + status = data[key].status + self.datatable.insert('', 'end', + values=[data[key].firstname, data[key].lastname, data[key].phone, + status], tags=(status, data[key].uid)) + except Exception as e: + print "datatable.ParticipantsList.load_data ", str(e) # TODO proper exception handling + pass + + +class VisitList(DataTable): + """ + class VisitList(DataTable) takes care of the data table holding the list of all appointments + even those that have not been confirmed yet. + """ + + def __init__(self, parent, colheaders): + DataTable.__init__(self, parent, colheaders) + self.colheaders = colheaders + self.load_data() + # TODO add these color settings in a 'settings and preferences section of the app' + self.datatable.tag_configure('active', background='#F1F8FF') # TODO change for non-language parameter + self.datatable.tag_configure('tentative', background='#F0F0F0') # TODO change for non-language parameter + + def load_data(self): + data = dict(DataManagement.read_candidate_data()) + for key, value in data.iteritems(): + if data[key].visitset is not None: # skip the search if visitset = None + current_visitset = data[key].visitset # set this candidate.visitset for the next step + # gather information about the candidate + # this candidatekey is not printed on screen but saved with the new Scheduler object + candidatekey = data[key].uid + candidate_firstname = data[key].firstname + candidate_lastname = data[key].lastname + candidate_fullname = str(candidate_firstname + ' ' + candidate_lastname) + for key, value in current_visitset.iteritems(): + if current_visitset[key].status is not None: + status = current_visitset[key].status + visit_label = current_visitset[key].visitlabel + if current_visitset[key].when is None: + when = current_visitset[key].whenearliest + else: + when = current_visitset[key].when + if current_visitset[key].where is None: + where = '' + else: + where = current_visitset[key].where + try: + self.datatable.insert('', 'end', + values=[candidate_fullname, visit_label, when, where, status], + tags=(status, candidatekey, visit_label)) + except Exception as e: + print "datatable.VisitList.load_data ", str(e) # TODO add proper error handling + pass diff --git a/scheduler/ui/datawindow.py b/scheduler/ui/datawindow.py index 2f5449f..712bca7 100644 --- a/scheduler/ui/datawindow.py +++ b/scheduler/ui/datawindow.py @@ -1,20 +1,24 @@ -#import standard packages +# import standard packages from Tkinter import * from ttk import * -#import internal packages +# import internal packages from visit import Visit +import candidate as Candidate import ui.dialogbox as DialogBox import lib.utilities as Utilities import lib.multilanguage as MultiLanguage import lib.datamanagement as DataManagement -#ref: http://effbot.org/tkinterbook/tkinter-newDialog-windows.htm -#TODO this class needs a major clean-up + + +# ref: http://effbot.org/tkinterbook/tkinter-newDialog-windows.htm +# TODO this class needs a major clean-up + + class DataWindow(Toplevel): - def __init__(self, parent, candidate_uuid): + def __init__(self, parent, candidate_uuid='new'): Toplevel.__init__(self, parent) - #create a transient window on top of parent window - print "running DataWindow(Toplevel) " + str(candidate_uuid) #TODO remove when done + # create a transient window on top of parent window self.transient(parent) self.parent = parent self.title(MultiLanguage.data_window_title) #TODO find a better title for the thing @@ -29,47 +33,50 @@ def __init__(self, parent, candidate_uuid): self.protocol("WM_DELETE_WINDOW", self.closedialog) Utilities.center_window(self) self.initial_focus.focus_set() - #self.deiconify() + # self.deiconify() self.wait_window(self) def body(self, master, candidate): + """Creates the body of 'datawindow'. param candidate is the candidate.uuid""" try: - data = dict(DataManagement.read_candidate_data()) #TODO better way to do this + data = dict(DataManagement.read_candidate_data()) # TODO better way to do this candidate = data.get(candidate) except Exception as e: - print str(e) #TODO manage exceptions - #Candidate section + print "datawindow.body ", str(e) # TODO manage exceptions + # Candidate section self.candidate_pane = Labelframe(self, text=MultiLanguage.candidate_pane, width=250, height=350, borderwidth=10) self.candidate_pane.pack(side=TOP, expand=YES, fill=BOTH, padx=5, pady=5) - #PSCID + # object unique id - does not appear on gui but needed to keep track of this candidate + self.candidate_uid = candidate.uid + # PSCID self.label_pscid = Label(self.candidate_pane, text=MultiLanguage.candidate_pscid) self.label_pscid.grid(column=0, row=0, padx=10, pady=5, sticky=N+S+E+W) self.text_pscid_var = StringVar() self.text_pscid_var.set(candidate.pscid) self.text_pscid = Entry(self.candidate_pane, textvariable=self.text_pscid_var) self.text_pscid.grid(column=0, row=1, padx=10, pady=5, sticky=N+S+E+W) - #status + # status self.label_status = Label(self.candidate_pane, text=MultiLanguage.candidate_status) self.label_status.grid(column=1, row=0, padx=10, pady=5, sticky=N+S+E+W) self.text_status_var = StringVar() self.text_status_var.set(candidate.status) self.text_status = Entry(self.candidate_pane, textvariable=self.text_status_var) self.text_status.grid(column=1, row=1, padx=10, pady=5, sticky=N+S+E+W) - #firstname + # firstname self.label_firstname = Label(self.candidate_pane, text=MultiLanguage.candidate_firstname) self.label_firstname.grid(column=0, row=2, padx=10, pady=5, sticky=N+S+E+W) self.text_firstname_var = StringVar() self.text_firstname_var.set(candidate.firstname) self.text_firstname = Entry(self.candidate_pane, textvariable=self.text_firstname_var) self.text_firstname.grid(column=0, row=3, padx=10, pady=5, sticky=N+S+E+W) - #lastname + # lastname self.label_lastname = Label(self.candidate_pane, text=MultiLanguage.candidate_lastname) self.label_lastname.grid(column=1, row=2, padx=10, pady=5, sticky=N+S+E+W) self.text_lastname_var = StringVar() self.text_lastname_var.set(candidate.lastname) self.text_lastname = Entry(self.candidate_pane, textvariable=self.text_lastname_var) self.text_lastname.grid(column=1, row=3, padx=10, pady=5, sticky=N+S+E+W) - #phone number + # phone number self.label_phone = Label(self.candidate_pane, text=MultiLanguage.candidate_phone) self.label_phone.grid(column=2, row=2, padx=10, pady=5, sticky=N+S+E+W) self.text_phone_var = StringVar() @@ -77,10 +84,10 @@ def body(self, master, candidate): self.text_phone = Entry(self.candidate_pane, textvariable=self.text_phone_var) self.text_phone.grid(column=2, row=3, padx=10, pady=5, sticky=N+S+E+W) - #Schedule Section - displayed as a table + # Schedule Section - displayed as a table self.schedule_pane = Labelframe(self, text=MultiLanguage.schedule_pane, width=250, height=350, borderwidth=10) self.schedule_pane.pack(side=TOP, expand=YES, fill=BOTH, padx=5, pady=5) - #top row (header) + # top row (header) self.label_visit_rank = Label(self.schedule_pane, text=MultiLanguage.schedule_visit_rank) self.label_visit_rank.grid(column=0, row=0, padx=5, pady=5, sticky=N+S+E+W) self.label_visit_label = Label(self.schedule_pane, text=MultiLanguage.col_visitlabel) @@ -97,8 +104,8 @@ def body(self, master, candidate): """ PSEUDOCODE 1. Get candidate.visitset - 2. Parse into a sorted (on visit.rank) list - 3. Print data on scree + 2. Parse into a sorted list (sorted on visit.rank) + 3. Print data on screen visit_set = candidate.visitset @@ -109,9 +116,9 @@ def body(self, master, candidate): for key, value in visit_list.iteritems(): """ - #TODO add logic "foreach" to create a table showing each visit - import lib.utilities as Utilities #TODO delete when done - #1- Get candidate visitset and parse into a list + # TODO add logic "foreach" to create a table showing each visit + import lib.utilities as Utilities # TODO delete when done + # 1- Get candidate visitset and parse into a list visit_list = [] visitset = candidate.visitset if visitset is None: @@ -119,35 +126,33 @@ def body(self, master, candidate): else: for key, value in visitset.iteritems(): visit_list.append(visitset[key]) - #2- Sort list on visit.rank + # 2- Sort list on visit.rank visit_list = sorted(visit_list, key=lambda visit: visit.rank) - #3- 'print' values on ui - import lib.entrybox as EntryBox #TODO relocate when done + # 3- 'print' values on ui x = 0 for x in range(len(visit_list)): - #rank + # rank label_visit_rank = Label(self.schedule_pane, text=visit_list[x].rank) label_visit_rank.grid(column=0, row=x+1, padx=5, pady=5, sticky=N+S+E+W) - #visitlabel + # visitlabel label_visit_label = Label(self.schedule_pane, text=visit_list[x].visitlabel) label_visit_label.grid(column=1, row=x+1, padx=5, pady=5, sticky=N+S+E+W) - #when + # when if visit_list[x].when == None: visit = visit_list[x] date_range = Visit.visit_date_range(visit) - print date_range label_visit_when = Label(self.schedule_pane, text=date_range) label_visit_when.grid(column=2, row=x+1, padx=5, pady=5, sticky=N+S+E+W) else: label_visit_when = Label(self.schedule_pane, text=visit_list[x].when) label_visit_when.grid(column=2, row=x+1, padx=5, pady=5, sticky=N+S+E+W) - #where + # where label_visit_where = Label(self.schedule_pane, text=visit_list[x].where) label_visit_where.grid(column=3, row=x+1, padx=5, pady=5, sticky=N+S+E+W) - #withwhom + # withwhom label_visit_where = Label(self.schedule_pane, text=visit_list[x].withwhom) label_visit_where.grid(column=4, row=x+1, padx=5, pady=5, sticky=N+S+E+W) - #status + # status label_visit_where = Label(self.schedule_pane, text=visit_list[x].status) label_visit_where.grid(column=5, row=x+1, padx=5, pady=5, sticky=N+S+E+W) @@ -165,10 +170,12 @@ def button_box(self): box.pack() def ok_button(self, event=None): - print "saving data and closing" #TODO remove when done + print "saving data and closing" # TODO remove when done + self.capture_data() if not self.validate(): self.initial_focus.focus_set() # put focus back return + #need to call treeview update here self.withdraw() self.closedialog() @@ -182,8 +189,26 @@ def cancel_button(self, event=None): return def closedialog(self, event=None): - self.parent.focus_set() #put focus back to parent window before destroying the window + self.parent.focus_set() # put focus back to parent window before destroying the window self.destroy() def validate(self): return 1 + + def capture_data(self): + """ + Grap the information from the window's text field and save the candidate information based on candidate_uid. + """ + # open the 'database' + db = dict(DataManagement.read_candidate_data()) + # and find candidate based on uid + uid = self.candidate_uid + candidate = db[uid] + # capture data from fields + candidate.pscid = self.text_pscid.get() + candidate.status = self.text_status.get() + candidate.firstname = self.text_firstname.get() + candidate.lastname = self.text_lastname.get() + candidate.phone = self.text_phone.get() + # save data + DataManagement.save_candidate_data(db) \ No newline at end of file diff --git a/scheduler/ui/dialogbox.py b/scheduler/ui/dialogbox.py index 2c60c97..182e830 100644 --- a/scheduler/ui/dialogbox.py +++ b/scheduler/ui/dialogbox.py @@ -9,7 +9,7 @@ class DialogBox(Toplevel): This class was created mainly because the native dialog box don't work as expected when called from a top-level window. This class (although it could be improved in many aspects) insure that the parent window cannot get focus while a dialog box is still active. """ - def __init__(self,parent, title, message, button_ok, button_cancel): + def __init__(self,parent, title, message, button1, button2): Toplevel.__init__(self,parent) self.transient(parent) self.parent = parent @@ -17,13 +17,13 @@ def __init__(self,parent, title, message, button_ok, button_cancel): body = Frame(self) self.initial_focus = self.body(body, message) body.pack(padx=4, pady=4) - self.buttonbox(button_ok, button_cancel) + self.buttonbox(button1, button2) self.grab_set() if not self.initial_focus: self.initial_focus = self - self.protocol("WM_DELETE_WINDOW", self.button_cancel) + self.protocol("WM_DELETE_WINDOW", self.button2) Utilities.center_window(self) self.initial_focus.focus_set() self.deiconify() @@ -34,25 +34,25 @@ def body(self, parent, message): label.pack(padx=4, pady=4) pass - def buttonbox(self, button_ok, button_cancel): + def buttonbox(self, button1, button2): #add a standard button box box = Frame(self) - b1 = Button(box, text=button_ok, width=12, command=self.button_ok, default=ACTIVE) + b1 = Button(box, text=button1, width=12, command=self.button1, default=ACTIVE) b1.pack(side=LEFT, padx=4, pady=4) - b2 = Button(box, text=button_cancel, width=12, command=self.button_cancel, default=ACTIVE) + b2 = Button(box, text=button2, width=12, command=self.button2, default=ACTIVE) b2.pack(side=LEFT, padx=4, pady=4) - self.bind("", self.button_ok) - self.bind("", self.button_cancel) + self.bind("", self.button1) + self.bind("", self.button2) box.pack() - def button_ok(self, event=None): + def button1(self, event=None): if not self.validate(): self.initial_focus.focus_set() #put focus on Button return self.buttonvalue = 1 self.closedialog() - def button_cancel(self, event=None): + def button2(self, event=None): self.buttonvalue = 2 self.closedialog() @@ -69,9 +69,9 @@ def validate(self): class ConfirmYesNo(DialogBox): def __init__(self, parent, message): title = MultiLanguage.dialog_title_confirm - button_yes = MultiLanguage.dialog_yes - button_no = MultiLanguage.dialog_no - DialogBox.__init__(self, parent, title, message, button_yes, button_no) + button1 = MultiLanguage.dialog_yes + button2 = MultiLanguage.dialog_no + DialogBox.__init__(self, parent, title, message, button1, button2) diff --git a/scheduler/ui/menubar.py b/scheduler/ui/menubar.py index 8f3e688..12303e4 100644 --- a/scheduler/ui/menubar.py +++ b/scheduler/ui/menubar.py @@ -2,7 +2,7 @@ import Tkinter #import internal packages import lib.multilanguage as MultiLanguage -import ui.dialogbox as DialogBox +import ui.datawindow as DataWindow class MenuBar(Tkinter.Menu): def __init__(self, parent): @@ -43,15 +43,9 @@ def app_settings(self): pass def quit_application(self): - # TODO Mac instance has a Python->quit menu on top of Application->Quitter - parent = Tkinter.Frame(self) - button_yes = MultiLanguage.dialog_yes - button_no = MultiLanguage.dialog_no - newwin = DialogBox.ConfirmYesNo(parent, MultiLanguage.dialog_quit) - if newwin.buttonvalue == 1: - self.quit() - else: - return + #TODO implement quit_application() + print 'running quit_application' + self.quit() pass def open_calendar(self): @@ -66,6 +60,7 @@ def dicom_anonymizer(self): def add_candidate(self): #TODO implement add_candidate() + DataWindow.DataWindow(self, "new") print 'running add_candidate' pass @@ -97,4 +92,4 @@ def open_help(self): def about_application(self): #TODO about_application() print 'running about_application' - pass + pass \ No newline at end of file diff --git a/scheduler/visit.py b/scheduler/visit.py index 22df527..7ae2f24 100644 --- a/scheduler/visit.py +++ b/scheduler/visit.py @@ -75,11 +75,17 @@ def __init__(self, rank, visitlabel, previousvisit, visitwindow, visitmargin, ac self.status = status def visit_date_range(self): - print 'I get here' - early_date = datetime.datetime.date(self.whenearliest) - late_date = datetime.datetime.date(self.whenlatest) - date_range = str(early_date), '<>', str(late_date) + #need to handle the case where a visit has no dates + #this works but Exception handling seems to broad + try: + early_date = datetime.datetime.date(self.whenearliest) + late_date = datetime.datetime.date(self.whenlatest) + date_range = str(early_date), '<>', str(late_date) + except Exception as e: + #print e + date_range = "" return date_range + From 2161f0924689d02b2b018e9031f1b38a2099cfe1 Mon Sep 17 00:00:00 2001 From: pemorin Date: Tue, 17 May 2016 13:31:46 -0400 Subject: [PATCH 32/89] 20160517 - added data files --- scheduler/studydata | 66 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 scheduler/studydata diff --git a/scheduler/studydata b/scheduler/studydata new file mode 100644 index 0000000..94c4f52 --- /dev/null +++ b/scheduler/studydata @@ -0,0 +1,66 @@ +(dp0 +S'b33966f0-66dc-11e5-8b46-e0b9a5f8b710' +p1 +(ivisit +VisitSetup +p2 +(dp3 +S'visitmargin' +p4 +I2 +sS'uid' +p5 +g1 +sS'visitwindow' +p6 +I10 +sS'visitlabel' +p7 +S'V1' +p8 +sS'rank' +p9 +I2 +sS'actions' +p10 +NsS'previousvisit' +p11 +S'V0' +p12 +sbsS'b32cbcc0-66dc-11e5-8d68-e0b9a5f8b710' +p13 +(ivisit +VisitSetup +p14 +(dp15 +g4 +Nsg5 +g13 +sg6 +Nsg7 +g12 +sg9 +I1 +sg10 +Nsg11 +NsbsS'b33966f1-66dc-11e5-83c1-e0b9a5f8b710' +p16 +(ivisit +VisitSetup +p17 +(dp18 +g4 +I10 +sg5 +g16 +sg6 +I20 +sg7 +S'V2' +p19 +sg9 +I3 +sg10 +Nsg11 +g8 +sbs. \ No newline at end of file From 594becccb166cfa230cd73ccf940d0b130ec77da Mon Sep 17 00:00:00 2001 From: pemorin Date: Tue, 17 May 2016 13:32:57 -0400 Subject: [PATCH 33/89] 20160517 - added data files --- scheduler/candidatedata | 572 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 572 insertions(+) create mode 100644 scheduler/candidatedata diff --git a/scheduler/candidatedata b/scheduler/candidatedata new file mode 100644 index 0000000..5b7d235 --- /dev/null +++ b/scheduler/candidatedata @@ -0,0 +1,572 @@ +(dp0 +S'b33a0330-66dc-11e5-a66f-e0b9a5f8b710' +p1 +(icandidate +Candidate +p2 +(dp3 +S'status' +p4 +S'active' +p5 +sS'pscid' +p6 +S'None' +p7 +sS'uid' +p8 +g1 +sS'firstname' +p9 +S'Marc' +p10 +sS'lastname' +p11 +S'St-Pierre' +p12 +sS'visitset' +p13 +(dp14 +S'V0' +p15 +(ivisit +Visit +p16 +(dp17 +g4 +S'active' +p18 +sS'visitmargin' +p19 +Nsg8 +S'b33a5151-66dc-11e5-bc4a-e0b9a5f8b710' +p20 +sS'visitwindow' +p21 +NsS'visitlabel' +p22 +g15 +sS'when' +p23 +cdatetime +datetime +p24 +(S'\x07\xde\x0c\x19\r\x0f\x00\x00\x00\x00' +p25 +tp26 +Rp27 +sS'rank' +p28 +I1 +sS'actions' +p29 +NsS'whenearliest' +p30 +NsS'previousvisit' +p31 +NsS'whenlatest' +p32 +NsS'where' +p33 +S'CRIUGM lobby' +p34 +sS'withwhom' +p35 +S'Annie' +p36 +sbsS'V1' +p37 +(ivisit +Visit +p38 +(dp39 +g4 +S'tentative' +p40 +sg19 +I2 +sg8 +S'b33a5152-66dc-11e5-8d1d-e0b9a5f8b710' +p41 +sg21 +I10 +sg22 +g37 +sg23 +Nsg28 +I2 +sg29 +Nsg30 +g24 +(S'\x07\xdf\x01\x02\x00\x00\x00\x00\x00\x00' +p42 +tp43 +Rp44 +sg31 +g15 +sg32 +g24 +(S'\x07\xdf\x01\x06\x00\x00\x00\x00\x00\x00' +p45 +tp46 +Rp47 +sg33 +Nsg35 +NsbsS'V2' +p48 +(ivisit +Visit +p49 +(dp50 +g4 +Nsg19 +I10 +sg8 +S'b33a5153-66dc-11e5-b200-e0b9a5f8b710' +p51 +sg21 +I20 +sg22 +g48 +sg23 +Nsg28 +I3 +sg29 +Nsg30 +Nsg31 +g37 +sg32 +Nsg33 +Nsg35 +NsbssS'phone' +p52 +S'412-897-9874' +p53 +sbsS'b3398e01-66dc-11e5-9720-e0b9a5f8b710' +p54 +(icandidate +Candidate +p55 +(dp56 +g4 +S'active' +p57 +sg6 +S'' +p58 +sg8 +g54 +sg9 +S'Sue' +p59 +sg11 +S'Allen' +p60 +sg13 +(dp61 +S'V0' +p62 +(ivisit +Visit +p63 +(dp64 +g4 +g18 +sg19 +Nsg8 +S'b33b62c0-66dc-11e5-84ad-e0b9a5f8b710' +p65 +sg21 +Nsg22 +g62 +sg23 +g24 +(S'\x07\xde\x0c\x1b\x0e\x1e\x00\x00\x00\x00' +p66 +tp67 +Rp68 +sg28 +I1 +sg29 +Nsg30 +Nsg31 +Nsg32 +Nsg33 +S'CRIUGM M-124' +p69 +sg35 +S'Jean' +p70 +sbsS'V1' +p71 +(ivisit +Visit +p72 +(dp73 +g4 +g18 +sg19 +I2 +sg8 +S'b33b62c1-66dc-11e5-b6fb-e0b9a5f8b710' +p74 +sg21 +I10 +sg22 +g71 +sg23 +g24 +(S'\x07\xdf\x02\x07\t\x0f\x00\x00\x00\x00' +p75 +tp76 +Rp77 +sg28 +I2 +sg29 +Nsg30 +g24 +(S'\x07\xdf\x01\x04\x00\x00\x00\x00\x00\x00' +p78 +tp79 +Rp80 +sg31 +g62 +sg32 +g24 +(S'\x07\xdf\x01\x08\x00\x00\x00\x00\x00\x00' +p81 +tp82 +Rp83 +sg33 +g69 +sg35 +g70 +sbsS'V2' +p84 +(ivisit +Visit +p85 +(dp86 +g4 +g40 +sg19 +I10 +sg8 +S'b33b62c2-66dc-11e5-947a-e0b9a5f8b710' +p87 +sg21 +I20 +sg22 +g84 +sg23 +Nsg28 +I3 +sg29 +Nsg30 +g24 +(S'\x07\xdf\x02\x11\x00\x00\x00\x00\x00\x00' +p88 +tp89 +Rp90 +sg31 +g71 +sg32 +g24 +(S'\x07\xdf\x03\t\x00\x00\x00\x00\x00\x00' +p91 +tp92 +Rp93 +sg33 +Nsg35 +Nsbssg52 +S'451-874-9632' +p94 +sbsS'b339dc22-66dc-11e5-b4a9-e0b9a5f8b710' +p95 +(icandidate +Candidate +p96 +(dp97 +g4 +S'active' +p98 +sg6 +g58 +sg8 +g95 +sg9 +S'Alain' +p99 +sg11 +S'Jeanson' +p100 +sg13 +(dp101 +S'V0' +p102 +(ivisit +Visit +p103 +(dp104 +g4 +g18 +sg19 +Nsg8 +S'b33bff00-66dc-11e5-aeea-e0b9a5f8b710' +p105 +sg21 +Nsg22 +g102 +sg23 +g24 +(S'\x07\xdf\x01\r\t\x0f\x00\x00\x00\x00' +p106 +tp107 +Rp108 +sg28 +I1 +sg29 +Nsg30 +Nsg31 +Nsg32 +Nsg33 +S'McDo' +p109 +sg35 +S'Scott' +p110 +sbsS'V1' +p111 +(ivisit +Visit +p112 +(dp113 +g4 +g40 +sg19 +I2 +sg8 +S'b33bff01-66dc-11e5-822c-e0b9a5f8b710' +p114 +sg21 +I10 +sg22 +g111 +sg23 +Nsg28 +I2 +sg29 +Nsg30 +g24 +(S'\x07\xdf\x01\x15\x00\x00\x00\x00\x00\x00' +p115 +tp116 +Rp117 +sg31 +g102 +sg32 +g24 +(S'\x07\xdf\x01\x19\x00\x00\x00\x00\x00\x00' +p118 +tp119 +Rp120 +sg33 +Nsg35 +NsbsS'V2' +p121 +(ivisit +Visit +p122 +(dp123 +g4 +Nsg19 +I10 +sg8 +S'b33bff02-66dc-11e5-a651-e0b9a5f8b710' +p124 +sg21 +I20 +sg22 +g121 +sg23 +Nsg28 +I3 +sg29 +Nsg30 +Nsg31 +g111 +sg32 +Nsg33 +Nsg35 +Nsbssg52 +S'245-874-6321' +p125 +sbsS'b3398e00-66dc-11e5-a42e-e0b9a5f8b710' +p126 +(icandidate +Candidate +p127 +(dp128 +g4 +S'active' +p129 +sg6 +g58 +sg8 +g126 +sg9 +S'Billy Bob' +p130 +sg11 +S'Roberts' +p131 +sg13 +(dp132 +S'V0' +p133 +(ivisit +Visit +p134 +(dp135 +g4 +g18 +sg19 +Nsg8 +S'b33c7430-66dc-11e5-920e-e0b9a5f8b710' +p136 +sg21 +Nsg22 +g133 +sg23 +g24 +(S'\x07\xdf\x02\x18\x0f\x1e\x00\x00\x00\x00' +p137 +tp138 +Rp139 +sg28 +I1 +sg29 +Nsg30 +Nsg31 +Nsg32 +Nsg33 +S'IGA' +p140 +sg35 +S'Charlie' +p141 +sbsS'V1' +p142 +(ivisit +Visit +p143 +(dp144 +g4 +g40 +sg19 +I2 +sg8 +S'b33c7431-66dc-11e5-b674-e0b9a5f8b710' +p145 +sg21 +I10 +sg22 +g142 +sg23 +Nsg28 +I2 +sg29 +Nsg30 +g24 +(S'\x07\xdf\x03\x04\x00\x00\x00\x00\x00\x00' +p146 +tp147 +Rp148 +sg31 +g133 +sg32 +g24 +(S'\x07\xdf\x03\x08\x00\x00\x00\x00\x00\x00' +p149 +tp150 +Rp151 +sg33 +Nsg35 +NsbsS'V2' +p152 +(ivisit +Visit +p153 +(dp154 +g4 +Nsg19 +I10 +sg8 +S'b33c7432-66dc-11e5-902c-e0b9a5f8b710' +p155 +sg21 +I20 +sg22 +g152 +sg23 +Nsg28 +I3 +sg29 +Nsg30 +Nsg31 +g142 +sg32 +Nsg33 +Nsg35 +Nsbssg52 +S'451-784-9856' +p156 +sS'otherphone' +p157 +S'514-874-9658' +p158 +sbsS'b339dc21-66dc-11e5-9e5e-e0b9a5f8b710' +p159 +(icandidate +Candidate +p160 +(dp161 +g4 +S'None' +p162 +sg6 +S'None' +p163 +sg8 +g159 +sg9 +S'Pierre' +p164 +sg11 +S'Tremblay' +p165 +sg13 +Nsg52 +S'547-852-9745' +p166 +sbsS'b339b50f-66dc-11e5-acb2-e0b9a5f8b710' +p167 +(icandidate +Candidate +p168 +(dp169 +g4 +Nsg6 +Nsg8 +g167 +sg9 +S'Alan' +p170 +sg11 +S'Parson' +p171 +sg13 +Nsg52 +S'451-874-8965' +p172 +sbs. \ No newline at end of file From 0dc73dfc99f67e4e19a4e375115cc2be2b9b68ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Tue, 17 May 2016 15:15:27 -0400 Subject: [PATCH 34/89] Added Windows executable in a zip folder, as well as the files that allows its creation on Windows (setup.py and win_build.sh) --- DicAT_application.py | 8 +- candidatedata | 560 ++++++++++++++++++ .../create_alldata.py => create_alldata.py | 4 +- {scheduler/lib => lib}/__init__.py | 0 {scheduler/lib => lib}/classtool.py | 0 {scheduler/lib => lib}/datamanagement.py | 0 {scheduler/lib => lib}/multilanguage.py | 0 {scheduler/lib => lib}/utilities.py | 0 scheduler/{ui => }/__init__.py | 0 scheduler/candidate.py | 2 + scheduler/candidatedata | 2 +- scheduler/tests/create1_StudySetup.py | 4 +- scheduler/tests/create2_CandidateList_test.py | 4 +- scheduler/tests/create3_CandidateVisit.py | 5 +- scheduler/tests/create4_PopulateVisits.py | 5 +- scheduler/tests/test.py | 4 +- scheduler/tests/test1.py | 2 +- scheduler/ui/datatable.py | 166 ------ scheduler/ui/datawindow.py | 214 ------- scheduler/ui/dialogbox.py | 78 --- scheduler/ui/menubar.py | 95 --- scheduler/ui/projectpane.py | 6 - scheduler/visit.py | 4 +- ...application.py => scheduler_application.py | 32 +- scheduler_candidate.py | 196 ++++++ scheduler_visit.py | 93 +++ studydata | 66 +++ 27 files changed, 957 insertions(+), 593 deletions(-) create mode 100644 candidatedata rename scheduler/tests/create_alldata.py => create_alldata.py (98%) rename {scheduler/lib => lib}/__init__.py (100%) rename {scheduler/lib => lib}/classtool.py (100%) rename {scheduler/lib => lib}/datamanagement.py (100%) rename {scheduler/lib => lib}/multilanguage.py (100%) rename {scheduler/lib => lib}/utilities.py (100%) rename scheduler/{ui => }/__init__.py (100%) delete mode 100644 scheduler/ui/datatable.py delete mode 100644 scheduler/ui/datawindow.py delete mode 100644 scheduler/ui/dialogbox.py delete mode 100644 scheduler/ui/menubar.py delete mode 100644 scheduler/ui/projectpane.py rename scheduler/application.py => scheduler_application.py (62%) create mode 100644 scheduler_candidate.py create mode 100644 scheduler_visit.py create mode 100644 studydata diff --git a/DicAT_application.py b/DicAT_application.py index 12b518d..7d798c1 100644 --- a/DicAT_application.py +++ b/DicAT_application.py @@ -5,6 +5,7 @@ from dicom_anonymizer_frame import dicom_anonymizer_frame_gui from IDMapper import IDMapper_frame_gui +from scheduler_application import UserInterface class DicAT_application(): @@ -36,7 +37,7 @@ def __init__(self, master, side=LEFT): # Add the pages to the notebook self.nb.add(self.page1, text='Welcome to DicAT!') self.nb.add(self.page2, text='DICOM anonymizer') - self.nb.add(self.page3, text='Scheduler', state='hidden') # hide scheduler for now + self.nb.add(self.page3, text='Scheduler') # hide scheduler for now self.nb.add(self.page4, text='ID key') # Draw @@ -47,6 +48,8 @@ def __init__(self, master, side=LEFT): #TODO: create a ID_key_GUI class self.id_key_frame() self.welcome_page() + self.scheduler_page() + #TODO: have header window display within the application, not a TopLevel window def dicom_anonymizer_tab(self): @@ -82,7 +85,10 @@ def welcome_page(self): ) welcome_message.pack(expand=1, fill='both') + def scheduler_page(self): + # start the scheduler frame + UserInterface(self.page3) if __name__ == "__main__": diff --git a/candidatedata b/candidatedata new file mode 100644 index 0000000..52d7fd9 --- /dev/null +++ b/candidatedata @@ -0,0 +1,560 @@ +(dp0 +S'c0f265f3-1c5e-11e6-a7db-406c8f0ea1c5' +p1 +(ischeduler_candidate +Candidate +p2 +(dp3 +S'status' +p4 +Vactive +p5 +sS'pscid' +p6 +NsS'uid' +p7 +g1 +sS'firstname' +p8 +S'Pierre' +p9 +sS'lastname' +p10 +S'Tremblay' +p11 +sS'visitset' +p12 +(dp13 +S'V0' +p14 +(ischeduler.visit +Visit +p15 +(dp16 +g4 +S'active' +p17 +sS'visitmargin' +p18 +Nsg7 +S'c0f3409e-1c5e-11e6-b618-406c8f0ea1c5' +p19 +sS'visitwindow' +p20 +NsS'visitlabel' +p21 +g14 +sS'when' +p22 +cdatetime +datetime +p23 +(S'\x07\xde\x0c\x19\r\x0f\x00\x00\x00\x00' +p24 +tp25 +Rp26 +sS'rank' +p27 +I1 +sS'actions' +p28 +NsS'whenearliest' +p29 +NsS'previousvisit' +p30 +NsS'whenlatest' +p31 +NsS'where' +p32 +S'CRIUGM lobby' +p33 +sS'withwhom' +p34 +S'Annie' +p35 +sbsS'V1' +p36 +(ischeduler.visit +Visit +p37 +(dp38 +g4 +S'tentative' +p39 +sg18 +I2 +sg7 +S'c0f342d9-1c5e-11e6-b2e1-406c8f0ea1c5' +p40 +sg20 +I10 +sg21 +g36 +sg22 +Nsg27 +I2 +sg28 +Nsg29 +g23 +(S'\x07\xdf\x01\x02\x00\x00\x00\x00\x00\x00' +p41 +tp42 +Rp43 +sg30 +g14 +sg31 +g23 +(S'\x07\xdf\x01\x06\x00\x00\x00\x00\x00\x00' +p44 +tp45 +Rp46 +sg32 +Nsg34 +NsbsS'V2' +p47 +(ischeduler.visit +Visit +p48 +(dp49 +g4 +Nsg18 +I10 +sg7 +S'c0f34454-1c5e-11e6-bef8-406c8f0ea1c5' +p50 +sg20 +I20 +sg21 +g47 +sg22 +Nsg27 +I3 +sg28 +Nsg29 +Nsg30 +g36 +sg31 +Nsg32 +Nsg34 +NsbssS'phone' +p51 +S'547-852-9745' +p52 +sbsS'c0f28b99-1c5e-11e6-9a6e-406c8f0ea1c5' +p53 +(ischeduler_candidate +Candidate +p54 +(dp55 +g4 +g5 +sg6 +Nsg7 +g53 +sg8 +S'Marc' +p56 +sg10 +S'St-Pierre' +p57 +sg12 +(dp58 +S'V0' +p59 +(ischeduler.visit +Visit +p60 +(dp61 +g4 +g17 +sg18 +Nsg7 +S'c1079242-1c5e-11e6-826a-406c8f0ea1c5' +p62 +sg20 +Nsg21 +g59 +sg22 +g23 +(S'\x07\xde\x0c\x1b\x0e\x1e\x00\x00\x00\x00' +p63 +tp64 +Rp65 +sg27 +I1 +sg28 +Nsg29 +Nsg30 +Nsg31 +Nsg32 +S'CRIUGM M-124' +p66 +sg34 +S'Jean' +p67 +sbsS'V1' +p68 +(ischeduler.visit +Visit +p69 +(dp70 +g4 +g17 +sg18 +I2 +sg7 +S'c10793bd-1c5e-11e6-8d22-406c8f0ea1c5' +p71 +sg20 +I10 +sg21 +g68 +sg22 +g23 +(S'\x07\xdf\x02\x07\t\x0f\x00\x00\x00\x00' +p72 +tp73 +Rp74 +sg27 +I2 +sg28 +Nsg29 +g23 +(S'\x07\xdf\x01\x04\x00\x00\x00\x00\x00\x00' +p75 +tp76 +Rp77 +sg30 +g59 +sg31 +g23 +(S'\x07\xdf\x01\x08\x00\x00\x00\x00\x00\x00' +p78 +tp79 +Rp80 +sg32 +g66 +sg34 +g67 +sbsS'V2' +p81 +(ischeduler.visit +Visit +p82 +(dp83 +g4 +g39 +sg18 +I10 +sg7 +S'c1079499-1c5e-11e6-b744-406c8f0ea1c5' +p84 +sg20 +I20 +sg21 +g81 +sg22 +Nsg27 +I3 +sg28 +Nsg29 +g23 +(S'\x07\xdf\x02\x11\x00\x00\x00\x00\x00\x00' +p85 +tp86 +Rp87 +sg30 +g68 +sg31 +g23 +(S'\x07\xdf\x03\t\x00\x00\x00\x00\x00\x00' +p88 +tp89 +Rp90 +sg32 +Nsg34 +Nsbssg51 +S'412-897-9874' +p91 +sbsS'c0f27735-1c5e-11e6-9c62-406c8f0ea1c5' +p92 +(ischeduler_candidate +Candidate +p93 +(dp94 +g4 +g5 +sg6 +Nsg7 +g92 +sg8 +S'Alain' +p95 +sg10 +S'Jeanson' +p96 +sg12 +(dp97 +S'V0' +p98 +(ischeduler.visit +Visit +p99 +(dp100 +g4 +g17 +sg18 +Nsg7 +S'c1081b05-1c5e-11e6-a1df-406c8f0ea1c5' +p101 +sg20 +Nsg21 +g98 +sg22 +g23 +(S'\x07\xdf\x01\r\t\x0f\x00\x00\x00\x00' +p102 +tp103 +Rp104 +sg27 +I1 +sg28 +Nsg29 +Nsg30 +Nsg31 +Nsg32 +S'McDo' +p105 +sg34 +S'Scott' +p106 +sbsS'V1' +p107 +(ischeduler.visit +Visit +p108 +(dp109 +g4 +g39 +sg18 +I2 +sg7 +S'c1081c11-1c5e-11e6-9c31-406c8f0ea1c5' +p110 +sg20 +I10 +sg21 +g107 +sg22 +Nsg27 +I2 +sg28 +Nsg29 +g23 +(S'\x07\xdf\x01\x15\x00\x00\x00\x00\x00\x00' +p111 +tp112 +Rp113 +sg30 +g98 +sg31 +g23 +(S'\x07\xdf\x01\x19\x00\x00\x00\x00\x00\x00' +p114 +tp115 +Rp116 +sg32 +Nsg34 +NsbsS'V2' +p117 +(ischeduler.visit +Visit +p118 +(dp119 +g4 +Nsg18 +I10 +sg7 +S'c1081ccf-1c5e-11e6-9dee-406c8f0ea1c5' +p120 +sg20 +I20 +sg21 +g117 +sg22 +Nsg27 +I3 +sg28 +Nsg29 +Nsg30 +g107 +sg31 +Nsg32 +Nsg34 +Nsbssg51 +S'245-874-6321' +p121 +sbsS'c0f25545-1c5e-11e6-86d5-406c8f0ea1c5' +p122 +(ischeduler_candidate +Candidate +p123 +(dp124 +g4 +g5 +sg6 +Nsg7 +g122 +sg8 +S'Alan' +p125 +sg10 +S'Parson' +p126 +sg12 +(dp127 +S'V0' +p128 +(ischeduler.visit +Visit +p129 +(dp130 +g4 +g17 +sg18 +Nsg7 +S'c1086c80-1c5e-11e6-a83b-406c8f0ea1c5' +p131 +sg20 +Nsg21 +g128 +sg22 +g23 +(S'\x07\xdf\x02\x18\x0f\x1e\x00\x00\x00\x00' +p132 +tp133 +Rp134 +sg27 +I1 +sg28 +Nsg29 +Nsg30 +Nsg31 +Nsg32 +S'IGA' +p135 +sg34 +S'Charlie' +p136 +sbsS'V1' +p137 +(ischeduler.visit +Visit +p138 +(dp139 +g4 +g39 +sg18 +I2 +sg7 +S'c1086d7a-1c5e-11e6-beef-406c8f0ea1c5' +p140 +sg20 +I10 +sg21 +g137 +sg22 +Nsg27 +I2 +sg28 +Nsg29 +g23 +(S'\x07\xdf\x03\x04\x00\x00\x00\x00\x00\x00' +p141 +tp142 +Rp143 +sg30 +g128 +sg31 +g23 +(S'\x07\xdf\x03\x08\x00\x00\x00\x00\x00\x00' +p144 +tp145 +Rp146 +sg32 +Nsg34 +NsbsS'V2' +p147 +(ischeduler.visit +Visit +p148 +(dp149 +g4 +Nsg18 +I10 +sg7 +S'c1086e42-1c5e-11e6-bd68-406c8f0ea1c5' +p150 +sg20 +I20 +sg21 +g147 +sg22 +Nsg27 +I3 +sg28 +Nsg29 +Nsg30 +g137 +sg31 +Nsg32 +Nsg34 +Nsbssg51 +S'451-874-8965' +p151 +sbsS'c0f23e75-1c5e-11e6-b22c-406c8f0ea1c5' +p152 +(ischeduler_candidate +Candidate +p153 +(dp154 +g4 +Nsg6 +I1234567 +sg7 +g152 +sg8 +S'Sue' +p155 +sg10 +S'Allen' +p156 +sg12 +Nsg51 +S'451-874-9632' +p157 +sbsS'c0f22d9e-1c5e-11e6-bb63-406c8f0ea1c5' +p158 +(ischeduler_candidate +Candidate +p159 +(dp160 +g4 +Nsg6 +Nsg7 +g158 +sg8 +S'Billy' +p161 +sg10 +S'Roberts' +p162 +sg12 +Nsg51 +S'451-784-9856' +p163 +sS'otherphone' +p164 +S'514-874-9658' +p165 +sbs. \ No newline at end of file diff --git a/scheduler/tests/create_alldata.py b/create_alldata.py similarity index 98% rename from scheduler/tests/create_alldata.py rename to create_alldata.py index 3102fd4..fb26016 100644 --- a/scheduler/tests/create_alldata.py +++ b/create_alldata.py @@ -1,5 +1,5 @@ -import visit -import candidate +import scheduler_candidate as candidate +import scheduler_visit as visit import lib.datamanagement as DataManagement import lib.utilities as Utilities diff --git a/scheduler/lib/__init__.py b/lib/__init__.py similarity index 100% rename from scheduler/lib/__init__.py rename to lib/__init__.py diff --git a/scheduler/lib/classtool.py b/lib/classtool.py similarity index 100% rename from scheduler/lib/classtool.py rename to lib/classtool.py diff --git a/scheduler/lib/datamanagement.py b/lib/datamanagement.py similarity index 100% rename from scheduler/lib/datamanagement.py rename to lib/datamanagement.py diff --git a/scheduler/lib/multilanguage.py b/lib/multilanguage.py similarity index 100% rename from scheduler/lib/multilanguage.py rename to lib/multilanguage.py diff --git a/scheduler/lib/utilities.py b/lib/utilities.py similarity index 100% rename from scheduler/lib/utilities.py rename to lib/utilities.py diff --git a/scheduler/ui/__init__.py b/scheduler/__init__.py similarity index 100% rename from scheduler/ui/__init__.py rename to scheduler/__init__.py diff --git a/scheduler/candidate.py b/scheduler/candidate.py index 0b9d52f..8663dd9 100644 --- a/scheduler/candidate.py +++ b/scheduler/candidate.py @@ -1,6 +1,8 @@ # import standard packages import datetime + import visit + # import internal packages import lib.datamanagement as DataManagement import lib.multilanguage as MultiLanguage diff --git a/scheduler/candidatedata b/scheduler/candidatedata index 5b7d235..7e110f9 100644 --- a/scheduler/candidatedata +++ b/scheduler/candidatedata @@ -415,7 +415,7 @@ g58 sg8 g126 sg9 -S'Billy Bob' +S'Billy' p130 sg11 S'Roberts' diff --git a/scheduler/tests/create1_StudySetup.py b/scheduler/tests/create1_StudySetup.py index 93a4abc..d3cc51e 100644 --- a/scheduler/tests/create1_StudySetup.py +++ b/scheduler/tests/create1_StudySetup.py @@ -1,5 +1,5 @@ -import visit -import lib.datamanagement as datamanagement +from scheduler import visit +import lib as datamanagement #GUI: Setting up the sequence of visit - must be done prior to anything else #setup the VisitSetup instances to match the study visit sequence diff --git a/scheduler/tests/create2_CandidateList_test.py b/scheduler/tests/create2_CandidateList_test.py index da2cdab..25b944c 100644 --- a/scheduler/tests/create2_CandidateList_test.py +++ b/scheduler/tests/create2_CandidateList_test.py @@ -1,5 +1,5 @@ -import candidate -import lib.datamanagement as datamanagement +from scheduler import candidate +import lib as datamanagement #GUI: Setting up a list of candidate diff --git a/scheduler/tests/create3_CandidateVisit.py b/scheduler/tests/create3_CandidateVisit.py index d5aa509..25e438b 100644 --- a/scheduler/tests/create3_CandidateVisit.py +++ b/scheduler/tests/create3_CandidateVisit.py @@ -1,7 +1,4 @@ -import candidate -import visit -import datetime -import lib.datamanagement as datamanagement +import lib as datamanagement #loading data candidatedb = dict(datamanagement.read_candidate_data()) diff --git a/scheduler/tests/create4_PopulateVisits.py b/scheduler/tests/create4_PopulateVisits.py index 58555b0..8f2df90 100644 --- a/scheduler/tests/create4_PopulateVisits.py +++ b/scheduler/tests/create4_PopulateVisits.py @@ -6,10 +6,7 @@ a045a531-a31f-11e4-a1c4-fc4dd4d3c3f3 """ -import visit -import candidate -import lib.datamanagement as datamanagement -import lib.utilities as utilities +import lib as datamanagement db = dict(datamanagement.read_candidate_data()) diff --git a/scheduler/tests/test.py b/scheduler/tests/test.py index 3da2c01..5703f12 100644 --- a/scheduler/tests/test.py +++ b/scheduler/tests/test.py @@ -1,5 +1,5 @@ -import lib.utilities as utilities -import lib.datamanagement as datamanagement +import lib as utilities +import lib as datamanagement db = dict(datamanagement.read_candidate_data()) diff --git a/scheduler/tests/test1.py b/scheduler/tests/test1.py index 555c0de..ec09e8f 100644 --- a/scheduler/tests/test1.py +++ b/scheduler/tests/test1.py @@ -1,4 +1,4 @@ -import candidate +from scheduler import candidate happycandidate = candidate.Candidate('Billy', 'Roberts', '451-784-9856', otherphone='514-874-9658') diff --git a/scheduler/ui/datatable.py b/scheduler/ui/datatable.py deleted file mode 100644 index 881935b..0000000 --- a/scheduler/ui/datatable.py +++ /dev/null @@ -1,166 +0,0 @@ -# import standard packages -from Tkinter import * -from ttk import * -# import internal packages -import ui.datawindow as DataWindow -import lib.datamanagement as DataManagement - -class DataTable(Frame): - """ - DataTable is a base class (which inherit from Frame) defining the functionalities of - this Tkinter.ttk.treeview widget. - Child clases are ParticipantsList(DataTable) and VisitList(DataTable) - """ - - def __init__(self, parent, colheaders): - Frame.__init__(self) - self.parent = parent - colheaders = colheaders - datatable = self.init_datatable(parent, colheaders) - - def init_datatable(self, parent, colheaders): - """Initialize the datatable (which is a tk.treeview and add scroll bars)""" - self.datatable = Treeview(parent, selectmode='browse', columns=colheaders, show="headings") - for col in colheaders: - self.datatable.heading(col, text=col.title(), - command=lambda c=col: self.treeview_sortby(self.datatable, c, 0)) - self.datatable.column(col, width=100, stretch="Yes", anchor="center") - # add vertical and horizontal scroll - self.verticalscroll = Scrollbar(parent, orient="vertical", command=self.datatable.yview) - self.horizontalscroll = Scrollbar(parent, orient="horizontal", command=self.datatable.xview) - self.datatable.configure(yscrollcommand=self.verticalscroll.set, xscroll=self.horizontalscroll.set) - self.verticalscroll.pack(side=RIGHT, expand=NO, fill=BOTH) - self.horizontalscroll.pack(side=BOTTOM, expand=NO, fill=BOTH) - self.datatable.pack(side=LEFT, expand=YES, fill=BOTH) - # Binding with events - self.datatable.bind('', self.ondoubleclick) - self.datatable.bind("<>", self.onrowclick) - self.datatable.bind('', self.onrightclik) - - def load_data(self): - """Should be overriden in child class""" - pass - - def update_data(self): - for i in self.datatable.get_children(): - self.datatable.delete(i) - self.load_data() - - def treeview_sortby(self, tree, column, descending): - """ - Sort treeview contents when a column is clicked on. - Taken from Dave's IDmapper - From: https://code.google.com/p/python-ttk/source/browse/trunk/pyttk-samples/treeview_multicolumn.py?r=21 - """ - # grab values to sort - data = [(tree.set(child, column), child) for child in tree.get_children('')] - # reorder data - data.sort(reverse=descending) - for index, item in enumerate(data): - tree.move(item[1], '', index) - # switch the heading so that it will sort in the opposite direction - tree.heading(column, command=lambda column=column: self.treeview_sortby(tree, column, int(not descending))) - - def ondoubleclick(self, event): - """ - Double clicking on a treeview line opens a 'data window' - and refresh the treeview data when the 'data window' is closed - """ - - # double clicking on blank space of the treeview when no valid line is selected generates a - # IndexOutOfRange error which is taken care of by this try:except block - try: - itemID = self.datatable.selection()[0] - item = self.datatable.item(itemID)['tags'] - parent = self.parent - candidate_uuid = item[1] - DataWindow.DataWindow(parent, candidate_uuid) - self.update_data() - except Exception as e: - print "Datatable ondoubleclick ", str(e) # TODO deal with exception or not!?! - - def onrightclik(self, event): - """Not used yet""" - print 'contextual menu for this item' - pass - - def onrowclick(self, event): - """Not used yet""" - item_id = str(self.datatable.focus()) - item = self.datatable.item(item_id)['values'] - # print item_id, item #TODO remove when done - - -class ParticipantsList(DataTable): - """ - class ParticipantsList(DataTable) takes care of the data table holding the list of participants - That list is comprised of all participants (even those that have not been called once. - """ - - def __init__(self, parent, colheaders): # expected is dataset - DataTable.__init__(self, parent, colheaders) - self.colheaders = colheaders - self.load_data() - # TODO add these color settings in a 'settings and preferences section of the app' - self.datatable.tag_configure('active', background='#F1F8FF') # TODO replace active tag by status variable value - - def load_data(self): - data = dict(DataManagement.read_candidate_data()) - try: - for key in data: - if data[key].status is None: - status = '' - else: - status = data[key].status - self.datatable.insert('', 'end', - values=[data[key].firstname, data[key].lastname, data[key].phone, - status], tags=(status, data[key].uid)) - except Exception as e: - print "datatable.ParticipantsList.load_data ", str(e) # TODO proper exception handling - pass - - -class VisitList(DataTable): - """ - class VisitList(DataTable) takes care of the data table holding the list of all appointments - even those that have not been confirmed yet. - """ - - def __init__(self, parent, colheaders): - DataTable.__init__(self, parent, colheaders) - self.colheaders = colheaders - self.load_data() - # TODO add these color settings in a 'settings and preferences section of the app' - self.datatable.tag_configure('active', background='#F1F8FF') # TODO change for non-language parameter - self.datatable.tag_configure('tentative', background='#F0F0F0') # TODO change for non-language parameter - - def load_data(self): - data = dict(DataManagement.read_candidate_data()) - for key, value in data.iteritems(): - if data[key].visitset is not None: # skip the search if visitset = None - current_visitset = data[key].visitset # set this candidate.visitset for the next step - # gather information about the candidate - # this candidatekey is not printed on screen but saved with the new Scheduler object - candidatekey = data[key].uid - candidate_firstname = data[key].firstname - candidate_lastname = data[key].lastname - candidate_fullname = str(candidate_firstname + ' ' + candidate_lastname) - for key, value in current_visitset.iteritems(): - if current_visitset[key].status is not None: - status = current_visitset[key].status - visit_label = current_visitset[key].visitlabel - if current_visitset[key].when is None: - when = current_visitset[key].whenearliest - else: - when = current_visitset[key].when - if current_visitset[key].where is None: - where = '' - else: - where = current_visitset[key].where - try: - self.datatable.insert('', 'end', - values=[candidate_fullname, visit_label, when, where, status], - tags=(status, candidatekey, visit_label)) - except Exception as e: - print "datatable.VisitList.load_data ", str(e) # TODO add proper error handling - pass diff --git a/scheduler/ui/datawindow.py b/scheduler/ui/datawindow.py deleted file mode 100644 index 712bca7..0000000 --- a/scheduler/ui/datawindow.py +++ /dev/null @@ -1,214 +0,0 @@ -# import standard packages -from Tkinter import * -from ttk import * -# import internal packages -from visit import Visit -import candidate as Candidate -import ui.dialogbox as DialogBox -import lib.utilities as Utilities -import lib.multilanguage as MultiLanguage -import lib.datamanagement as DataManagement - - - -# ref: http://effbot.org/tkinterbook/tkinter-newDialog-windows.htm -# TODO this class needs a major clean-up - - -class DataWindow(Toplevel): - def __init__(self, parent, candidate_uuid='new'): - Toplevel.__init__(self, parent) - # create a transient window on top of parent window - self.transient(parent) - self.parent = parent - self.title(MultiLanguage.data_window_title) #TODO find a better title for the thing - body = Frame(self) - self.initial_focus = self.body(body, candidate_uuid) - body.pack(padx=5, pady=5) - - self.button_box() - self.grab_set() - if not self.initial_focus: - self.initial_focus = self - self.protocol("WM_DELETE_WINDOW", self.closedialog) - Utilities.center_window(self) - self.initial_focus.focus_set() - # self.deiconify() - self.wait_window(self) - - def body(self, master, candidate): - """Creates the body of 'datawindow'. param candidate is the candidate.uuid""" - try: - data = dict(DataManagement.read_candidate_data()) # TODO better way to do this - candidate = data.get(candidate) - except Exception as e: - print "datawindow.body ", str(e) # TODO manage exceptions - # Candidate section - self.candidate_pane = Labelframe(self, text=MultiLanguage.candidate_pane, width=250, height=350, borderwidth=10) - self.candidate_pane.pack(side=TOP, expand=YES, fill=BOTH, padx=5, pady=5) - # object unique id - does not appear on gui but needed to keep track of this candidate - self.candidate_uid = candidate.uid - # PSCID - self.label_pscid = Label(self.candidate_pane, text=MultiLanguage.candidate_pscid) - self.label_pscid.grid(column=0, row=0, padx=10, pady=5, sticky=N+S+E+W) - self.text_pscid_var = StringVar() - self.text_pscid_var.set(candidate.pscid) - self.text_pscid = Entry(self.candidate_pane, textvariable=self.text_pscid_var) - self.text_pscid.grid(column=0, row=1, padx=10, pady=5, sticky=N+S+E+W) - # status - self.label_status = Label(self.candidate_pane, text=MultiLanguage.candidate_status) - self.label_status.grid(column=1, row=0, padx=10, pady=5, sticky=N+S+E+W) - self.text_status_var = StringVar() - self.text_status_var.set(candidate.status) - self.text_status = Entry(self.candidate_pane, textvariable=self.text_status_var) - self.text_status.grid(column=1, row=1, padx=10, pady=5, sticky=N+S+E+W) - # firstname - self.label_firstname = Label(self.candidate_pane, text=MultiLanguage.candidate_firstname) - self.label_firstname.grid(column=0, row=2, padx=10, pady=5, sticky=N+S+E+W) - self.text_firstname_var = StringVar() - self.text_firstname_var.set(candidate.firstname) - self.text_firstname = Entry(self.candidate_pane, textvariable=self.text_firstname_var) - self.text_firstname.grid(column=0, row=3, padx=10, pady=5, sticky=N+S+E+W) - # lastname - self.label_lastname = Label(self.candidate_pane, text=MultiLanguage.candidate_lastname) - self.label_lastname.grid(column=1, row=2, padx=10, pady=5, sticky=N+S+E+W) - self.text_lastname_var = StringVar() - self.text_lastname_var.set(candidate.lastname) - self.text_lastname = Entry(self.candidate_pane, textvariable=self.text_lastname_var) - self.text_lastname.grid(column=1, row=3, padx=10, pady=5, sticky=N+S+E+W) - # phone number - self.label_phone = Label(self.candidate_pane, text=MultiLanguage.candidate_phone) - self.label_phone.grid(column=2, row=2, padx=10, pady=5, sticky=N+S+E+W) - self.text_phone_var = StringVar() - self.text_phone_var.set(candidate.phone) - self.text_phone = Entry(self.candidate_pane, textvariable=self.text_phone_var) - self.text_phone.grid(column=2, row=3, padx=10, pady=5, sticky=N+S+E+W) - - # Schedule Section - displayed as a table - self.schedule_pane = Labelframe(self, text=MultiLanguage.schedule_pane, width=250, height=350, borderwidth=10) - self.schedule_pane.pack(side=TOP, expand=YES, fill=BOTH, padx=5, pady=5) - # top row (header) - self.label_visit_rank = Label(self.schedule_pane, text=MultiLanguage.schedule_visit_rank) - self.label_visit_rank.grid(column=0, row=0, padx=5, pady=5, sticky=N+S+E+W) - self.label_visit_label = Label(self.schedule_pane, text=MultiLanguage.col_visitlabel) - self.label_visit_label.grid(column=1, row=0, padx=5, pady=5, sticky=N+S+E+W) - self.label_visit_when = Label(self.schedule_pane, text=MultiLanguage.col_when) - self.label_visit_when.grid(column=2, row=0, padx=5, pady=5, sticky=NSEW) - self.label_visit_status = Label(self.schedule_pane, text=MultiLanguage.col_where) - self.label_visit_status.grid(column=3, row=0, padx=5, pady=5, sticky=N+S+E+W) - self.label_visit_status = Label(self.schedule_pane, text=MultiLanguage.col_withwhom) - self.label_visit_status.grid(column=4, row=0, padx=5, pady=5, sticky=N+S+E+W) - self.label_visit_status = Label(self.schedule_pane, text=MultiLanguage.col_status) - self.label_visit_status.grid(column=5, row=0, padx=5, pady=5, sticky=N+S+E+W) - - """ - PSEUDOCODE - 1. Get candidate.visitset - 2. Parse into a sorted list (sorted on visit.rank) - 3. Print data on screen - - - visit_set = candidate.visitset - for key, value in study_setup.iteritems(): - visit_list.append(study_setup[key]) - visit_list = sorted(visit_list, key=lambda visit: visit.rank) - - for key, value in visit_list.iteritems(): - - """ - # TODO add logic "foreach" to create a table showing each visit - import lib.utilities as Utilities # TODO delete when done - # 1- Get candidate visitset and parse into a list - visit_list = [] - visitset = candidate.visitset - if visitset is None: - print 'no visit yet' - else: - for key, value in visitset.iteritems(): - visit_list.append(visitset[key]) - # 2- Sort list on visit.rank - visit_list = sorted(visit_list, key=lambda visit: visit.rank) - # 3- 'print' values on ui - x = 0 - for x in range(len(visit_list)): - # rank - label_visit_rank = Label(self.schedule_pane, text=visit_list[x].rank) - label_visit_rank.grid(column=0, row=x+1, padx=5, pady=5, sticky=N+S+E+W) - # visitlabel - label_visit_label = Label(self.schedule_pane, text=visit_list[x].visitlabel) - label_visit_label.grid(column=1, row=x+1, padx=5, pady=5, sticky=N+S+E+W) - # when - if visit_list[x].when == None: - visit = visit_list[x] - date_range = Visit.visit_date_range(visit) - label_visit_when = Label(self.schedule_pane, text=date_range) - label_visit_when.grid(column=2, row=x+1, padx=5, pady=5, sticky=N+S+E+W) - else: - label_visit_when = Label(self.schedule_pane, text=visit_list[x].when) - label_visit_when.grid(column=2, row=x+1, padx=5, pady=5, sticky=N+S+E+W) - # where - label_visit_where = Label(self.schedule_pane, text=visit_list[x].where) - label_visit_where.grid(column=3, row=x+1, padx=5, pady=5, sticky=N+S+E+W) - # withwhom - label_visit_where = Label(self.schedule_pane, text=visit_list[x].withwhom) - label_visit_where.grid(column=4, row=x+1, padx=5, pady=5, sticky=N+S+E+W) - # status - label_visit_where = Label(self.schedule_pane, text=visit_list[x].status) - label_visit_where.grid(column=5, row=x+1, padx=5, pady=5, sticky=N+S+E+W) - - - - def button_box(self): - # add standard button box - box = Frame(self) - w = Button(box, text="OK", width=10, command=self.ok_button, default=ACTIVE) - w.pack(side=LEFT, padx=5, pady=5) - w = Button(box, text="Cancel", width=10, command=self.cancel_button) - w.pack(side=LEFT, padx=5, pady=5) - self.bind("", self.ok_button) - self.bind("", self.closedialog) - box.pack() - - def ok_button(self, event=None): - print "saving data and closing" # TODO remove when done - self.capture_data() - if not self.validate(): - self.initial_focus.focus_set() # put focus back - return - #need to call treeview update here - self.withdraw() - self.closedialog() - - def cancel_button(self, event=None): - print "close without saving" - parent = Frame(self) - newwin = DialogBox.ConfirmYesNo(parent, MultiLanguage.dialog_close) - if newwin.buttonvalue == 1: - self.closedialog() - else: - return - - def closedialog(self, event=None): - self.parent.focus_set() # put focus back to parent window before destroying the window - self.destroy() - - def validate(self): - return 1 - - def capture_data(self): - """ - Grap the information from the window's text field and save the candidate information based on candidate_uid. - """ - # open the 'database' - db = dict(DataManagement.read_candidate_data()) - # and find candidate based on uid - uid = self.candidate_uid - candidate = db[uid] - # capture data from fields - candidate.pscid = self.text_pscid.get() - candidate.status = self.text_status.get() - candidate.firstname = self.text_firstname.get() - candidate.lastname = self.text_lastname.get() - candidate.phone = self.text_phone.get() - # save data - DataManagement.save_candidate_data(db) \ No newline at end of file diff --git a/scheduler/ui/dialogbox.py b/scheduler/ui/dialogbox.py deleted file mode 100644 index 182e830..0000000 --- a/scheduler/ui/dialogbox.py +++ /dev/null @@ -1,78 +0,0 @@ -#import standard packages -from Tkinter import * -#import internal packages -import lib.utilities as Utilities -import lib.multilanguage as MultiLanguage - -class DialogBox(Toplevel): - """ - This class was created mainly because the native dialog box don't work as expected when called from a top-level window. - This class (although it could be improved in many aspects) insure that the parent window cannot get focus while a dialog box is still active. - """ - def __init__(self,parent, title, message, button1, button2): - Toplevel.__init__(self,parent) - self.transient(parent) - self.parent = parent - self.title(title) - body = Frame(self) - self.initial_focus = self.body(body, message) - body.pack(padx=4, pady=4) - self.buttonbox(button1, button2) - self.grab_set() - - if not self.initial_focus: - self.initial_focus = self - - self.protocol("WM_DELETE_WINDOW", self.button2) - Utilities.center_window(self) - self.initial_focus.focus_set() - self.deiconify() - self.wait_window(self) - - def body(self, parent, message): - label = Label(self, text=message) - label.pack(padx=4, pady=4) - pass - - def buttonbox(self, button1, button2): - #add a standard button box - box = Frame(self) - b1 = Button(box, text=button1, width=12, command=self.button1, default=ACTIVE) - b1.pack(side=LEFT, padx=4, pady=4) - b2 = Button(box, text=button2, width=12, command=self.button2, default=ACTIVE) - b2.pack(side=LEFT, padx=4, pady=4) - self.bind("", self.button1) - self.bind("", self.button2) - box.pack() - - def button1(self, event=None): - if not self.validate(): - self.initial_focus.focus_set() #put focus on Button - return - self.buttonvalue = 1 - self.closedialog() - - def button2(self, event=None): - self.buttonvalue = 2 - self.closedialog() - - - def closedialog(self, event=None): - #put focus back to parent window before destroying the window - self.parent.focus_set() - self.destroy() - - def validate(self): - return 1 - -######################################################################################### -class ConfirmYesNo(DialogBox): - def __init__(self, parent, message): - title = MultiLanguage.dialog_title_confirm - button1 = MultiLanguage.dialog_yes - button2 = MultiLanguage.dialog_no - DialogBox.__init__(self, parent, title, message, button1, button2) - - - - diff --git a/scheduler/ui/menubar.py b/scheduler/ui/menubar.py deleted file mode 100644 index 12303e4..0000000 --- a/scheduler/ui/menubar.py +++ /dev/null @@ -1,95 +0,0 @@ -#import standard packages -import Tkinter -#import internal packages -import lib.multilanguage as MultiLanguage -import ui.datawindow as DataWindow - -class MenuBar(Tkinter.Menu): - def __init__(self, parent): - Tkinter.Menu.__init__(self, parent) - # create an APPLICATION pulldown menu - application_menu = Tkinter.Menu(self, tearoff=False) - self.add_cascade(label=MultiLanguage.application_menu,underline=0, menu=application_menu) - application_menu.add_command(label=MultiLanguage.application_setting, underline=1, command=self.app_settings) - application_menu.add_separator() - application_menu.add_command(label=MultiLanguage.application_quit, underline=1, command=self.quit_application) - # create a CANDIDATE pulldown menu - candidate_menu = Tkinter.Menu(self, tearoff=False) - self.add_cascade(label=MultiLanguage.candidate_menu, underline=0, menu=candidate_menu) - candidate_menu.add_command(label=MultiLanguage.candidate_add, command=self.add_candidate) - candidate_menu.add_command(label=MultiLanguage.candidate_find, command=self.find_candidate) - candidate_menu.add_command(label=MultiLanguage.candidate_update, command=self.update_candidate) - candidate_menu.add_separator() - candidate_menu.add_command(label=MultiLanguage.candidate_get_id, command=self.get_candidate_id) - candidate_menu.add_separator() - candidate_menu.add_command(label=MultiLanguage.candidate_exclude_include_toggle, command=self.exclude_candidate) - # create a CALENDAR pulldown menu - calendar_menu = Tkinter.Menu(self, tearoff=False) - self.add_cascade(label=MultiLanguage.calendar_menu, underline=0, menu=calendar_menu) - calendar_menu.add_command(label=MultiLanguage.calendar_new_appointment, command=self.open_calendar) - # create a DICOM_anonymizer pulldown men - anonymizer_menu = Tkinter.Menu(self, tearoff=0) # TODO add relevant menu - self.add_cascade(label=MultiLanguage.anonymizer_menu, underline=0, menu=anonymizer_menu) - anonymizer_menu.add_command(label=MultiLanguage.anonymizer_run, command=self.dicom_anonymizer) - # create a HELP pulldown menu - help_menu = Tkinter.Menu(self, tearoff=0) - self.add_cascade(label=MultiLanguage.help_menu, underline=0, menu=help_menu) - help_menu.add_command(label=MultiLanguage.help_get_help, command=self.open_help) - help_menu.add_command(label=MultiLanguage.help_about_window, command=self.about_application) - - def app_settings(self): - #TODO implement app_settings() - print 'running appsettings' - pass - - def quit_application(self): - #TODO implement quit_application() - print 'running quit_application' - self.quit() - pass - - def open_calendar(self): - #TODO implement open_calendar() - print 'running open_calendar' - pass - - def dicom_anonymizer(self): - #TODO implement dicom_anonymizer() - print 'running dicom anonymizer' - pass - - def add_candidate(self): - #TODO implement add_candidate() - DataWindow.DataWindow(self, "new") - print 'running add_candidate' - pass - - def find_candidate(self): - #TODO implement find_candidate() - print 'running find_candidate' - pass - - def update_candidate(self): - #TODO implement update_candidate() - print 'running update_candidate' - pass - - def get_candidate_id(self): - #TODO get_candidate_id() - print 'running get_candidate_id' - pass - - def exclude_candidate(self): #need renaming - #TODO exclude_candidate() - print 'running incativate_candidate' - pass - - def open_help(self): - #TODO open_help() - print 'running open_help' - pass - - def about_application(self): - #TODO about_application() - print 'running about_application' - pass \ No newline at end of file diff --git a/scheduler/ui/projectpane.py b/scheduler/ui/projectpane.py deleted file mode 100644 index bed5d89..0000000 --- a/scheduler/ui/projectpane.py +++ /dev/null @@ -1,6 +0,0 @@ -from Tkinter import * -import lib.multilanguage as multilanguage - -class ProjectPane(LabelFrame): - def __init__(self, parent): - LabelFrame.__init__(self, parent) \ No newline at end of file diff --git a/scheduler/visit.py b/scheduler/visit.py index 7ae2f24..c9644ce 100644 --- a/scheduler/visit.py +++ b/scheduler/visit.py @@ -1,8 +1,10 @@ #import standard packages #import internal packages -import lib.utilities as utilities import datetime +import lib.utilities as utilities + + class VisitSetup(): """ The VisitSetup() class is used to define a study (or project) in terms of sequence of visits and also serves as a diff --git a/scheduler/application.py b/scheduler_application.py similarity index 62% rename from scheduler/application.py rename to scheduler_application.py index d83e633..12287b7 100644 --- a/scheduler/application.py +++ b/scheduler_application.py @@ -14,29 +14,33 @@ class UserInterface(Frame): def __init__(self, parent): Frame.__init__(self) self.parent = parent - self.parent.title(MultiLanguage.app_title) - self.pack(side=TOP, expand=YES, fill=BOTH, padx=10, pady=10) + #self.parent.title(MultiLanguage.app_title) + self.initialize() + + def initialize(self): + self.frame = Frame(self.parent) + self.frame.pack(side=TOP, expand=YES, fill=BOTH, padx=10, pady=10) # TODO create classe for project info pane - self.project_infopane = Labelframe(self, text=MultiLanguage.project_info_pane, width=250, height=350, + self.frame.project_infopane = Labelframe(self, text=MultiLanguage.project_info_pane, width=250, height=350, borderwidth=10) # TODO add dynamic resize - self.project_infopane.pack(side=LEFT, expand=NO, fill=BOTH) + self.frame.project_infopane.pack(side=LEFT, expand=NO, fill=BOTH) # This area (datapane) is composed of one Panedwindow containing two Labelframe - self.data_pane = Panedwindow(self, width=1000, height=500, orient=HORIZONTAL) # TODO add dynamic resize - self.data_pane.pack(side=RIGHT, expand=YES, fill=BOTH) - self.candidate_pane = Labelframe(self.data_pane, text=MultiLanguage.candidate_pane, width=100, height=450, + self.frame.data_pane = Panedwindow(self, width=1000, height=500, orient=HORIZONTAL) # TODO add dynamic resize + self.frame.data_pane.pack(side=RIGHT, expand=YES, fill=BOTH) + self.frame.candidate_pane = Labelframe(self.frame.data_pane, text=MultiLanguage.candidate_pane, width=100, height=450, borderwidth=10) # TODO add dynamic resize - self.visit_pane = Labelframe(self.data_pane, text=MultiLanguage.calendar_pane, width=100, height=350, + self.frame.visit_pane = Labelframe(self.frame.data_pane, text=MultiLanguage.calendar_pane, width=100, height=350, borderwidth=10) # TODO add dynamic resize - self.data_pane.add(self.candidate_pane) - self.data_pane.add(self.visit_pane) + self.frame.data_pane.add(self.frame.candidate_pane) + self.frame.data_pane.add(self.frame.visit_pane) # create data tables (treeview) visit_column_headers = ('candidate', 'visitlabel', 'when', 'where', 'status') - self.visit_table = DataTable.VisitList(self.visit_pane, visit_column_headers) - self.visit_table.pack(side=BOTTOM, expand=YES, fill=BOTH) + self.frame.visit_table = DataTable.VisitList(self.frame.visit_pane, visit_column_headers) + self.frame.visit_table.pack(side=BOTTOM, expand=YES, fill=BOTH) column_header = ('firstname', 'lastname', 'phone', 'status') - self.data_table = DataTable.ParticipantsList(self.candidate_pane, column_header) - self.data_table.pack(side=BOTTOM, expand=YES, fill=BOTH) + self.frame.data_table = DataTable.ParticipantsList(self.frame.candidate_pane, column_header) + self.frame.data_table.pack(side=BOTTOM, expand=YES, fill=BOTH) """ #create a filter section in each data_pane(not implemented yet) diff --git a/scheduler_candidate.py b/scheduler_candidate.py new file mode 100644 index 0000000..631c7ed --- /dev/null +++ b/scheduler_candidate.py @@ -0,0 +1,196 @@ +# import standard packages +import datetime + +from scheduler import visit + +# import internal packages +import lib.datamanagement as DataManagement +import lib.multilanguage as MultiLanguage +import lib.utilities as Utilities + + +class Candidate: + """ + The Candidate class defines the candidates/participants of the study + + Attributes: + uid: A unique identifier using python's uuid1 method. Used as key to store and retrieve objects from + files and/or dictionaries. + firstname: First name of the candidate + lastname: Last name of the candidate + visitset: A dictionnary containing all visits (planed or not) + phone: A primary phone number + status: Status of this candidate + pscid: Loris (clinical results database) specific ID + + kwargs: Not implemented yet! + + Code example: + candidatedb = {} #setup a dict to receive the candidates + candidatedata = candidate.Candidate('Billy', 'Roberts', '451-784-9856', otherphone='514-874-9658') #instanciate one candidate + candidatedb[candidatedata.uid] = candidatedata #add candidate to dict + DataManagement.save_candidate_data(candidatedb) #save data to file + """ + def __init__(self, firstname, lastname, phone, uid=None, visitset = None, status = None, pscid=None, **kwargs): #TODO *kwarg + self.uid = Utilities.generate_uid() + self.firstname = firstname + self.lastname = lastname + self.visitset = visitset + self.phone = phone + self.status = status + self.pscid = pscid + #...many other attributes + if kwargs is not None: + for key, value in kwargs.iteritems(): + setattr(self, key, value) + + def setup_visitset(self): + """ + When creating the first visit for a candidate, a complete set of visits is added based on a study/project visit list + There are no parameters passed to this method since it will simply create a new 'empty' + This method will: + 1-open studydata (the study visit list) and 'parse' the dict to a sorted list (dict cannot be sorted) + 2-create a temporary list of visit_labels that will serve as a key within the Candidate.visit_set dictionary + 3-instantiate individual visits based on the study visit list + Usage: + Called by Candidate.set_visit_date() + """ + visit_list =[] + visit_label_templist = [] + study_setup = dict() + try: + #1-open studydata + study_setup = dict(DataManagement.read_studydata()) + except Exception as e: + print str(e) #TODO add error login (in case a study data file does not exist) + for key, value in study_setup.iteritems(): + #2-parse into a sorted list + visit_list.append(study_setup[key]) + visit_list = sorted(visit_list, key=lambda visit: visit.rank) + #create a temporary list of visitlabels + for each in visit_list: + visit_label_templist.append(each.visitlabel) + #3-instantiate individual visit based on each instance of VisitSetup() + self.visitset = {} #setup a dict to receive a set of Visit() + count = 0 + #set values of : uid, rank, visit_label, previous_visit, visit_window, visitmargin, + for key in visit_list: + mainkey = str(visit_label_templist[count]) + rank =key.rank + visit_label = key.visitlabel + previous_visit = key.previousvisit + visit_window = key.visitwindow + visit_margin = key.visitmargin + visit_data = visit.Visit(rank, visit_label, previous_visit, visit_window, visit_margin,) + self.visitset[mainkey] = visit_data + count += 1 + + def set_visit_date(self, visitlabel, visitdate, visittime, visitwhere, visitwhom): + """ + This method update the visit information (according to visitlabel) + This method will: + 1-Check if visitset==None. If so, then Candidate.setup_visitset() is called to setup Candidate.visitset + 2-Check if a date is already set for this visitlabel + 3-Set values of Visit.when, 'Visit.where, Visit.whithwhom and Visit.status for current visit + Usage: Called by GUI methods + Return: current_visit as current Visit(Visit(VisitSetup) instance + """ + #1-Check to see if visitset == None before trying to create a new date instance + if self.visitset is None: + self.setup_visitset() + self.set_candidate_status_active() #Candidate.status='active' since we're setting up a first visit + #get current visit within visitset + current_visit = self.visitset.get(visitlabel) + #2-Check to see if this visit already has a date + if current_visit.when is not None: + print "date already exists" #TODO add confirmation of change log??? + pass + #concatenate visitdate and visittime and parse into a datetime object + visitwhen = visitdate + ' ' + visittime + when = datetime.datetime.strptime(visitwhen, '%Y-%m-%d %H:%M') + #3-Set values of Visit.when, 'Visit.where, Visit.whithwhom and Visit.status for current visit + current_visit.when = when + current_visit.where = visitwhere + current_visit.withwhom = visitwhom + current_visit.status = "active" #that is the status of the visit + return current_visit + + + """ + def set_next_visit_window(self, candidate, current_visit): + #get the current visit object as argument. Will search and look for the next visit (visit where previousvisit == current_visit_label) + next_visit_searchset = candidate.visitset + current_visit_label = current_visit.visitlabel + next_visit = "" + for key in next_visit_searchset: + visit_data = next_visit_searchset[key] + if visit_data.previousvisit == current_visit_label: + next_visit = candidate.visitset.get(visit_data.visitlabel) #TODO debug when current is last visit + #gather info about current_visit (mostly for my own biological computer! else I get lost) + current_visit_date = current_visit.when + current_visit_year = current_visit_date.year #get the year of the current visit date + next_visit_window = next_visit.visitwindow + next_visit_margin = next_visit.visitmargin + #set dates for the next visit + next_visit_early = int(current_visit_date.strftime('%j')) + (next_visit_window - next_visit_margin) #this properly handle change of year + next_visit_early_date = datetime.datetime(current_visit_year, 1, 1) + datetime.timedelta(next_visit_early - 1) + next_visit_late = int(current_visit_date.strftime('%j')) + (next_visit_window + next_visit_margin) + next_visit_late_date = datetime.datetime(current_visit_year, 1, 1) + datetime.timedelta(next_visit_late - 1) + next_visit.when_earliest = next_visit_early_date + next_visit.when_latest = next_visit_late_date + next_visit.status = "tentative" + Utilities.print_object((next_visit)) + """ + def set_next_visit_window(self, candidate, current_visit): + """ + This method will 'calculate' a min and max date when the next visit should occur + 1-Get candidate.visitset (as visit_searchset) and current_visit.visitlabel + 2-Identify which visit (in visitset) has previousvisit == current_visit.visitlabel + 3-Get + Usage: Currently called by GUI function (TODO RETHINK THIS LOGIC maybe it should be called when running Candidate.set_visit_date()) + """ + + + #get the current visit object as argument. Will search and look for the next visit (visit where previousvisit == current_visitlabel) + + #1- Get Candidate.visitset and current_visit / next_visit will == Visit(VisitSetup) of the next visit (relative to current_visit) + visit_searchset = candidate.visitset + current_visitlabel = current_visit.visitlabel + next_visit = "" + #2-Identify which visit (in visitset) has previousvisit == current_visit.visitlabel + for key in visit_searchset: + visit_data = visit_searchset[key] + if visit_data.previousvisit == current_visitlabel: + next_visit = candidate.visitset.get(visit_data.visitlabel) #TODO debug when current is last visit + #3-Calculate a min and max date for the next visit to occur based on Visit.visitwindow and Visit.visitmargin + current_visitdate = current_visit.when + current_visityear = current_visitdate.year #get the year of the current visit date + next_visitwindow = next_visit.visitwindow + nextvisitmargin = next_visit.visitmargin + #set dates for the next visit + nextvisitearly = int(current_visitdate.strftime('%j')) + (next_visitwindow - nextvisitmargin) #this properly handle change of year + nextvisitearlydate = datetime.datetime(current_visityear, 1, 1) + datetime.timedelta(nextvisitearly - 1) + nextvisitlate = int(current_visitdate.strftime('%j')) + (next_visitwindow + nextvisitmargin) + nextvisitlatedate = datetime.datetime(current_visityear, 1, 1) + datetime.timedelta(nextvisitlate - 1) + next_visit.whenearliest = nextvisitearlydate + next_visit.whenlatest = nextvisitlatedate + next_visit.status = "tentative" #set this visit.status + + def get_active_visit(self, candidate): + candidatefullname = str(candidate.firstname + ' ' + candidate.lastname) + currentvisitset = candidate.visitset + activevisit = [] + if currentvisitset is None: + return + elif currentvisitset is not None: + for key in currentvisitset: + if currentvisitset[key].status == MultiLanguage.status_active: + visitlabel = currentvisitset[key].visitlabel + when = currentvisitset[key].when.strftime('%Y-%m-%d %Hh%m') + where = currentvisitset[key].where + who = currentvisitset[key].withwhom + activevisit = [candidatefullname, visitlabel, when, where, who] + return activevisit + + def set_candidate_status_active(self): + self.status = MultiLanguage.status_active #set the Candidate.status to 'active' \ No newline at end of file diff --git a/scheduler_visit.py b/scheduler_visit.py new file mode 100644 index 0000000..c9644ce --- /dev/null +++ b/scheduler_visit.py @@ -0,0 +1,93 @@ +#import standard packages +#import internal packages +import datetime + +import lib.utilities as utilities + + +class VisitSetup(): + """ + The VisitSetup() class is used to define a study (or project) in terms of sequence of visits and also serves as a + base class for Visit(VisitSetup). A study (or project) can have as many visits as required. There is no class for + study as it is merely a simple dictionnary. + + Code example: This study contains 3 visits. Since V0 is the first visit, it doesn't have any values for + 'previousvisit', 'visitwindow' and ' visitmargin' + study = {} + visit = visit.VisitSetup(1, 'V0') #a uid (uuid1) is automatically generated + study[visit.uid] = visit #VisitSetup.uid is a unique ID used as key + visit = visit.VisitSetup(2, 'V1', 'V0', 10, 2) + study[visit.uid] = visit + visit = visit.VisitSetup(3, 'V2', 'V1', 20, 10) + study[visit.uid] = visit + + This is the parent class of Visit(VisitSetup), furthermore Visit(VisitSetup) objects will be 'instanciated' from + each instance of VisitSetup(object). + Both VisitSetup(object) class and instances are used to create individual instances of Visit(VisitSetup) + + Attributes: + uid: A unique identifier using python's uuid1 method. Used as key to store and retrieve objects from + files and/or dictionaries. + rank: The rank of the visit in the sequence (int). Useful to sort the visits in order of occurrence. + visitlabel: The label of the visit (string) such as V1, V2 or anything else the user may come up with + previousvisit: The visitlabel (string) of the visit occurring before this one. Used to plan this visit based on + the date of the previous visit. (default to None) + visitwindow: The number of days (int) between this visit and the previous visit. (default to None) + visitmargin: The margin (in number of days (int)) that is an allowed deviation (a +/- few days ). Basically, + this allow the 'calculation' of a date window when this visit should occur. (default to None) + mandatory: Indicate if this visit is mandatory. Default to Yes + actions: A list of action points (or simply reminders) specific to that visit (i.e.'reserve room 101'). + This is not implemented yet (default to None) + """ + + def __init__(self, rank, visitlabel, previousvisit = None, visitwindow = None, visitmargin = None, actions = None, uid=None): + self.uid = utilities.generate_uid() + self.rank = rank + self.visitlabel = visitlabel + self.previousvisit = previousvisit + self.visitwindow = visitwindow + self.visitmargin = visitmargin + self.actions = actions #not implemented yet! + + +class Visit(VisitSetup): + """ + The Visit(VisitSetup) class help define individual visit of the candidate using VisitSetup(object) instances as 'templates'. + Upon creation of the first meeting with a candidate, the Candidate(object) instance will get a full set of Visit(VisitSetup) instances. + This set of visits is contained in Candidate.visitset + Each time a visit is being setup, a 'time window' is calculated to define the earliest and latest date at which + the 'nextVisit' should occur. + + Attributes: (In addition of parent class attributes.) + when: Date at which this visit is occurring. (default to None) + when_earliest: Earliest date when this visit should occur. Set to value when previous visit is activated. (default to None) + when_latest: Latest date when this visit should occur. Set to value when previous visit is activated. (default to None) + where: Place where this meeting is taking place. (default to None) + whitwhom: Professional meeting the study candidate at the reception. (default to None) + status: Status of this visit. Set to active when 'when' is set (default to None) + """ + def __init__(self, rank, visitlabel, previousvisit, visitwindow, visitmargin, actions=None, uid=None, when = None, + whenearliest = None, whenlatest = None, where = None, withwhom = None, status = None): + VisitSetup.__init__(self, rank, visitlabel, previousvisit, visitwindow, visitmargin, actions, uid) + self.when = when + self.whenearliest = whenearliest + self.whenlatest = whenlatest + self.where = where + self.withwhom = withwhom + self.status = status + + def visit_date_range(self): + #need to handle the case where a visit has no dates + #this works but Exception handling seems to broad + try: + early_date = datetime.datetime.date(self.whenearliest) + late_date = datetime.datetime.date(self.whenlatest) + date_range = str(early_date), '<>', str(late_date) + except Exception as e: + #print e + date_range = "" + return date_range + + + + diff --git a/studydata b/studydata new file mode 100644 index 0000000..d0820c7 --- /dev/null +++ b/studydata @@ -0,0 +1,66 @@ +(dp0 +S'c0f1fc21-1c5e-11e6-b766-406c8f0ea1c5' +p1 +(ischeduler_visit +VisitSetup +p2 +(dp3 +S'visitmargin' +p4 +I2 +sS'uid' +p5 +g1 +sS'visitwindow' +p6 +I10 +sS'visitlabel' +p7 +S'V1' +p8 +sS'rank' +p9 +I2 +sS'actions' +p10 +NsS'previousvisit' +p11 +S'V0' +p12 +sbsS'c0f0636b-1c5e-11e6-9f17-406c8f0ea1c5' +p13 +(ischeduler_visit +VisitSetup +p14 +(dp15 +g4 +Nsg5 +g13 +sg6 +Nsg7 +g12 +sg9 +I1 +sg10 +Nsg11 +NsbsS'c0f214b3-1c5e-11e6-91bd-406c8f0ea1c5' +p16 +(ischeduler_visit +VisitSetup +p17 +(dp18 +g4 +I10 +sg5 +g16 +sg6 +I20 +sg7 +S'V2' +p19 +sg9 +I3 +sg10 +Nsg11 +g8 +sbs. \ No newline at end of file From e356d48fc572db68d6eac59850c4a68d0969da93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Tue, 17 May 2016 16:13:19 -0400 Subject: [PATCH 35/89] Added Windows executable in a zip folder, as well as the files that allows its creation on Windows (setup.py and win_build.sh) --- scheduler_application.py | 30 +++--- ui/datawindow.py | 212 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 228 insertions(+), 14 deletions(-) create mode 100644 ui/datawindow.py diff --git a/scheduler_application.py b/scheduler_application.py index 12287b7..8d8fdbd 100644 --- a/scheduler_application.py +++ b/scheduler_application.py @@ -18,29 +18,31 @@ def __init__(self, parent): self.initialize() def initialize(self): + + # initialize frame self.frame = Frame(self.parent) self.frame.pack(side=TOP, expand=YES, fill=BOTH, padx=10, pady=10) # TODO create classe for project info pane - self.frame.project_infopane = Labelframe(self, text=MultiLanguage.project_info_pane, width=250, height=350, + self.project_infopane = Labelframe(self.frame, text=MultiLanguage.project_info_pane, width=250, height=350, borderwidth=10) # TODO add dynamic resize - self.frame.project_infopane.pack(side=LEFT, expand=NO, fill=BOTH) + self.project_infopane.pack(side=LEFT, expand=NO, fill=BOTH) # This area (datapane) is composed of one Panedwindow containing two Labelframe - self.frame.data_pane = Panedwindow(self, width=1000, height=500, orient=HORIZONTAL) # TODO add dynamic resize - self.frame.data_pane.pack(side=RIGHT, expand=YES, fill=BOTH) - self.frame.candidate_pane = Labelframe(self.frame.data_pane, text=MultiLanguage.candidate_pane, width=100, height=450, + self.data_pane = Panedwindow(self.frame, width=1000, height=500, orient=HORIZONTAL) # TODO add dynamic resize + self.data_pane.pack(side=RIGHT, expand=YES, fill=BOTH) + self.candidate_pane = Labelframe(self.data_pane, text=MultiLanguage.candidate_pane, width=100, height=450, borderwidth=10) # TODO add dynamic resize - self.frame.visit_pane = Labelframe(self.frame.data_pane, text=MultiLanguage.calendar_pane, width=100, height=350, + self.visit_pane = Labelframe(self.data_pane, text=MultiLanguage.calendar_pane, width=100, height=350, borderwidth=10) # TODO add dynamic resize - self.frame.data_pane.add(self.frame.candidate_pane) - self.frame.data_pane.add(self.frame.visit_pane) + self.data_pane.add(self.candidate_pane) + self.data_pane.add(self.visit_pane) # create data tables (treeview) visit_column_headers = ('candidate', 'visitlabel', 'when', 'where', 'status') - self.frame.visit_table = DataTable.VisitList(self.frame.visit_pane, visit_column_headers) - self.frame.visit_table.pack(side=BOTTOM, expand=YES, fill=BOTH) + self.visit_table = DataTable.VisitList(self.visit_pane, visit_column_headers) + self.visit_table.pack(side=BOTTOM, expand=YES, fill=BOTH) column_header = ('firstname', 'lastname', 'phone', 'status') - self.frame.data_table = DataTable.ParticipantsList(self.frame.candidate_pane, column_header) - self.frame.data_table.pack(side=BOTTOM, expand=YES, fill=BOTH) + self.data_table = DataTable.ParticipantsList(self.candidate_pane, column_header) + self.data_table.pack(side=BOTTOM, expand=YES, fill=BOTH) """ #create a filter section in each data_pane(not implemented yet) @@ -70,5 +72,5 @@ def __init__(self): app.mainloop() """ #Application main loop -app=Application() -app.mainloop() \ No newline at end of file +#app=Application() +#app.mainloop() \ No newline at end of file diff --git a/ui/datawindow.py b/ui/datawindow.py new file mode 100644 index 0000000..c75c1f7 --- /dev/null +++ b/ui/datawindow.py @@ -0,0 +1,212 @@ +# import standard packages +from Tkinter import * +from ttk import * +# import internal packages +from scheduler_visit import Visit +import ui.dialogbox as DialogBox +import lib.utilities as Utilities +import lib.multilanguage as MultiLanguage +import lib.datamanagement as DataManagement + + + +# ref: http://effbot.org/tkinterbook/tkinter-newDialog-windows.htm +# TODO this class needs a major clean-up + + +class DataWindow(Toplevel): + def __init__(self, parent, candidate_uuid='new'): + Toplevel.__init__(self, parent) + # create a transient window on top of parent window + self.transient(parent) + self.parent = parent + self.title(MultiLanguage.data_window_title) #TODO find a better title for the thing + body = Frame(self) + self.initial_focus = self.body(body, candidate_uuid) + body.pack(padx=5, pady=5) + + self.button_box() + self.grab_set() + if not self.initial_focus: + self.initial_focus = self + self.protocol("WM_DELETE_WINDOW", self.closedialog) + Utilities.center_window(self) + self.initial_focus.focus_set() + # self.deiconify() + self.wait_window(self) + + def body(self, master, candidate): + """Creates the body of 'datawindow'. param candidate is the candidate.uuid""" + try: + data = dict(DataManagement.read_candidate_data()) # TODO better way to do this + candidate = data.get(candidate) + except Exception as e: + print "datawindow.body ", str(e) # TODO manage exceptions + # Candidate section + self.candidate_pane = Labelframe(self, text=MultiLanguage.candidate_pane, width=250, height=350, borderwidth=10) + self.candidate_pane.pack(side=TOP, expand=YES, fill=BOTH, padx=5, pady=5) + # object unique id - does not appear on gui but needed to keep track of this candidate + self.candidate_uid = candidate.uid + # PSCID + self.label_pscid = Label(self.candidate_pane, text=MultiLanguage.candidate_pscid) + self.label_pscid.grid(column=0, row=0, padx=10, pady=5, sticky=N+S+E+W) + self.text_pscid_var = StringVar() + self.text_pscid_var.set(candidate.pscid) + self.text_pscid = Entry(self.candidate_pane, textvariable=self.text_pscid_var) + self.text_pscid.grid(column=0, row=1, padx=10, pady=5, sticky=N+S+E+W) + # status + self.label_status = Label(self.candidate_pane, text=MultiLanguage.candidate_status) + self.label_status.grid(column=1, row=0, padx=10, pady=5, sticky=N+S+E+W) + self.text_status_var = StringVar() + self.text_status_var.set(candidate.status) + self.text_status = Entry(self.candidate_pane, textvariable=self.text_status_var) + self.text_status.grid(column=1, row=1, padx=10, pady=5, sticky=N+S+E+W) + # firstname + self.label_firstname = Label(self.candidate_pane, text=MultiLanguage.candidate_firstname) + self.label_firstname.grid(column=0, row=2, padx=10, pady=5, sticky=N+S+E+W) + self.text_firstname_var = StringVar() + self.text_firstname_var.set(candidate.firstname) + self.text_firstname = Entry(self.candidate_pane, textvariable=self.text_firstname_var) + self.text_firstname.grid(column=0, row=3, padx=10, pady=5, sticky=N+S+E+W) + # lastname + self.label_lastname = Label(self.candidate_pane, text=MultiLanguage.candidate_lastname) + self.label_lastname.grid(column=1, row=2, padx=10, pady=5, sticky=N+S+E+W) + self.text_lastname_var = StringVar() + self.text_lastname_var.set(candidate.lastname) + self.text_lastname = Entry(self.candidate_pane, textvariable=self.text_lastname_var) + self.text_lastname.grid(column=1, row=3, padx=10, pady=5, sticky=N+S+E+W) + # phone number + self.label_phone = Label(self.candidate_pane, text=MultiLanguage.candidate_phone) + self.label_phone.grid(column=2, row=2, padx=10, pady=5, sticky=N+S+E+W) + self.text_phone_var = StringVar() + self.text_phone_var.set(candidate.phone) + self.text_phone = Entry(self.candidate_pane, textvariable=self.text_phone_var) + self.text_phone.grid(column=2, row=3, padx=10, pady=5, sticky=N+S+E+W) + + # Schedule Section - displayed as a table + self.schedule_pane = Labelframe(self, text=MultiLanguage.schedule_pane, width=250, height=350, borderwidth=10) + self.schedule_pane.pack(side=TOP, expand=YES, fill=BOTH, padx=5, pady=5) + # top row (header) + self.label_visit_rank = Label(self.schedule_pane, text=MultiLanguage.schedule_visit_rank) + self.label_visit_rank.grid(column=0, row=0, padx=5, pady=5, sticky=N+S+E+W) + self.label_visit_label = Label(self.schedule_pane, text=MultiLanguage.col_visitlabel) + self.label_visit_label.grid(column=1, row=0, padx=5, pady=5, sticky=N+S+E+W) + self.label_visit_when = Label(self.schedule_pane, text=MultiLanguage.col_when) + self.label_visit_when.grid(column=2, row=0, padx=5, pady=5, sticky=NSEW) + self.label_visit_status = Label(self.schedule_pane, text=MultiLanguage.col_where) + self.label_visit_status.grid(column=3, row=0, padx=5, pady=5, sticky=N+S+E+W) + self.label_visit_status = Label(self.schedule_pane, text=MultiLanguage.col_withwhom) + self.label_visit_status.grid(column=4, row=0, padx=5, pady=5, sticky=N+S+E+W) + self.label_visit_status = Label(self.schedule_pane, text=MultiLanguage.col_status) + self.label_visit_status.grid(column=5, row=0, padx=5, pady=5, sticky=N+S+E+W) + + """ + PSEUDOCODE + 1. Get candidate.visitset + 2. Parse into a sorted list (sorted on visit.rank) + 3. Print data on screen + + + visit_set = candidate.visitset + for key, value in study_setup.iteritems(): + visit_list.append(study_setup[key]) + visit_list = sorted(visit_list, key=lambda visit: visit.rank) + + for key, value in visit_list.iteritems(): + + """ + # TODO add logic "foreach" to create a table showing each visit + # 1- Get candidate visitset and parse into a list + visit_list = [] + visitset = candidate.visitset + if visitset is None: + print 'no visit yet' + else: + for key, value in visitset.iteritems(): + visit_list.append(visitset[key]) + # 2- Sort list on visit.rank + visit_list = sorted(visit_list, key=lambda visit: visit.rank) + # 3- 'print' values on ui + x = 0 + for x in range(len(visit_list)): + # rank + label_visit_rank = Label(self.schedule_pane, text=visit_list[x].rank) + label_visit_rank.grid(column=0, row=x+1, padx=5, pady=5, sticky=N+S+E+W) + # visitlabel + label_visit_label = Label(self.schedule_pane, text=visit_list[x].visitlabel) + label_visit_label.grid(column=1, row=x+1, padx=5, pady=5, sticky=N+S+E+W) + # when + if visit_list[x].when == None: + visit = visit_list[x] + date_range = Visit.visit_date_range(visit) + label_visit_when = Label(self.schedule_pane, text=date_range) + label_visit_when.grid(column=2, row=x+1, padx=5, pady=5, sticky=N+S+E+W) + else: + label_visit_when = Label(self.schedule_pane, text=visit_list[x].when) + label_visit_when.grid(column=2, row=x+1, padx=5, pady=5, sticky=N+S+E+W) + # where + label_visit_where = Label(self.schedule_pane, text=visit_list[x].where) + label_visit_where.grid(column=3, row=x+1, padx=5, pady=5, sticky=N+S+E+W) + # withwhom + label_visit_where = Label(self.schedule_pane, text=visit_list[x].withwhom) + label_visit_where.grid(column=4, row=x+1, padx=5, pady=5, sticky=N+S+E+W) + # status + label_visit_where = Label(self.schedule_pane, text=visit_list[x].status) + label_visit_where.grid(column=5, row=x+1, padx=5, pady=5, sticky=N+S+E+W) + + + + def button_box(self): + # add standard button box + box = Frame(self) + w = Button(box, text="OK", width=10, command=self.ok_button, default=ACTIVE) + w.pack(side=LEFT, padx=5, pady=5) + w = Button(box, text="Cancel", width=10, command=self.cancel_button) + w.pack(side=LEFT, padx=5, pady=5) + self.bind("", self.ok_button) + self.bind("", self.closedialog) + box.pack() + + def ok_button(self, event=None): + print "saving data and closing" # TODO remove when done + self.capture_data() + if not self.validate(): + self.initial_focus.focus_set() # put focus back + return + #need to call treeview update here + self.withdraw() + self.closedialog() + + def cancel_button(self, event=None): + print "close without saving" + parent = Frame(self) + newwin = DialogBox.ConfirmYesNo(parent, MultiLanguage.dialog_close) + if newwin.buttonvalue == 1: + self.closedialog() + else: + return + + def closedialog(self, event=None): + self.parent.focus_set() # put focus back to parent window before destroying the window + self.destroy() + + def validate(self): + return 1 + + def capture_data(self): + """ + Grap the information from the window's text field and save the candidate information based on candidate_uid. + """ + # open the 'database' + db = dict(DataManagement.read_candidate_data()) + # and find candidate based on uid + uid = self.candidate_uid + candidate = db[uid] + # capture data from fields + candidate.pscid = self.text_pscid.get() + candidate.status = self.text_status.get() + candidate.firstname = self.text_firstname.get() + candidate.lastname = self.text_lastname.get() + candidate.phone = self.text_phone.get() + # save data + DataManagement.save_candidate_data(db) \ No newline at end of file From 9500b5f1dc1b43d1d1c6568b97b30f17d4a09b2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Fri, 20 May 2016 08:06:04 -0400 Subject: [PATCH 36/89] Added candidatedata, will need to be removed... --- candidatedata | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/candidatedata b/candidatedata index 52d7fd9..8196330 100644 --- a/candidatedata +++ b/candidatedata @@ -519,42 +519,49 @@ Candidate p153 (dp154 g4 -Nsg6 -I1234567 +S'None' +p155 +sg6 +S'1234567' +p156 sg7 g152 sg8 S'Sue' -p155 +p157 sg10 S'Allen' -p156 +p158 sg12 Nsg51 S'451-874-9632' -p157 +p159 sbsS'c0f22d9e-1c5e-11e6-bb63-406c8f0ea1c5' -p158 +p160 (ischeduler_candidate Candidate -p159 -(dp160 +p161 +(dp162 g4 -Nsg6 -Nsg7 -g158 +S'None' +p163 +sg6 +S'None' +p164 +sg7 +g160 sg8 S'Billy' -p161 +p165 sg10 S'Roberts' -p162 +p166 sg12 Nsg51 S'451-784-9856' -p163 +p167 sS'otherphone' -p164 +p168 S'514-874-9658' -p165 +p169 sbs. \ No newline at end of file From a9404f2cffab27952099b65d70fddb064d641a4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Fri, 17 Jun 2016 16:29:14 -0400 Subject: [PATCH 37/89] Put the menu back on the application! --- create_alldata.py | 110 ------------------ dicat/DicAT_application.py | 11 +- dicat/lib/__init__.py | 6 +- lib/__init__.py | 8 -- lib/classtool.py | 20 ---- lib/datamanagement.py | 52 --------- lib/multilanguage.py | 222 ------------------------------------- lib/utilities.py | 122 -------------------- scheduler_application.py | 76 ------------- scheduler_candidate.py | 196 -------------------------------- scheduler_visit.py | 93 ---------------- ui/datawindow.py | 212 ----------------------------------- 12 files changed, 12 insertions(+), 1116 deletions(-) delete mode 100644 create_alldata.py delete mode 100644 lib/__init__.py delete mode 100644 lib/classtool.py delete mode 100644 lib/datamanagement.py delete mode 100644 lib/multilanguage.py delete mode 100644 lib/utilities.py delete mode 100644 scheduler_application.py delete mode 100644 scheduler_candidate.py delete mode 100644 scheduler_visit.py delete mode 100644 ui/datawindow.py diff --git a/create_alldata.py b/create_alldata.py deleted file mode 100644 index fb26016..0000000 --- a/create_alldata.py +++ /dev/null @@ -1,110 +0,0 @@ -import scheduler_candidate as candidate -import scheduler_visit as visit -import lib.datamanagement as DataManagement -import lib.utilities as Utilities - -#create studysetup -#saving (DataManagement.save_study_data(studydb)) after each visit is really not necessary -#but this mimics the way the application will work -# rank, visitlabel, previousvisit=None, visitwindow = None, visitmargin = None, mandatory = 'Yes, actions = None, uid=None -# -studydb = {} -studyvisit = visit.VisitSetup(1, 'V0', None) -studydb[studyvisit.uid] = studyvisit -Utilities.print_object(studyvisit) -DataManagement.save_study_data(studydb) -studyvisit = visit.VisitSetup(2, 'V1', 'V0', 10, 2) -studydb[studyvisit.uid] = studyvisit -DataManagement.save_study_data(studydb) -studyvisit = visit.VisitSetup(3, 'V2', 'V1', 20, 10) -studydb[studyvisit.uid] = studyvisit -DataManagement.save_study_data(studydb) - -#create a list of candidate -#saving (DataManagement.save_candidate_data(candidatedb)) after each candidate is really not necessary -#firstname, lastname, phone, uid=None, visitset = None, status = None, pscid=None, **kwargs -#but this mimics the way the application wil work -candidatedb = {} -candidatedata = candidate.Candidate('Billy', 'Roberts', '451-784-9856', otherphone='514-874-9658') -candidatedb[candidatedata.uid] = candidatedata -DataManagement.save_candidate_data(candidatedb) - -candidatedata = candidate.Candidate('Sue', 'Allen', '451-874-9632', None, None, None, 1234567) -candidatedb[candidatedata.uid] = candidatedata -DataManagement.save_candidate_data(candidatedb) - -candidatedata = candidate.Candidate('Alan', 'Parson', '451-874-8965') -candidatedb[candidatedata.uid] = candidatedata -DataManagement.save_candidate_data(candidatedb) - -candidatedata = candidate.Candidate('Pierre', 'Tremblay', '547-852-9745') -candidatedb[candidatedata.uid] = candidatedata -DataManagement.save_candidate_data(candidatedb) - -candidatedata = candidate.Candidate('Alain', 'Jeanson', '245-874-6321') -candidatedb[candidatedata.uid] = candidatedata -DataManagement.save_candidate_data(candidatedb) - -candidatedata = candidate.Candidate('Marc', 'St-Pierre', '412-897-9874') -candidatedb[candidatedata.uid] = candidatedata -DataManagement.save_candidate_data(candidatedb) - - -#add visit data to candidates -db = dict(DataManagement.read_candidate_data()) -#get all key values -keylist = [] -for key in db: - keylist.append(key) - -candidate1 = db.get(keylist[0]) -visitlabel = 'V0' #TODO selection from droplist -visitdate = '2014-12-25' #TODO add regex controls -visittime = '13:15' #TODO add regex controls -visitwhere = 'CRIUGM lobby' -visitwhom = 'Annie' -thisvisit = candidate1.set_visit_date(visitlabel, visitdate, visittime, visitwhere, visitwhom) -candidate1.set_next_visit_window(candidate1, thisvisit) -DataManagement.save_candidate_data(db) -Utilities.print_object(thisvisit) - -candidate2 = db.get(keylist[1]) -visitlabel = 'V0' #TODO selection from droplist -visitdate = '2014-12-27' #TODO add regex controls -visittime = '14:30' #TODO add regex controls -visitwhere = 'CRIUGM M-124' -visitwhom = 'Jean' -thisvisit = candidate2.set_visit_date(visitlabel, visitdate, visittime, visitwhere, visitwhom) -candidate2.set_next_visit_window(candidate2, thisvisit) -DataManagement.save_candidate_data(db) - -candidate2 = db.get(keylist[1]) -visitlabel = 'V1' #TODO selection from droplist -visitdate = '2015-02-07' #TODO add regex controls -visittime = '09:15' #TODO add regex controls -visitwhere = 'CRIUGM M-124' -visitwhom = 'Jean' -thisvisit = candidate2.set_visit_date(visitlabel, visitdate, visittime, visitwhere, visitwhom) -candidate2.set_next_visit_window(candidate2, thisvisit) -DataManagement.save_candidate_data(db) - -candidate3 = db.get(keylist[2]) -visitlabel = 'V0' #TODO selection from droplist -visitdate = '2015-01-13' #TODO add regex controls -visittime = '09:15' #TODO add regex controls -visitwhere = 'McDo' -visitwhom = 'Scott' -thisvisit = candidate3.set_visit_date(visitlabel, visitdate, visittime, visitwhere, visitwhom) -candidate3.set_next_visit_window(candidate3, thisvisit) -DataManagement.save_candidate_data(db) - -candidate4 = db.get(keylist[3]) -visitlabel = 'V0' #TODO selection from droplist -visitdate = '2015-02-24' #TODO add regex controls -visittime = '15:30' #TODO add regex controls -visitwhere = 'IGA' -visitwhom = 'Charlie' -thisvisit = candidate4.set_visit_date(visitlabel, visitdate, visittime, visitwhere, visitwhom) -candidate4.set_next_visit_window(candidate4, thisvisit) - -DataManagement.save_candidate_data(db) diff --git a/dicat/DicAT_application.py b/dicat/DicAT_application.py index 06e1699..57d9cfc 100644 --- a/dicat/DicAT_application.py +++ b/dicat/DicAT_application.py @@ -7,6 +7,7 @@ from IDMapper import IDMapper_frame_gui from scheduler_application import UserInterface from welcome_frame import welcome_frame_gui +import ui.menubar as MenuBar class DicAT_application(): @@ -36,7 +37,7 @@ def __init__(self, master, side=LEFT): self.page4 = ttk.Frame(self.nb) # Add the pages to the notebook - self.nb.add(self.page1, text='Welcome to DicAT!') + self.nb.add(self.page1, text='Welcome to DICAT!') self.nb.add(self.page2, text='DICOM de-identifier') self.nb.add(self.page3, text='Scheduler') # hide scheduler for now self.nb.add(self.page4, text='ID key') @@ -48,7 +49,7 @@ def __init__(self, master, side=LEFT): self.dicom_deidentifier_tab() self.id_key_frame() self.welcome_page() - self.scheduler_page() + self.scheduler_page(master) def dicom_deidentifier_tab(self): @@ -68,9 +69,11 @@ def welcome_page(self): # start the Welcome page welcome_frame_gui(self.page1) - def scheduler_page(self): + def scheduler_page(self, master): - # start the scheduler frame + # initialize the menu bar and start the scheduler frame + menu = MenuBar.MenuBar(master) + master.config(menu=menu) UserInterface(self.page3) if __name__ == "__main__": diff --git a/dicat/lib/__init__.py b/dicat/lib/__init__.py index 918ddf7..5c9d53e 100644 --- a/dicat/lib/__init__.py +++ b/dicat/lib/__init__.py @@ -1,4 +1,8 @@ """ From the documentation at https://docs.python.org/2/tutorial/modules.html#packages -The __init__.py files are required to make Python treat the directories as containing packages; this is done to prevent directories with a common name, such as string, from unintentionally hiding valid modules that occur later on the module search path. In the simplest case, __init__.py can just be an empty file, but it can also execute initialization code for the package or set the __all__ variable, described later. + +The __init__.py files are required to make Python treat the directories as containing packages; this is done to prevent +directories with a common name, such as string, from unintentionally hiding valid modules that occur later on the module +search path. In the simplest case, __init__.py can just be an empty file, but it can also execute initialization code +for the package or set the __all__ variable, described later. """ diff --git a/lib/__init__.py b/lib/__init__.py deleted file mode 100644 index 5c9d53e..0000000 --- a/lib/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -""" -From the documentation at https://docs.python.org/2/tutorial/modules.html#packages - -The __init__.py files are required to make Python treat the directories as containing packages; this is done to prevent -directories with a common name, such as string, from unintentionally hiding valid modules that occur later on the module -search path. In the simplest case, __init__.py can just be an empty file, but it can also execute initialization code -for the package or set the __all__ variable, described later. -""" diff --git a/lib/classtool.py b/lib/classtool.py deleted file mode 100644 index 3a3083e..0000000 --- a/lib/classtool.py +++ /dev/null @@ -1,20 +0,0 @@ -class ClassTool(): - """ - Not used! Useful for development as inheritable class MyClass(ClassTool). - """ - #no constructor - def __repr__(self): - attributes =[] - for key in sorted(self.__dict__): - attributes.append('%s=%s' %(key, getattr(self, key))) - return ', '.join(attributes) - - def gatherattributes(self): - attributes =[] - for key in sorted(self.__dict__): - attributes.append('%s' %(key)) - return ', '.join(attributes) - - def getframesize(self): - print str(self.winfo_width()) - diff --git a/lib/datamanagement.py b/lib/datamanagement.py deleted file mode 100644 index 2dbcac2..0000000 --- a/lib/datamanagement.py +++ /dev/null @@ -1,52 +0,0 @@ -#imports from standard packages -import os.path -import pickle -""" -The data_management.py file contains functions related to data management only. -Generic functions: savedata(data, datafilename) and readdata(datafile). Currently, these are not being used. - -Specific functions read_candidate_data(), save_candidate_data(), read_studydata() and save_study_data() are used to get/save candidate data and study setup data respectively. -""" - - -def read_candidate_data(): - """Read and return the content of a file called candidatedata. Returns nothing if file doesn't exist""" - - #check to see if file exists before loading it - if os.path.isfile("candidatedata"): - #load file - db = pickle.load(open("candidatedata", "rb")) - else: - db = "" - return db - - -def save_candidate_data(data): - """Save data in a pickle file named candididatedata. - Will overwrite any existing file. Will create one if it doesn't exist""" - pickle.dump(data, open("candidatedata", "wb")) - - -def read_studydata(): - """Read and return the content of a file called studydata. Returns nothing if file doesn't exist""" - #check to see if file exists before loading it - if os.path.isfile("studydata"): - #load file - db = pickle.load(open("studydata", "rb")) - else: - db = "" - return db - - -def save_study_data(data): - """Save data in a pickle file named studydata. - Will overwrite any existing file. Will create one if it doesn't exist""" - pickle.dump(data, open("studydata", "wb")) - - - -#self-test "module" TODO remove -if __name__ == '__main__': - print 'testing module: datamanagement.py' - data=dict(read_candidate_data()); - print data; diff --git a/lib/multilanguage.py b/lib/multilanguage.py deleted file mode 100644 index affc9ef..0000000 --- a/lib/multilanguage.py +++ /dev/null @@ -1,222 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -#read language preference from appdata file -#from utilities import readappdata #TODO replace and remove -#language = readappdata()[0] - -language = "en" #TODO make dynamic - -if language == "fr": - ###################### TOP LEVEL MENU BAR ###################### - app_title = u"Outils LORIS" - #APPLICATION menu - application_menu = u"Application" - application_setting = u"Préferences" - application_quit = u"Quitter" - #PROJECT menu - #menuproject = u"Projet" #TODO remove? - #openproject = u"Ouvrir un projet" - #modifyproject = u"Modifier le projet ouvert" - #newproject = u"Créer un nouveau projet" - #CANDIDATE menu - candidate_menu = u"Candidat" - candidate_add = u"Nouveau candidat" - candidate_find = u"Trouver candidat" - candidate_update = u"Mettre à jour" - candidate_exclude_include_toggle = u"Inclure/Exclure un candidat" - candidate_get_id = u"Obtenir l'identifiant d'un candidat" - #clear_all_field = u"Effacer" - #ANONYMIZER menu - anonymizer_menu = u"DICOM" - anonymizer_run = u"Anonymizer" - #CALENDAR menu - calendar_menu = u"Calendrier" - calendar_new_appointment = u"Nouveau Rendez-vous" - #HELP menu - help_menu = u"Aide" - help_get_help = u"Obtenir de l'aide" - help_about_window = u"A propos de ..." - ###################### PROJECT INFO PANE ####################### - project_info_pane = u"Projet" - project_detail_pane = u"Détails du Projet" - visit_detail_pane = u"Détails des Visites" - project_name = u"Projet" - project_start = u"Début" - project_end = u"Fin" - target_recruitment = u"Cible de recrutement" - current_recruitment = u"Recrutement actuel" - total_visit = u"Nombre de Visites" - #################### MULTI-TAB DATA SECTION ##################### - calendar_pane = u"Calendrier" - candidate_pane = u"Candidats" - - label_candidate_table = u"Faites un double-clic sur l'une des lignes pour remplir les champs ci-dessus" - datatable_id = u"ID" - datatable_firstname = u"Prénom" - datatable_lastname = "Nom" - datatable_dob = u"Date de Naissance" - datatable_phone = u"Téléphone" - datatable_address = u"Adresse" - datatable_city = u"Ville" - datatable_province = u"Province" - datatable_country = u"Pays" - datatable_postal_code = u"Code Postal" - - calendar_monday = u"Lundi" - calendar_tuesday = u"Mardi" - calendar_wednesday = u"Mercredi" - calendar_thursday = u"Jeudi" - calendar_friday = u"Vendredi" - calendar_saturday = u"Samedi" - calendar_sunday = u"Dimanche" - calendar_january = u"Janvier" - calendar_february = u"Février" - calendar_march = u"Mars" - calendar_april = u"Avril" - calendar_may = u"Mai" - calendar_june = u"Juin" - calendar_jully = u"Juillet" - calendar_august = u"Août" - calendar_september = u"Septembre" - calendar_october = u"Octobre" - calendar_november = u"Novembre" - calendar_december = u"Décembre" - - ################ COLUMN HEADER ################## - col_candidate = u"Candidat" - col_visitlabel = u"Visite" - col_when = u"Date/Heure" - col_where = u"Endroit" - col_status = u"Statut" - col_withwhom = u"Avec qui" - #################### STATUS ##################### - status_active = u"actif" - status_tentative = u"provisoire" - ################# DATA WINDOWS ################## - data_window_title = u"DATA WINDOW" #TODO trouver un titre français - ################## DIALOGBOX #################### - # very not sure what to do about that section - dialog_yes = u"Oui" - dialog_no = u"Non" - dialog_title_confirm = u"Veuillez confirmer!" - dialog_close = u"Vous êtes sur le point de fermer cette fenêtre sans sauvegarder!\n\nVoulez-vous continuer?" - ################ DATA WINDOW ################### - schedule_pane = u"Calendrier" - candidate_pane = u"Candidat" - candidate_firstname = u"Prénom" - candidate_lastname = u"Nom de famille" - candidate_phone = u"Téléphone" - candidate_pscid = u"ID" - candidate_status = u"Status" - schedule_visit_label = u"Visite" - schedule_visit_rank = u"#" - schedule_visit_status = u"Status" - schedule_visit_when = u"Date" - schedule_optional =u"Optionnel" - -elif language == "en": - app_title = u"LORIS tools" - #APPLICATION menu - application_menu = u"Application" - application_setting = u"Preferences" - application_quit = u"Quit" - #PROJECT menu - #menuproject = u"Project" #TODO remove? - #openproject = u"Open project" - #modifyproject = u"Modify open project" - #newproject = u"New project" - #CANDIDATE menu - candidate_menu = u"Candidate" - candidate_add = u"New candidate" - candidate_find = u"Find a candidate" - candidate_update = u"Update" - candidate_exclude_include_toggle = u"Include/Exclude a candidate" - candidate_get_id = u"Get a canditate ID" - #clear_all_field = u"Clear" - #CALENDAR menu - calendar_menu = u"Calendar" - calendar_new_appointment = u"New appointment" - #ANONYMIZER menu - anonymizer_menu = u"DICOM" - anonymizer_run = u"Anonymizer" - #HELP menu - help_menu = u"Help" - help_get_help = u"Get some help" - help_about_window = u"About this..." - ###################### PROJECT INFO PANE ####################### - project_info_pane = u"Project Informations" - project_detail_pane = u"Project Details" - visit_detail_pane = u"Visit Details" - project_name = u"Project" - project_start = u"Start" - project_end = u"End" - target_recruitment = u"Recruitment target" - current_recruitment = u"Current recruitment" - total_visit = u"Total number of Visits" - #################### MULTI-TAB DATA SECTION ##################### - calendar_pane = u"Calendar" - candidate_pane = u"Candidates" - label_candidate_table = u"Double click on row to populate fields above" - datatable_id = u"ID" - datatable_firstname = u"First Name" - datatable_lastname = u"Last Name" - datatable_dob = u"Date of Birth" - datatable_phone = u"Phone" - datatable_address = u"Address" - datatable_city = u"City" - datatable_province = u"Province" - datatable_country = u"Country" - datatable_postal_code = u"Postal Code" - - calendar_monday = u"Monday" - calendar_tuesday = u"Tuesday" - calendar_wednesday = u"Wednesday" - calendar_thursday = u"Thursday" - calendar_friday = u"Friday" - calendar_saturday = u"Saturday" - calendar_sunday = u"Sunday" - calendar_january = u"January" - calendar_february = u"February" - calendar_march = u"Marc" - calendar_april = u"April" - calendar_may = u"May" - calendar_june = u"June" - calendar_jully = u"Jully" - calendar_august = u"August" - calendar_september = u"September" - calendar_october = u"October" - calendar_november = u"November" - calendar_december = u"December" - - ################ COLUMN HEADER ################## - col_candidate = u"Candidate" - col_visitlabel = u"Visit" - col_when = u"Date/Time" - col_where = u"Place" - col_status = u"Status" - col_withwhom = u"Whom" - #################### STATUS ##################### - status_active = u"active" - status_tentative = u"tentative" - ################# DATA WINDOWS ################## - data_window_title =u"Data Window" - ################## DIALOGBOX #################### - # very not sure what to do about that section - dialog_yes = u"Yes" - dialog_no = u"No" - dialog_title_confirm = u"Please confirm!" - dialog_close = u"You are about to close this window without saving! \n\nDo you want to continue?" - ################ DATA WINDOW ################### - schedule_pane = u"Calendar" - candidate_pane = u"Candidate" - candidate_firstname = u"Firstname" - candidate_lastname = u"Lastname" - candidate_phone = u"Phone" - candidate_pscid = u"ID" - candidate_status = u"Status" - schedule_visit_label = u"Visit" - schedule_visit_rank = u"#" - schedule_visit_status = u"Status" - schedule_visit_when = u"Date" - schedule_optional =u"Optional" \ No newline at end of file diff --git a/lib/utilities.py b/lib/utilities.py deleted file mode 100644 index 3e7a979..0000000 --- a/lib/utilities.py +++ /dev/null @@ -1,122 +0,0 @@ -#imports from standard packages -from uuid import uuid1 -import time - -""" -This file contains utility functions used throughout the application -""" - -def generate_uid(): - """ - will generate a random UUID. - see python documentation https://docs.python.org/2/library/uuid.html - """ - ui = str(uuid1()) - return ui - -def is_unique(data, dataset): - """ - will verify if 'data' passed as argument is unique in a dataset - """ - seen = set() - return not any(value in seen or seen.add(value) for value in data) - - -def describe(something): - """ - this will return the object(something) class name and type as well as all attributes excluding those starting with a double underscore - """ - #will return the object type and class (Type:Class) as text - objectclass = str(something.__class__.__name__) - objecttype = str(type(something)) - #will return a list of all non"__" attributes, including use defined ones (**kwargs) - attributes = str(filter(lambda a: not a.startswith('__'), dir(something))) - returnvalue = objectclass, " (", objecttype, "): ", attributes - print returnvalue - -def get_current_date(): - """ - will return today's date as a string of format yyyymmdd - """ - return time.strftime("%Y%m%d") - -def get_current_time(option): - """ - will return current time as a string of format hhmmss - """ - if option == 1: - return time.strftime("%H%M%S") - elif option == 2: - return time.strftime("%H:%M:%S") - else: - pass - - -def error_log(message): - # append/save timestamp and exception to errorlog.txt - # object Exception e is sent as is and this method is taking care of parsing it to string - # and adding a timestamp - console = 1 #TODO use this value to send error message to the console instead of the file - if console == 0: - timestamp = time.strftime("%c") # date and time representation using this format: 11/07/14 12:50:35 - fullmessage = timestamp + ": " + str(message) + "\n" - anyfile = open("errorlog.txt", "a") #this is required since a file object is later expected - anyfile.write(fullmessage) - anyfile.close() - elif console == 1: - print message + "\n" #TODO remove when done - - - -def center_window(win): - win.update_idletasks() - width = win.winfo_width() - height = win.winfo_height() - x = (win.winfo_screenwidth() // 2) - (width // 2) - y = (win.winfo_screenheight() // 2) - (height // 2)-200 - win.geometry('{}x{}+{}+{}'.format(width, height, x, y)) - - - - - -######################################################################################## -def gather_attributes(something): - """ - Receive an object and return an array containing the object attributes and value - """ - attributes =[] - for key in sorted(something.__dict__): - attributes.append('%s' %(key)) - return attributes - - -def search_uid(db, value): - return filter(lambda candidate: candidate['uid'] == value, db) - - -def print_object(something): - #for dev only! - #will print key, attributes, value in the console. - #most likely will be an dict or a class instance. - #so this is for the dict - if type(something) is dict: - for key, value in something.iteritems(): - c = something.get(key) - for attr, value in c.__dict__.iteritems(): - print attr,": ", value - print "\n" - elif type(something) is list: - for item in something: - print item - else: #and this for the class instance - for attr, value in something.__dict__.iteritems(): - print attr, value - print "\n\n" - - -# self-test "module" TODO remove before release -if __name__ == '__main__': - import lib.datamanagement as DataManagement - data=dict(DataManagement.read_studydata()) - print_object(data) \ No newline at end of file diff --git a/scheduler_application.py b/scheduler_application.py deleted file mode 100644 index 8d8fdbd..0000000 --- a/scheduler_application.py +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env python - -#import standard packages -from Tkinter import * -from ttk import * -#import internal packages -import ui.menubar as MenuBar -import ui.datatable as DataTable -import lib.multilanguage as MultiLanguage - - -class UserInterface(Frame): - - def __init__(self, parent): - Frame.__init__(self) - self.parent = parent - #self.parent.title(MultiLanguage.app_title) - self.initialize() - - def initialize(self): - - # initialize frame - self.frame = Frame(self.parent) - self.frame.pack(side=TOP, expand=YES, fill=BOTH, padx=10, pady=10) - # TODO create classe for project info pane - self.project_infopane = Labelframe(self.frame, text=MultiLanguage.project_info_pane, width=250, height=350, - borderwidth=10) # TODO add dynamic resize - self.project_infopane.pack(side=LEFT, expand=NO, fill=BOTH) - # This area (datapane) is composed of one Panedwindow containing two Labelframe - self.data_pane = Panedwindow(self.frame, width=1000, height=500, orient=HORIZONTAL) # TODO add dynamic resize - self.data_pane.pack(side=RIGHT, expand=YES, fill=BOTH) - self.candidate_pane = Labelframe(self.data_pane, text=MultiLanguage.candidate_pane, width=100, height=450, - borderwidth=10) # TODO add dynamic resize - self.visit_pane = Labelframe(self.data_pane, text=MultiLanguage.calendar_pane, width=100, height=350, - borderwidth=10) # TODO add dynamic resize - self.data_pane.add(self.candidate_pane) - self.data_pane.add(self.visit_pane) - - # create data tables (treeview) - visit_column_headers = ('candidate', 'visitlabel', 'when', 'where', 'status') - self.visit_table = DataTable.VisitList(self.visit_pane, visit_column_headers) - self.visit_table.pack(side=BOTTOM, expand=YES, fill=BOTH) - column_header = ('firstname', 'lastname', 'phone', 'status') - self.data_table = DataTable.ParticipantsList(self.candidate_pane, column_header) - self.data_table.pack(side=BOTTOM, expand=YES, fill=BOTH) - - """ - #create a filter section in each data_pane(not implemented yet) - #TODO This whole section needs to be replaced by REAL CODE actively filtering the data - self.filter_candidate = Labelframe(self.candidate_pane, text='Filters', width=220, height=50, borderwidth=10) - self.filter_candidate.pack(side=TOP, expand=NO, fill=BOTH, pady=5) - self.filter_candidate_label = Label(self.filter_candidate, text='Filter for Non-Active / Active / Excluded / Group...') - self.filter_candidate_label.pack(side=TOP, expand=NO, fill=BOTH) - self.filter_visit = Labelframe(self.visit_pane, text='Filters', width=220, height=50, borderwidth=10) - self.filter_visit.pack(side=TOP, expand=NO, fill=BOTH, pady=5) - self.filter_candidate_label = Label(self.filter_visit, text='Filters for Active / Tentative / Closed ...') - self.filter_candidate_label.pack(side=TOP, expand=NO, fill=BOTH) - """ - - -class Application(Tk): - def __init__(self): - Tk.__init__(self) - menu = MenuBar.MenuBar(self) - self.config(menu=menu) - frame = UserInterface(self) - -""" -#Application main loop -if __name__ == "__main__": - app=Application() - app.mainloop() -""" -#Application main loop -#app=Application() -#app.mainloop() \ No newline at end of file diff --git a/scheduler_candidate.py b/scheduler_candidate.py deleted file mode 100644 index 631c7ed..0000000 --- a/scheduler_candidate.py +++ /dev/null @@ -1,196 +0,0 @@ -# import standard packages -import datetime - -from scheduler import visit - -# import internal packages -import lib.datamanagement as DataManagement -import lib.multilanguage as MultiLanguage -import lib.utilities as Utilities - - -class Candidate: - """ - The Candidate class defines the candidates/participants of the study - - Attributes: - uid: A unique identifier using python's uuid1 method. Used as key to store and retrieve objects from - files and/or dictionaries. - firstname: First name of the candidate - lastname: Last name of the candidate - visitset: A dictionnary containing all visits (planed or not) - phone: A primary phone number - status: Status of this candidate - pscid: Loris (clinical results database) specific ID - - kwargs: Not implemented yet! - - Code example: - candidatedb = {} #setup a dict to receive the candidates - candidatedata = candidate.Candidate('Billy', 'Roberts', '451-784-9856', otherphone='514-874-9658') #instanciate one candidate - candidatedb[candidatedata.uid] = candidatedata #add candidate to dict - DataManagement.save_candidate_data(candidatedb) #save data to file - """ - def __init__(self, firstname, lastname, phone, uid=None, visitset = None, status = None, pscid=None, **kwargs): #TODO *kwarg - self.uid = Utilities.generate_uid() - self.firstname = firstname - self.lastname = lastname - self.visitset = visitset - self.phone = phone - self.status = status - self.pscid = pscid - #...many other attributes - if kwargs is not None: - for key, value in kwargs.iteritems(): - setattr(self, key, value) - - def setup_visitset(self): - """ - When creating the first visit for a candidate, a complete set of visits is added based on a study/project visit list - There are no parameters passed to this method since it will simply create a new 'empty' - This method will: - 1-open studydata (the study visit list) and 'parse' the dict to a sorted list (dict cannot be sorted) - 2-create a temporary list of visit_labels that will serve as a key within the Candidate.visit_set dictionary - 3-instantiate individual visits based on the study visit list - Usage: - Called by Candidate.set_visit_date() - """ - visit_list =[] - visit_label_templist = [] - study_setup = dict() - try: - #1-open studydata - study_setup = dict(DataManagement.read_studydata()) - except Exception as e: - print str(e) #TODO add error login (in case a study data file does not exist) - for key, value in study_setup.iteritems(): - #2-parse into a sorted list - visit_list.append(study_setup[key]) - visit_list = sorted(visit_list, key=lambda visit: visit.rank) - #create a temporary list of visitlabels - for each in visit_list: - visit_label_templist.append(each.visitlabel) - #3-instantiate individual visit based on each instance of VisitSetup() - self.visitset = {} #setup a dict to receive a set of Visit() - count = 0 - #set values of : uid, rank, visit_label, previous_visit, visit_window, visitmargin, - for key in visit_list: - mainkey = str(visit_label_templist[count]) - rank =key.rank - visit_label = key.visitlabel - previous_visit = key.previousvisit - visit_window = key.visitwindow - visit_margin = key.visitmargin - visit_data = visit.Visit(rank, visit_label, previous_visit, visit_window, visit_margin,) - self.visitset[mainkey] = visit_data - count += 1 - - def set_visit_date(self, visitlabel, visitdate, visittime, visitwhere, visitwhom): - """ - This method update the visit information (according to visitlabel) - This method will: - 1-Check if visitset==None. If so, then Candidate.setup_visitset() is called to setup Candidate.visitset - 2-Check if a date is already set for this visitlabel - 3-Set values of Visit.when, 'Visit.where, Visit.whithwhom and Visit.status for current visit - Usage: Called by GUI methods - Return: current_visit as current Visit(Visit(VisitSetup) instance - """ - #1-Check to see if visitset == None before trying to create a new date instance - if self.visitset is None: - self.setup_visitset() - self.set_candidate_status_active() #Candidate.status='active' since we're setting up a first visit - #get current visit within visitset - current_visit = self.visitset.get(visitlabel) - #2-Check to see if this visit already has a date - if current_visit.when is not None: - print "date already exists" #TODO add confirmation of change log??? - pass - #concatenate visitdate and visittime and parse into a datetime object - visitwhen = visitdate + ' ' + visittime - when = datetime.datetime.strptime(visitwhen, '%Y-%m-%d %H:%M') - #3-Set values of Visit.when, 'Visit.where, Visit.whithwhom and Visit.status for current visit - current_visit.when = when - current_visit.where = visitwhere - current_visit.withwhom = visitwhom - current_visit.status = "active" #that is the status of the visit - return current_visit - - - """ - def set_next_visit_window(self, candidate, current_visit): - #get the current visit object as argument. Will search and look for the next visit (visit where previousvisit == current_visit_label) - next_visit_searchset = candidate.visitset - current_visit_label = current_visit.visitlabel - next_visit = "" - for key in next_visit_searchset: - visit_data = next_visit_searchset[key] - if visit_data.previousvisit == current_visit_label: - next_visit = candidate.visitset.get(visit_data.visitlabel) #TODO debug when current is last visit - #gather info about current_visit (mostly for my own biological computer! else I get lost) - current_visit_date = current_visit.when - current_visit_year = current_visit_date.year #get the year of the current visit date - next_visit_window = next_visit.visitwindow - next_visit_margin = next_visit.visitmargin - #set dates for the next visit - next_visit_early = int(current_visit_date.strftime('%j')) + (next_visit_window - next_visit_margin) #this properly handle change of year - next_visit_early_date = datetime.datetime(current_visit_year, 1, 1) + datetime.timedelta(next_visit_early - 1) - next_visit_late = int(current_visit_date.strftime('%j')) + (next_visit_window + next_visit_margin) - next_visit_late_date = datetime.datetime(current_visit_year, 1, 1) + datetime.timedelta(next_visit_late - 1) - next_visit.when_earliest = next_visit_early_date - next_visit.when_latest = next_visit_late_date - next_visit.status = "tentative" - Utilities.print_object((next_visit)) - """ - def set_next_visit_window(self, candidate, current_visit): - """ - This method will 'calculate' a min and max date when the next visit should occur - 1-Get candidate.visitset (as visit_searchset) and current_visit.visitlabel - 2-Identify which visit (in visitset) has previousvisit == current_visit.visitlabel - 3-Get - Usage: Currently called by GUI function (TODO RETHINK THIS LOGIC maybe it should be called when running Candidate.set_visit_date()) - """ - - - #get the current visit object as argument. Will search and look for the next visit (visit where previousvisit == current_visitlabel) - - #1- Get Candidate.visitset and current_visit / next_visit will == Visit(VisitSetup) of the next visit (relative to current_visit) - visit_searchset = candidate.visitset - current_visitlabel = current_visit.visitlabel - next_visit = "" - #2-Identify which visit (in visitset) has previousvisit == current_visit.visitlabel - for key in visit_searchset: - visit_data = visit_searchset[key] - if visit_data.previousvisit == current_visitlabel: - next_visit = candidate.visitset.get(visit_data.visitlabel) #TODO debug when current is last visit - #3-Calculate a min and max date for the next visit to occur based on Visit.visitwindow and Visit.visitmargin - current_visitdate = current_visit.when - current_visityear = current_visitdate.year #get the year of the current visit date - next_visitwindow = next_visit.visitwindow - nextvisitmargin = next_visit.visitmargin - #set dates for the next visit - nextvisitearly = int(current_visitdate.strftime('%j')) + (next_visitwindow - nextvisitmargin) #this properly handle change of year - nextvisitearlydate = datetime.datetime(current_visityear, 1, 1) + datetime.timedelta(nextvisitearly - 1) - nextvisitlate = int(current_visitdate.strftime('%j')) + (next_visitwindow + nextvisitmargin) - nextvisitlatedate = datetime.datetime(current_visityear, 1, 1) + datetime.timedelta(nextvisitlate - 1) - next_visit.whenearliest = nextvisitearlydate - next_visit.whenlatest = nextvisitlatedate - next_visit.status = "tentative" #set this visit.status - - def get_active_visit(self, candidate): - candidatefullname = str(candidate.firstname + ' ' + candidate.lastname) - currentvisitset = candidate.visitset - activevisit = [] - if currentvisitset is None: - return - elif currentvisitset is not None: - for key in currentvisitset: - if currentvisitset[key].status == MultiLanguage.status_active: - visitlabel = currentvisitset[key].visitlabel - when = currentvisitset[key].when.strftime('%Y-%m-%d %Hh%m') - where = currentvisitset[key].where - who = currentvisitset[key].withwhom - activevisit = [candidatefullname, visitlabel, when, where, who] - return activevisit - - def set_candidate_status_active(self): - self.status = MultiLanguage.status_active #set the Candidate.status to 'active' \ No newline at end of file diff --git a/scheduler_visit.py b/scheduler_visit.py deleted file mode 100644 index c9644ce..0000000 --- a/scheduler_visit.py +++ /dev/null @@ -1,93 +0,0 @@ -#import standard packages -#import internal packages -import datetime - -import lib.utilities as utilities - - -class VisitSetup(): - """ - The VisitSetup() class is used to define a study (or project) in terms of sequence of visits and also serves as a - base class for Visit(VisitSetup). A study (or project) can have as many visits as required. There is no class for - study as it is merely a simple dictionnary. - - Code example: This study contains 3 visits. Since V0 is the first visit, it doesn't have any values for - 'previousvisit', 'visitwindow' and ' visitmargin' - study = {} - visit = visit.VisitSetup(1, 'V0') #a uid (uuid1) is automatically generated - study[visit.uid] = visit #VisitSetup.uid is a unique ID used as key - visit = visit.VisitSetup(2, 'V1', 'V0', 10, 2) - study[visit.uid] = visit - visit = visit.VisitSetup(3, 'V2', 'V1', 20, 10) - study[visit.uid] = visit - - This is the parent class of Visit(VisitSetup), furthermore Visit(VisitSetup) objects will be 'instanciated' from - each instance of VisitSetup(object). - Both VisitSetup(object) class and instances are used to create individual instances of Visit(VisitSetup) - - Attributes: - uid: A unique identifier using python's uuid1 method. Used as key to store and retrieve objects from - files and/or dictionaries. - rank: The rank of the visit in the sequence (int). Useful to sort the visits in order of occurrence. - visitlabel: The label of the visit (string) such as V1, V2 or anything else the user may come up with - previousvisit: The visitlabel (string) of the visit occurring before this one. Used to plan this visit based on - the date of the previous visit. (default to None) - visitwindow: The number of days (int) between this visit and the previous visit. (default to None) - visitmargin: The margin (in number of days (int)) that is an allowed deviation (a +/- few days ). Basically, - this allow the 'calculation' of a date window when this visit should occur. (default to None) - mandatory: Indicate if this visit is mandatory. Default to Yes - actions: A list of action points (or simply reminders) specific to that visit (i.e.'reserve room 101'). - This is not implemented yet (default to None) - """ - - def __init__(self, rank, visitlabel, previousvisit = None, visitwindow = None, visitmargin = None, actions = None, uid=None): - self.uid = utilities.generate_uid() - self.rank = rank - self.visitlabel = visitlabel - self.previousvisit = previousvisit - self.visitwindow = visitwindow - self.visitmargin = visitmargin - self.actions = actions #not implemented yet! - - -class Visit(VisitSetup): - """ - The Visit(VisitSetup) class help define individual visit of the candidate using VisitSetup(object) instances as 'templates'. - Upon creation of the first meeting with a candidate, the Candidate(object) instance will get a full set of Visit(VisitSetup) instances. - This set of visits is contained in Candidate.visitset - Each time a visit is being setup, a 'time window' is calculated to define the earliest and latest date at which - the 'nextVisit' should occur. - - Attributes: (In addition of parent class attributes.) - when: Date at which this visit is occurring. (default to None) - when_earliest: Earliest date when this visit should occur. Set to value when previous visit is activated. (default to None) - when_latest: Latest date when this visit should occur. Set to value when previous visit is activated. (default to None) - where: Place where this meeting is taking place. (default to None) - whitwhom: Professional meeting the study candidate at the reception. (default to None) - status: Status of this visit. Set to active when 'when' is set (default to None) - """ - def __init__(self, rank, visitlabel, previousvisit, visitwindow, visitmargin, actions=None, uid=None, when = None, - whenearliest = None, whenlatest = None, where = None, withwhom = None, status = None): - VisitSetup.__init__(self, rank, visitlabel, previousvisit, visitwindow, visitmargin, actions, uid) - self.when = when - self.whenearliest = whenearliest - self.whenlatest = whenlatest - self.where = where - self.withwhom = withwhom - self.status = status - - def visit_date_range(self): - #need to handle the case where a visit has no dates - #this works but Exception handling seems to broad - try: - early_date = datetime.datetime.date(self.whenearliest) - late_date = datetime.datetime.date(self.whenlatest) - date_range = str(early_date), '<>', str(late_date) - except Exception as e: - #print e - date_range = "" - return date_range - - - - diff --git a/ui/datawindow.py b/ui/datawindow.py deleted file mode 100644 index c75c1f7..0000000 --- a/ui/datawindow.py +++ /dev/null @@ -1,212 +0,0 @@ -# import standard packages -from Tkinter import * -from ttk import * -# import internal packages -from scheduler_visit import Visit -import ui.dialogbox as DialogBox -import lib.utilities as Utilities -import lib.multilanguage as MultiLanguage -import lib.datamanagement as DataManagement - - - -# ref: http://effbot.org/tkinterbook/tkinter-newDialog-windows.htm -# TODO this class needs a major clean-up - - -class DataWindow(Toplevel): - def __init__(self, parent, candidate_uuid='new'): - Toplevel.__init__(self, parent) - # create a transient window on top of parent window - self.transient(parent) - self.parent = parent - self.title(MultiLanguage.data_window_title) #TODO find a better title for the thing - body = Frame(self) - self.initial_focus = self.body(body, candidate_uuid) - body.pack(padx=5, pady=5) - - self.button_box() - self.grab_set() - if not self.initial_focus: - self.initial_focus = self - self.protocol("WM_DELETE_WINDOW", self.closedialog) - Utilities.center_window(self) - self.initial_focus.focus_set() - # self.deiconify() - self.wait_window(self) - - def body(self, master, candidate): - """Creates the body of 'datawindow'. param candidate is the candidate.uuid""" - try: - data = dict(DataManagement.read_candidate_data()) # TODO better way to do this - candidate = data.get(candidate) - except Exception as e: - print "datawindow.body ", str(e) # TODO manage exceptions - # Candidate section - self.candidate_pane = Labelframe(self, text=MultiLanguage.candidate_pane, width=250, height=350, borderwidth=10) - self.candidate_pane.pack(side=TOP, expand=YES, fill=BOTH, padx=5, pady=5) - # object unique id - does not appear on gui but needed to keep track of this candidate - self.candidate_uid = candidate.uid - # PSCID - self.label_pscid = Label(self.candidate_pane, text=MultiLanguage.candidate_pscid) - self.label_pscid.grid(column=0, row=0, padx=10, pady=5, sticky=N+S+E+W) - self.text_pscid_var = StringVar() - self.text_pscid_var.set(candidate.pscid) - self.text_pscid = Entry(self.candidate_pane, textvariable=self.text_pscid_var) - self.text_pscid.grid(column=0, row=1, padx=10, pady=5, sticky=N+S+E+W) - # status - self.label_status = Label(self.candidate_pane, text=MultiLanguage.candidate_status) - self.label_status.grid(column=1, row=0, padx=10, pady=5, sticky=N+S+E+W) - self.text_status_var = StringVar() - self.text_status_var.set(candidate.status) - self.text_status = Entry(self.candidate_pane, textvariable=self.text_status_var) - self.text_status.grid(column=1, row=1, padx=10, pady=5, sticky=N+S+E+W) - # firstname - self.label_firstname = Label(self.candidate_pane, text=MultiLanguage.candidate_firstname) - self.label_firstname.grid(column=0, row=2, padx=10, pady=5, sticky=N+S+E+W) - self.text_firstname_var = StringVar() - self.text_firstname_var.set(candidate.firstname) - self.text_firstname = Entry(self.candidate_pane, textvariable=self.text_firstname_var) - self.text_firstname.grid(column=0, row=3, padx=10, pady=5, sticky=N+S+E+W) - # lastname - self.label_lastname = Label(self.candidate_pane, text=MultiLanguage.candidate_lastname) - self.label_lastname.grid(column=1, row=2, padx=10, pady=5, sticky=N+S+E+W) - self.text_lastname_var = StringVar() - self.text_lastname_var.set(candidate.lastname) - self.text_lastname = Entry(self.candidate_pane, textvariable=self.text_lastname_var) - self.text_lastname.grid(column=1, row=3, padx=10, pady=5, sticky=N+S+E+W) - # phone number - self.label_phone = Label(self.candidate_pane, text=MultiLanguage.candidate_phone) - self.label_phone.grid(column=2, row=2, padx=10, pady=5, sticky=N+S+E+W) - self.text_phone_var = StringVar() - self.text_phone_var.set(candidate.phone) - self.text_phone = Entry(self.candidate_pane, textvariable=self.text_phone_var) - self.text_phone.grid(column=2, row=3, padx=10, pady=5, sticky=N+S+E+W) - - # Schedule Section - displayed as a table - self.schedule_pane = Labelframe(self, text=MultiLanguage.schedule_pane, width=250, height=350, borderwidth=10) - self.schedule_pane.pack(side=TOP, expand=YES, fill=BOTH, padx=5, pady=5) - # top row (header) - self.label_visit_rank = Label(self.schedule_pane, text=MultiLanguage.schedule_visit_rank) - self.label_visit_rank.grid(column=0, row=0, padx=5, pady=5, sticky=N+S+E+W) - self.label_visit_label = Label(self.schedule_pane, text=MultiLanguage.col_visitlabel) - self.label_visit_label.grid(column=1, row=0, padx=5, pady=5, sticky=N+S+E+W) - self.label_visit_when = Label(self.schedule_pane, text=MultiLanguage.col_when) - self.label_visit_when.grid(column=2, row=0, padx=5, pady=5, sticky=NSEW) - self.label_visit_status = Label(self.schedule_pane, text=MultiLanguage.col_where) - self.label_visit_status.grid(column=3, row=0, padx=5, pady=5, sticky=N+S+E+W) - self.label_visit_status = Label(self.schedule_pane, text=MultiLanguage.col_withwhom) - self.label_visit_status.grid(column=4, row=0, padx=5, pady=5, sticky=N+S+E+W) - self.label_visit_status = Label(self.schedule_pane, text=MultiLanguage.col_status) - self.label_visit_status.grid(column=5, row=0, padx=5, pady=5, sticky=N+S+E+W) - - """ - PSEUDOCODE - 1. Get candidate.visitset - 2. Parse into a sorted list (sorted on visit.rank) - 3. Print data on screen - - - visit_set = candidate.visitset - for key, value in study_setup.iteritems(): - visit_list.append(study_setup[key]) - visit_list = sorted(visit_list, key=lambda visit: visit.rank) - - for key, value in visit_list.iteritems(): - - """ - # TODO add logic "foreach" to create a table showing each visit - # 1- Get candidate visitset and parse into a list - visit_list = [] - visitset = candidate.visitset - if visitset is None: - print 'no visit yet' - else: - for key, value in visitset.iteritems(): - visit_list.append(visitset[key]) - # 2- Sort list on visit.rank - visit_list = sorted(visit_list, key=lambda visit: visit.rank) - # 3- 'print' values on ui - x = 0 - for x in range(len(visit_list)): - # rank - label_visit_rank = Label(self.schedule_pane, text=visit_list[x].rank) - label_visit_rank.grid(column=0, row=x+1, padx=5, pady=5, sticky=N+S+E+W) - # visitlabel - label_visit_label = Label(self.schedule_pane, text=visit_list[x].visitlabel) - label_visit_label.grid(column=1, row=x+1, padx=5, pady=5, sticky=N+S+E+W) - # when - if visit_list[x].when == None: - visit = visit_list[x] - date_range = Visit.visit_date_range(visit) - label_visit_when = Label(self.schedule_pane, text=date_range) - label_visit_when.grid(column=2, row=x+1, padx=5, pady=5, sticky=N+S+E+W) - else: - label_visit_when = Label(self.schedule_pane, text=visit_list[x].when) - label_visit_when.grid(column=2, row=x+1, padx=5, pady=5, sticky=N+S+E+W) - # where - label_visit_where = Label(self.schedule_pane, text=visit_list[x].where) - label_visit_where.grid(column=3, row=x+1, padx=5, pady=5, sticky=N+S+E+W) - # withwhom - label_visit_where = Label(self.schedule_pane, text=visit_list[x].withwhom) - label_visit_where.grid(column=4, row=x+1, padx=5, pady=5, sticky=N+S+E+W) - # status - label_visit_where = Label(self.schedule_pane, text=visit_list[x].status) - label_visit_where.grid(column=5, row=x+1, padx=5, pady=5, sticky=N+S+E+W) - - - - def button_box(self): - # add standard button box - box = Frame(self) - w = Button(box, text="OK", width=10, command=self.ok_button, default=ACTIVE) - w.pack(side=LEFT, padx=5, pady=5) - w = Button(box, text="Cancel", width=10, command=self.cancel_button) - w.pack(side=LEFT, padx=5, pady=5) - self.bind("", self.ok_button) - self.bind("", self.closedialog) - box.pack() - - def ok_button(self, event=None): - print "saving data and closing" # TODO remove when done - self.capture_data() - if not self.validate(): - self.initial_focus.focus_set() # put focus back - return - #need to call treeview update here - self.withdraw() - self.closedialog() - - def cancel_button(self, event=None): - print "close without saving" - parent = Frame(self) - newwin = DialogBox.ConfirmYesNo(parent, MultiLanguage.dialog_close) - if newwin.buttonvalue == 1: - self.closedialog() - else: - return - - def closedialog(self, event=None): - self.parent.focus_set() # put focus back to parent window before destroying the window - self.destroy() - - def validate(self): - return 1 - - def capture_data(self): - """ - Grap the information from the window's text field and save the candidate information based on candidate_uid. - """ - # open the 'database' - db = dict(DataManagement.read_candidate_data()) - # and find candidate based on uid - uid = self.candidate_uid - candidate = db[uid] - # capture data from fields - candidate.pscid = self.text_pscid.get() - candidate.status = self.text_status.get() - candidate.firstname = self.text_firstname.get() - candidate.lastname = self.text_lastname.get() - candidate.phone = self.text_phone.get() - # save data - DataManagement.save_candidate_data(db) \ No newline at end of file From 46bd4081a887f3850684352269dc203996a48820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Thu, 23 Jun 2016 09:02:15 -0400 Subject: [PATCH 38/89] Removed the DICOM anonymizer menu --- dicat/ui/menubar.py | 91 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 dicat/ui/menubar.py diff --git a/dicat/ui/menubar.py b/dicat/ui/menubar.py new file mode 100644 index 0000000..ff3f5e3 --- /dev/null +++ b/dicat/ui/menubar.py @@ -0,0 +1,91 @@ +#import standard packages +import Tkinter +#import internal packages +import lib.multilanguage as MultiLanguage +import ui.datawindow as DataWindow + +class MenuBar(Tkinter.Menu): + def __init__(self, parent): + Tkinter.Menu.__init__(self, parent) + # create an APPLICATION pulldown menu + application_menu = Tkinter.Menu(self, tearoff=False) + self.add_cascade(label=MultiLanguage.application_menu,underline=0, menu=application_menu) + application_menu.add_command(label=MultiLanguage.application_setting, underline=1, command=self.app_settings) + application_menu.add_separator() + application_menu.add_command(label=MultiLanguage.application_quit, underline=1, command=self.quit_application) + # create a CANDIDATE pulldown menu + candidate_menu = Tkinter.Menu(self, tearoff=False) + self.add_cascade(label=MultiLanguage.candidate_menu, underline=0, menu=candidate_menu) + candidate_menu.add_command(label=MultiLanguage.candidate_add, command=self.add_candidate) + candidate_menu.add_command(label=MultiLanguage.candidate_find, command=self.find_candidate) + candidate_menu.add_command(label=MultiLanguage.candidate_update, command=self.update_candidate) + candidate_menu.add_separator() + candidate_menu.add_command(label=MultiLanguage.candidate_get_id, command=self.get_candidate_id) + candidate_menu.add_separator() + candidate_menu.add_command(label=MultiLanguage.candidate_exclude_include_toggle, command=self.exclude_candidate) + # create a CALENDAR pulldown menu + calendar_menu = Tkinter.Menu(self, tearoff=False) + self.add_cascade(label=MultiLanguage.calendar_menu, underline=0, menu=calendar_menu) + calendar_menu.add_command(label=MultiLanguage.calendar_new_appointment, command=self.open_calendar) + # create a HELP pulldown menu + help_menu = Tkinter.Menu(self, tearoff=0) + self.add_cascade(label=MultiLanguage.help_menu, underline=0, menu=help_menu) + help_menu.add_command(label=MultiLanguage.help_get_help, command=self.open_help) + help_menu.add_command(label=MultiLanguage.help_about_window, command=self.about_application) + + def app_settings(self): + #TODO implement app_settings() + print 'running appsettings' + pass + + def quit_application(self): + #TODO implement quit_application() + print 'running quit_application' + self.quit() + pass + + def open_calendar(self): + #TODO implement open_calendar() + print 'running open_calendar' + pass + + def dicom_anonymizer(self): + #TODO implement dicom_anonymizer() + print 'running dicom anonymizer' + pass + + def add_candidate(self): + #TODO implement add_candidate() + DataWindow.DataWindow(self, "new") + print 'running add_candidate' + pass + + def find_candidate(self): + #TODO implement find_candidate() + print 'running find_candidate' + pass + + def update_candidate(self): + #TODO implement update_candidate() + print 'running update_candidate' + pass + + def get_candidate_id(self): + #TODO get_candidate_id() + print 'running get_candidate_id' + pass + + def exclude_candidate(self): #need renaming + #TODO exclude_candidate() + print 'running incativate_candidate' + pass + + def open_help(self): + #TODO open_help() + print 'running open_help' + pass + + def about_application(self): + #TODO about_application() + print 'running about_application' + pass \ No newline at end of file From 72a79935e5ee66403acf8a65f26f9c1ee465bd33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Fri, 24 Jun 2016 07:57:38 -0400 Subject: [PATCH 39/89] Moved the project info pane into the window pane of the scheduler instead. --- dicat/IDMapper.py | 4 ++- dicat/scheduler_application.py | 59 ++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 dicat/scheduler_application.py diff --git a/dicat/IDMapper.py b/dicat/IDMapper.py index 4748869..c7e314f 100644 --- a/dicat/IDMapper.py +++ b/dicat/IDMapper.py @@ -4,6 +4,8 @@ from Tkinter import * from xml.dom import minidom +import lib.datamanagement as DataManagement + import ttk @@ -183,7 +185,7 @@ def LoadXML(self, file): """Parses the XML file and loads the data into the current window""" try: - xmldoc = minidom.parse(file) + xmldoc = DataManagement.read_xmlfile(file) xmlitemlist = xmldoc.getElementsByTagName('Candidate') for s in xmlitemlist: identifier = s.getElementsByTagName("Identifier")[0].firstChild.nodeValue diff --git a/dicat/scheduler_application.py b/dicat/scheduler_application.py new file mode 100644 index 0000000..9033e92 --- /dev/null +++ b/dicat/scheduler_application.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python + +#import standard packages +from Tkinter import * +from ttk import * +#import internal packages +import ui.menubar as MenuBar +import ui.datatable as DataTable +import lib.multilanguage as MultiLanguage + + +class UserInterface(Frame): + + def __init__(self, parent): + Frame.__init__(self) + self.parent = parent + #self.parent.title(MultiLanguage.app_title) + self.initialize() + + def initialize(self): + + # initialize frame + self.frame = Frame(self.parent) + self.frame.pack(side=TOP, expand=YES, fill=BOTH, padx=10, pady=10) + + # TODO create class for project info pane + # This area (datapane) is composed of one Panedwindow containing two Labelframe + self.data_pane = Panedwindow(self.frame, width=1000, height=500, orient=HORIZONTAL) # TODO add dynamic resize + self.data_pane.pack(side=RIGHT, expand=YES, fill=BOTH) + self.project_infopane = Labelframe(self.data_pane, text=MultiLanguage.project_info_pane, width=250, height=350, + borderwidth=10) # TODO add dynamic resize + self.candidate_pane = Labelframe(self.data_pane, text=MultiLanguage.candidate_pane, width=100, height=450, + borderwidth=10) # TODO add dynamic resize + self.visit_pane = Labelframe(self.data_pane, text=MultiLanguage.calendar_pane, width=100, height=350, + borderwidth=10) # TODO add dynamic resize + self.data_pane.add(self.project_infopane) + self.data_pane.add(self.candidate_pane) + self.data_pane.add(self.visit_pane) + + # create data tables (treeview) + visit_column_headers = ('candidate', 'visitlabel', 'when', 'where', 'status') + self.visit_table = DataTable.VisitList(self.visit_pane, visit_column_headers) + self.visit_table.pack(side=BOTTOM, expand=YES, fill=BOTH) + column_header = ('firstname', 'lastname', 'phone', 'status') + self.data_table = DataTable.ParticipantsList(self.candidate_pane, column_header) + self.data_table.pack(side=BOTTOM, expand=YES, fill=BOTH) + + """ + #create a filter section in each data_pane(not implemented yet) + #TODO This whole section needs to be replaced by REAL CODE actively filtering the data + self.filter_candidate = Labelframe(self.candidate_pane, text='Filters', width=220, height=50, borderwidth=10) + self.filter_candidate.pack(side=TOP, expand=NO, fill=BOTH, pady=5) + self.filter_candidate_label = Label(self.filter_candidate, text='Filter for Non-Active / Active / Excluded / Group...') + self.filter_candidate_label.pack(side=TOP, expand=NO, fill=BOTH) + self.filter_visit = Labelframe(self.visit_pane, text='Filters', width=220, height=50, borderwidth=10) + self.filter_visit.pack(side=TOP, expand=NO, fill=BOTH, pady=5) + self.filter_candidate_label = Label(self.filter_visit, text='Filters for Active / Tentative / Closed ...') + self.filter_candidate_label.pack(side=TOP, expand=NO, fill=BOTH) + """ From 0c7e4bc64433d91cbef9fa6117ebaf04f77dd5ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Fri, 24 Jun 2016 09:32:24 -0400 Subject: [PATCH 40/89] Added the scheduler scripts to github. --- dicat/ui/__init__.py | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 dicat/ui/__init__.py diff --git a/dicat/ui/__init__.py b/dicat/ui/__init__.py new file mode 100644 index 0000000..5c9d53e --- /dev/null +++ b/dicat/ui/__init__.py @@ -0,0 +1,8 @@ +""" +From the documentation at https://docs.python.org/2/tutorial/modules.html#packages + +The __init__.py files are required to make Python treat the directories as containing packages; this is done to prevent +directories with a common name, such as string, from unintentionally hiding valid modules that occur later on the module +search path. In the simplest case, __init__.py can just be an empty file, but it can also execute initialization code +for the package or set the __all__ variable, described later. +""" From b4df5845bb8b5532fcb833da763cc07690bf84f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Fri, 24 Jun 2016 09:40:05 -0400 Subject: [PATCH 41/89] Push the rest of DICAT scripts --- dicat/DicAT_application.py | 2 +- dicat/IDMapper.py | 4 +- dicat/lib/classtool.py | 20 +++ dicat/lib/datamanagement.py | 64 ++++++++++ dicat/lib/multilanguage.py | 222 +++++++++++++++++++++++++++++++++ dicat/lib/utilities.py | 122 ++++++++++++++++++ dicat/scheduler_application.py | 50 +++++--- dicat/scheduler_candidate.py | 196 +++++++++++++++++++++++++++++ dicat/scheduler_visit.py | 93 ++++++++++++++ dicat/ui/datatable.py | 170 +++++++++++++++++++++++++ dicat/ui/datawindow.py | 213 +++++++++++++++++++++++++++++++ dicat/ui/dialogbox.py | 78 ++++++++++++ dicat/ui/menubar.py | 2 +- dicat/ui/newcandidatewindow.py | 74 +++++++++++ dicat/ui/projectpane.py | 6 + 15 files changed, 1298 insertions(+), 18 deletions(-) create mode 100644 dicat/lib/classtool.py create mode 100644 dicat/lib/datamanagement.py create mode 100644 dicat/lib/multilanguage.py create mode 100644 dicat/lib/utilities.py create mode 100644 dicat/scheduler_candidate.py create mode 100644 dicat/scheduler_visit.py create mode 100644 dicat/ui/datatable.py create mode 100644 dicat/ui/datawindow.py create mode 100644 dicat/ui/dialogbox.py create mode 100644 dicat/ui/newcandidatewindow.py create mode 100644 dicat/ui/projectpane.py diff --git a/dicat/DicAT_application.py b/dicat/DicAT_application.py index 57d9cfc..a7b38fe 100644 --- a/dicat/DicAT_application.py +++ b/dicat/DicAT_application.py @@ -72,7 +72,7 @@ def welcome_page(self): def scheduler_page(self, master): # initialize the menu bar and start the scheduler frame - menu = MenuBar.MenuBar(master) + menu = MenuBar.SchedulerMenuBar(master) master.config(menu=menu) UserInterface(self.page3) diff --git a/dicat/IDMapper.py b/dicat/IDMapper.py index c7e314f..bc62864 100644 --- a/dicat/IDMapper.py +++ b/dicat/IDMapper.py @@ -2,7 +2,6 @@ import Tkinter, Tkconstants, tkFileDialog, tkMessageBox, re, datetime from Tkinter import * -from xml.dom import minidom import lib.datamanagement as DataManagement @@ -186,7 +185,8 @@ def LoadXML(self, file): """Parses the XML file and loads the data into the current window""" try: xmldoc = DataManagement.read_xmlfile(file) - xmlitemlist = xmldoc.getElementsByTagName('Candidate') + xmldata = xmldoc.getElementsByTagName('data') + xmlitemlist = xmldata.getElementsByTagName('Candidate') for s in xmlitemlist: identifier = s.getElementsByTagName("Identifier")[0].firstChild.nodeValue realname = s.getElementsByTagName("RealName")[0].firstChild.nodeValue diff --git a/dicat/lib/classtool.py b/dicat/lib/classtool.py new file mode 100644 index 0000000..3a3083e --- /dev/null +++ b/dicat/lib/classtool.py @@ -0,0 +1,20 @@ +class ClassTool(): + """ + Not used! Useful for development as inheritable class MyClass(ClassTool). + """ + #no constructor + def __repr__(self): + attributes =[] + for key in sorted(self.__dict__): + attributes.append('%s=%s' %(key, getattr(self, key))) + return ', '.join(attributes) + + def gatherattributes(self): + attributes =[] + for key in sorted(self.__dict__): + attributes.append('%s' %(key)) + return ', '.join(attributes) + + def getframesize(self): + print str(self.winfo_width()) + diff --git a/dicat/lib/datamanagement.py b/dicat/lib/datamanagement.py new file mode 100644 index 0000000..82c630b --- /dev/null +++ b/dicat/lib/datamanagement.py @@ -0,0 +1,64 @@ +#imports from standard packages +import os.path +import pickle +from xml.dom import minidom +""" +The data_management.py file contains functions related to data management only. +Generic functions: savedata(data, datafilename) and readdata(datafile). Currently, these are not being used. + +Specific functions read_candidate_data(), save_candidate_data(), read_studydata() and save_study_data() are used to get/save candidate data and study setup data respectively. +""" + +def read_xmlfile(xmlfile): + + """Parses the XML file and loads the data into the current window""" + try: + xmldoc = minidom.parse(xmlfile) + return xmldoc + + except: + message = "ERROR: could not read file " + xmlfile + print message #TODO: create a log class to display the messages + + +def read_candidate_data(): + """Read and return the content of a file called candidatedata. Returns nothing if file doesn't exist""" + + #check to see if file exists before loading it + if os.path.isfile("candidatedata"): + #load file + db = pickle.load(open("candidatedata", "rb")) + else: + db = "" + return db + + +def save_candidate_data(data): + """Save data in a pickle file named candididatedata. + Will overwrite any existing file. Will create one if it doesn't exist""" + pickle.dump(data, open("candidatedata", "wb")) + + +def read_studydata(): + """Read and return the content of a file called studydata. Returns nothing if file doesn't exist""" + #check to see if file exists before loading it + if os.path.isfile("studydata"): + #load file + db = pickle.load(open("studydata", "rb")) + else: + db = "" + return db + + +def save_study_data(data): + """Save data in a pickle file named studydata. + Will overwrite any existing file. Will create one if it doesn't exist""" + pickle.dump(data, open("studydata", "wb")) + + + +#self-test "module" TODO remove +if __name__ == '__main__': + print 'testing module: datamanagement.py' + data=dict(read_candidate_data()); + print data; diff --git a/dicat/lib/multilanguage.py b/dicat/lib/multilanguage.py new file mode 100644 index 0000000..1fc2b5d --- /dev/null +++ b/dicat/lib/multilanguage.py @@ -0,0 +1,222 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +#read language preference from appdata file +#from utilities import readappdata #TODO replace and remove +#language = readappdata()[0] + +language = "en" #TODO make dynamic + +if language == "fr": + ###################### TOP LEVEL MENU BAR ###################### + app_title = u"Outils LORIS" + #APPLICATION menu + application_menu = u"Application" + application_setting = u"Préferences" + application_quit = u"Quitter" + #PROJECT menu + #menuproject = u"Projet" #TODO remove? + #openproject = u"Ouvrir un projet" + #modifyproject = u"Modifier le projet ouvert" + #newproject = u"Créer un nouveau projet" + #CANDIDATE menu + candidate_menu = u"Candidat" + candidate_add = u"Nouveau candidat" + candidate_find = u"Trouver candidat" + candidate_update = u"Mettre à jour" + candidate_exclude_include_toggle = u"Inclure/Exclure un candidat" + candidate_get_id = u"Obtenir l'identifiant d'un candidat" + #clear_all_field = u"Effacer" + #ANONYMIZER menu + anonymizer_menu = u"DICOM" + anonymizer_run = u"Anonymizer" + #CALENDAR menu + calendar_menu = u"Calendrier" + calendar_new_appointment = u"Nouveau Rendez-vous" + #HELP menu + help_menu = u"Aide" + help_get_help = u"Obtenir de l'aide" + help_about_window = u"A propos de ..." + ###################### PROJECT INFO PANE ####################### + project_info_pane = u"Projet" + project_detail_pane = u"Détails du Projet" + visit_detail_pane = u"Détails des Visites" + project_name = u"Projet" + project_start = u"Début" + project_end = u"Fin" + target_recruitment = u"Cible de recrutement" + current_recruitment = u"Recrutement actuel" + total_visit = u"Nombre de Visites" + #################### MULTI-TAB DATA SECTION ##################### + calendar_pane = u"Calendrier" + candidate_pane = u"Candidats" + + label_candidate_table = u"Faites un double-clic sur l'une des lignes pour remplir les champs ci-dessus" + datatable_id = u"ID" + datatable_firstname = u"Prénom" + datatable_lastname = "Nom" + datatable_dob = u"Date de Naissance" + datatable_phone = u"Téléphone" + datatable_address = u"Adresse" + datatable_city = u"Ville" + datatable_province = u"Province" + datatable_country = u"Pays" + datatable_postal_code = u"Code Postal" + + calendar_monday = u"Lundi" + calendar_tuesday = u"Mardi" + calendar_wednesday = u"Mercredi" + calendar_thursday = u"Jeudi" + calendar_friday = u"Vendredi" + calendar_saturday = u"Samedi" + calendar_sunday = u"Dimanche" + calendar_january = u"Janvier" + calendar_february = u"Février" + calendar_march = u"Mars" + calendar_april = u"Avril" + calendar_may = u"Mai" + calendar_june = u"Juin" + calendar_jully = u"Juillet" + calendar_august = u"Août" + calendar_september = u"Septembre" + calendar_october = u"Octobre" + calendar_november = u"Novembre" + calendar_december = u"Décembre" + + ################ COLUMN HEADER ################## + col_candidate = u"Candidat" + col_visitlabel = u"Visite" + col_when = u"Date/Heure" + col_where = u"Endroit" + col_status = u"Statut" + col_withwhom = u"Avec qui" + #################### STATUS ##################### + status_active = u"actif" + status_tentative = u"provisoire" + ################# DATA WINDOWS ################## + data_window_title = u"DATA WINDOW" #TODO trouver un titre français + ################## DIALOGBOX #################### + # very not sure what to do about that section + dialog_yes = u"Oui" + dialog_no = u"Non" + dialog_title_confirm = u"Veuillez confirmer!" + dialog_close = u"Vous êtes sur le point de fermer cette fenêtre sans sauvegarder!\n\nVoulez-vous continuer?" + ################ DATA WINDOW ################### + schedule_pane = u"Calendrier" + candidate_pane = u"Candidat" + candidate_firstname = u"Prénom" + candidate_lastname = u"Nom de famille" + candidate_phone = u"Téléphone" + candidate_pscid = u"ID" + candidate_status = u"Status" + schedule_visit_label = u"Visite" + schedule_visit_rank = u"#" + schedule_visit_status = u"Status" + schedule_visit_when = u"Date" + schedule_optional =u"Optionnel" + +elif language == "en": + app_title = u"LORIS tools" + #APPLICATION menu + application_menu = u"Application" + application_setting = u"Preferences" + application_quit = u"Quit" + #PROJECT menu + #menuproject = u"Project" #TODO remove? + #openproject = u"Open project" + #modifyproject = u"Modify open project" + #newproject = u"New project" + #CANDIDATE menu + candidate_menu = u"Candidate" + candidate_add = u"New candidate" + candidate_find = u"Find a candidate" + candidate_update = u"Update" + candidate_exclude_include_toggle = u"Include/Exclude a candidate" + candidate_get_id = u"Get a canditate ID" + #clear_all_field = u"Clear" + #CALENDAR menu + calendar_menu = u"Calendar" + calendar_new_appointment = u"New appointment" + #ANONYMIZER menu + anonymizer_menu = u"DICOM" + anonymizer_run = u"Anonymizer" + #HELP menu + help_menu = u"Help" + help_get_help = u"Get some help" + help_about_window = u"About this..." + ###################### PROJECT INFO PANE ####################### + project_info_pane = u"Project Information" + project_detail_pane = u"Project Details" + visit_detail_pane = u"Visit Details" + project_name = u"Project" + project_start = u"Start" + project_end = u"End" + target_recruitment = u"Recruitment target" + current_recruitment = u"Current recruitment" + total_visit = u"Total number of Visits" + #################### MULTI-TAB DATA SECTION ##################### + calendar_pane = u"Calendar" + candidate_pane = u"Candidates" + label_candidate_table = u"Double click on row to populate fields above" + datatable_id = u"ID" + datatable_firstname = u"First Name" + datatable_lastname = u"Last Name" + datatable_dob = u"Date of Birth" + datatable_phone = u"Phone" + datatable_address = u"Address" + datatable_city = u"City" + datatable_province = u"Province" + datatable_country = u"Country" + datatable_postal_code = u"Postal Code" + + calendar_monday = u"Monday" + calendar_tuesday = u"Tuesday" + calendar_wednesday = u"Wednesday" + calendar_thursday = u"Thursday" + calendar_friday = u"Friday" + calendar_saturday = u"Saturday" + calendar_sunday = u"Sunday" + calendar_january = u"January" + calendar_february = u"February" + calendar_march = u"Marc" + calendar_april = u"April" + calendar_may = u"May" + calendar_june = u"June" + calendar_jully = u"Jully" + calendar_august = u"August" + calendar_september = u"September" + calendar_october = u"October" + calendar_november = u"November" + calendar_december = u"December" + + ################ COLUMN HEADER ################## + col_candidate = u"Candidate" + col_visitlabel = u"Visit" + col_when = u"Date/Time" + col_where = u"Place" + col_status = u"Status" + col_withwhom = u"Whom" + #################### STATUS ##################### + status_active = u"active" + status_tentative = u"tentative" + ################# DATA WINDOWS ################## + data_window_title =u"Data Window" + ################## DIALOGBOX #################### + # very not sure what to do about that section + dialog_yes = u"Yes" + dialog_no = u"No" + dialog_title_confirm = u"Please confirm!" + dialog_close = u"You are about to close this window without saving! \n\nDo you want to continue?" + ################ DATA WINDOW ################### + schedule_pane = u"Calendar" + candidate_pane = u"Candidate" + candidate_firstname = u"Firstname" + candidate_lastname = u"Lastname" + candidate_phone = u"Phone" + candidate_pscid = u"ID" + candidate_status = u"Status" + schedule_visit_label = u"Visit" + schedule_visit_rank = u"#" + schedule_visit_status = u"Status" + schedule_visit_when = u"Date" + schedule_optional =u"Optional" \ No newline at end of file diff --git a/dicat/lib/utilities.py b/dicat/lib/utilities.py new file mode 100644 index 0000000..3e7a979 --- /dev/null +++ b/dicat/lib/utilities.py @@ -0,0 +1,122 @@ +#imports from standard packages +from uuid import uuid1 +import time + +""" +This file contains utility functions used throughout the application +""" + +def generate_uid(): + """ + will generate a random UUID. + see python documentation https://docs.python.org/2/library/uuid.html + """ + ui = str(uuid1()) + return ui + +def is_unique(data, dataset): + """ + will verify if 'data' passed as argument is unique in a dataset + """ + seen = set() + return not any(value in seen or seen.add(value) for value in data) + + +def describe(something): + """ + this will return the object(something) class name and type as well as all attributes excluding those starting with a double underscore + """ + #will return the object type and class (Type:Class) as text + objectclass = str(something.__class__.__name__) + objecttype = str(type(something)) + #will return a list of all non"__" attributes, including use defined ones (**kwargs) + attributes = str(filter(lambda a: not a.startswith('__'), dir(something))) + returnvalue = objectclass, " (", objecttype, "): ", attributes + print returnvalue + +def get_current_date(): + """ + will return today's date as a string of format yyyymmdd + """ + return time.strftime("%Y%m%d") + +def get_current_time(option): + """ + will return current time as a string of format hhmmss + """ + if option == 1: + return time.strftime("%H%M%S") + elif option == 2: + return time.strftime("%H:%M:%S") + else: + pass + + +def error_log(message): + # append/save timestamp and exception to errorlog.txt + # object Exception e is sent as is and this method is taking care of parsing it to string + # and adding a timestamp + console = 1 #TODO use this value to send error message to the console instead of the file + if console == 0: + timestamp = time.strftime("%c") # date and time representation using this format: 11/07/14 12:50:35 + fullmessage = timestamp + ": " + str(message) + "\n" + anyfile = open("errorlog.txt", "a") #this is required since a file object is later expected + anyfile.write(fullmessage) + anyfile.close() + elif console == 1: + print message + "\n" #TODO remove when done + + + +def center_window(win): + win.update_idletasks() + width = win.winfo_width() + height = win.winfo_height() + x = (win.winfo_screenwidth() // 2) - (width // 2) + y = (win.winfo_screenheight() // 2) - (height // 2)-200 + win.geometry('{}x{}+{}+{}'.format(width, height, x, y)) + + + + + +######################################################################################## +def gather_attributes(something): + """ + Receive an object and return an array containing the object attributes and value + """ + attributes =[] + for key in sorted(something.__dict__): + attributes.append('%s' %(key)) + return attributes + + +def search_uid(db, value): + return filter(lambda candidate: candidate['uid'] == value, db) + + +def print_object(something): + #for dev only! + #will print key, attributes, value in the console. + #most likely will be an dict or a class instance. + #so this is for the dict + if type(something) is dict: + for key, value in something.iteritems(): + c = something.get(key) + for attr, value in c.__dict__.iteritems(): + print attr,": ", value + print "\n" + elif type(something) is list: + for item in something: + print item + else: #and this for the class instance + for attr, value in something.__dict__.iteritems(): + print attr, value + print "\n\n" + + +# self-test "module" TODO remove before release +if __name__ == '__main__': + import lib.datamanagement as DataManagement + data=dict(DataManagement.read_studydata()) + print_object(data) \ No newline at end of file diff --git a/dicat/scheduler_application.py b/dicat/scheduler_application.py index 9033e92..a82a192 100644 --- a/dicat/scheduler_application.py +++ b/dicat/scheduler_application.py @@ -4,7 +4,6 @@ from Tkinter import * from ttk import * #import internal packages -import ui.menubar as MenuBar import ui.datatable as DataTable import lib.multilanguage as MultiLanguage @@ -23,26 +22,49 @@ def initialize(self): self.frame = Frame(self.parent) self.frame.pack(side=TOP, expand=YES, fill=BOTH, padx=10, pady=10) - # TODO create class for project info pane - # This area (datapane) is composed of one Panedwindow containing two Labelframe - self.data_pane = Panedwindow(self.frame, width=1000, height=500, orient=HORIZONTAL) # TODO add dynamic resize + xmlfile = "new_data_test.xml" + + # This area (datapane) is one Panedwindow containing 3 Labelframes + self.data_pane = Panedwindow( self.frame, width=1000, + height=500, orient=HORIZONTAL + ) # TODO add dynamic resize self.data_pane.pack(side=RIGHT, expand=YES, fill=BOTH) - self.project_infopane = Labelframe(self.data_pane, text=MultiLanguage.project_info_pane, width=250, height=350, - borderwidth=10) # TODO add dynamic resize - self.candidate_pane = Labelframe(self.data_pane, text=MultiLanguage.candidate_pane, width=100, height=450, - borderwidth=10) # TODO add dynamic resize - self.visit_pane = Labelframe(self.data_pane, text=MultiLanguage.calendar_pane, width=100, height=350, - borderwidth=10) # TODO add dynamic resize + # TODO create class for project info pane + self.project_infopane = Labelframe(self.data_pane, + text=MultiLanguage.project_info_pane, + width=250, + height=350, + borderwidth=10 + ) # TODO add dynamic resize + self.candidate_pane = Labelframe( self.data_pane, + text=MultiLanguage.candidate_pane, + width=100, + height=450, + borderwidth=10 + ) # TODO add dynamic resize + self.visit_pane = Labelframe( self.data_pane, + text=MultiLanguage.calendar_pane, + width=100, + height=350, + borderwidth=10 + ) # TODO add dynamic resize self.data_pane.add(self.project_infopane) self.data_pane.add(self.candidate_pane) self.data_pane.add(self.visit_pane) # create data tables (treeview) - visit_column_headers = ('candidate', 'visitlabel', 'when', 'where', 'status') - self.visit_table = DataTable.VisitList(self.visit_pane, visit_column_headers) + visit_column_headers = ( 'candidate', 'visitlabel', + 'when', 'where', + 'status' + ) + self.visit_table = DataTable.VisitList( self.visit_pane, + visit_column_headers + ) self.visit_table.pack(side=BOTTOM, expand=YES, fill=BOTH) - column_header = ('firstname', 'lastname', 'phone', 'status') - self.data_table = DataTable.ParticipantsList(self.candidate_pane, column_header) + candidate_column_headers = ('firstname', 'lastname', 'phone', 'status') + self.data_table = DataTable.ParticipantsList( self.candidate_pane, + candidate_column_headers + ) self.data_table.pack(side=BOTTOM, expand=YES, fill=BOTH) """ diff --git a/dicat/scheduler_candidate.py b/dicat/scheduler_candidate.py new file mode 100644 index 0000000..631c7ed --- /dev/null +++ b/dicat/scheduler_candidate.py @@ -0,0 +1,196 @@ +# import standard packages +import datetime + +from scheduler import visit + +# import internal packages +import lib.datamanagement as DataManagement +import lib.multilanguage as MultiLanguage +import lib.utilities as Utilities + + +class Candidate: + """ + The Candidate class defines the candidates/participants of the study + + Attributes: + uid: A unique identifier using python's uuid1 method. Used as key to store and retrieve objects from + files and/or dictionaries. + firstname: First name of the candidate + lastname: Last name of the candidate + visitset: A dictionnary containing all visits (planed or not) + phone: A primary phone number + status: Status of this candidate + pscid: Loris (clinical results database) specific ID + + kwargs: Not implemented yet! + + Code example: + candidatedb = {} #setup a dict to receive the candidates + candidatedata = candidate.Candidate('Billy', 'Roberts', '451-784-9856', otherphone='514-874-9658') #instanciate one candidate + candidatedb[candidatedata.uid] = candidatedata #add candidate to dict + DataManagement.save_candidate_data(candidatedb) #save data to file + """ + def __init__(self, firstname, lastname, phone, uid=None, visitset = None, status = None, pscid=None, **kwargs): #TODO *kwarg + self.uid = Utilities.generate_uid() + self.firstname = firstname + self.lastname = lastname + self.visitset = visitset + self.phone = phone + self.status = status + self.pscid = pscid + #...many other attributes + if kwargs is not None: + for key, value in kwargs.iteritems(): + setattr(self, key, value) + + def setup_visitset(self): + """ + When creating the first visit for a candidate, a complete set of visits is added based on a study/project visit list + There are no parameters passed to this method since it will simply create a new 'empty' + This method will: + 1-open studydata (the study visit list) and 'parse' the dict to a sorted list (dict cannot be sorted) + 2-create a temporary list of visit_labels that will serve as a key within the Candidate.visit_set dictionary + 3-instantiate individual visits based on the study visit list + Usage: + Called by Candidate.set_visit_date() + """ + visit_list =[] + visit_label_templist = [] + study_setup = dict() + try: + #1-open studydata + study_setup = dict(DataManagement.read_studydata()) + except Exception as e: + print str(e) #TODO add error login (in case a study data file does not exist) + for key, value in study_setup.iteritems(): + #2-parse into a sorted list + visit_list.append(study_setup[key]) + visit_list = sorted(visit_list, key=lambda visit: visit.rank) + #create a temporary list of visitlabels + for each in visit_list: + visit_label_templist.append(each.visitlabel) + #3-instantiate individual visit based on each instance of VisitSetup() + self.visitset = {} #setup a dict to receive a set of Visit() + count = 0 + #set values of : uid, rank, visit_label, previous_visit, visit_window, visitmargin, + for key in visit_list: + mainkey = str(visit_label_templist[count]) + rank =key.rank + visit_label = key.visitlabel + previous_visit = key.previousvisit + visit_window = key.visitwindow + visit_margin = key.visitmargin + visit_data = visit.Visit(rank, visit_label, previous_visit, visit_window, visit_margin,) + self.visitset[mainkey] = visit_data + count += 1 + + def set_visit_date(self, visitlabel, visitdate, visittime, visitwhere, visitwhom): + """ + This method update the visit information (according to visitlabel) + This method will: + 1-Check if visitset==None. If so, then Candidate.setup_visitset() is called to setup Candidate.visitset + 2-Check if a date is already set for this visitlabel + 3-Set values of Visit.when, 'Visit.where, Visit.whithwhom and Visit.status for current visit + Usage: Called by GUI methods + Return: current_visit as current Visit(Visit(VisitSetup) instance + """ + #1-Check to see if visitset == None before trying to create a new date instance + if self.visitset is None: + self.setup_visitset() + self.set_candidate_status_active() #Candidate.status='active' since we're setting up a first visit + #get current visit within visitset + current_visit = self.visitset.get(visitlabel) + #2-Check to see if this visit already has a date + if current_visit.when is not None: + print "date already exists" #TODO add confirmation of change log??? + pass + #concatenate visitdate and visittime and parse into a datetime object + visitwhen = visitdate + ' ' + visittime + when = datetime.datetime.strptime(visitwhen, '%Y-%m-%d %H:%M') + #3-Set values of Visit.when, 'Visit.where, Visit.whithwhom and Visit.status for current visit + current_visit.when = when + current_visit.where = visitwhere + current_visit.withwhom = visitwhom + current_visit.status = "active" #that is the status of the visit + return current_visit + + + """ + def set_next_visit_window(self, candidate, current_visit): + #get the current visit object as argument. Will search and look for the next visit (visit where previousvisit == current_visit_label) + next_visit_searchset = candidate.visitset + current_visit_label = current_visit.visitlabel + next_visit = "" + for key in next_visit_searchset: + visit_data = next_visit_searchset[key] + if visit_data.previousvisit == current_visit_label: + next_visit = candidate.visitset.get(visit_data.visitlabel) #TODO debug when current is last visit + #gather info about current_visit (mostly for my own biological computer! else I get lost) + current_visit_date = current_visit.when + current_visit_year = current_visit_date.year #get the year of the current visit date + next_visit_window = next_visit.visitwindow + next_visit_margin = next_visit.visitmargin + #set dates for the next visit + next_visit_early = int(current_visit_date.strftime('%j')) + (next_visit_window - next_visit_margin) #this properly handle change of year + next_visit_early_date = datetime.datetime(current_visit_year, 1, 1) + datetime.timedelta(next_visit_early - 1) + next_visit_late = int(current_visit_date.strftime('%j')) + (next_visit_window + next_visit_margin) + next_visit_late_date = datetime.datetime(current_visit_year, 1, 1) + datetime.timedelta(next_visit_late - 1) + next_visit.when_earliest = next_visit_early_date + next_visit.when_latest = next_visit_late_date + next_visit.status = "tentative" + Utilities.print_object((next_visit)) + """ + def set_next_visit_window(self, candidate, current_visit): + """ + This method will 'calculate' a min and max date when the next visit should occur + 1-Get candidate.visitset (as visit_searchset) and current_visit.visitlabel + 2-Identify which visit (in visitset) has previousvisit == current_visit.visitlabel + 3-Get + Usage: Currently called by GUI function (TODO RETHINK THIS LOGIC maybe it should be called when running Candidate.set_visit_date()) + """ + + + #get the current visit object as argument. Will search and look for the next visit (visit where previousvisit == current_visitlabel) + + #1- Get Candidate.visitset and current_visit / next_visit will == Visit(VisitSetup) of the next visit (relative to current_visit) + visit_searchset = candidate.visitset + current_visitlabel = current_visit.visitlabel + next_visit = "" + #2-Identify which visit (in visitset) has previousvisit == current_visit.visitlabel + for key in visit_searchset: + visit_data = visit_searchset[key] + if visit_data.previousvisit == current_visitlabel: + next_visit = candidate.visitset.get(visit_data.visitlabel) #TODO debug when current is last visit + #3-Calculate a min and max date for the next visit to occur based on Visit.visitwindow and Visit.visitmargin + current_visitdate = current_visit.when + current_visityear = current_visitdate.year #get the year of the current visit date + next_visitwindow = next_visit.visitwindow + nextvisitmargin = next_visit.visitmargin + #set dates for the next visit + nextvisitearly = int(current_visitdate.strftime('%j')) + (next_visitwindow - nextvisitmargin) #this properly handle change of year + nextvisitearlydate = datetime.datetime(current_visityear, 1, 1) + datetime.timedelta(nextvisitearly - 1) + nextvisitlate = int(current_visitdate.strftime('%j')) + (next_visitwindow + nextvisitmargin) + nextvisitlatedate = datetime.datetime(current_visityear, 1, 1) + datetime.timedelta(nextvisitlate - 1) + next_visit.whenearliest = nextvisitearlydate + next_visit.whenlatest = nextvisitlatedate + next_visit.status = "tentative" #set this visit.status + + def get_active_visit(self, candidate): + candidatefullname = str(candidate.firstname + ' ' + candidate.lastname) + currentvisitset = candidate.visitset + activevisit = [] + if currentvisitset is None: + return + elif currentvisitset is not None: + for key in currentvisitset: + if currentvisitset[key].status == MultiLanguage.status_active: + visitlabel = currentvisitset[key].visitlabel + when = currentvisitset[key].when.strftime('%Y-%m-%d %Hh%m') + where = currentvisitset[key].where + who = currentvisitset[key].withwhom + activevisit = [candidatefullname, visitlabel, when, where, who] + return activevisit + + def set_candidate_status_active(self): + self.status = MultiLanguage.status_active #set the Candidate.status to 'active' \ No newline at end of file diff --git a/dicat/scheduler_visit.py b/dicat/scheduler_visit.py new file mode 100644 index 0000000..c9644ce --- /dev/null +++ b/dicat/scheduler_visit.py @@ -0,0 +1,93 @@ +#import standard packages +#import internal packages +import datetime + +import lib.utilities as utilities + + +class VisitSetup(): + """ + The VisitSetup() class is used to define a study (or project) in terms of sequence of visits and also serves as a + base class for Visit(VisitSetup). A study (or project) can have as many visits as required. There is no class for + study as it is merely a simple dictionnary. + + Code example: This study contains 3 visits. Since V0 is the first visit, it doesn't have any values for + 'previousvisit', 'visitwindow' and ' visitmargin' + study = {} + visit = visit.VisitSetup(1, 'V0') #a uid (uuid1) is automatically generated + study[visit.uid] = visit #VisitSetup.uid is a unique ID used as key + visit = visit.VisitSetup(2, 'V1', 'V0', 10, 2) + study[visit.uid] = visit + visit = visit.VisitSetup(3, 'V2', 'V1', 20, 10) + study[visit.uid] = visit + + This is the parent class of Visit(VisitSetup), furthermore Visit(VisitSetup) objects will be 'instanciated' from + each instance of VisitSetup(object). + Both VisitSetup(object) class and instances are used to create individual instances of Visit(VisitSetup) + + Attributes: + uid: A unique identifier using python's uuid1 method. Used as key to store and retrieve objects from + files and/or dictionaries. + rank: The rank of the visit in the sequence (int). Useful to sort the visits in order of occurrence. + visitlabel: The label of the visit (string) such as V1, V2 or anything else the user may come up with + previousvisit: The visitlabel (string) of the visit occurring before this one. Used to plan this visit based on + the date of the previous visit. (default to None) + visitwindow: The number of days (int) between this visit and the previous visit. (default to None) + visitmargin: The margin (in number of days (int)) that is an allowed deviation (a +/- few days ). Basically, + this allow the 'calculation' of a date window when this visit should occur. (default to None) + mandatory: Indicate if this visit is mandatory. Default to Yes + actions: A list of action points (or simply reminders) specific to that visit (i.e.'reserve room 101'). + This is not implemented yet (default to None) + """ + + def __init__(self, rank, visitlabel, previousvisit = None, visitwindow = None, visitmargin = None, actions = None, uid=None): + self.uid = utilities.generate_uid() + self.rank = rank + self.visitlabel = visitlabel + self.previousvisit = previousvisit + self.visitwindow = visitwindow + self.visitmargin = visitmargin + self.actions = actions #not implemented yet! + + +class Visit(VisitSetup): + """ + The Visit(VisitSetup) class help define individual visit of the candidate using VisitSetup(object) instances as 'templates'. + Upon creation of the first meeting with a candidate, the Candidate(object) instance will get a full set of Visit(VisitSetup) instances. + This set of visits is contained in Candidate.visitset + Each time a visit is being setup, a 'time window' is calculated to define the earliest and latest date at which + the 'nextVisit' should occur. + + Attributes: (In addition of parent class attributes.) + when: Date at which this visit is occurring. (default to None) + when_earliest: Earliest date when this visit should occur. Set to value when previous visit is activated. (default to None) + when_latest: Latest date when this visit should occur. Set to value when previous visit is activated. (default to None) + where: Place where this meeting is taking place. (default to None) + whitwhom: Professional meeting the study candidate at the reception. (default to None) + status: Status of this visit. Set to active when 'when' is set (default to None) + """ + def __init__(self, rank, visitlabel, previousvisit, visitwindow, visitmargin, actions=None, uid=None, when = None, + whenearliest = None, whenlatest = None, where = None, withwhom = None, status = None): + VisitSetup.__init__(self, rank, visitlabel, previousvisit, visitwindow, visitmargin, actions, uid) + self.when = when + self.whenearliest = whenearliest + self.whenlatest = whenlatest + self.where = where + self.withwhom = withwhom + self.status = status + + def visit_date_range(self): + #need to handle the case where a visit has no dates + #this works but Exception handling seems to broad + try: + early_date = datetime.datetime.date(self.whenearliest) + late_date = datetime.datetime.date(self.whenlatest) + date_range = str(early_date), '<>', str(late_date) + except Exception as e: + #print e + date_range = "" + return date_range + + + + diff --git a/dicat/ui/datatable.py b/dicat/ui/datatable.py new file mode 100644 index 0000000..8828dd8 --- /dev/null +++ b/dicat/ui/datatable.py @@ -0,0 +1,170 @@ +# import standard packages +from Tkinter import * +from ttk import * +# import internal packages +import ui.datawindow as DataWindow +import lib.datamanagement as DataManagement + +class DataTable(Frame): + """ + DataTable is a base class (which inherit from Frame) defining the functionalities of + this Tkinter.ttk.treeview widget. + Child clases are ParticipantsList(DataTable) and VisitList(DataTable) + """ + + def __init__(self, parent, colheaders): + Frame.__init__(self) + self.parent = parent + colheaders = colheaders + datatable = self.init_datatable(parent, colheaders) + + def init_datatable(self, parent, colheaders): + """Initialize the datatable (which is a tk.treeview and add scroll bars)""" + self.datatable = Treeview(parent, selectmode='browse', columns=colheaders, show="headings") + for col in colheaders: + self.datatable.heading(col, text=col.title(), + command=lambda c=col: self.treeview_sortby(self.datatable, c, 0)) + self.datatable.column(col, width=100, stretch="Yes", anchor="center") + # add vertical and horizontal scroll + self.verticalscroll = Scrollbar(parent, orient="vertical", command=self.datatable.yview) + self.horizontalscroll = Scrollbar(parent, orient="horizontal", command=self.datatable.xview) + self.datatable.configure(yscrollcommand=self.verticalscroll.set, xscroll=self.horizontalscroll.set) + self.verticalscroll.pack(side=RIGHT, expand=NO, fill=BOTH) + self.horizontalscroll.pack(side=BOTTOM, expand=NO, fill=BOTH) + self.datatable.pack(side=LEFT, expand=YES, fill=BOTH) + # Binding with events + self.datatable.bind('', self.ondoubleclick) + self.datatable.bind("<>", self.onrowclick) + self.datatable.bind('', self.onrightclik) + + def load_data(self): + """Should be overriden in child class""" + pass + + def update_data(self): + for i in self.datatable.get_children(): + self.datatable.delete(i) + self.load_data() + + def treeview_sortby(self, tree, column, descending): + """ + Sort treeview contents when a column is clicked on. + Taken from Dave's IDmapper + From: https://code.google.com/p/python-ttk/source/browse/trunk/pyttk-samples/treeview_multicolumn.py?r=21 + """ + # grab values to sort + data = [(tree.set(child, column), child) for child in tree.get_children('')] + # reorder data + data.sort(reverse=descending) + for index, item in enumerate(data): + tree.move(item[1], '', index) + # switch the heading so that it will sort in the opposite direction + tree.heading(column, command=lambda column=column: self.treeview_sortby(tree, column, int(not descending))) + + def ondoubleclick(self, event): + """ + Double clicking on a treeview line opens a 'data window' + and refresh the treeview data when the 'data window' is closed + """ + + # double clicking on blank space of the treeview when no valid line is selected generates a + # IndexOutOfRange error which is taken care of by this try:except block + try: + itemID = self.datatable.selection()[0] + item = self.datatable.item(itemID)['tags'] + parent = self.parent + candidate_uuid = item[1] + DataWindow.DataWindow(parent, candidate_uuid) + self.update_data() + except Exception as e: + print "Datatable ondoubleclick ", str(e) # TODO deal with exception or not!?! + + def onrightclik(self, event): + """Not used yet""" + print 'contextual menu for this item' + pass + + def onrowclick(self, event): + """Not used yet""" + item_id = str(self.datatable.focus()) + item = self.datatable.item(item_id)['values'] + # print item_id, item #TODO remove when done + + +class ParticipantsList(DataTable): + """ + class ParticipantsList(DataTable) takes care of the data table holding the list of participants + That list is comprised of all participants (even those that have not been called once. + """ + + def __init__(self, parent, colheaders): # expected is dataset + DataTable.__init__(self, parent, colheaders) + self.colheaders = colheaders + self.load_data() + # TODO add these color settings in a 'settings and preferences section of the app' + self.datatable.tag_configure('active', background='#F1F8FF') # TODO replace active tag by status variable value + + def load_data(self): + data = dict(DataManagement.read_candidate_data()) + try: + for key in data: + if data[key].status is None: + status = '' + else: + status = data[key].status + self.datatable.insert( '', 'end', + values=[data[key].firstname, + data[key].lastname, data[key].phone, + status + ], + tags=(status, data[key].uid) + ) + except Exception as e: + print "datatable.ParticipantsList.load_data ", str(e) # TODO proper exception handling + pass + + +class VisitList(DataTable): + """ + class VisitList(DataTable) takes care of the data table holding the list of all appointments + even those that have not been confirmed yet. + """ + + def __init__(self, parent, colheaders): + DataTable.__init__(self, parent, colheaders) + self.colheaders = colheaders + self.load_data() + # TODO add these color settings in a 'settings and preferences section of the app' + self.datatable.tag_configure('active', background='#F1F8FF') # TODO change for non-language parameter + self.datatable.tag_configure('tentative', background='#F0F0F0') # TODO change for non-language parameter + + def load_data(self): + data = dict(DataManagement.read_candidate_data()) + for key, value in data.iteritems(): + if data[key].visitset is not None: # skip the search if visitset = None + current_visitset = data[key].visitset # set this candidate.visitset for the next step + # gather information about the candidate + # this candidatekey is not printed on screen but saved with the new Scheduler object + candidatekey = data[key].uid + candidate_firstname = data[key].firstname + candidate_lastname = data[key].lastname + candidate_fullname = str(candidate_firstname + ' ' + candidate_lastname) + for key, value in current_visitset.iteritems(): + if current_visitset[key].status is not None: + status = current_visitset[key].status + visit_label = current_visitset[key].visitlabel + if current_visitset[key].when is None: + when = current_visitset[key].whenearliest + else: + when = current_visitset[key].when + if current_visitset[key].where is None: + where = '' + else: + where = current_visitset[key].where + try: + self.datatable.insert('', 'end', + values=[candidate_fullname, visit_label, when, where, status], + tags=(status, candidatekey, visit_label)) + except Exception as e: + print "datatable.VisitList.load_data ", str(e) # TODO add proper error handling + pass diff --git a/dicat/ui/datawindow.py b/dicat/ui/datawindow.py new file mode 100644 index 0000000..98515fb --- /dev/null +++ b/dicat/ui/datawindow.py @@ -0,0 +1,213 @@ +# import standard packages +from Tkinter import * +from ttk import * +# import internal packages +from scheduler_visit import Visit +import ui.dialogbox as DialogBox +import lib.utilities as Utilities +import lib.multilanguage as MultiLanguage +import lib.datamanagement as DataManagement + + + +# ref: http://effbot.org/tkinterbook/tkinter-newDialog-windows.htm +# TODO this class needs a major clean-up + + +class DataWindow(Toplevel): + def __init__(self, parent, candidate_uuid='new'): + Toplevel.__init__(self, parent) + # create a transient window on top of parent window + self.transient(parent) + self.parent = parent + self.title(MultiLanguage.data_window_title) #TODO find a better title for the thing + body = Frame(self) + self.initial_focus = self.body(body, candidate_uuid) + body.pack(padx=5, pady=5) + + self.button_box() + + self.grab_set() + if not self.initial_focus: + self.initial_focus = self + self.protocol("WM_DELETE_WINDOW", self.closedialog) + Utilities.center_window(self) + self.initial_focus.focus_set() + # self.deiconify() + self.wait_window(self) + + def body(self, master, candidate): + """Creates the body of 'datawindow'. param candidate is the candidate.uuid""" + try: + data = dict(DataManagement.read_candidate_data()) # TODO better way to do this + candidate = data.get(candidate) + except Exception as e: + print "datawindow.body ", str(e) # TODO manage exceptions + # Candidate section + self.candidate_pane = Labelframe(self, text=MultiLanguage.candidate_pane, width=250, height=350, borderwidth=10) + self.candidate_pane.pack(side=TOP, expand=YES, fill=BOTH, padx=5, pady=5) + # object unique id - does not appear on gui but needed to keep track of this candidate + self.candidate_uid = candidate.uid + # PSCID + self.label_pscid = Label(self.candidate_pane, text=MultiLanguage.candidate_pscid) + self.label_pscid.grid(column=0, row=0, padx=10, pady=5, sticky=N+S+E+W) + self.text_pscid_var = StringVar() + self.text_pscid_var.set(candidate.pscid) + self.text_pscid = Entry(self.candidate_pane, textvariable=self.text_pscid_var) + self.text_pscid.grid(column=0, row=1, padx=10, pady=5, sticky=N+S+E+W) + # status + self.label_status = Label(self.candidate_pane, text=MultiLanguage.candidate_status) + self.label_status.grid(column=1, row=0, padx=10, pady=5, sticky=N+S+E+W) + self.text_status_var = StringVar() + self.text_status_var.set(candidate.status) + self.text_status = Entry(self.candidate_pane, textvariable=self.text_status_var) + self.text_status.grid(column=1, row=1, padx=10, pady=5, sticky=N+S+E+W) + # firstname + self.label_firstname = Label(self.candidate_pane, text=MultiLanguage.candidate_firstname) + self.label_firstname.grid(column=0, row=2, padx=10, pady=5, sticky=N+S+E+W) + self.text_firstname_var = StringVar() + self.text_firstname_var.set(candidate.firstname) + self.text_firstname = Entry(self.candidate_pane, textvariable=self.text_firstname_var) + self.text_firstname.grid(column=0, row=3, padx=10, pady=5, sticky=N+S+E+W) + # lastname + self.label_lastname = Label(self.candidate_pane, text=MultiLanguage.candidate_lastname) + self.label_lastname.grid(column=1, row=2, padx=10, pady=5, sticky=N+S+E+W) + self.text_lastname_var = StringVar() + self.text_lastname_var.set(candidate.lastname) + self.text_lastname = Entry(self.candidate_pane, textvariable=self.text_lastname_var) + self.text_lastname.grid(column=1, row=3, padx=10, pady=5, sticky=N+S+E+W) + # phone number + self.label_phone = Label(self.candidate_pane, text=MultiLanguage.candidate_phone) + self.label_phone.grid(column=2, row=2, padx=10, pady=5, sticky=N+S+E+W) + self.text_phone_var = StringVar() + self.text_phone_var.set(candidate.phone) + self.text_phone = Entry(self.candidate_pane, textvariable=self.text_phone_var) + self.text_phone.grid(column=2, row=3, padx=10, pady=5, sticky=N+S+E+W) + + # Schedule Section - displayed as a table + self.schedule_pane = Labelframe(self, text=MultiLanguage.schedule_pane, width=250, height=350, borderwidth=10) + self.schedule_pane.pack(side=TOP, expand=YES, fill=BOTH, padx=5, pady=5) + # top row (header) + self.label_visit_rank = Label(self.schedule_pane, text=MultiLanguage.schedule_visit_rank) + self.label_visit_rank.grid(column=0, row=0, padx=5, pady=5, sticky=N+S+E+W) + self.label_visit_label = Label(self.schedule_pane, text=MultiLanguage.col_visitlabel) + self.label_visit_label.grid(column=1, row=0, padx=5, pady=5, sticky=N+S+E+W) + self.label_visit_when = Label(self.schedule_pane, text=MultiLanguage.col_when) + self.label_visit_when.grid(column=2, row=0, padx=5, pady=5, sticky=NSEW) + self.label_visit_status = Label(self.schedule_pane, text=MultiLanguage.col_where) + self.label_visit_status.grid(column=3, row=0, padx=5, pady=5, sticky=N+S+E+W) + self.label_visit_status = Label(self.schedule_pane, text=MultiLanguage.col_withwhom) + self.label_visit_status.grid(column=4, row=0, padx=5, pady=5, sticky=N+S+E+W) + self.label_visit_status = Label(self.schedule_pane, text=MultiLanguage.col_status) + self.label_visit_status.grid(column=5, row=0, padx=5, pady=5, sticky=N+S+E+W) + + """ + PSEUDOCODE + 1. Get candidate.visitset + 2. Parse into a sorted list (sorted on visit.rank) + 3. Print data on screen + + + visit_set = candidate.visitset + for key, value in study_setup.iteritems(): + visit_list.append(study_setup[key]) + visit_list = sorted(visit_list, key=lambda visit: visit.rank) + + for key, value in visit_list.iteritems(): + + """ + # TODO add logic "foreach" to create a table showing each visit + # 1- Get candidate visitset and parse into a list + visit_list = [] + visitset = candidate.visitset + if visitset is None: + print 'no visit yet' + else: + for key, value in visitset.iteritems(): + visit_list.append(visitset[key]) + # 2- Sort list on visit.rank + visit_list = sorted(visit_list, key=lambda visit: visit.rank) + # 3- 'print' values on ui + x = 0 + for x in range(len(visit_list)): + # rank + label_visit_rank = Label(self.schedule_pane, text=visit_list[x].rank) + label_visit_rank.grid(column=0, row=x+1, padx=5, pady=5, sticky=N+S+E+W) + # visitlabel + label_visit_label = Label(self.schedule_pane, text=visit_list[x].visitlabel) + label_visit_label.grid(column=1, row=x+1, padx=5, pady=5, sticky=N+S+E+W) + # when + if visit_list[x].when == None: + visit = visit_list[x] + date_range = Visit.visit_date_range(visit) + label_visit_when = Label(self.schedule_pane, text=date_range) + label_visit_when.grid(column=2, row=x+1, padx=5, pady=5, sticky=N+S+E+W) + else: + label_visit_when = Label(self.schedule_pane, text=visit_list[x].when) + label_visit_when.grid(column=2, row=x+1, padx=5, pady=5, sticky=N+S+E+W) + # where + label_visit_where = Label(self.schedule_pane, text=visit_list[x].where) + label_visit_where.grid(column=3, row=x+1, padx=5, pady=5, sticky=N+S+E+W) + # withwhom + label_visit_where = Label(self.schedule_pane, text=visit_list[x].withwhom) + label_visit_where.grid(column=4, row=x+1, padx=5, pady=5, sticky=N+S+E+W) + # status + label_visit_where = Label(self.schedule_pane, text=visit_list[x].status) + label_visit_where.grid(column=5, row=x+1, padx=5, pady=5, sticky=N+S+E+W) + + + + def button_box(self): + # add standard button box + box = Frame(self) + w = Button(box, text="OK", width=10, command=self.ok_button, default=ACTIVE) + w.pack(side=LEFT, padx=5, pady=5) + w = Button(box, text="Cancel", width=10, command=self.cancel_button) + w.pack(side=LEFT, padx=5, pady=5) + self.bind("", self.ok_button) + self.bind("", self.closedialog) + box.pack() + + def ok_button(self, event=None): + print "saving data and closing" # TODO remove when done + self.capture_data() + if not self.validate(): + self.initial_focus.focus_set() # put focus back + return + #need to call treeview update here + self.withdraw() + self.closedialog() + + def cancel_button(self, event=None): + print "close without saving" + parent = Frame(self) + newwin = DialogBox.ConfirmYesNo(parent, MultiLanguage.dialog_close) + if newwin.buttonvalue == 1: + self.closedialog() + else: + return + + def closedialog(self, event=None): + self.parent.focus_set() # put focus back to parent window before destroying the window + self.destroy() + + def validate(self): + return 1 + + def capture_data(self): + """ + Grap the information from the window's text field and save the candidate information based on candidate_uid. + """ + # open the 'database' + db = dict(DataManagement.read_candidate_data()) + # and find candidate based on uid + uid = self.candidate_uid + candidate = db[uid] + # capture data from fields + candidate.pscid = self.text_pscid.get() + candidate.status = self.text_status.get() + candidate.firstname = self.text_firstname.get() + candidate.lastname = self.text_lastname.get() + candidate.phone = self.text_phone.get() + # save data + DataManagement.save_candidate_data(db) \ No newline at end of file diff --git a/dicat/ui/dialogbox.py b/dicat/ui/dialogbox.py new file mode 100644 index 0000000..182e830 --- /dev/null +++ b/dicat/ui/dialogbox.py @@ -0,0 +1,78 @@ +#import standard packages +from Tkinter import * +#import internal packages +import lib.utilities as Utilities +import lib.multilanguage as MultiLanguage + +class DialogBox(Toplevel): + """ + This class was created mainly because the native dialog box don't work as expected when called from a top-level window. + This class (although it could be improved in many aspects) insure that the parent window cannot get focus while a dialog box is still active. + """ + def __init__(self,parent, title, message, button1, button2): + Toplevel.__init__(self,parent) + self.transient(parent) + self.parent = parent + self.title(title) + body = Frame(self) + self.initial_focus = self.body(body, message) + body.pack(padx=4, pady=4) + self.buttonbox(button1, button2) + self.grab_set() + + if not self.initial_focus: + self.initial_focus = self + + self.protocol("WM_DELETE_WINDOW", self.button2) + Utilities.center_window(self) + self.initial_focus.focus_set() + self.deiconify() + self.wait_window(self) + + def body(self, parent, message): + label = Label(self, text=message) + label.pack(padx=4, pady=4) + pass + + def buttonbox(self, button1, button2): + #add a standard button box + box = Frame(self) + b1 = Button(box, text=button1, width=12, command=self.button1, default=ACTIVE) + b1.pack(side=LEFT, padx=4, pady=4) + b2 = Button(box, text=button2, width=12, command=self.button2, default=ACTIVE) + b2.pack(side=LEFT, padx=4, pady=4) + self.bind("", self.button1) + self.bind("", self.button2) + box.pack() + + def button1(self, event=None): + if not self.validate(): + self.initial_focus.focus_set() #put focus on Button + return + self.buttonvalue = 1 + self.closedialog() + + def button2(self, event=None): + self.buttonvalue = 2 + self.closedialog() + + + def closedialog(self, event=None): + #put focus back to parent window before destroying the window + self.parent.focus_set() + self.destroy() + + def validate(self): + return 1 + +######################################################################################### +class ConfirmYesNo(DialogBox): + def __init__(self, parent, message): + title = MultiLanguage.dialog_title_confirm + button1 = MultiLanguage.dialog_yes + button2 = MultiLanguage.dialog_no + DialogBox.__init__(self, parent, title, message, button1, button2) + + + + diff --git a/dicat/ui/menubar.py b/dicat/ui/menubar.py index ff3f5e3..a54cbeb 100644 --- a/dicat/ui/menubar.py +++ b/dicat/ui/menubar.py @@ -4,7 +4,7 @@ import lib.multilanguage as MultiLanguage import ui.datawindow as DataWindow -class MenuBar(Tkinter.Menu): +class SchedulerMenuBar(Tkinter.Menu): def __init__(self, parent): Tkinter.Menu.__init__(self, parent) # create an APPLICATION pulldown menu diff --git a/dicat/ui/newcandidatewindow.py b/dicat/ui/newcandidatewindow.py new file mode 100644 index 0000000..6619719 --- /dev/null +++ b/dicat/ui/newcandidatewindow.py @@ -0,0 +1,74 @@ +#import standard packages +from Tkinter import * +from ttk import * +#import internal packages +import ui.dialogbox as DialogBox +import lib.utilities as Utilities +import lib.multilanguage as MultiLanguage +import lib.datamanagement as DataManagement + +class NewCandidateWindow(TopLevel): + def __init__(self, parent): + Toplevel.__init__(self, parent) + #create a transient window on top of parent window + print "running NewCandidate(Toplevel) " #TODO remove when done + self.transient(parent) + self.parent = parent + self.title(MultiLanguage.new_candidate_title) + body = Frame(self) + self.initial_focus = self.body(body, candidate_uuid) + body.pack(padx=5, pady=5) + + self.button_box() + self.grab_set() + if not self.initial_focus: + self.initial_focus = self + self.protocol("WM_DELETE_WINDOW", self.closedialog) + Utilities.center_window(self) + self.initial_focus.focus_set() + #self.deiconify() + self.wait_window(self) + + def body(self, master, candidate): + #Candidate section + self.candidate_pane = Labelframe(self, text=MultiLanguage.candidate_pane, width=250, height=350, borderwidth=10) + self.candidate_pane.pack(side=TOP, expand=YES, fill=BOTH, padx=5, pady=5) + #PSCID + self.label_pscid = Label(self.candidate_pane, text=MultiLanguage.candidate_pscid) + self.label_pscid.grid(column=0, row=0, padx=10, pady=5, sticky=N+S+E+W) + self.text_pscid_var = StringVar() + self.text_pscid_var.set(candidate.pscid) + self.text_pscid = Entry(self.candidate_pane, textvariable=self.text_pscid_var) + self.text_pscid.grid(column=0, row=1, padx=10, pady=5, sticky=N+S+E+W) + #status + self.label_status = Label(self.candidate_pane, text=MultiLanguage.candidate_status) + self.label_status.grid(column=1, row=0, padx=10, pady=5, sticky=N+S+E+W) + self.text_status_var = StringVar() + self.text_status_var.set(candidate.status) + self.text_status = Entry(self.candidate_pane, textvariable=self.text_status_var) + self.text_status.grid(column=1, row=1, padx=10, pady=5, sticky=N+S+E+W) + #firstname + self.label_firstname = Label(self.candidate_pane, text=MultiLanguage.candidate_firstname) + self.label_firstname.grid(column=0, row=2, padx=10, pady=5, sticky=N+S+E+W) + self.text_firstname_var = StringVar() + self.text_firstname_var.set(candidate.firstname) + self.text_firstname = Entry(self.candidate_pane, textvariable=self.text_firstname_var) + self.text_firstname.grid(column=0, row=3, padx=10, pady=5, sticky=N+S+E+W) + #lastname + self.label_lastname = Label(self.candidate_pane, text=MultiLanguage.candidate_lastname) + self.label_lastname.grid(column=1, row=2, padx=10, pady=5, sticky=N+S+E+W) + self.text_lastname_var = StringVar() + self.text_lastname_var.set(candidate.lastname) + self.text_lastname = Entry(self.candidate_pane, textvariable=self.text_lastname_var) + self.text_lastname.grid(column=1, row=3, padx=10, pady=5, sticky=N+S+E+W) + #phone number + self.label_phone = Label(self.candidate_pane, text=MultiLanguage.candidate_phone) + self.label_phone.grid(column=2, row=2, padx=10, pady=5, sticky=N+S+E+W) + self.text_phone_var = StringVar() + self.text_phone_var.set(candidate.phone) + self.text_pone = Entry(self.candidate_pane, textvariable=self.text_phone_var) + self.text_pone.grid(column=2, row=3, padx=10, pady=5, sticky=N+S+E+W) + #pscid + #self.label_pscid = Label(self.candidate_pane, textvariable=self.pscid_var) + + diff --git a/dicat/ui/projectpane.py b/dicat/ui/projectpane.py new file mode 100644 index 0000000..bed5d89 --- /dev/null +++ b/dicat/ui/projectpane.py @@ -0,0 +1,6 @@ +from Tkinter import * +import lib.multilanguage as multilanguage + +class ProjectPane(LabelFrame): + def __init__(self, parent): + LabelFrame.__init__(self, parent) \ No newline at end of file From 3177c85a8456ba279942fc8a13099d04ee5abeae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Fri, 24 Jun 2016 10:01:20 -0400 Subject: [PATCH 42/89] removing duplicate files --- scheduler/candidate.py | 196 ------ scheduler/candidatedata | 572 ------------------ scheduler/documentation/namingConvention | 17 - scheduler/documentation/someBurningQuestions | 1 - scheduler/studydata | 66 -- scheduler/tests/create1_StudySetup.py | 23 - scheduler/tests/create2_CandidateList_test.py | 30 - scheduler/tests/create3_CandidateVisit.py | 25 - scheduler/tests/create4_PopulateVisits.py | 84 --- scheduler/tests/test.py | 6 - scheduler/tests/test1.py | 7 - scheduler/tests/test5.py | 19 - scheduler/visit.py | 93 --- 13 files changed, 1139 deletions(-) delete mode 100644 scheduler/candidate.py delete mode 100644 scheduler/candidatedata delete mode 100644 scheduler/documentation/namingConvention delete mode 100644 scheduler/documentation/someBurningQuestions delete mode 100644 scheduler/studydata delete mode 100644 scheduler/tests/create1_StudySetup.py delete mode 100644 scheduler/tests/create2_CandidateList_test.py delete mode 100644 scheduler/tests/create3_CandidateVisit.py delete mode 100644 scheduler/tests/create4_PopulateVisits.py delete mode 100644 scheduler/tests/test.py delete mode 100644 scheduler/tests/test1.py delete mode 100644 scheduler/tests/test5.py delete mode 100644 scheduler/visit.py diff --git a/scheduler/candidate.py b/scheduler/candidate.py deleted file mode 100644 index 8663dd9..0000000 --- a/scheduler/candidate.py +++ /dev/null @@ -1,196 +0,0 @@ -# import standard packages -import datetime - -import visit - -# import internal packages -import lib.datamanagement as DataManagement -import lib.multilanguage as MultiLanguage -import lib.utilities as Utilities - - -class Candidate: - """ - The Candidate class defines the candidates/participants of the study - - Attributes: - uid: A unique identifier using python's uuid1 method. Used as key to store and retrieve objects from - files and/or dictionaries. - firstname: First name of the candidate - lastname: Last name of the candidate - visitset: A dictionnary containing all visits (planed or not) - phone: A primary phone number - status: Status of this candidate - pscid: Loris (clinical results database) specific ID - - kwargs: Not implemented yet! - - Code example: - candidatedb = {} #setup a dict to receive the candidates - candidatedata = candidate.Candidate('Billy', 'Roberts', '451-784-9856', otherphone='514-874-9658') #instanciate one candidate - candidatedb[candidatedata.uid] = candidatedata #add candidate to dict - DataManagement.save_candidate_data(candidatedb) #save data to file - """ - def __init__(self, firstname, lastname, phone, uid=None, visitset = None, status = None, pscid=None, **kwargs): #TODO *kwarg - self.uid = Utilities.generate_uid() - self.firstname = firstname - self.lastname = lastname - self.visitset = visitset - self.phone = phone - self.status = status - self.pscid = pscid - #...many other attributes - if kwargs is not None: - for key, value in kwargs.iteritems(): - setattr(self, key, value) - - def setup_visitset(self): - """ - When creating the first visit for a candidate, a complete set of visits is added based on a study/project visit list - There are no parameters passed to this method since it will simply create a new 'empty' - This method will: - 1-open studydata (the study visit list) and 'parse' the dict to a sorted list (dict cannot be sorted) - 2-create a temporary list of visit_labels that will serve as a key within the Candidate.visit_set dictionary - 3-instantiate individual visits based on the study visit list - Usage: - Called by Candidate.set_visit_date() - """ - visit_list =[] - visit_label_templist = [] - study_setup = dict() - try: - #1-open studydata - study_setup = dict(DataManagement.read_studydata()) - except Exception as e: - print str(e) #TODO add error login (in case a study data file does not exist) - for key, value in study_setup.iteritems(): - #2-parse into a sorted list - visit_list.append(study_setup[key]) - visit_list = sorted(visit_list, key=lambda visit: visit.rank) - #create a temporary list of visitlabels - for each in visit_list: - visit_label_templist.append(each.visitlabel) - #3-instantiate individual visit based on each instance of VisitSetup() - self.visitset = {} #setup a dict to receive a set of Visit() - count = 0 - #set values of : uid, rank, visit_label, previous_visit, visit_window, visitmargin, - for key in visit_list: - mainkey = str(visit_label_templist[count]) - rank =key.rank - visit_label = key.visitlabel - previous_visit = key.previousvisit - visit_window = key.visitwindow - visit_margin = key.visitmargin - visit_data = visit.Visit(rank, visit_label, previous_visit, visit_window, visit_margin,) - self.visitset[mainkey] = visit_data - count += 1 - - def set_visit_date(self, visitlabel, visitdate, visittime, visitwhere, visitwhom): - """ - This method update the visit information (according to visitlabel) - This method will: - 1-Check if visitset==None. If so, then Candidate.setup_visitset() is called to setup Candidate.visitset - 2-Check if a date is already set for this visitlabel - 3-Set values of Visit.when, 'Visit.where, Visit.whithwhom and Visit.status for current visit - Usage: Called by GUI methods - Return: current_visit as current Visit(Visit(VisitSetup) instance - """ - #1-Check to see if visitset == None before trying to create a new date instance - if self.visitset is None: - self.setup_visitset() - self.set_candidate_status_active() #Candidate.status='active' since we're setting up a first visit - #get current visit within visitset - current_visit = self.visitset.get(visitlabel) - #2-Check to see if this visit already has a date - if current_visit.when is not None: - print "date already exists" #TODO add confirmation of change log??? - pass - #concatenate visitdate and visittime and parse into a datetime object - visitwhen = visitdate + ' ' + visittime - when = datetime.datetime.strptime(visitwhen, '%Y-%m-%d %H:%M') - #3-Set values of Visit.when, 'Visit.where, Visit.whithwhom and Visit.status for current visit - current_visit.when = when - current_visit.where = visitwhere - current_visit.withwhom = visitwhom - current_visit.status = "active" #that is the status of the visit - return current_visit - - - """ - def set_next_visit_window(self, candidate, current_visit): - #get the current visit object as argument. Will search and look for the next visit (visit where previousvisit == current_visit_label) - next_visit_searchset = candidate.visitset - current_visit_label = current_visit.visitlabel - next_visit = "" - for key in next_visit_searchset: - visit_data = next_visit_searchset[key] - if visit_data.previousvisit == current_visit_label: - next_visit = candidate.visitset.get(visit_data.visitlabel) #TODO debug when current is last visit - #gather info about current_visit (mostly for my own biological computer! else I get lost) - current_visit_date = current_visit.when - current_visit_year = current_visit_date.year #get the year of the current visit date - next_visit_window = next_visit.visitwindow - next_visit_margin = next_visit.visitmargin - #set dates for the next visit - next_visit_early = int(current_visit_date.strftime('%j')) + (next_visit_window - next_visit_margin) #this properly handle change of year - next_visit_early_date = datetime.datetime(current_visit_year, 1, 1) + datetime.timedelta(next_visit_early - 1) - next_visit_late = int(current_visit_date.strftime('%j')) + (next_visit_window + next_visit_margin) - next_visit_late_date = datetime.datetime(current_visit_year, 1, 1) + datetime.timedelta(next_visit_late - 1) - next_visit.when_earliest = next_visit_early_date - next_visit.when_latest = next_visit_late_date - next_visit.status = "tentative" - Utilities.print_object((next_visit)) - """ - def set_next_visit_window(self, candidate, current_visit): - """ - This method will 'calculate' a min and max date when the next visit should occur - 1-Get candidate.visitset (as visit_searchset) and current_visit.visitlabel - 2-Identify which visit (in visitset) has previousvisit == current_visit.visitlabel - 3-Get - Usage: Currently called by GUI function (TODO RETHINK THIS LOGIC maybe it should be called when running Candidate.set_visit_date()) - """ - - - #get the current visit object as argument. Will search and look for the next visit (visit where previousvisit == current_visitlabel) - - #1- Get Candidate.visitset and current_visit / next_visit will == Visit(VisitSetup) of the next visit (relative to current_visit) - visit_searchset = candidate.visitset - current_visitlabel = current_visit.visitlabel - next_visit = "" - #2-Identify which visit (in visitset) has previousvisit == current_visit.visitlabel - for key in visit_searchset: - visit_data = visit_searchset[key] - if visit_data.previousvisit == current_visitlabel: - next_visit = candidate.visitset.get(visit_data.visitlabel) #TODO debug when current is last visit - #3-Calculate a min and max date for the next visit to occur based on Visit.visitwindow and Visit.visitmargin - current_visitdate = current_visit.when - current_visityear = current_visitdate.year #get the year of the current visit date - next_visitwindow = next_visit.visitwindow - nextvisitmargin = next_visit.visitmargin - #set dates for the next visit - nextvisitearly = int(current_visitdate.strftime('%j')) + (next_visitwindow - nextvisitmargin) #this properly handle change of year - nextvisitearlydate = datetime.datetime(current_visityear, 1, 1) + datetime.timedelta(nextvisitearly - 1) - nextvisitlate = int(current_visitdate.strftime('%j')) + (next_visitwindow + nextvisitmargin) - nextvisitlatedate = datetime.datetime(current_visityear, 1, 1) + datetime.timedelta(nextvisitlate - 1) - next_visit.whenearliest = nextvisitearlydate - next_visit.whenlatest = nextvisitlatedate - next_visit.status = "tentative" #set this visit.status - - def get_active_visit(self, candidate): - candidatefullname = str(candidate.firstname + ' ' + candidate.lastname) - currentvisitset = candidate.visitset - activevisit = [] - if currentvisitset is None: - return - elif currentvisitset is not None: - for key in currentvisitset: - if currentvisitset[key].status == MultiLanguage.status_active: - visitlabel = currentvisitset[key].visitlabel - when = currentvisitset[key].when.strftime('%Y-%m-%d %Hh%m') - where = currentvisitset[key].where - who = currentvisitset[key].withwhom - activevisit = [candidatefullname, visitlabel, when, where, who] - return activevisit - - def set_candidate_status_active(self): - self.status = MultiLanguage.status_active #set the Candidate.status to 'active' \ No newline at end of file diff --git a/scheduler/candidatedata b/scheduler/candidatedata deleted file mode 100644 index 7e110f9..0000000 --- a/scheduler/candidatedata +++ /dev/null @@ -1,572 +0,0 @@ -(dp0 -S'b33a0330-66dc-11e5-a66f-e0b9a5f8b710' -p1 -(icandidate -Candidate -p2 -(dp3 -S'status' -p4 -S'active' -p5 -sS'pscid' -p6 -S'None' -p7 -sS'uid' -p8 -g1 -sS'firstname' -p9 -S'Marc' -p10 -sS'lastname' -p11 -S'St-Pierre' -p12 -sS'visitset' -p13 -(dp14 -S'V0' -p15 -(ivisit -Visit -p16 -(dp17 -g4 -S'active' -p18 -sS'visitmargin' -p19 -Nsg8 -S'b33a5151-66dc-11e5-bc4a-e0b9a5f8b710' -p20 -sS'visitwindow' -p21 -NsS'visitlabel' -p22 -g15 -sS'when' -p23 -cdatetime -datetime -p24 -(S'\x07\xde\x0c\x19\r\x0f\x00\x00\x00\x00' -p25 -tp26 -Rp27 -sS'rank' -p28 -I1 -sS'actions' -p29 -NsS'whenearliest' -p30 -NsS'previousvisit' -p31 -NsS'whenlatest' -p32 -NsS'where' -p33 -S'CRIUGM lobby' -p34 -sS'withwhom' -p35 -S'Annie' -p36 -sbsS'V1' -p37 -(ivisit -Visit -p38 -(dp39 -g4 -S'tentative' -p40 -sg19 -I2 -sg8 -S'b33a5152-66dc-11e5-8d1d-e0b9a5f8b710' -p41 -sg21 -I10 -sg22 -g37 -sg23 -Nsg28 -I2 -sg29 -Nsg30 -g24 -(S'\x07\xdf\x01\x02\x00\x00\x00\x00\x00\x00' -p42 -tp43 -Rp44 -sg31 -g15 -sg32 -g24 -(S'\x07\xdf\x01\x06\x00\x00\x00\x00\x00\x00' -p45 -tp46 -Rp47 -sg33 -Nsg35 -NsbsS'V2' -p48 -(ivisit -Visit -p49 -(dp50 -g4 -Nsg19 -I10 -sg8 -S'b33a5153-66dc-11e5-b200-e0b9a5f8b710' -p51 -sg21 -I20 -sg22 -g48 -sg23 -Nsg28 -I3 -sg29 -Nsg30 -Nsg31 -g37 -sg32 -Nsg33 -Nsg35 -NsbssS'phone' -p52 -S'412-897-9874' -p53 -sbsS'b3398e01-66dc-11e5-9720-e0b9a5f8b710' -p54 -(icandidate -Candidate -p55 -(dp56 -g4 -S'active' -p57 -sg6 -S'' -p58 -sg8 -g54 -sg9 -S'Sue' -p59 -sg11 -S'Allen' -p60 -sg13 -(dp61 -S'V0' -p62 -(ivisit -Visit -p63 -(dp64 -g4 -g18 -sg19 -Nsg8 -S'b33b62c0-66dc-11e5-84ad-e0b9a5f8b710' -p65 -sg21 -Nsg22 -g62 -sg23 -g24 -(S'\x07\xde\x0c\x1b\x0e\x1e\x00\x00\x00\x00' -p66 -tp67 -Rp68 -sg28 -I1 -sg29 -Nsg30 -Nsg31 -Nsg32 -Nsg33 -S'CRIUGM M-124' -p69 -sg35 -S'Jean' -p70 -sbsS'V1' -p71 -(ivisit -Visit -p72 -(dp73 -g4 -g18 -sg19 -I2 -sg8 -S'b33b62c1-66dc-11e5-b6fb-e0b9a5f8b710' -p74 -sg21 -I10 -sg22 -g71 -sg23 -g24 -(S'\x07\xdf\x02\x07\t\x0f\x00\x00\x00\x00' -p75 -tp76 -Rp77 -sg28 -I2 -sg29 -Nsg30 -g24 -(S'\x07\xdf\x01\x04\x00\x00\x00\x00\x00\x00' -p78 -tp79 -Rp80 -sg31 -g62 -sg32 -g24 -(S'\x07\xdf\x01\x08\x00\x00\x00\x00\x00\x00' -p81 -tp82 -Rp83 -sg33 -g69 -sg35 -g70 -sbsS'V2' -p84 -(ivisit -Visit -p85 -(dp86 -g4 -g40 -sg19 -I10 -sg8 -S'b33b62c2-66dc-11e5-947a-e0b9a5f8b710' -p87 -sg21 -I20 -sg22 -g84 -sg23 -Nsg28 -I3 -sg29 -Nsg30 -g24 -(S'\x07\xdf\x02\x11\x00\x00\x00\x00\x00\x00' -p88 -tp89 -Rp90 -sg31 -g71 -sg32 -g24 -(S'\x07\xdf\x03\t\x00\x00\x00\x00\x00\x00' -p91 -tp92 -Rp93 -sg33 -Nsg35 -Nsbssg52 -S'451-874-9632' -p94 -sbsS'b339dc22-66dc-11e5-b4a9-e0b9a5f8b710' -p95 -(icandidate -Candidate -p96 -(dp97 -g4 -S'active' -p98 -sg6 -g58 -sg8 -g95 -sg9 -S'Alain' -p99 -sg11 -S'Jeanson' -p100 -sg13 -(dp101 -S'V0' -p102 -(ivisit -Visit -p103 -(dp104 -g4 -g18 -sg19 -Nsg8 -S'b33bff00-66dc-11e5-aeea-e0b9a5f8b710' -p105 -sg21 -Nsg22 -g102 -sg23 -g24 -(S'\x07\xdf\x01\r\t\x0f\x00\x00\x00\x00' -p106 -tp107 -Rp108 -sg28 -I1 -sg29 -Nsg30 -Nsg31 -Nsg32 -Nsg33 -S'McDo' -p109 -sg35 -S'Scott' -p110 -sbsS'V1' -p111 -(ivisit -Visit -p112 -(dp113 -g4 -g40 -sg19 -I2 -sg8 -S'b33bff01-66dc-11e5-822c-e0b9a5f8b710' -p114 -sg21 -I10 -sg22 -g111 -sg23 -Nsg28 -I2 -sg29 -Nsg30 -g24 -(S'\x07\xdf\x01\x15\x00\x00\x00\x00\x00\x00' -p115 -tp116 -Rp117 -sg31 -g102 -sg32 -g24 -(S'\x07\xdf\x01\x19\x00\x00\x00\x00\x00\x00' -p118 -tp119 -Rp120 -sg33 -Nsg35 -NsbsS'V2' -p121 -(ivisit -Visit -p122 -(dp123 -g4 -Nsg19 -I10 -sg8 -S'b33bff02-66dc-11e5-a651-e0b9a5f8b710' -p124 -sg21 -I20 -sg22 -g121 -sg23 -Nsg28 -I3 -sg29 -Nsg30 -Nsg31 -g111 -sg32 -Nsg33 -Nsg35 -Nsbssg52 -S'245-874-6321' -p125 -sbsS'b3398e00-66dc-11e5-a42e-e0b9a5f8b710' -p126 -(icandidate -Candidate -p127 -(dp128 -g4 -S'active' -p129 -sg6 -g58 -sg8 -g126 -sg9 -S'Billy' -p130 -sg11 -S'Roberts' -p131 -sg13 -(dp132 -S'V0' -p133 -(ivisit -Visit -p134 -(dp135 -g4 -g18 -sg19 -Nsg8 -S'b33c7430-66dc-11e5-920e-e0b9a5f8b710' -p136 -sg21 -Nsg22 -g133 -sg23 -g24 -(S'\x07\xdf\x02\x18\x0f\x1e\x00\x00\x00\x00' -p137 -tp138 -Rp139 -sg28 -I1 -sg29 -Nsg30 -Nsg31 -Nsg32 -Nsg33 -S'IGA' -p140 -sg35 -S'Charlie' -p141 -sbsS'V1' -p142 -(ivisit -Visit -p143 -(dp144 -g4 -g40 -sg19 -I2 -sg8 -S'b33c7431-66dc-11e5-b674-e0b9a5f8b710' -p145 -sg21 -I10 -sg22 -g142 -sg23 -Nsg28 -I2 -sg29 -Nsg30 -g24 -(S'\x07\xdf\x03\x04\x00\x00\x00\x00\x00\x00' -p146 -tp147 -Rp148 -sg31 -g133 -sg32 -g24 -(S'\x07\xdf\x03\x08\x00\x00\x00\x00\x00\x00' -p149 -tp150 -Rp151 -sg33 -Nsg35 -NsbsS'V2' -p152 -(ivisit -Visit -p153 -(dp154 -g4 -Nsg19 -I10 -sg8 -S'b33c7432-66dc-11e5-902c-e0b9a5f8b710' -p155 -sg21 -I20 -sg22 -g152 -sg23 -Nsg28 -I3 -sg29 -Nsg30 -Nsg31 -g142 -sg32 -Nsg33 -Nsg35 -Nsbssg52 -S'451-784-9856' -p156 -sS'otherphone' -p157 -S'514-874-9658' -p158 -sbsS'b339dc21-66dc-11e5-9e5e-e0b9a5f8b710' -p159 -(icandidate -Candidate -p160 -(dp161 -g4 -S'None' -p162 -sg6 -S'None' -p163 -sg8 -g159 -sg9 -S'Pierre' -p164 -sg11 -S'Tremblay' -p165 -sg13 -Nsg52 -S'547-852-9745' -p166 -sbsS'b339b50f-66dc-11e5-acb2-e0b9a5f8b710' -p167 -(icandidate -Candidate -p168 -(dp169 -g4 -Nsg6 -Nsg8 -g167 -sg9 -S'Alan' -p170 -sg11 -S'Parson' -p171 -sg13 -Nsg52 -S'451-874-8965' -p172 -sbs. \ No newline at end of file diff --git a/scheduler/documentation/namingConvention b/scheduler/documentation/namingConvention deleted file mode 100644 index 6e15538..0000000 --- a/scheduler/documentation/namingConvention +++ /dev/null @@ -1,17 +0,0 @@ -Naming convention according to https://www.python.org/dev/peps/pep-0008/ - -Indentation: Use 4 spaces per indentation level - -Package and Module Names: Should be called one per line (yes: import os; no: import os, tkinter, ttk) - -Class names: Use the CapWords convention - -Function names: Use lowercase with words separated by underscores - -Method names: Use lowercase with words separated by underscores - Use one leading underscore only for non-public methods and instance variables - To avoid name clashes with subclasses, use two leading underscores to invoke Python's name mangling rules. - -Instance variable names: Use lowercase with words separated by underscores - -Constant names: Use uppercase with words separated by underscores. Constant should only be defined at the module level. \ No newline at end of file diff --git a/scheduler/documentation/someBurningQuestions b/scheduler/documentation/someBurningQuestions deleted file mode 100644 index e1ebd72..0000000 --- a/scheduler/documentation/someBurningQuestions +++ /dev/null @@ -1 +0,0 @@ -How do deal with the shebang line (#!/usr/bin/env python) for Window and Mac? \ No newline at end of file diff --git a/scheduler/studydata b/scheduler/studydata deleted file mode 100644 index 94c4f52..0000000 --- a/scheduler/studydata +++ /dev/null @@ -1,66 +0,0 @@ -(dp0 -S'b33966f0-66dc-11e5-8b46-e0b9a5f8b710' -p1 -(ivisit -VisitSetup -p2 -(dp3 -S'visitmargin' -p4 -I2 -sS'uid' -p5 -g1 -sS'visitwindow' -p6 -I10 -sS'visitlabel' -p7 -S'V1' -p8 -sS'rank' -p9 -I2 -sS'actions' -p10 -NsS'previousvisit' -p11 -S'V0' -p12 -sbsS'b32cbcc0-66dc-11e5-8d68-e0b9a5f8b710' -p13 -(ivisit -VisitSetup -p14 -(dp15 -g4 -Nsg5 -g13 -sg6 -Nsg7 -g12 -sg9 -I1 -sg10 -Nsg11 -NsbsS'b33966f1-66dc-11e5-83c1-e0b9a5f8b710' -p16 -(ivisit -VisitSetup -p17 -(dp18 -g4 -I10 -sg5 -g16 -sg6 -I20 -sg7 -S'V2' -p19 -sg9 -I3 -sg10 -Nsg11 -g8 -sbs. \ No newline at end of file diff --git a/scheduler/tests/create1_StudySetup.py b/scheduler/tests/create1_StudySetup.py deleted file mode 100644 index d3cc51e..0000000 --- a/scheduler/tests/create1_StudySetup.py +++ /dev/null @@ -1,23 +0,0 @@ -from scheduler import visit -import lib as datamanagement - -#GUI: Setting up the sequence of visit - must be done prior to anything else -#setup the VisitSetup instances to match the study visit sequence -#the visitdata of each instance will serve to populate individual Visit() instances -#TODO visitlabels MUST start with a letter - ADD regex -#TODO visitlabels MUST be unique - -studydb = {} -studyvisit = visit.VisitSetup(1, 'V0') -studydb[studyvisit.uid] = studyvisit - -studyvisit = visit.VisitSetup(2, 'V1', 'V0', 10, 2) -studydb[studyvisit.uid] = studyvisit - -studyvisit = visit.VisitSetup(3, 'V2', 'V1', 20, 10) -studydb[studyvisit.uid] = studyvisit - - -datamanagement.save_study_data(studydb) - -#TESTED diff --git a/scheduler/tests/create2_CandidateList_test.py b/scheduler/tests/create2_CandidateList_test.py deleted file mode 100644 index 25b944c..0000000 --- a/scheduler/tests/create2_CandidateList_test.py +++ /dev/null @@ -1,30 +0,0 @@ -from scheduler import candidate -import lib as datamanagement - - -#GUI: Setting up a list of candidate -#print '\nGUI: ENTERING DATA FOR MULTIPLE CANDIDATES' -#TODO add duplicate control by checking for name+phone number -candidatedb = {} - -candidatedata = candidate.Candidate('Billy', 'Roberts', '451-784-9856', otherphone='514-874-9658') -candidatedb[candidatedata.uid] = candidatedata - -candidatedata = candidate.Candidate('Sue', 'Allen', '451-874-9632') -candidatedb[candidatedata.uid] = candidatedata - -candidatedata = candidate.Candidate('Alan', 'Parson', '451-874-8965') -candidatedb[candidatedata.uid] = candidatedata - -candidatedata = candidate.Candidate('Pierre', 'Tremblay', '547-852-9745') -candidatedb[candidatedata.uid] = candidatedata - -candidatedata = candidate.Candidate('Alain', 'Jeanson', '245-874-6321') -candidatedb[candidatedata.uid] = candidatedata - -candidatedata = candidate.Candidate('Marc', 'St-Pierre', '412-897-9874') -candidatedb[candidatedata.uid] = candidatedata - -datamanagement.save_candidate_data(candidatedb) - -#TESTED diff --git a/scheduler/tests/create3_CandidateVisit.py b/scheduler/tests/create3_CandidateVisit.py deleted file mode 100644 index 25e438b..0000000 --- a/scheduler/tests/create3_CandidateVisit.py +++ /dev/null @@ -1,25 +0,0 @@ -import lib as datamanagement - -#loading data -candidatedb = dict(datamanagement.read_candidate_data()) -#GUI: selecting a candidate from db -#print '\nGUI: SELECTING ONE CANDIDATE,...' -happycandidate = candidatedb.get("a045a530-a31f-11e4-9c66-fc4dd4d3c3f3") -#Upon setting the first visit with a candidate, we will dump a complete visitset into candidate.visitset -#set values of : visitlabel, visitdate, visittime, where, whom -#print '\nGUI: ...AND SETTING UP THE FIRST VISIT (date, time...)' -#print 'system: collecting information from application' -visitlabel = 'V0' #TODO selection from droplist -visitdate = '2014-12-22' #TODO add regex controls -visittime = '13:15' #TODO add regex controls -visitwhere = 'CRIUGM lobby' -visitwhom = 'me' -#print 'system: create visitset instance if necessary and add collected information to proper visit in Candidate.visitset' -thisvisit = happycandidate.set_visit_date(visitlabel, visitdate, visittime, visitwhere, visitwhom) -happycandidate.set_next_visit_window(happycandidate, thisvisit) -#print'\nGUI: NOW THIS CANDIDATE HAS A DATE FOR HIS/HER FIRST VISIT + A RANGE FOR THE FOLLOWING VISIT' -#print '\nGUI: put on screen all active visits (sorted by datetime) => see test3.py' -datamanagement.save_candidate_data(candidatedb) - - -#TESTED diff --git a/scheduler/tests/create4_PopulateVisits.py b/scheduler/tests/create4_PopulateVisits.py deleted file mode 100644 index 8f2df90..0000000 --- a/scheduler/tests/create4_PopulateVisits.py +++ /dev/null @@ -1,84 +0,0 @@ -""" -a045a534-a31f-11e4-bc02-fc4dd4d3c3f3 -a04493c0-a31f-11e4-9414-fc4dd4d3c3f3 -a045a533-a31f-11e4-aba9-fc4dd4d3c3f3 -a045a532-a31f-11e4-96a6-fc4dd4d3c3f3 -a045a531-a31f-11e4-a1c4-fc4dd4d3c3f3 -""" - -import lib as datamanagement - -db = dict(datamanagement.read_candidate_data()) - -#get all key values -keylist = [] -for key in db: - keylist.append(key) - -candidate1 = db.get(keylist[0]) -candidate2 = db.get(keylist[1]) -candidate3 = db.get(keylist[2]) -candidate4 = db.get(keylist[3]) -candidate5 = db.get(keylist[4]) - -visitlabel = 'V0' #TODO selection from droplist -visitdate = '2014-12-25' #TODO add regex controls -visittime = '13:15' #TODO add regex controls -visitwhere = 'CRIUGM lobby' -visitwhom = 'Annie' -thisvisit = candidate1.set_visit_date(visitlabel, visitdate, visittime, visitwhere, visitwhom) -candidate1.set_next_visit_window(candidate1, thisvisit) - -visitlabel = 'V1' #TODO selection from droplist -visitdate = '2014-12-27' #TODO add regex controls -visittime = '14:30' #TODO add regex controls -visitwhere = 'CRIUGM M-124' -visitwhom = 'Jean' -thisvisit = candidate2.set_visit_date(visitlabel, visitdate, visittime, visitwhere, visitwhom) -candidate2.set_next_visit_window(candidate2, thisvisit) - -visitlabel = 'V1' #TODO selection from droplist -visitdate = '2015-01-13' #TODO add regex controls -visittime = '09:15' #TODO add regex controls -visitwhere = 'McDo' -visitwhom = 'Scott' -thisvisit = candidate3.set_visit_date(visitlabel, visitdate, visittime, visitwhere, visitwhom) -candidate3.set_next_visit_window(candidate3, thisvisit) - -visitlabel = 'V0' #TODO selection from droplist -visitdate = '2015-02-24' #TODO add regex controls -visittime = '15:30' #TODO add regex controls -visitwhere = 'IGA' -visitwhom = 'Charlie' -thisvisit = candidate4.set_visit_date(visitlabel, visitdate, visittime, visitwhere, visitwhom) -candidate4.set_next_visit_window(candidate4, thisvisit) - -datamanagement.save_candidate_data(db) - - -""" -######################################################################### -######################################################################### - -print '\nGUI: print on screen all active visits (sorted by datetime)' -currentvisitset = {} -for key, value in candidatedb.iteritems(): - if candidatedb[key].visitset is not None: #skip the search if visitset = None - currentvisitset = candidatedb[key].visitset #set this candidate.visitset for the next step - #gather information about the candidate - candidatekey = candidatedb[key].uid #not printed on screen but saved with the new Scheduler object (after all it is the candidate unique id^^) - candidatefirstname = candidatedb[key].firstname - candidatelastname = candidatedb[key].lastname - for key, value in currentvisitset.iteritems(): - if currentvisitset[key].status is not None: - visitlabel = currentvisitset[key].visitlabel - when = currentvisitset[key].when - where = currentvisitset[key].where - whom = currentvisitset[key].withwhom - status = currentvisitset[key].status - print candidatefirstname, candidatelastname, visitlabel, when, where, whom, status - #create a new scheduler object with these informations - - - -""" diff --git a/scheduler/tests/test.py b/scheduler/tests/test.py deleted file mode 100644 index 5703f12..0000000 --- a/scheduler/tests/test.py +++ /dev/null @@ -1,6 +0,0 @@ -import lib as utilities -import lib as datamanagement - -db = dict(datamanagement.read_candidate_data()) - -utilities.printobject(db) diff --git a/scheduler/tests/test1.py b/scheduler/tests/test1.py deleted file mode 100644 index ec09e8f..0000000 --- a/scheduler/tests/test1.py +++ /dev/null @@ -1,7 +0,0 @@ -from scheduler import candidate - -happycandidate = candidate.Candidate('Billy', 'Roberts', '451-784-9856', otherphone='514-874-9658') - - -for attr, value in happycandidate.__dict__.iteritems(): - print attr, " = ", value diff --git a/scheduler/tests/test5.py b/scheduler/tests/test5.py deleted file mode 100644 index f26d399..0000000 --- a/scheduler/tests/test5.py +++ /dev/null @@ -1,19 +0,0 @@ -import Tkinter as tk -import ttk - -class App: - def __init__(self): - self.root = tk.Tk() - self.tree = ttk.Treeview() - self.tree.pack() - for i in range(10): - self.tree.insert("", "end", text="Item %s" % i) - self.tree.bind("", self.OnDoubleClick) - self.root.mainloop() - - def OnDoubleClick(self, event): - item = self.tree.selection()[0] - print "you clicked on", self.tree.item(item,"text") - -if __name__ == "__main__": - app=App() diff --git a/scheduler/visit.py b/scheduler/visit.py deleted file mode 100644 index c9644ce..0000000 --- a/scheduler/visit.py +++ /dev/null @@ -1,93 +0,0 @@ -#import standard packages -#import internal packages -import datetime - -import lib.utilities as utilities - - -class VisitSetup(): - """ - The VisitSetup() class is used to define a study (or project) in terms of sequence of visits and also serves as a - base class for Visit(VisitSetup). A study (or project) can have as many visits as required. There is no class for - study as it is merely a simple dictionnary. - - Code example: This study contains 3 visits. Since V0 is the first visit, it doesn't have any values for - 'previousvisit', 'visitwindow' and ' visitmargin' - study = {} - visit = visit.VisitSetup(1, 'V0') #a uid (uuid1) is automatically generated - study[visit.uid] = visit #VisitSetup.uid is a unique ID used as key - visit = visit.VisitSetup(2, 'V1', 'V0', 10, 2) - study[visit.uid] = visit - visit = visit.VisitSetup(3, 'V2', 'V1', 20, 10) - study[visit.uid] = visit - - This is the parent class of Visit(VisitSetup), furthermore Visit(VisitSetup) objects will be 'instanciated' from - each instance of VisitSetup(object). - Both VisitSetup(object) class and instances are used to create individual instances of Visit(VisitSetup) - - Attributes: - uid: A unique identifier using python's uuid1 method. Used as key to store and retrieve objects from - files and/or dictionaries. - rank: The rank of the visit in the sequence (int). Useful to sort the visits in order of occurrence. - visitlabel: The label of the visit (string) such as V1, V2 or anything else the user may come up with - previousvisit: The visitlabel (string) of the visit occurring before this one. Used to plan this visit based on - the date of the previous visit. (default to None) - visitwindow: The number of days (int) between this visit and the previous visit. (default to None) - visitmargin: The margin (in number of days (int)) that is an allowed deviation (a +/- few days ). Basically, - this allow the 'calculation' of a date window when this visit should occur. (default to None) - mandatory: Indicate if this visit is mandatory. Default to Yes - actions: A list of action points (or simply reminders) specific to that visit (i.e.'reserve room 101'). - This is not implemented yet (default to None) - """ - - def __init__(self, rank, visitlabel, previousvisit = None, visitwindow = None, visitmargin = None, actions = None, uid=None): - self.uid = utilities.generate_uid() - self.rank = rank - self.visitlabel = visitlabel - self.previousvisit = previousvisit - self.visitwindow = visitwindow - self.visitmargin = visitmargin - self.actions = actions #not implemented yet! - - -class Visit(VisitSetup): - """ - The Visit(VisitSetup) class help define individual visit of the candidate using VisitSetup(object) instances as 'templates'. - Upon creation of the first meeting with a candidate, the Candidate(object) instance will get a full set of Visit(VisitSetup) instances. - This set of visits is contained in Candidate.visitset - Each time a visit is being setup, a 'time window' is calculated to define the earliest and latest date at which - the 'nextVisit' should occur. - - Attributes: (In addition of parent class attributes.) - when: Date at which this visit is occurring. (default to None) - when_earliest: Earliest date when this visit should occur. Set to value when previous visit is activated. (default to None) - when_latest: Latest date when this visit should occur. Set to value when previous visit is activated. (default to None) - where: Place where this meeting is taking place. (default to None) - whitwhom: Professional meeting the study candidate at the reception. (default to None) - status: Status of this visit. Set to active when 'when' is set (default to None) - """ - def __init__(self, rank, visitlabel, previousvisit, visitwindow, visitmargin, actions=None, uid=None, when = None, - whenearliest = None, whenlatest = None, where = None, withwhom = None, status = None): - VisitSetup.__init__(self, rank, visitlabel, previousvisit, visitwindow, visitmargin, actions, uid) - self.when = when - self.whenearliest = whenearliest - self.whenlatest = whenlatest - self.where = where - self.withwhom = withwhom - self.status = status - - def visit_date_range(self): - #need to handle the case where a visit has no dates - #this works but Exception handling seems to broad - try: - early_date = datetime.datetime.date(self.whenearliest) - late_date = datetime.datetime.date(self.whenlatest) - date_range = str(early_date), '<>', str(late_date) - except Exception as e: - #print e - date_range = "" - return date_range - - - - From 50f68b69882f2d8d6177009b169b7ac8d03c6849 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Fri, 24 Jun 2016 10:02:42 -0400 Subject: [PATCH 43/89] Removed scheduler folder --- scheduler/.idea/.name | 1 - scheduler/.idea/codeStyleSettings.xml | 13 - scheduler/.idea/dictionaries/PierreEMorin.xml | 3 - scheduler/.idea/dictionaries/pemorin.xml | 7 - scheduler/.idea/misc.xml | 14 - scheduler/.idea/modules.xml | 8 - scheduler/.idea/scheduler.iml | 8 - scheduler/.idea/workspace.xml | 944 ------------------ scheduler/__init__.py | 8 - 9 files changed, 1006 deletions(-) delete mode 100644 scheduler/.idea/.name delete mode 100644 scheduler/.idea/codeStyleSettings.xml delete mode 100644 scheduler/.idea/dictionaries/PierreEMorin.xml delete mode 100644 scheduler/.idea/dictionaries/pemorin.xml delete mode 100644 scheduler/.idea/misc.xml delete mode 100644 scheduler/.idea/modules.xml delete mode 100644 scheduler/.idea/scheduler.iml delete mode 100644 scheduler/.idea/workspace.xml delete mode 100644 scheduler/__init__.py diff --git a/scheduler/.idea/.name b/scheduler/.idea/.name deleted file mode 100644 index ec7c259..0000000 --- a/scheduler/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -scheduler \ No newline at end of file diff --git a/scheduler/.idea/codeStyleSettings.xml b/scheduler/.idea/codeStyleSettings.xml deleted file mode 100644 index 7fb8ed0..0000000 --- a/scheduler/.idea/codeStyleSettings.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/scheduler/.idea/dictionaries/PierreEMorin.xml b/scheduler/.idea/dictionaries/PierreEMorin.xml deleted file mode 100644 index c82ff06..0000000 --- a/scheduler/.idea/dictionaries/PierreEMorin.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/scheduler/.idea/dictionaries/pemorin.xml b/scheduler/.idea/dictionaries/pemorin.xml deleted file mode 100644 index 877122b..0000000 --- a/scheduler/.idea/dictionaries/pemorin.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - dictionnary - - - \ No newline at end of file diff --git a/scheduler/.idea/misc.xml b/scheduler/.idea/misc.xml deleted file mode 100644 index df245c4..0000000 --- a/scheduler/.idea/misc.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/scheduler/.idea/modules.xml b/scheduler/.idea/modules.xml deleted file mode 100644 index 7e8c9ec..0000000 --- a/scheduler/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/scheduler/.idea/scheduler.iml b/scheduler/.idea/scheduler.iml deleted file mode 100644 index 0e18433..0000000 --- a/scheduler/.idea/scheduler.iml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/scheduler/.idea/workspace.xml b/scheduler/.idea/workspace.xml deleted file mode 100644 index 8f3b8dd..0000000 --- a/scheduler/.idea/workspace.xml +++ /dev/null @@ -1,944 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Buildout - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1437155263605 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/scheduler/__init__.py b/scheduler/__init__.py deleted file mode 100644 index 5c9d53e..0000000 --- a/scheduler/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -""" -From the documentation at https://docs.python.org/2/tutorial/modules.html#packages - -The __init__.py files are required to make Python treat the directories as containing packages; this is done to prevent -directories with a common name, such as string, from unintentionally hiding valid modules that occur later on the module -search path. In the simplest case, __init__.py can just be an empty file, but it can also execute initialization code -for the package or set the __all__ variable, described later. -""" From 6d216c2ab63e815ae237a597f0e230ba62594f7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Fri, 24 Jun 2016 10:03:37 -0400 Subject: [PATCH 44/89] Fixed a minor import bug --- dicat/scheduler_candidate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dicat/scheduler_candidate.py b/dicat/scheduler_candidate.py index 631c7ed..3ab5d94 100644 --- a/dicat/scheduler_candidate.py +++ b/dicat/scheduler_candidate.py @@ -1,7 +1,7 @@ # import standard packages import datetime -from scheduler import visit +from scheduler_visit import Visit # import internal packages import lib.datamanagement as DataManagement From ab3e92a0f0244a690062bd74974aec08b7bd9f94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Fri, 24 Jun 2016 10:09:36 -0400 Subject: [PATCH 45/89] Removed unnecessary dictionary --- candidatedata | 567 -------------------------------------------------- studydata | 66 ------ 2 files changed, 633 deletions(-) delete mode 100644 candidatedata delete mode 100644 studydata diff --git a/candidatedata b/candidatedata deleted file mode 100644 index 8196330..0000000 --- a/candidatedata +++ /dev/null @@ -1,567 +0,0 @@ -(dp0 -S'c0f265f3-1c5e-11e6-a7db-406c8f0ea1c5' -p1 -(ischeduler_candidate -Candidate -p2 -(dp3 -S'status' -p4 -Vactive -p5 -sS'pscid' -p6 -NsS'uid' -p7 -g1 -sS'firstname' -p8 -S'Pierre' -p9 -sS'lastname' -p10 -S'Tremblay' -p11 -sS'visitset' -p12 -(dp13 -S'V0' -p14 -(ischeduler.visit -Visit -p15 -(dp16 -g4 -S'active' -p17 -sS'visitmargin' -p18 -Nsg7 -S'c0f3409e-1c5e-11e6-b618-406c8f0ea1c5' -p19 -sS'visitwindow' -p20 -NsS'visitlabel' -p21 -g14 -sS'when' -p22 -cdatetime -datetime -p23 -(S'\x07\xde\x0c\x19\r\x0f\x00\x00\x00\x00' -p24 -tp25 -Rp26 -sS'rank' -p27 -I1 -sS'actions' -p28 -NsS'whenearliest' -p29 -NsS'previousvisit' -p30 -NsS'whenlatest' -p31 -NsS'where' -p32 -S'CRIUGM lobby' -p33 -sS'withwhom' -p34 -S'Annie' -p35 -sbsS'V1' -p36 -(ischeduler.visit -Visit -p37 -(dp38 -g4 -S'tentative' -p39 -sg18 -I2 -sg7 -S'c0f342d9-1c5e-11e6-b2e1-406c8f0ea1c5' -p40 -sg20 -I10 -sg21 -g36 -sg22 -Nsg27 -I2 -sg28 -Nsg29 -g23 -(S'\x07\xdf\x01\x02\x00\x00\x00\x00\x00\x00' -p41 -tp42 -Rp43 -sg30 -g14 -sg31 -g23 -(S'\x07\xdf\x01\x06\x00\x00\x00\x00\x00\x00' -p44 -tp45 -Rp46 -sg32 -Nsg34 -NsbsS'V2' -p47 -(ischeduler.visit -Visit -p48 -(dp49 -g4 -Nsg18 -I10 -sg7 -S'c0f34454-1c5e-11e6-bef8-406c8f0ea1c5' -p50 -sg20 -I20 -sg21 -g47 -sg22 -Nsg27 -I3 -sg28 -Nsg29 -Nsg30 -g36 -sg31 -Nsg32 -Nsg34 -NsbssS'phone' -p51 -S'547-852-9745' -p52 -sbsS'c0f28b99-1c5e-11e6-9a6e-406c8f0ea1c5' -p53 -(ischeduler_candidate -Candidate -p54 -(dp55 -g4 -g5 -sg6 -Nsg7 -g53 -sg8 -S'Marc' -p56 -sg10 -S'St-Pierre' -p57 -sg12 -(dp58 -S'V0' -p59 -(ischeduler.visit -Visit -p60 -(dp61 -g4 -g17 -sg18 -Nsg7 -S'c1079242-1c5e-11e6-826a-406c8f0ea1c5' -p62 -sg20 -Nsg21 -g59 -sg22 -g23 -(S'\x07\xde\x0c\x1b\x0e\x1e\x00\x00\x00\x00' -p63 -tp64 -Rp65 -sg27 -I1 -sg28 -Nsg29 -Nsg30 -Nsg31 -Nsg32 -S'CRIUGM M-124' -p66 -sg34 -S'Jean' -p67 -sbsS'V1' -p68 -(ischeduler.visit -Visit -p69 -(dp70 -g4 -g17 -sg18 -I2 -sg7 -S'c10793bd-1c5e-11e6-8d22-406c8f0ea1c5' -p71 -sg20 -I10 -sg21 -g68 -sg22 -g23 -(S'\x07\xdf\x02\x07\t\x0f\x00\x00\x00\x00' -p72 -tp73 -Rp74 -sg27 -I2 -sg28 -Nsg29 -g23 -(S'\x07\xdf\x01\x04\x00\x00\x00\x00\x00\x00' -p75 -tp76 -Rp77 -sg30 -g59 -sg31 -g23 -(S'\x07\xdf\x01\x08\x00\x00\x00\x00\x00\x00' -p78 -tp79 -Rp80 -sg32 -g66 -sg34 -g67 -sbsS'V2' -p81 -(ischeduler.visit -Visit -p82 -(dp83 -g4 -g39 -sg18 -I10 -sg7 -S'c1079499-1c5e-11e6-b744-406c8f0ea1c5' -p84 -sg20 -I20 -sg21 -g81 -sg22 -Nsg27 -I3 -sg28 -Nsg29 -g23 -(S'\x07\xdf\x02\x11\x00\x00\x00\x00\x00\x00' -p85 -tp86 -Rp87 -sg30 -g68 -sg31 -g23 -(S'\x07\xdf\x03\t\x00\x00\x00\x00\x00\x00' -p88 -tp89 -Rp90 -sg32 -Nsg34 -Nsbssg51 -S'412-897-9874' -p91 -sbsS'c0f27735-1c5e-11e6-9c62-406c8f0ea1c5' -p92 -(ischeduler_candidate -Candidate -p93 -(dp94 -g4 -g5 -sg6 -Nsg7 -g92 -sg8 -S'Alain' -p95 -sg10 -S'Jeanson' -p96 -sg12 -(dp97 -S'V0' -p98 -(ischeduler.visit -Visit -p99 -(dp100 -g4 -g17 -sg18 -Nsg7 -S'c1081b05-1c5e-11e6-a1df-406c8f0ea1c5' -p101 -sg20 -Nsg21 -g98 -sg22 -g23 -(S'\x07\xdf\x01\r\t\x0f\x00\x00\x00\x00' -p102 -tp103 -Rp104 -sg27 -I1 -sg28 -Nsg29 -Nsg30 -Nsg31 -Nsg32 -S'McDo' -p105 -sg34 -S'Scott' -p106 -sbsS'V1' -p107 -(ischeduler.visit -Visit -p108 -(dp109 -g4 -g39 -sg18 -I2 -sg7 -S'c1081c11-1c5e-11e6-9c31-406c8f0ea1c5' -p110 -sg20 -I10 -sg21 -g107 -sg22 -Nsg27 -I2 -sg28 -Nsg29 -g23 -(S'\x07\xdf\x01\x15\x00\x00\x00\x00\x00\x00' -p111 -tp112 -Rp113 -sg30 -g98 -sg31 -g23 -(S'\x07\xdf\x01\x19\x00\x00\x00\x00\x00\x00' -p114 -tp115 -Rp116 -sg32 -Nsg34 -NsbsS'V2' -p117 -(ischeduler.visit -Visit -p118 -(dp119 -g4 -Nsg18 -I10 -sg7 -S'c1081ccf-1c5e-11e6-9dee-406c8f0ea1c5' -p120 -sg20 -I20 -sg21 -g117 -sg22 -Nsg27 -I3 -sg28 -Nsg29 -Nsg30 -g107 -sg31 -Nsg32 -Nsg34 -Nsbssg51 -S'245-874-6321' -p121 -sbsS'c0f25545-1c5e-11e6-86d5-406c8f0ea1c5' -p122 -(ischeduler_candidate -Candidate -p123 -(dp124 -g4 -g5 -sg6 -Nsg7 -g122 -sg8 -S'Alan' -p125 -sg10 -S'Parson' -p126 -sg12 -(dp127 -S'V0' -p128 -(ischeduler.visit -Visit -p129 -(dp130 -g4 -g17 -sg18 -Nsg7 -S'c1086c80-1c5e-11e6-a83b-406c8f0ea1c5' -p131 -sg20 -Nsg21 -g128 -sg22 -g23 -(S'\x07\xdf\x02\x18\x0f\x1e\x00\x00\x00\x00' -p132 -tp133 -Rp134 -sg27 -I1 -sg28 -Nsg29 -Nsg30 -Nsg31 -Nsg32 -S'IGA' -p135 -sg34 -S'Charlie' -p136 -sbsS'V1' -p137 -(ischeduler.visit -Visit -p138 -(dp139 -g4 -g39 -sg18 -I2 -sg7 -S'c1086d7a-1c5e-11e6-beef-406c8f0ea1c5' -p140 -sg20 -I10 -sg21 -g137 -sg22 -Nsg27 -I2 -sg28 -Nsg29 -g23 -(S'\x07\xdf\x03\x04\x00\x00\x00\x00\x00\x00' -p141 -tp142 -Rp143 -sg30 -g128 -sg31 -g23 -(S'\x07\xdf\x03\x08\x00\x00\x00\x00\x00\x00' -p144 -tp145 -Rp146 -sg32 -Nsg34 -NsbsS'V2' -p147 -(ischeduler.visit -Visit -p148 -(dp149 -g4 -Nsg18 -I10 -sg7 -S'c1086e42-1c5e-11e6-bd68-406c8f0ea1c5' -p150 -sg20 -I20 -sg21 -g147 -sg22 -Nsg27 -I3 -sg28 -Nsg29 -Nsg30 -g137 -sg31 -Nsg32 -Nsg34 -Nsbssg51 -S'451-874-8965' -p151 -sbsS'c0f23e75-1c5e-11e6-b22c-406c8f0ea1c5' -p152 -(ischeduler_candidate -Candidate -p153 -(dp154 -g4 -S'None' -p155 -sg6 -S'1234567' -p156 -sg7 -g152 -sg8 -S'Sue' -p157 -sg10 -S'Allen' -p158 -sg12 -Nsg51 -S'451-874-9632' -p159 -sbsS'c0f22d9e-1c5e-11e6-bb63-406c8f0ea1c5' -p160 -(ischeduler_candidate -Candidate -p161 -(dp162 -g4 -S'None' -p163 -sg6 -S'None' -p164 -sg7 -g160 -sg8 -S'Billy' -p165 -sg10 -S'Roberts' -p166 -sg12 -Nsg51 -S'451-784-9856' -p167 -sS'otherphone' -p168 -S'514-874-9658' -p169 -sbs. \ No newline at end of file diff --git a/studydata b/studydata deleted file mode 100644 index d0820c7..0000000 --- a/studydata +++ /dev/null @@ -1,66 +0,0 @@ -(dp0 -S'c0f1fc21-1c5e-11e6-b766-406c8f0ea1c5' -p1 -(ischeduler_visit -VisitSetup -p2 -(dp3 -S'visitmargin' -p4 -I2 -sS'uid' -p5 -g1 -sS'visitwindow' -p6 -I10 -sS'visitlabel' -p7 -S'V1' -p8 -sS'rank' -p9 -I2 -sS'actions' -p10 -NsS'previousvisit' -p11 -S'V0' -p12 -sbsS'c0f0636b-1c5e-11e6-9f17-406c8f0ea1c5' -p13 -(ischeduler_visit -VisitSetup -p14 -(dp15 -g4 -Nsg5 -g13 -sg6 -Nsg7 -g12 -sg9 -I1 -sg10 -Nsg11 -NsbsS'c0f214b3-1c5e-11e6-91bd-406c8f0ea1c5' -p16 -(ischeduler_visit -VisitSetup -p17 -(dp18 -g4 -I10 -sg5 -g16 -sg6 -I20 -sg7 -S'V2' -p19 -sg9 -I3 -sg10 -Nsg11 -g8 -sbs. \ No newline at end of file From e41bef6b945462ab06f3d830323ba12d83438e15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Fri, 24 Jun 2016 12:40:35 -0400 Subject: [PATCH 46/89] Imported the candidate data based on the XML values --- dicat/lib/datamanagement.py | 26 +++++++++++++++------ dicat/scheduler_application.py | 16 ++++++++----- dicat/ui/datatable.py | 42 +++++++++++++++++++++------------- 3 files changed, 55 insertions(+), 29 deletions(-) diff --git a/dicat/lib/datamanagement.py b/dicat/lib/datamanagement.py index 82c630b..2364da1 100644 --- a/dicat/lib/datamanagement.py +++ b/dicat/lib/datamanagement.py @@ -21,16 +21,28 @@ def read_xmlfile(xmlfile): print message #TODO: create a log class to display the messages -def read_candidate_data(): +def read_candidate_data(xmlfile): """Read and return the content of a file called candidatedata. Returns nothing if file doesn't exist""" + data = {} #check to see if file exists before loading it - if os.path.isfile("candidatedata"): - #load file - db = pickle.load(open("candidatedata", "rb")) + if os.path.isfile(xmlfile): + # read the xml file + xmldoc = read_xmlfile(xmlfile) + xmldata = xmldoc.getElementsByTagName('data')[0] + xmlcandlist = xmldata.getElementsByTagName('Candidate') + for cand in xmlcandlist: + data[cand] = {} + for elem in cand.childNodes: + tag = elem.localName + if not tag or tag == "Visit": + continue + val = cand.getElementsByTagName(elem.localName)[0].firstChild.nodeValue + data[cand][tag] = val else: - db = "" - return db + data = "" + + return data def save_candidate_data(data): @@ -60,5 +72,5 @@ def save_study_data(data): #self-test "module" TODO remove if __name__ == '__main__': print 'testing module: datamanagement.py' - data=dict(read_candidate_data()); + data=dict(read_candidate_data("../new_data_test.xml")); print data; diff --git a/dicat/scheduler_application.py b/dicat/scheduler_application.py index a82a192..65f46a3 100644 --- a/dicat/scheduler_application.py +++ b/dicat/scheduler_application.py @@ -57,13 +57,17 @@ def initialize(self): 'when', 'where', 'status' ) - self.visit_table = DataTable.VisitList( self.visit_pane, - visit_column_headers - ) - self.visit_table.pack(side=BOTTOM, expand=YES, fill=BOTH) - candidate_column_headers = ('firstname', 'lastname', 'phone', 'status') +# self.visit_table = DataTable.VisitList( self.visit_pane, +# visit_column_headers, +# xmlfile +# ) +# self.visit_table.pack(side=BOTTOM, expand=YES, fill=BOTH) + candidate_column_headers = ( 'identifier', 'firstname', 'lastname', + 'gender', 'phone', 'status' + ) self.data_table = DataTable.ParticipantsList( self.candidate_pane, - candidate_column_headers + candidate_column_headers, + xmlfile ) self.data_table.pack(side=BOTTOM, expand=YES, fill=BOTH) diff --git a/dicat/ui/datatable.py b/dicat/ui/datatable.py index 8828dd8..a789517 100644 --- a/dicat/ui/datatable.py +++ b/dicat/ui/datatable.py @@ -97,28 +97,38 @@ class ParticipantsList(DataTable) takes care of the data table holding the list That list is comprised of all participants (even those that have not been called once. """ - def __init__(self, parent, colheaders): # expected is dataset + def __init__(self, parent, colheaders, xmlfile): # expected is dataset DataTable.__init__(self, parent, colheaders) self.colheaders = colheaders - self.load_data() + self.load_data(xmlfile) # TODO add these color settings in a 'settings and preferences section of the app' self.datatable.tag_configure('active', background='#F1F8FF') # TODO replace active tag by status variable value - def load_data(self): - data = dict(DataManagement.read_candidate_data()) + def load_data(self, xmlfile): + data = DataManagement.read_candidate_data(xmlfile) + try: for key in data: - if data[key].status is None: - status = '' + + if "CandidateStatus" not in data[key].keys(): + status = "" else: - status = data[key].status + status = data[key]["CandidateStatus"] + + if "PhoneNumber" not in data[key].keys(): + phone = "" + else: + phone = data[key]["PhoneNumber"] self.datatable.insert( '', 'end', - values=[data[key].firstname, - data[key].lastname, data[key].phone, - status + values=[ data[key]["Identifier"], + data[key]["FirstName"], + data[key]["LastName"], + data[key]["Gender"], + phone, + status ], - tags=(status, data[key].uid) - ) + tags=(status, data[key]["Identifier"]) + ) except Exception as e: print "datatable.ParticipantsList.load_data ", str(e) # TODO proper exception handling pass @@ -130,16 +140,16 @@ class VisitList(DataTable) takes care of the data table holding the list of all even those that have not been confirmed yet. """ - def __init__(self, parent, colheaders): + def __init__(self, parent, colheaders, xmlfile): DataTable.__init__(self, parent, colheaders) self.colheaders = colheaders - self.load_data() + self.load_data(xmlfile) # TODO add these color settings in a 'settings and preferences section of the app' self.datatable.tag_configure('active', background='#F1F8FF') # TODO change for non-language parameter self.datatable.tag_configure('tentative', background='#F0F0F0') # TODO change for non-language parameter - def load_data(self): - data = dict(DataManagement.read_candidate_data()) + def load_data(self, xmlfile): + data = dict(DataManagement.read_candidate_data(xmlfile)) for key, value in data.iteritems(): if data[key].visitset is not None: # skip the search if visitset = None current_visitset = data[key].visitset # set this candidate.visitset for the next step From 2a5b6a6dc3540e9acb2bba5ec883ba09dbc61ad0 Mon Sep 17 00:00:00 2001 From: Tara Campbell Date: Sat, 25 Jun 2016 09:54:07 +0200 Subject: [PATCH 47/89] Fixing window closing buttons. --- dicat/ui/datawindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dicat/ui/datawindow.py b/dicat/ui/datawindow.py index 98515fb..79aafcc 100644 --- a/dicat/ui/datawindow.py +++ b/dicat/ui/datawindow.py @@ -139,7 +139,7 @@ def body(self, master, candidate): # when if visit_list[x].when == None: visit = visit_list[x] - date_range = Visit.visit_date_range(visit) + date_range = visit.visit_date_range() label_visit_when = Label(self.schedule_pane, text=date_range) label_visit_when.grid(column=2, row=x+1, padx=5, pady=5, sticky=N+S+E+W) else: From 89406ca5477951222ce46568e74f55ba89936465 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Sat, 25 Jun 2016 04:11:19 -0400 Subject: [PATCH 48/89] Started the visit pane --- dicat/ui/datatable.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/dicat/ui/datatable.py b/dicat/ui/datatable.py index a789517..370bee9 100644 --- a/dicat/ui/datatable.py +++ b/dicat/ui/datatable.py @@ -154,10 +154,9 @@ def load_data(self, xmlfile): if data[key].visitset is not None: # skip the search if visitset = None current_visitset = data[key].visitset # set this candidate.visitset for the next step # gather information about the candidate - # this candidatekey is not printed on screen but saved with the new Scheduler object - candidatekey = data[key].uid - candidate_firstname = data[key].firstname - candidate_lastname = data[key].lastname + candidate_id = data[key]["Identifier"] + candidate_firstname = data[key]["FirstName"] + candidate_lastname = data[key]["LastName"] candidate_fullname = str(candidate_firstname + ' ' + candidate_lastname) for key, value in current_visitset.iteritems(): if current_visitset[key].status is not None: @@ -174,7 +173,7 @@ def load_data(self, xmlfile): try: self.datatable.insert('', 'end', values=[candidate_fullname, visit_label, when, where, status], - tags=(status, candidatekey, visit_label)) + tags=(status, candidate_id, visit_label)) except Exception as e: print "datatable.VisitList.load_data ", str(e) # TODO add proper error handling pass From 077bc645559f38d2d5f3a9b8932eb65a86188951 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Sat, 25 Jun 2016 05:31:24 -0400 Subject: [PATCH 49/89] Finished reading the data from the XML file on the front page of the scheduler. --- dicat/lib/datamanagement.py | 42 +++++- dicat/new_data_test.xml | 227 +++++++++++++++++++++++++++++++++ dicat/scheduler_application.py | 15 +-- dicat/ui/datatable.py | 52 +++++--- 4 files changed, 307 insertions(+), 29 deletions(-) create mode 100644 dicat/new_data_test.xml diff --git a/dicat/lib/datamanagement.py b/dicat/lib/datamanagement.py index 2364da1..85b9c8c 100644 --- a/dicat/lib/datamanagement.py +++ b/dicat/lib/datamanagement.py @@ -44,6 +44,46 @@ def read_candidate_data(xmlfile): return data +def read_visitset_data(xmlfile): + """Read and return the content of a file called candidatedata. Returns nothing if file doesn't exist""" + + data = {} + #check to see if file exists before loading it + if os.path.isfile(xmlfile): + # read the xml file + xmldoc = read_xmlfile(xmlfile) + xmldata = xmldoc.getElementsByTagName('data')[0] + xmlcandlist = xmldata.getElementsByTagName('Candidate') + for cand in xmlcandlist: + data[cand] = {} + for cand_elem in cand.childNodes: + cand_tag = cand_elem.localName + tags_to_ignore = ( "Gender", "DateOfBirth", + "PhoneNumber", "CandidateStatus" + ) + if not cand_tag or cand_tag in tags_to_ignore: + continue + if cand_tag == "Visit": + xmlvisitlist = cand.getElementsByTagName('Visit') + read_visit_data(xmlvisitlist, cand, data) + + val = cand.getElementsByTagName(cand_tag)[0].firstChild.nodeValue + data[cand][cand_tag] = val + else: + data = "" + + return data + +def read_visit_data(xmlvisitlist, cand, data): + data[cand]["VisitSet"] = {} + for visit in xmlvisitlist: + data[cand]["VisitSet"][visit] = {} + for visit_elem in visit.childNodes: + visit_tag = visit_elem.localName + if not visit_tag: + continue + visit_val = visit.getElementsByTagName(visit_tag)[0].firstChild.nodeValue + data[cand]["VisitSet"][visit][visit_tag] = visit_val def save_candidate_data(data): """Save data in a pickle file named candididatedata. @@ -72,5 +112,5 @@ def save_study_data(data): #self-test "module" TODO remove if __name__ == '__main__': print 'testing module: datamanagement.py' - data=dict(read_candidate_data("../new_data_test.xml")); + data=dict(read_visitset_data("../new_data_test.xml")); print data; diff --git a/dicat/new_data_test.xml b/dicat/new_data_test.xml new file mode 100644 index 0000000..138465d --- /dev/null +++ b/dicat/new_data_test.xml @@ -0,0 +1,227 @@ + + + + DICAT test + This is a test project. + + 0 + 100 + 2000 + 2050 + + + Project 1 + + Subproject 1 + + V0 + 1 + 25-35 + + V1 + 2 + 55-65 + + V2 + 3 + 95-105 + + + V3 + 4 + + + + Subproject 2 + + V0 + 1 + + + + + + + + MTL0002 + Sepia + Calamari + Female + 2015-06-14 + 444-555-6666 + active + + V0 + completed + + 2016-01-06 13:15:00 + 2016-01-06 14:15:00 + Douglas + Annie + + + V1 + completed + 2016-04-05 13:15:00 + 2016-04-05 14:15:00 + Douglas + Jennifer + + + V2 + scheduled + 2016-06-05 13:15:00 + 2016-06-05 14:15:00 + Douglas + Jennifer + + + + MTL0003 + Bibi + LaPraline + Female + 2010-08-12 + 450-345-6789 + excluded + + V0 + completed + + 2016-01-06 13:15:00 + 2016-01-06 14:15:00 + Douglas + Annie + + + + MTL0001 + Blues + Singer + Male + 2015-06-14 + 444-555-6666 + active + + V0 + completed + + 2016-01-06 13:15:00 + 2016-01-06 14:15:00 + Douglas + Annie + + + V1 + completed + 2016-04-05 13:15:00 + 2016-04-05 14:15:00 + Douglas + Jennifer + + + V2 + scheduled + 2016-06-05 13:15:00 + 2016-06-05 14:15:00 + Douglas + Jennifer + + + + MTL0006 + Ali + Gator + Male + 2014-02-05 + 514-758-8903 + active + + V0 + completed + + 2016-01-06 13:15:00 + 2016-01-06 14:15:00 + Douglas + Annie + + + V1 + scheduled + 2016-04-05 13:15:00 + 2016-04-05 14:15:00 + Douglas + Jennifer + + + + MTL0004 + Pikachu + Pokemon + Male + 1996-01-01 + 543-453-5432 + withdrawn + + V0 + completed + + 2016-01-06 13:15:00 + 2016-01-06 14:15:00 + Douglas + Annie + + + + MTL0005 + Bilou + Doudou + Female + 2014-02-03 + 450-758-9385 + withdrawn + + V0 + completed + + 2016-01-06 13:15:00 + 2016-01-06 14:15:00 + Douglas + Annie + + + + MTL9999 + Lego + Phantom + Male + 2012-04-29 + 432-654-9093 + active + + V0 + completed + + 2016-01-06 13:15:00 + 2016-01-06 14:15:00 + Douglas + Annie + + + V1 + scheduled + 2016-04-05 13:15:00 + 2016-04-05 14:15:00 + Douglas + Jennifer + + + + MTL0025 + Santa + Claus + Male + 2015-12-25 + + + \ No newline at end of file diff --git a/dicat/scheduler_application.py b/dicat/scheduler_application.py index 65f46a3..cf325ec 100644 --- a/dicat/scheduler_application.py +++ b/dicat/scheduler_application.py @@ -53,15 +53,14 @@ def initialize(self): self.data_pane.add(self.visit_pane) # create data tables (treeview) - visit_column_headers = ( 'candidate', 'visitlabel', - 'when', 'where', - 'status' + visit_column_headers = ( 'identifier', 'candidate', 'visitlabel', + 'when', 'where', 'status' ) -# self.visit_table = DataTable.VisitList( self.visit_pane, -# visit_column_headers, -# xmlfile -# ) -# self.visit_table.pack(side=BOTTOM, expand=YES, fill=BOTH) + self.visit_table = DataTable.VisitList( self.visit_pane, + visit_column_headers, + xmlfile + ) + self.visit_table.pack(side=BOTTOM, expand=YES, fill=BOTH) candidate_column_headers = ( 'identifier', 'firstname', 'lastname', 'gender', 'phone', 'status' ) diff --git a/dicat/ui/datatable.py b/dicat/ui/datatable.py index 370bee9..1dbffcb 100644 --- a/dicat/ui/datatable.py +++ b/dicat/ui/datatable.py @@ -149,31 +149,43 @@ def __init__(self, parent, colheaders, xmlfile): self.datatable.tag_configure('tentative', background='#F0F0F0') # TODO change for non-language parameter def load_data(self, xmlfile): - data = dict(DataManagement.read_candidate_data(xmlfile)) - for key, value in data.iteritems(): - if data[key].visitset is not None: # skip the search if visitset = None - current_visitset = data[key].visitset # set this candidate.visitset for the next step + data = dict(DataManagement.read_visitset_data(xmlfile)) + for cand_key, value in data.iteritems(): + if "VisitSet" in data[cand_key].keys(): # skip the search if visitset = None + current_visitset = data[cand_key]["VisitSet"] # set this candidate.visitset for the next step # gather information about the candidate - candidate_id = data[key]["Identifier"] - candidate_firstname = data[key]["FirstName"] - candidate_lastname = data[key]["LastName"] - candidate_fullname = str(candidate_firstname + ' ' + candidate_lastname) - for key, value in current_visitset.iteritems(): - if current_visitset[key].status is not None: - status = current_visitset[key].status - visit_label = current_visitset[key].visitlabel - if current_visitset[key].when is None: - when = current_visitset[key].whenearliest + candidate_id = data[cand_key]["Identifier"] + candidate_firstname = data[cand_key]["FirstName"] + candidate_lastname = data[cand_key]["LastName"] + candidate_fullname = str( candidate_firstname + + ' ' + + candidate_lastname + ) + for visit_key, value in current_visitset.iteritems(): + if "VisitStatus" in current_visitset[visit_key].keys(): + status = current_visitset[visit_key]["VisitStatus"] + visit_label = current_visitset[visit_key]["VisitLabel"] + print current_visitset[ visit_key].keys() + if "VisitStartWhen" not in current_visitset[visit_key].keys(): + when = '' #TODO check what would be the next visit and set status to "to_schedule" + #when = current_visitset[visit_key].whenearliest else: - when = current_visitset[key].when - if current_visitset[key].where is None: + when = current_visitset[visit_key]["VisitStartWhen"] + if "VisitWhere" not in current_visitset[visit_key].keys(): where = '' else: - where = current_visitset[key].where + where = current_visitset[visit_key]["VisitWhere"] try: - self.datatable.insert('', 'end', - values=[candidate_fullname, visit_label, when, where, status], - tags=(status, candidate_id, visit_label)) + row_values = [ candidate_id, candidate_fullname, + visit_label, when, + where, status + ] + row_tags = (status, candidate_id, visit_label) + self.datatable.insert('', + 'end', + values = row_values, + tags = row_tags + ) except Exception as e: print "datatable.VisitList.load_data ", str(e) # TODO add proper error handling pass From 0e0b4a2588a4ecf4470becd6d2d7a3aa5a69bc27 Mon Sep 17 00:00:00 2001 From: Tara Campbell Date: Sat, 25 Jun 2016 14:15:42 +0200 Subject: [PATCH 50/89] Destory edit table frame in the anonymizer if it already exists. --- dicat/dicom_anonymizer_frame.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dicat/dicom_anonymizer_frame.py b/dicat/dicom_anonymizer_frame.py index 96bae38..db18876 100755 --- a/dicat/dicom_anonymizer_frame.py +++ b/dicat/dicom_anonymizer_frame.py @@ -102,6 +102,11 @@ def askdirectory(self): return self.dirname def deidentify(self): + + # clear edit table if it exists + if hasattr(self, 'field_edit_win'): + self.field_edit_win.destroy() + # Read the XML file with the identifying DICOM fields load_xml = PathMethods.resource_path("data/fields_to_zap.xml") XML_filename = load_xml.return_path() From 7d7ce1a53492bd55f3188639794c526be2a76c98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Sat, 25 Jun 2016 08:30:17 -0400 Subject: [PATCH 51/89] Pop up window with candidate information displaying. Still need to make the OK and cancel buttons work though... --- dicat/lib/multilanguage.py | 3 + dicat/ui/datatable.py | 67 ++++++--- dicat/ui/datawindow.py | 279 +++++++++++++++++++++++++++---------- 3 files changed, 257 insertions(+), 92 deletions(-) diff --git a/dicat/lib/multilanguage.py b/dicat/lib/multilanguage.py index 1fc2b5d..9f36229 100644 --- a/dicat/lib/multilanguage.py +++ b/dicat/lib/multilanguage.py @@ -109,6 +109,8 @@ candidate_phone = u"Téléphone" candidate_pscid = u"ID" candidate_status = u"Status" + candidate_gender = u"Sexe" + schedule_visit_label = u"Visite" schedule_visit_rank = u"#" schedule_visit_status = u"Status" @@ -215,6 +217,7 @@ candidate_phone = u"Phone" candidate_pscid = u"ID" candidate_status = u"Status" + candidate_gender = u"Sex" schedule_visit_label = u"Visit" schedule_visit_rank = u"#" schedule_visit_status = u"Status" diff --git a/dicat/ui/datatable.py b/dicat/ui/datatable.py index 1dbffcb..8a41267 100644 --- a/dicat/ui/datatable.py +++ b/dicat/ui/datatable.py @@ -12,30 +12,54 @@ class DataTable(Frame): Child clases are ParticipantsList(DataTable) and VisitList(DataTable) """ - def __init__(self, parent, colheaders): + def __init__(self, parent, colheaders, xmlfile): Frame.__init__(self) self.parent = parent - colheaders = colheaders - datatable = self.init_datatable(parent, colheaders) + self.init_datatable(parent, colheaders, xmlfile) + + """ + Initialize the datatable (which is a tk.treeview & add scroll bars) + """ + def init_datatable(self, parent, colheaders, xmlfile): + + # initialize the treeview datatable + self.datatable = Treeview( parent, selectmode='browse', + columns=colheaders, show="headings" + ) - def init_datatable(self, parent, colheaders): - """Initialize the datatable (which is a tk.treeview and add scroll bars)""" - self.datatable = Treeview(parent, selectmode='browse', columns=colheaders, show="headings") + # add the column headers to the datatable for col in colheaders: - self.datatable.heading(col, text=col.title(), - command=lambda c=col: self.treeview_sortby(self.datatable, c, 0)) - self.datatable.column(col, width=100, stretch="Yes", anchor="center") + self.datatable.heading( + col, + text=col.title(), + command=lambda c=col: self.treeview_sortby(self.datatable, c, 0) + ) + self.datatable.column( col, width=100, + stretch="Yes", anchor="center" + ) + # add vertical and horizontal scroll - self.verticalscroll = Scrollbar(parent, orient="vertical", command=self.datatable.yview) - self.horizontalscroll = Scrollbar(parent, orient="horizontal", command=self.datatable.xview) - self.datatable.configure(yscrollcommand=self.verticalscroll.set, xscroll=self.horizontalscroll.set) + self.verticalscroll = Scrollbar( parent, orient="vertical", + command=self.datatable.yview + ) + self.horizontalscroll = Scrollbar( parent, orient="horizontal", + command=self.datatable.xview + ) + self.datatable.configure( yscrollcommand=self.verticalscroll.set, + xscroll=self.horizontalscroll.set + ) self.verticalscroll.pack(side=RIGHT, expand=NO, fill=BOTH) self.horizontalscroll.pack(side=BOTTOM, expand=NO, fill=BOTH) + + # draw the datatable self.datatable.pack(side=LEFT, expand=YES, fill=BOTH) - # Binding with events - self.datatable.bind('', self.ondoubleclick) - self.datatable.bind("<>", self.onrowclick) - self.datatable.bind('', self.onrightclik) + + # bind with events + self.datatable.bind('', lambda event: self.ondoubleclick(xmlfile, event)) + + #self.datatable.bind('', self.ondoubleclick(xmlfile)) + self.datatable.bind("<>", self.onrowclick ) + self.datatable.bind('', self.onrightclik ) def load_data(self): """Should be overriden in child class""" @@ -61,7 +85,7 @@ def treeview_sortby(self, tree, column, descending): # switch the heading so that it will sort in the opposite direction tree.heading(column, command=lambda column=column: self.treeview_sortby(tree, column, int(not descending))) - def ondoubleclick(self, event): + def ondoubleclick(self, xmlfile, event): """ Double clicking on a treeview line opens a 'data window' and refresh the treeview data when the 'data window' is closed @@ -73,8 +97,8 @@ def ondoubleclick(self, event): itemID = self.datatable.selection()[0] item = self.datatable.item(itemID)['tags'] parent = self.parent - candidate_uuid = item[1] - DataWindow.DataWindow(parent, candidate_uuid) + candidate_id = item[1] + DataWindow.DataWindow(parent, xmlfile, candidate_id) self.update_data() except Exception as e: print "Datatable ondoubleclick ", str(e) # TODO deal with exception or not!?! @@ -98,7 +122,7 @@ class ParticipantsList(DataTable) takes care of the data table holding the list """ def __init__(self, parent, colheaders, xmlfile): # expected is dataset - DataTable.__init__(self, parent, colheaders) + DataTable.__init__(self, parent, colheaders, xmlfile) self.colheaders = colheaders self.load_data(xmlfile) # TODO add these color settings in a 'settings and preferences section of the app' @@ -141,7 +165,7 @@ class VisitList(DataTable) takes care of the data table holding the list of all """ def __init__(self, parent, colheaders, xmlfile): - DataTable.__init__(self, parent, colheaders) + DataTable.__init__(self, parent, colheaders, xmlfile) self.colheaders = colheaders self.load_data(xmlfile) # TODO add these color settings in a 'settings and preferences section of the app' @@ -165,7 +189,6 @@ def load_data(self, xmlfile): if "VisitStatus" in current_visitset[visit_key].keys(): status = current_visitset[visit_key]["VisitStatus"] visit_label = current_visitset[visit_key]["VisitLabel"] - print current_visitset[ visit_key].keys() if "VisitStartWhen" not in current_visitset[visit_key].keys(): when = '' #TODO check what would be the next visit and set status to "to_schedule" #when = current_visitset[visit_key].whenearliest diff --git a/dicat/ui/datawindow.py b/dicat/ui/datawindow.py index 79aafcc..b207762 100644 --- a/dicat/ui/datawindow.py +++ b/dicat/ui/datawindow.py @@ -15,14 +15,15 @@ class DataWindow(Toplevel): - def __init__(self, parent, candidate_uuid='new'): + + def __init__(self, parent, xmlfile, candidate_uuid='new'): Toplevel.__init__(self, parent) # create a transient window on top of parent window self.transient(parent) self.parent = parent self.title(MultiLanguage.data_window_title) #TODO find a better title for the thing body = Frame(self) - self.initial_focus = self.body(body, candidate_uuid) + self.initial_focus = self.body(body, candidate_uuid, xmlfile) body.pack(padx=5, pady=5) self.button_box() @@ -36,76 +37,171 @@ def __init__(self, parent, candidate_uuid='new'): # self.deiconify() self.wait_window(self) - def body(self, master, candidate): + + def body(self, master, candidate, xmlfile): """Creates the body of 'datawindow'. param candidate is the candidate.uuid""" try: - data = dict(DataManagement.read_candidate_data()) # TODO better way to do this - candidate = data.get(candidate) + cand_data = DataManagement.read_candidate_data(xmlfile) # TODO better way to do this + visit_data = DataManagement.read_visitset_data(xmlfile) + visitset = {} + cand_info = {} + for cand_key in cand_data: + if cand_data[cand_key]["Identifier"] == candidate: + cand_info = cand_data[cand_key] + break + for cand_key in visit_data: + if visit_data[cand_key]["Identifier"] == candidate: + visitset = visit_data[cand_key]["VisitSet"] + break except Exception as e: print "datawindow.body ", str(e) # TODO manage exceptions + # Candidate section - self.candidate_pane = Labelframe(self, text=MultiLanguage.candidate_pane, width=250, height=350, borderwidth=10) - self.candidate_pane.pack(side=TOP, expand=YES, fill=BOTH, padx=5, pady=5) + self.candidate_pane = Labelframe( self, + text=MultiLanguage.candidate_pane, + width=250, + height=350, + borderwidth=10 + ) + self.candidate_pane.pack( side=TOP, expand=YES, fill=BOTH, + padx=5, pady=5 + ) + # object unique id - does not appear on gui but needed to keep track of this candidate - self.candidate_uid = candidate.uid + #self.candidate_uid = candidate["Identifier"] + # PSCID - self.label_pscid = Label(self.candidate_pane, text=MultiLanguage.candidate_pscid) + self.label_pscid = Label( self.candidate_pane, + text=MultiLanguage.candidate_pscid + ) self.label_pscid.grid(column=0, row=0, padx=10, pady=5, sticky=N+S+E+W) self.text_pscid_var = StringVar() - self.text_pscid_var.set(candidate.pscid) - self.text_pscid = Entry(self.candidate_pane, textvariable=self.text_pscid_var) + self.text_pscid_var.set(cand_info["Identifier"]) + self.text_pscid = Entry( self.candidate_pane, + textvariable=self.text_pscid_var + ) self.text_pscid.grid(column=0, row=1, padx=10, pady=5, sticky=N+S+E+W) - # status - self.label_status = Label(self.candidate_pane, text=MultiLanguage.candidate_status) - self.label_status.grid(column=1, row=0, padx=10, pady=5, sticky=N+S+E+W) - self.text_status_var = StringVar() - self.text_status_var.set(candidate.status) - self.text_status = Entry(self.candidate_pane, textvariable=self.text_status_var) - self.text_status.grid(column=1, row=1, padx=10, pady=5, sticky=N+S+E+W) + # firstname - self.label_firstname = Label(self.candidate_pane, text=MultiLanguage.candidate_firstname) - self.label_firstname.grid(column=0, row=2, padx=10, pady=5, sticky=N+S+E+W) + self.label_firstname = Label( self.candidate_pane, + text=MultiLanguage.candidate_firstname + ) + self.label_firstname.grid( column=1, row=0, + padx=10, pady=5, + sticky=N+S+E+W + ) self.text_firstname_var = StringVar() - self.text_firstname_var.set(candidate.firstname) - self.text_firstname = Entry(self.candidate_pane, textvariable=self.text_firstname_var) - self.text_firstname.grid(column=0, row=3, padx=10, pady=5, sticky=N+S+E+W) + self.text_firstname_var.set(cand_info["FirstName"]) + self.text_firstname = Entry( self.candidate_pane, + textvariable=self.text_firstname_var + ) + self.text_firstname.grid( column=1, row=1, + padx=10, pady=5, + sticky=N+S+E+W + ) + # lastname - self.label_lastname = Label(self.candidate_pane, text=MultiLanguage.candidate_lastname) - self.label_lastname.grid(column=1, row=2, padx=10, pady=5, sticky=N+S+E+W) + self.label_lastname = Label( self.candidate_pane, + text=MultiLanguage.candidate_lastname + ) + self.label_lastname.grid(column=2, row=0, padx=10, pady=5, sticky=N+S+E+W) self.text_lastname_var = StringVar() - self.text_lastname_var.set(candidate.lastname) - self.text_lastname = Entry(self.candidate_pane, textvariable=self.text_lastname_var) - self.text_lastname.grid(column=1, row=3, padx=10, pady=5, sticky=N+S+E+W) + self.text_lastname_var.set(cand_info["LastName"]) + self.text_lastname = Entry( self.candidate_pane, + textvariable=self.text_lastname_var + ) + self.text_lastname.grid(column=2, row=1, padx=10, pady=5, sticky=N+S+E+W) + + # gender + self.label_gender = Label( self.candidate_pane, + text=MultiLanguage.candidate_gender + ) + self.label_gender.grid(column=0, row=2, padx=10, pady=5, sticky=N+S+E+W) + self.text_gender_var = StringVar() + self.text_gender_var.set(cand_info["Gender"]) + self.text_gender = Entry( self.candidate_pane, + textvariable=self.text_gender_var + ) + self.text_gender.grid(column=0, row=3, padx=10, pady=5, sticky=N+S+E+W) + + # status + self.label_status = Label( self.candidate_pane, + text=MultiLanguage.candidate_status + ) + self.label_status.grid( column=1, row=2, + padx=10, pady=5, + sticky=N+S+E+W + ) + self.text_status_var = StringVar() + self.text_status_var.set(cand_info["CandidateStatus"]) + self.text_status = Entry( self.candidate_pane, + textvariable=self.text_status_var + ) + self.text_status.grid(column=1, row=3, padx=10, pady=5, sticky=N+S+E+W) + # phone number - self.label_phone = Label(self.candidate_pane, text=MultiLanguage.candidate_phone) + self.label_phone = Label( self.candidate_pane, + text=MultiLanguage.candidate_phone + ) self.label_phone.grid(column=2, row=2, padx=10, pady=5, sticky=N+S+E+W) self.text_phone_var = StringVar() - self.text_phone_var.set(candidate.phone) - self.text_phone = Entry(self.candidate_pane, textvariable=self.text_phone_var) + self.text_phone_var.set(cand_info["PhoneNumber"]) + self.text_phone = Entry( self.candidate_pane, + textvariable=self.text_phone_var + ) self.text_phone.grid(column=2, row=3, padx=10, pady=5, sticky=N+S+E+W) # Schedule Section - displayed as a table - self.schedule_pane = Labelframe(self, text=MultiLanguage.schedule_pane, width=250, height=350, borderwidth=10) + self.schedule_pane = Labelframe( self, + text=MultiLanguage.schedule_pane, + width=250, + height=350, + borderwidth=10 + ) self.schedule_pane.pack(side=TOP, expand=YES, fill=BOTH, padx=5, pady=5) + # top row (header) - self.label_visit_rank = Label(self.schedule_pane, text=MultiLanguage.schedule_visit_rank) - self.label_visit_rank.grid(column=0, row=0, padx=5, pady=5, sticky=N+S+E+W) - self.label_visit_label = Label(self.schedule_pane, text=MultiLanguage.col_visitlabel) - self.label_visit_label.grid(column=1, row=0, padx=5, pady=5, sticky=N+S+E+W) - self.label_visit_when = Label(self.schedule_pane, text=MultiLanguage.col_when) - self.label_visit_when.grid(column=2, row=0, padx=5, pady=5, sticky=NSEW) - self.label_visit_status = Label(self.schedule_pane, text=MultiLanguage.col_where) - self.label_visit_status.grid(column=3, row=0, padx=5, pady=5, sticky=N+S+E+W) - self.label_visit_status = Label(self.schedule_pane, text=MultiLanguage.col_withwhom) - self.label_visit_status.grid(column=4, row=0, padx=5, pady=5, sticky=N+S+E+W) - self.label_visit_status = Label(self.schedule_pane, text=MultiLanguage.col_status) - self.label_visit_status.grid(column=5, row=0, padx=5, pady=5, sticky=N+S+E+W) + self.label_visit_label = Label( self.schedule_pane, + text=MultiLanguage.col_visitlabel + ) + self.label_visit_label.grid( column=1, row=0, + padx=5, pady=5, + sticky=N+S+E+W + ) + self.label_visit_when = Label( self.schedule_pane, + text=MultiLanguage.col_when + ) + self.label_visit_when.grid( column=2, row=0, + padx=5, pady=5, + sticky=NSEW + ) + self.label_visit_status = Label( self.schedule_pane, + text=MultiLanguage.col_where + ) + self.label_visit_status.grid( column=3, row=0, + padx=5, pady=5, + sticky=N+S+E+W + ) + self.label_visit_status = Label( self.schedule_pane, + text=MultiLanguage.col_withwhom + ) + self.label_visit_status.grid( column=4, row=0, + padx=5, pady=5, + sticky=N+S+E+W + ) + self.label_visit_status = Label( self.schedule_pane, + text=MultiLanguage.col_status + ) + self.label_visit_status.grid( column=5, row=0, + padx=5, pady=5, + sticky=N+S+E+W + ) """ PSEUDOCODE 1. Get candidate.visitset 2. Parse into a sorted list (sorted on visit.rank) - 3. Print data on screen + 3. Print cand_data on screen visit_set = candidate.visitset @@ -119,48 +215,86 @@ def body(self, master, candidate): # TODO add logic "foreach" to create a table showing each visit # 1- Get candidate visitset and parse into a list visit_list = [] - visitset = candidate.visitset - if visitset is None: + if len(visitset.keys()) == 0: print 'no visit yet' else: for key, value in visitset.iteritems(): visit_list.append(visitset[key]) + # 2- Sort list on visit.rank - visit_list = sorted(visit_list, key=lambda visit: visit.rank) + visit_list = sorted( visit_list, + key=lambda visit: visit["VisitStartWhen"] + ) + # 3- 'print' values on ui x = 0 for x in range(len(visit_list)): - # rank - label_visit_rank = Label(self.schedule_pane, text=visit_list[x].rank) - label_visit_rank.grid(column=0, row=x+1, padx=5, pady=5, sticky=N+S+E+W) # visitlabel - label_visit_label = Label(self.schedule_pane, text=visit_list[x].visitlabel) - label_visit_label.grid(column=1, row=x+1, padx=5, pady=5, sticky=N+S+E+W) + label_visit_label = Label( self.schedule_pane, + text=visit_list[x]["VisitLabel"] + ) + label_visit_label.grid( column=1, row=x+1, + padx=5, pady=5, + sticky=N+S+E+W + ) # when - if visit_list[x].when == None: - visit = visit_list[x] - date_range = visit.visit_date_range() - label_visit_when = Label(self.schedule_pane, text=date_range) - label_visit_when.grid(column=2, row=x+1, padx=5, pady=5, sticky=N+S+E+W) + visit_when = "" + if "VisitStartWhen" not in visit_list[x].keys(): + #visit = visit_list[x]["VisitLabel"] + #date_range = visit.visit_date_range() + #TODO: implement automatic range for next visit + visit_when = "" else: - label_visit_when = Label(self.schedule_pane, text=visit_list[x].when) - label_visit_when.grid(column=2, row=x+1, padx=5, pady=5, sticky=N+S+E+W) + print "iNINI" + visit_when = visit_list[x]["VisitStartWhen"] + label_visit_when = Label(self.schedule_pane, text=visit_when) + label_visit_when.grid( column=2, row=x+1, + padx=5, pady=5, + sticky=N+S+E+W + ) + # where - label_visit_where = Label(self.schedule_pane, text=visit_list[x].where) - label_visit_where.grid(column=3, row=x+1, padx=5, pady=5, sticky=N+S+E+W) + visit_where = "" + if "VisitWhere" in visit_list[x].keys(): + visit_where = visit_list[x]["VisitWhere"] + label_visit_where = Label(self.schedule_pane, text=visit_where) + label_visit_where.grid( column=3, row=x+1, + padx=5, pady=5, + sticky=N+S+E+W + ) + # withwhom - label_visit_where = Label(self.schedule_pane, text=visit_list[x].withwhom) - label_visit_where.grid(column=4, row=x+1, padx=5, pady=5, sticky=N+S+E+W) - # status - label_visit_where = Label(self.schedule_pane, text=visit_list[x].status) - label_visit_where.grid(column=5, row=x+1, padx=5, pady=5, sticky=N+S+E+W) + visit_with_whom = "" + if "VisitWithWhom" in visit_list[x].keys(): + visit_with_whom = visit_list[x]["VisitWithWhom"] + label_visit_with_whom = Label( self.schedule_pane, + text=visit_with_whom + ) + label_visit_with_whom.grid( column=4, row=x+1, + padx=5, pady=5, + sticky=N+S+E+W + ) + # status + visit_status = '' + if "VisitStatus" in visit_list[x].keys(): + visit_status = visit_list[x]["VisitStatus"] + label_visit_status = Label( self.schedule_pane, + text=visit_status + ) + label_visit_status.grid( column=5, row=x+1, + padx=5, pady=5, + sticky=N+S+E+W + ) def button_box(self): # add standard button box box = Frame(self) - w = Button(box, text="OK", width=10, command=self.ok_button, default=ACTIVE) + w = Button( box, text="OK", + width=10, command=self.ok_button, + default=ACTIVE + ) w.pack(side=LEFT, padx=5, pady=5) w = Button(box, text="Cancel", width=10, command=self.cancel_button) w.pack(side=LEFT, padx=5, pady=5) @@ -168,6 +302,7 @@ def button_box(self): self.bind("", self.closedialog) box.pack() + def ok_button(self, event=None): print "saving data and closing" # TODO remove when done self.capture_data() @@ -177,7 +312,8 @@ def ok_button(self, event=None): #need to call treeview update here self.withdraw() self.closedialog() - + + def cancel_button(self, event=None): print "close without saving" parent = Frame(self) @@ -186,14 +322,17 @@ def cancel_button(self, event=None): self.closedialog() else: return - + + def closedialog(self, event=None): self.parent.focus_set() # put focus back to parent window before destroying the window self.destroy() + def validate(self): return 1 + def capture_data(self): """ Grap the information from the window's text field and save the candidate information based on candidate_uid. From bc4422c1fb7f5819dc047aa61208ef2c64d00cd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Sat, 25 Jun 2016 09:16:10 -0400 Subject: [PATCH 52/89] Modified xmlfile to be a global variable defined in config.py and called by the different classes that uses it. --- dicat/lib/config.py | 7 +++++++ dicat/lib/datamanagement.py | 19 ++++++++++--------- dicat/scheduler_application.py | 9 ++++----- dicat/ui/datatable.py | 34 +++++++++++++++++----------------- dicat/ui/datawindow.py | 11 +++++------ 5 files changed, 43 insertions(+), 37 deletions(-) create mode 100644 dicat/lib/config.py diff --git a/dicat/lib/config.py b/dicat/lib/config.py new file mode 100644 index 0000000..4fe6616 --- /dev/null +++ b/dicat/lib/config.py @@ -0,0 +1,7 @@ +""" +This file will contain global variables that can be used in all class, assuming +the config.py script gets imported. +Access to the variable would then be config.variablename +""" + +xmlfile = "" # to be access by config.xmlfile \ No newline at end of file diff --git a/dicat/lib/datamanagement.py b/dicat/lib/datamanagement.py index 85b9c8c..7c86a39 100644 --- a/dicat/lib/datamanagement.py +++ b/dicat/lib/datamanagement.py @@ -2,6 +2,7 @@ import os.path import pickle from xml.dom import minidom +import config as Config """ The data_management.py file contains functions related to data management only. Generic functions: savedata(data, datafilename) and readdata(datafile). Currently, these are not being used. @@ -9,26 +10,26 @@ Specific functions read_candidate_data(), save_candidate_data(), read_studydata() and save_study_data() are used to get/save candidate data and study setup data respectively. """ -def read_xmlfile(xmlfile): +def read_xmlfile(): """Parses the XML file and loads the data into the current window""" try: - xmldoc = minidom.parse(xmlfile) + xmldoc = minidom.parse(Config.xmlfile) return xmldoc except: - message = "ERROR: could not read file " + xmlfile + message = "ERROR: could not read file " + Config.xmlfile print message #TODO: create a log class to display the messages -def read_candidate_data(xmlfile): +def read_candidate_data(): """Read and return the content of a file called candidatedata. Returns nothing if file doesn't exist""" data = {} #check to see if file exists before loading it - if os.path.isfile(xmlfile): + if os.path.isfile(Config.xmlfile): # read the xml file - xmldoc = read_xmlfile(xmlfile) + xmldoc = read_xmlfile() xmldata = xmldoc.getElementsByTagName('data')[0] xmlcandlist = xmldata.getElementsByTagName('Candidate') for cand in xmlcandlist: @@ -44,14 +45,14 @@ def read_candidate_data(xmlfile): return data -def read_visitset_data(xmlfile): +def read_visitset_data(): """Read and return the content of a file called candidatedata. Returns nothing if file doesn't exist""" data = {} #check to see if file exists before loading it - if os.path.isfile(xmlfile): + if os.path.isfile(Config.xmlfile): # read the xml file - xmldoc = read_xmlfile(xmlfile) + xmldoc = read_xmlfile() xmldata = xmldoc.getElementsByTagName('data')[0] xmlcandlist = xmldata.getElementsByTagName('Candidate') for cand in xmlcandlist: diff --git a/dicat/scheduler_application.py b/dicat/scheduler_application.py index cf325ec..43cc893 100644 --- a/dicat/scheduler_application.py +++ b/dicat/scheduler_application.py @@ -6,6 +6,7 @@ #import internal packages import ui.datatable as DataTable import lib.multilanguage as MultiLanguage +import lib.config as Config class UserInterface(Frame): @@ -22,7 +23,7 @@ def initialize(self): self.frame = Frame(self.parent) self.frame.pack(side=TOP, expand=YES, fill=BOTH, padx=10, pady=10) - xmlfile = "new_data_test.xml" + Config.xmlfile = "new_data_test.xml" # This area (datapane) is one Panedwindow containing 3 Labelframes self.data_pane = Panedwindow( self.frame, width=1000, @@ -57,16 +58,14 @@ def initialize(self): 'when', 'where', 'status' ) self.visit_table = DataTable.VisitList( self.visit_pane, - visit_column_headers, - xmlfile + visit_column_headers ) self.visit_table.pack(side=BOTTOM, expand=YES, fill=BOTH) candidate_column_headers = ( 'identifier', 'firstname', 'lastname', 'gender', 'phone', 'status' ) self.data_table = DataTable.ParticipantsList( self.candidate_pane, - candidate_column_headers, - xmlfile + candidate_column_headers ) self.data_table.pack(side=BOTTOM, expand=YES, fill=BOTH) diff --git a/dicat/ui/datatable.py b/dicat/ui/datatable.py index 8a41267..364acaa 100644 --- a/dicat/ui/datatable.py +++ b/dicat/ui/datatable.py @@ -12,15 +12,15 @@ class DataTable(Frame): Child clases are ParticipantsList(DataTable) and VisitList(DataTable) """ - def __init__(self, parent, colheaders, xmlfile): + def __init__(self, parent, colheaders): Frame.__init__(self) self.parent = parent - self.init_datatable(parent, colheaders, xmlfile) + self.init_datatable(parent, colheaders) """ Initialize the datatable (which is a tk.treeview & add scroll bars) """ - def init_datatable(self, parent, colheaders, xmlfile): + def init_datatable(self, parent, colheaders): # initialize the treeview datatable self.datatable = Treeview( parent, selectmode='browse', @@ -55,9 +55,9 @@ def init_datatable(self, parent, colheaders, xmlfile): self.datatable.pack(side=LEFT, expand=YES, fill=BOTH) # bind with events - self.datatable.bind('', lambda event: self.ondoubleclick(xmlfile, event)) + #self.datatable.bind('', lambda event: self.ondoubleclick(event)) - #self.datatable.bind('', self.ondoubleclick(xmlfile)) + self.datatable.bind('', self.ondoubleclick) self.datatable.bind("<>", self.onrowclick ) self.datatable.bind('', self.onrightclik ) @@ -85,7 +85,7 @@ def treeview_sortby(self, tree, column, descending): # switch the heading so that it will sort in the opposite direction tree.heading(column, command=lambda column=column: self.treeview_sortby(tree, column, int(not descending))) - def ondoubleclick(self, xmlfile, event): + def ondoubleclick(self, event): """ Double clicking on a treeview line opens a 'data window' and refresh the treeview data when the 'data window' is closed @@ -98,7 +98,7 @@ def ondoubleclick(self, xmlfile, event): item = self.datatable.item(itemID)['tags'] parent = self.parent candidate_id = item[1] - DataWindow.DataWindow(parent, xmlfile, candidate_id) + DataWindow.DataWindow(parent, candidate_id) self.update_data() except Exception as e: print "Datatable ondoubleclick ", str(e) # TODO deal with exception or not!?! @@ -121,15 +121,15 @@ class ParticipantsList(DataTable) takes care of the data table holding the list That list is comprised of all participants (even those that have not been called once. """ - def __init__(self, parent, colheaders, xmlfile): # expected is dataset - DataTable.__init__(self, parent, colheaders, xmlfile) + def __init__(self, parent, colheaders): # expected is dataset + DataTable.__init__(self, parent, colheaders) self.colheaders = colheaders - self.load_data(xmlfile) + self.load_data() # TODO add these color settings in a 'settings and preferences section of the app' self.datatable.tag_configure('active', background='#F1F8FF') # TODO replace active tag by status variable value - def load_data(self, xmlfile): - data = DataManagement.read_candidate_data(xmlfile) + def load_data(self): + data = DataManagement.read_candidate_data() try: for key in data: @@ -164,16 +164,16 @@ class VisitList(DataTable) takes care of the data table holding the list of all even those that have not been confirmed yet. """ - def __init__(self, parent, colheaders, xmlfile): - DataTable.__init__(self, parent, colheaders, xmlfile) + def __init__(self, parent, colheaders): + DataTable.__init__(self, parent, colheaders) self.colheaders = colheaders - self.load_data(xmlfile) + self.load_data() # TODO add these color settings in a 'settings and preferences section of the app' self.datatable.tag_configure('active', background='#F1F8FF') # TODO change for non-language parameter self.datatable.tag_configure('tentative', background='#F0F0F0') # TODO change for non-language parameter - def load_data(self, xmlfile): - data = dict(DataManagement.read_visitset_data(xmlfile)) + def load_data(self): + data = DataManagement.read_visitset_data() for cand_key, value in data.iteritems(): if "VisitSet" in data[cand_key].keys(): # skip the search if visitset = None current_visitset = data[cand_key]["VisitSet"] # set this candidate.visitset for the next step diff --git a/dicat/ui/datawindow.py b/dicat/ui/datawindow.py index b207762..969cb36 100644 --- a/dicat/ui/datawindow.py +++ b/dicat/ui/datawindow.py @@ -16,14 +16,14 @@ class DataWindow(Toplevel): - def __init__(self, parent, xmlfile, candidate_uuid='new'): + def __init__(self, parent, candidate_uuid='new'): Toplevel.__init__(self, parent) # create a transient window on top of parent window self.transient(parent) self.parent = parent self.title(MultiLanguage.data_window_title) #TODO find a better title for the thing body = Frame(self) - self.initial_focus = self.body(body, candidate_uuid, xmlfile) + self.initial_focus = self.body(body, candidate_uuid) body.pack(padx=5, pady=5) self.button_box() @@ -38,11 +38,11 @@ def __init__(self, parent, xmlfile, candidate_uuid='new'): self.wait_window(self) - def body(self, master, candidate, xmlfile): + def body(self, master, candidate): """Creates the body of 'datawindow'. param candidate is the candidate.uuid""" try: - cand_data = DataManagement.read_candidate_data(xmlfile) # TODO better way to do this - visit_data = DataManagement.read_visitset_data(xmlfile) + cand_data = DataManagement.read_candidate_data() # TODO better way to do this + visit_data = DataManagement.read_visitset_data() visitset = {} cand_info = {} for cand_key in cand_data: @@ -245,7 +245,6 @@ def body(self, master, candidate, xmlfile): #TODO: implement automatic range for next visit visit_when = "" else: - print "iNINI" visit_when = visit_list[x]["VisitStartWhen"] label_visit_when = Label(self.schedule_pane, text=visit_when) label_visit_when.grid( column=2, row=x+1, From a600fdf9d52bf6ee04151414116d6cc67f1ec4f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Thu, 7 Jul 2016 15:59:38 -0400 Subject: [PATCH 53/89] Committing changes that were not saved --- dicat/IDMapper.py | 8 +++++--- dicat/lib/datamanagement.py | 12 ++++++------ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/dicat/IDMapper.py b/dicat/IDMapper.py index bc62864..526713d 100644 --- a/dicat/IDMapper.py +++ b/dicat/IDMapper.py @@ -185,11 +185,13 @@ def LoadXML(self, file): """Parses the XML file and loads the data into the current window""" try: xmldoc = DataManagement.read_xmlfile(file) - xmldata = xmldoc.getElementsByTagName('data') - xmlitemlist = xmldata.getElementsByTagName('Candidate') + #xmldata = xmldoc.getElementsByTagName('data')[0] + xmlitemlist = xmldoc.getElementsByTagName('Candidate') for s in xmlitemlist: identifier = s.getElementsByTagName("Identifier")[0].firstChild.nodeValue - realname = s.getElementsByTagName("RealName")[0].firstChild.nodeValue + realname = s.getElementsByTagName("RealName")[0].firstChild.nodeValue + #lastname = s.getElementsByTagName("LastName")[0].firstChild.nodeValue + #realname = firstname + " " + lastname dob = s.getElementsByTagName("DateOfBirth")[0].firstChild.nodeValue self.AddIdentifierAction(identifier, realname, dob, False) except: diff --git a/dicat/lib/datamanagement.py b/dicat/lib/datamanagement.py index 7c86a39..2c4f38d 100644 --- a/dicat/lib/datamanagement.py +++ b/dicat/lib/datamanagement.py @@ -10,15 +10,15 @@ Specific functions read_candidate_data(), save_candidate_data(), read_studydata() and save_study_data() are used to get/save candidate data and study setup data respectively. """ -def read_xmlfile(): +def read_xmlfile(xmlfile): - """Parses the XML file and loads the data into the current window""" + """Parses the XML file return the data into xmldoc""" try: - xmldoc = minidom.parse(Config.xmlfile) + xmldoc = minidom.parse(xmlfile) return xmldoc except: - message = "ERROR: could not read file " + Config.xmlfile + message = "ERROR: could not read file " + xmlfile print message #TODO: create a log class to display the messages @@ -29,7 +29,7 @@ def read_candidate_data(): #check to see if file exists before loading it if os.path.isfile(Config.xmlfile): # read the xml file - xmldoc = read_xmlfile() + xmldoc = read_xmlfile(Config.xmlfile) xmldata = xmldoc.getElementsByTagName('data')[0] xmlcandlist = xmldata.getElementsByTagName('Candidate') for cand in xmlcandlist: @@ -52,7 +52,7 @@ def read_visitset_data(): #check to see if file exists before loading it if os.path.isfile(Config.xmlfile): # read the xml file - xmldoc = read_xmlfile() + xmldoc = read_xmlfile(Config.xmlfile) xmldata = xmldoc.getElementsByTagName('data')[0] xmlcandlist = xmldata.getElementsByTagName('Candidate') for cand in xmlcandlist: From c43d4d7ba11d87efc1ed434b9b4fd3c92440ffaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Thu, 7 Jul 2016 16:01:38 -0400 Subject: [PATCH 54/89] On 'View DICOM fields' click, remove dicom edit table if it already exists. --- dicat/dicom_anonymizer_frame.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/dicat/dicom_anonymizer_frame.py b/dicat/dicom_anonymizer_frame.py index 96bae38..f0e1a76 100755 --- a/dicat/dicom_anonymizer_frame.py +++ b/dicat/dicom_anonymizer_frame.py @@ -102,6 +102,11 @@ def askdirectory(self): return self.dirname def deidentify(self): + + # clear edit table if it exists + if hasattr(self, 'field_edit_win'): + self.field_edit_win.destroy() + # Read the XML file with the identifying DICOM fields load_xml = PathMethods.resource_path("data/fields_to_zap.xml") XML_filename = load_xml.return_path() @@ -267,4 +272,4 @@ def collect_edited_data(self): columnspan=2, padx=(0, 10), sticky=E+W - ) \ No newline at end of file + ) From 00e67f24b44a77d5fd3dc264b87ad4c7ab86b410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Thu, 7 Jul 2016 16:06:22 -0400 Subject: [PATCH 55/89] Rename DicAT_application.py to be DICAT.py --- dicat/DICAT.py | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 dicat/DICAT.py diff --git a/dicat/DICAT.py b/dicat/DICAT.py new file mode 100644 index 0000000..8e02872 --- /dev/null +++ b/dicat/DICAT.py @@ -0,0 +1,83 @@ +#!/usr/bin/python + +import ttk +from Tkinter import * + +from dicom_anonymizer_frame import dicom_deidentifier_frame_gui +from IDMapper import IDMapper_frame_gui +from welcome_frame import welcome_frame_gui + +class DicAT_application(): + + # Constructor of the class DicAT called with a parent widget ("master") + # to which we will add a number of child widgets. The constructor starts + # by creating a "Frame" widget. A frame is a simple container. + def __init__(self, master, side=LEFT): + + self.dir_opt = {} + + # Title of the application + master.title("DICAT") + + # Use notebook (nb) from ttk from Tkinter to create tabs + self.nb = ttk.Notebook(master) + + # Add frames as pages for ttk.Notebook + self.page1 = ttk.Frame(self.nb) + + # Second page, DICOM anonymizer + self.page2 = ttk.Frame(self.nb) + + # Third page, Scheduler + self.page3 = ttk.Frame(self.nb) + + # Fourth page, ID key + self.page4 = ttk.Frame(self.nb) + + # Add the pages to the notebook + self.nb.add(self.page1, text='Welcome to DicAT!') + self.nb.add(self.page2, text='DICOM de-identifier') + self.nb.add(self.page3, text='Scheduler', state='hidden') # hide scheduler for now + self.nb.add(self.page4, text='ID key') + + # Draw + self.nb.pack(expand=1, fill='both') + + # Draw content of the different tabs' frame + self.dicom_deidentifier_tab() + self.id_key_frame() + self.welcome_page() + + def dicom_deidentifier_tab(self): + + # start dicom_anonymizer_frame_gui method + dicom_deidentifier_frame_gui(self.page2) + + + def id_key_frame(self): + + # start the ID mapper frame gui + IDMapper_frame_gui(self.page4) + + + def welcome_page(self): + + # start the Welcome page + welcome_frame_gui(self.page1) + + + +if __name__ == "__main__": + + # Create a Tk root widget. + root = Tk() + + app = DicAT_application(root) + + # The window won't appear until we've entered the Tkinter event loop. + # The program will stay in the event loop until we close the window. + root.mainloop() + + # Some development environments won't terminate the Python process unless it is + # explicitly mentioned to destroy the main window when the loop is terminated. +# root.destroy() From d05413620a72e290f218c8c4c1597163d9c11669 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Thu, 7 Jul 2016 16:13:29 -0400 Subject: [PATCH 56/89] Modified the documentation to rename DICAT_application.py to DICAT.py --- README.md | 4 +- dicat/DicAT_application.py | 83 ------------------------- docs/How_To_Create_DICAT_Executables.md | 37 +++++------ 3 files changed, 21 insertions(+), 103 deletions(-) delete mode 100644 dicat/DicAT_application.py diff --git a/README.md b/README.md index 92ba34d..ecc88f7 100644 --- a/README.md +++ b/README.md @@ -42,9 +42,9 @@ To install DICAT source code on a computer, download and save the content of the Before running DICAT, make sure your systems contains a [Python](https://www.python.org) compiler with the [TkInter](https://wiki.python.org/moin/TkInter) library (usually, TkInter comes by default with most Python installations. The [PyDICOM](http://www.pydicom.org) package is also required by DICAT. -DICAT can be started by executing `DICAT_application.py` script with a Python compiler. On UNIX computers (Linux and Mac OS X), open a terminal, go to the main directory of DICAT source code (`dicat` directory) and run the following: +DICAT can be started by executing `DICAT.py` script with a Python compiler. On UNIX computers (Linux and Mac OS X), open a terminal, go to the main directory of DICAT source code (`dicat` directory) and run the following: -```python DICAT_application.py``` +```python DICAT.py``` ## How to use the DICOM de-identifier of DICAT? diff --git a/dicat/DicAT_application.py b/dicat/DicAT_application.py deleted file mode 100644 index 8e02872..0000000 --- a/dicat/DicAT_application.py +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/python - -import ttk -from Tkinter import * - -from dicom_anonymizer_frame import dicom_deidentifier_frame_gui -from IDMapper import IDMapper_frame_gui -from welcome_frame import welcome_frame_gui - -class DicAT_application(): - - # Constructor of the class DicAT called with a parent widget ("master") - # to which we will add a number of child widgets. The constructor starts - # by creating a "Frame" widget. A frame is a simple container. - def __init__(self, master, side=LEFT): - - self.dir_opt = {} - - # Title of the application - master.title("DICAT") - - # Use notebook (nb) from ttk from Tkinter to create tabs - self.nb = ttk.Notebook(master) - - # Add frames as pages for ttk.Notebook - self.page1 = ttk.Frame(self.nb) - - # Second page, DICOM anonymizer - self.page2 = ttk.Frame(self.nb) - - # Third page, Scheduler - self.page3 = ttk.Frame(self.nb) - - # Fourth page, ID key - self.page4 = ttk.Frame(self.nb) - - # Add the pages to the notebook - self.nb.add(self.page1, text='Welcome to DicAT!') - self.nb.add(self.page2, text='DICOM de-identifier') - self.nb.add(self.page3, text='Scheduler', state='hidden') # hide scheduler for now - self.nb.add(self.page4, text='ID key') - - # Draw - self.nb.pack(expand=1, fill='both') - - # Draw content of the different tabs' frame - self.dicom_deidentifier_tab() - self.id_key_frame() - self.welcome_page() - - def dicom_deidentifier_tab(self): - - # start dicom_anonymizer_frame_gui method - dicom_deidentifier_frame_gui(self.page2) - - - def id_key_frame(self): - - # start the ID mapper frame gui - IDMapper_frame_gui(self.page4) - - - def welcome_page(self): - - # start the Welcome page - welcome_frame_gui(self.page1) - - - -if __name__ == "__main__": - - # Create a Tk root widget. - root = Tk() - - app = DicAT_application(root) - - # The window won't appear until we've entered the Tkinter event loop. - # The program will stay in the event loop until we close the window. - root.mainloop() - - # Some development environments won't terminate the Python process unless it is - # explicitly mentioned to destroy the main window when the loop is terminated. -# root.destroy() diff --git a/docs/How_To_Create_DICAT_Executables.md b/docs/How_To_Create_DICAT_Executables.md index e198a3e..ce3f218 100644 --- a/docs/How_To_Create_DICAT_Executables.md +++ b/docs/How_To_Create_DICAT_Executables.md @@ -2,27 +2,27 @@ Here is described the procedure used to create DICAT executables for Window, Mac OS X and Linux workstation. In order to be able to follow the steps mentioned below, you will need to install [Pyinstaller](http://www.pyinstaller.org). -## 1) Run pyinstaller on DICAT_application.py +## 1) Run pyinstaller on DICAT.py -In the terminal/console, go into the `dicat` directory hosting the DICAT_application.py script and run the following command depending on the OS used. +In the terminal/console, go into the `dicat` directory hosting the DICAT.py script and run the following command depending on the OS used. ### On Mac OS X -```pyinstaller --onefile --windowed --icon=images/DICAT_logo.icns DICAT_application.py``` +```pyinstaller --onefile --windowed --icon=images/DICAT_logo.icns DICAT.py``` ### On Windows -```pyinstaller --onefile --windowed --icon=images\dicat_logo_HsB_2.ico DICAT_application.py``` +```pyinstaller --onefile --windowed --icon=images\dicat_logo_HsB_2.ico DICAT.py``` ### On Linux (tested on Ubuntu) -```pyinstaller --onefile DICAT_application.py``` +```pyinstaller --onefile DICAT.py``` -Executing this command will create a `DICAT_application.spec` file in the same directory, as well as a `build` and `dist` directory. +Executing this command will create a `DICAT.spec` file in the same directory, as well as a `build` and `dist` directory. -## 2) Edit DICAT_application.spec +## 2) Edit DICAT.spec -Edit the `DICAT_application.spec` file to include the path to the image and the XML file used by the application (a.k.a. `images/DICAT_logo.gif` and `data/fields_to_zap.xml`). +Edit the `DICAT.spec` file to include the path to the image and the XML file used by the application (a.k.a. `images/DICAT_logo.gif` and `data/fields_to_zap.xml`). To do so, insert the following lines after the `Analysis` block of the spec file (Note, the /PATH/TO/DICOM_anonymizer should be updated with the proper full path). @@ -42,7 +42,7 @@ a.datas += [ FYI, the analysis block of the spec file looks like: ``` -a = Analysis(['DICAT_application.py'], +a = Analysis(['DICAT.py'], pathex=['/PATH/TO/DICOM_anonymizer/dicat'], binaries=None, datas=None, @@ -55,15 +55,15 @@ a = Analysis(['DICAT_application.py'], cipher=block_cipher) ``` -## 3) Rerun pyinstaller using DICAT_application.spec +## 3) Rerun pyinstaller using DICAT.spec Once the path to the images have been added, rerun the pyinstaller command on the spec file as follows. ### On Mac OS X -```pyinstaller --onefile --windowed --icon=images/DICAT_logo.icns DICAT_application.spec``` +```pyinstaller --onefile --windowed --icon=images/DICAT_logo.icns DICAT.spec``` -A `DICAT_application.app` will be located in the dist directory created by the pyinstaller command. +A `DICAT.app` will be located in the dist directory created by the pyinstaller command. To include the DICAT logo to the app, execute the following steps: @@ -74,9 +74,9 @@ To include the DICAT logo to the app, execute the following steps: * Choose 'Copy' from the 'Edit' menu. * Close the info window -2. Paste the icon to the `DICAT_application.app` item - * Go to the `DICAT_application.app` item in the Finder - * Click on the `DICAT_application.app` item +2. Paste the icon to the `DICAT.app` item + * Go to the `DICAT.app` item in the Finder + * Click on the `DICAT.app` item * Choose 'Get Info' from the 'File' menu. * In the info window that pops up, click on the icon * Choose 'Paste' from the 'Edit' menu. @@ -88,15 +88,15 @@ Congratulations! You just created the DICAT app for Mac OS X!! Note: Make sure the paths in the spec file contains \\ instead of \. -```pyinstaller --onefile --windowed --icon=images\DICAT_logo.icns DICAT_application.spec``` +```pyinstaller --onefile --windowed --icon=images\DICAT_logo.icns DICAT.spec``` -A `DICAT_application.exe` will be located in the dist directory created by the pyinstaller command. +A `DICAT.exe` will be located in the dist directory created by the pyinstaller command. Congratulations! You just created the DICAT executable for Windows!! ### On Linux (tested on Ubuntu) -```pyinstaller --onefile DICAT_application.spec``` +```pyinstaller --onefile DICAT.spec``` To include the DICAT logo to the application, execute the following steps: @@ -108,3 +108,4 @@ Congratulations! You just created the DICAT application for Linux!! +DICAT \ No newline at end of file From 1cabf0b7d9bb557386fef6d946e886ca99210b13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Mon, 11 Jul 2016 16:07:44 -0400 Subject: [PATCH 57/89] Enabled edition of candidate's information --- dicat/IDMapper.py | 15 ++-- dicat/lib/datamanagement.py | 135 ++++++++++++++++++++++++++++++----- dicat/lib/multilanguage.py | 8 ++- dicat/lib/utilities.py | 2 +- dicat/new_data_test.xml | 5 +- dicat/scheduler_candidate.py | 2 +- dicat/ui/datawindow.py | 51 +++++++++---- dicat/ui/dialogbox.py | 16 +++-- 8 files changed, 185 insertions(+), 49 deletions(-) diff --git a/dicat/IDMapper.py b/dicat/IDMapper.py index 526713d..7388fa1 100644 --- a/dicat/IDMapper.py +++ b/dicat/IDMapper.py @@ -4,6 +4,7 @@ from Tkinter import * import lib.datamanagement as DataManagement +import lib.config as Config import ttk @@ -174,7 +175,7 @@ def InitUI(self): self.error.grid(row=3, column=3) - def LoadXML(self, file): + def LoadXML(self): global xmlitemlist global xmldoc @@ -184,7 +185,7 @@ def LoadXML(self, file): """Parses the XML file and loads the data into the current window""" try: - xmldoc = DataManagement.read_xmlfile(file) + xmldoc = DataManagement.read_xmlfile(Config.xmlfile) #xmldata = xmldoc.getElementsByTagName('data')[0] xmlitemlist = xmldoc.getElementsByTagName('Candidate') for s in xmlitemlist: @@ -194,8 +195,8 @@ def LoadXML(self, file): #realname = firstname + " " + lastname dob = s.getElementsByTagName("DateOfBirth")[0].firstChild.nodeValue self.AddIdentifierAction(identifier, realname, dob, False) - except: - pass + except Exception as e: + print str(e) #TODO add error login (in case a candidate data file does not exist) def SaveMapAction(self): @@ -377,7 +378,8 @@ def openfilename(self): if self.filename: # Load the data - self.LoadXML(self.filename) + Config.xmlfile = self.filename + self.LoadXML() return self.filename @@ -393,7 +395,8 @@ def createfilename(self): open(self.filename, 'w') # Load the data - self.LoadXML(self.filename) + Config.xmlfile = self.filename + self.LoadXML() return self.filename diff --git a/dicat/lib/datamanagement.py b/dicat/lib/datamanagement.py index 2c4f38d..fd5834f 100644 --- a/dicat/lib/datamanagement.py +++ b/dicat/lib/datamanagement.py @@ -11,8 +11,16 @@ """ def read_xmlfile(xmlfile): + """ + Parses the XML file and return the data into xmldoc. + + :param: xmlfile + + :return: xmldoc + :rtype: object + + """ - """Parses the XML file return the data into xmldoc""" try: xmldoc = minidom.parse(xmlfile) return xmldoc @@ -21,40 +29,120 @@ def read_xmlfile(xmlfile): message = "ERROR: could not read file " + xmlfile print message #TODO: create a log class to display the messages +def read_data(xmlfile): + """ + + :param xmlfile: XML file to read to grep the data + :type xmlfile: str + + :return xmldata: everthing that is available under the data tag in the XML + :rtype xmldata: object + :return xmlcandlist: list of candidates + :rtype xmlcandlist: list + + """ + + global xmldoc + + xmldoc = read_xmlfile(xmlfile) + xmldata = xmldoc.getElementsByTagName('data')[0] + xmlcandlist = xmldata.getElementsByTagName('Candidate') + + return xmldata, xmlcandlist def read_candidate_data(): - """Read and return the content of a file called candidatedata. Returns nothing if file doesn't exist""" + """ + Read and return the candidate level content of an XML file specified in the + global variable Config.xmlfile. Returns an empty dictionary nothing if file + doesn't exist. + + :param: None + + :return: data + :rtype: dict + + """ data = {} - #check to see if file exists before loading it + # check to see if file exists before loading it if os.path.isfile(Config.xmlfile): # read the xml file - xmldoc = read_xmlfile(Config.xmlfile) - xmldata = xmldoc.getElementsByTagName('data')[0] - xmlcandlist = xmldata.getElementsByTagName('Candidate') + (xmldata, xmlcandlist) = read_data(Config.xmlfile) for cand in xmlcandlist: data[cand] = {} for elem in cand.childNodes: tag = elem.localName if not tag or tag == "Visit": continue - val = cand.getElementsByTagName(elem.localName)[0].firstChild.nodeValue + val = cand.getElementsByTagName(tag)[0].firstChild.nodeValue data[cand][tag] = val else: data = "" return data + +def save_candidate_data(cand_data): + """ + Save the updated candidate information into the xml file (defined by the + global variable Config.xmlfile). + + :param cand_data: data dictionary with the updated information + :type cand_data: dict + + :return: None + + """ + + # check to see if xmldoc global variable and file exist before saving + if os.path.isfile(Config.xmlfile) and xmldoc: + # read the xml file + (xmldata, xmlcandlist) = read_data(Config.xmlfile) + for cand in xmlcandlist: + for elem in cand.childNodes: + tag = elem.localName + if not tag: + continue + val = cand.getElementsByTagName(tag)[0].firstChild.nodeValue + if tag == "Identifier" and val == cand_data['Identifier']: + xml_firstname = cand.getElementsByTagName("FirstName")[0] + xml_lastname = cand.getElementsByTagName("LastName")[0] + xml_gender = cand.getElementsByTagName("Gender")[0] + xml_phone = cand.getElementsByTagName("PhoneNumber")[0] + xml_status = cand.getElementsByTagName("CandidateStatus")[0] + + xml_firstname.firstChild.nodeValue = cand_data['FirstName'] + xml_lastname.firstChild.nodeValue = cand_data['LastName'] + xml_gender.firstChild.nodeValue = cand_data['Gender'] + xml_status.firstChild.nodeValue = cand_data['CandStatus'] + xml_phone.firstChild.nodeValue = cand_data['Phone'] + break + + print cand.getElementsByTagName("FirstName")[0].firstChild.nodeValue + + # update the xml file with the correct values + f = open(Config.xmlfile, "w") + xmldoc.writexml(f) + + def read_visitset_data(): - """Read and return the content of a file called candidatedata. Returns nothing if file doesn't exist""" + """ + Read and return the visit set content of an XML file specified in the + global variable Config.xmlfile. Returns an empty dictionary nothing if + file doesn't exist. + + :param: None + + :return: data + :rtype: dict + + """ data = {} #check to see if file exists before loading it if os.path.isfile(Config.xmlfile): # read the xml file - xmldoc = read_xmlfile(Config.xmlfile) - xmldata = xmldoc.getElementsByTagName('data')[0] - xmlcandlist = xmldata.getElementsByTagName('Candidate') + (xmldata, xmlcandlist) = read_data(Config.xmlfile) for cand in xmlcandlist: data[cand] = {} for cand_elem in cand.childNodes: @@ -75,7 +163,23 @@ def read_visitset_data(): return data + def read_visit_data(xmlvisitlist, cand, data): + """ + Read the visit data related to a specific candidate and update the data + dictionary. + + :param xmlvisitlist: list of visits to loop through + :type xmlvisitlist: list + :param cand: candidate key + :type cand: object + :param data: data dictionary containing all candidate and visit's data + :type data: dict + + :return: None + + """ + data[cand]["VisitSet"] = {} for visit in xmlvisitlist: data[cand]["VisitSet"][visit] = {} @@ -86,13 +190,8 @@ def read_visit_data(xmlvisitlist, cand, data): visit_val = visit.getElementsByTagName(visit_tag)[0].firstChild.nodeValue data[cand]["VisitSet"][visit][visit_tag] = visit_val -def save_candidate_data(data): - """Save data in a pickle file named candididatedata. - Will overwrite any existing file. Will create one if it doesn't exist""" - pickle.dump(data, open("candidatedata", "wb")) - -def read_studydata(): +def read_study_data(): """Read and return the content of a file called studydata. Returns nothing if file doesn't exist""" #check to see if file exists before loading it if os.path.isfile("studydata"): @@ -100,7 +199,7 @@ def read_studydata(): db = pickle.load(open("studydata", "rb")) else: db = "" - return db + return db def save_study_data(data): diff --git a/dicat/lib/multilanguage.py b/dicat/lib/multilanguage.py index 9f36229..684ac0f 100644 --- a/dicat/lib/multilanguage.py +++ b/dicat/lib/multilanguage.py @@ -54,7 +54,7 @@ label_candidate_table = u"Faites un double-clic sur l'une des lignes pour remplir les champs ci-dessus" datatable_id = u"ID" datatable_firstname = u"Prénom" - datatable_lastname = "Nom" + datatable_lastname = u"Nom de famille" datatable_dob = u"Date de Naissance" datatable_phone = u"Téléphone" datatable_address = u"Adresse" @@ -101,6 +101,9 @@ dialog_no = u"Non" dialog_title_confirm = u"Veuillez confirmer!" dialog_close = u"Vous êtes sur le point de fermer cette fenêtre sans sauvegarder!\n\nVoulez-vous continuer?" + dialog_title_error = u"Erreur" + dialog_ok = u"OK" + dialog_missing_candidate_info = u"Les champs 'Identifiant', 'Prénom', 'Nom de famille', 'Sexe', 'Date de naissance' sont requis!" ################ DATA WINDOW ################### schedule_pane = u"Calendrier" candidate_pane = u"Candidat" @@ -209,6 +212,9 @@ dialog_no = u"No" dialog_title_confirm = u"Please confirm!" dialog_close = u"You are about to close this window without saving! \n\nDo you want to continue?" + dialog_title_error = u"Error" + dialog_ok = u"OK" + dialog_missing_candidate_info = u"'Identifier', 'Firstname', 'Lastname', 'Gender', 'Date of Birth' fields are required!" ################ DATA WINDOW ################### schedule_pane = u"Calendar" candidate_pane = u"Candidate" diff --git a/dicat/lib/utilities.py b/dicat/lib/utilities.py index 3e7a979..d3ce267 100644 --- a/dicat/lib/utilities.py +++ b/dicat/lib/utilities.py @@ -118,5 +118,5 @@ def print_object(something): # self-test "module" TODO remove before release if __name__ == '__main__': import lib.datamanagement as DataManagement - data=dict(DataManagement.read_studydata()) + data=dict(DataManagement.read_study_data()) print_object(data) \ No newline at end of file diff --git a/dicat/new_data_test.xml b/dicat/new_data_test.xml index 138465d..f49a231 100644 --- a/dicat/new_data_test.xml +++ b/dicat/new_data_test.xml @@ -1,5 +1,4 @@ - - + DICAT test This is a test project. @@ -222,6 +221,8 @@ Claus Male 2015-12-25 + + \ No newline at end of file diff --git a/dicat/scheduler_candidate.py b/dicat/scheduler_candidate.py index 3ab5d94..3a35b84 100644 --- a/dicat/scheduler_candidate.py +++ b/dicat/scheduler_candidate.py @@ -60,7 +60,7 @@ def setup_visitset(self): study_setup = dict() try: #1-open studydata - study_setup = dict(DataManagement.read_studydata()) + study_setup = dict(DataManagement.read_study_data()) except Exception as e: print str(e) #TODO add error login (in case a study data file does not exist) for key, value in study_setup.iteritems(): diff --git a/dicat/ui/datawindow.py b/dicat/ui/datawindow.py index 969cb36..59c038d 100644 --- a/dicat/ui/datawindow.py +++ b/dicat/ui/datawindow.py @@ -78,7 +78,8 @@ def body(self, master, candidate): self.text_pscid_var = StringVar() self.text_pscid_var.set(cand_info["Identifier"]) self.text_pscid = Entry( self.candidate_pane, - textvariable=self.text_pscid_var + textvariable=self.text_pscid_var, + state='disable' ) self.text_pscid.grid(column=0, row=1, padx=10, pady=5, sticky=N+S+E+W) @@ -304,7 +305,12 @@ def button_box(self): def ok_button(self, event=None): print "saving data and closing" # TODO remove when done - self.capture_data() + message = self.capture_data() + if not message: + parent = Frame(self) + newwin = DialogBox.ErrorMessage(parent, MultiLanguage.dialog_missing_candidate_info) + if newwin.buttonvalue == 1: + return # to stay on the candidate pop up page after clicking OK if not self.validate(): self.initial_focus.focus_set() # put focus back return @@ -334,18 +340,35 @@ def validate(self): def capture_data(self): """ - Grap the information from the window's text field and save the candidate information based on candidate_uid. + Grep the information from the pop up window's text fields and save the + candidate information based on the pscid. + + :param: None + + :return: None + """ - # open the 'database' - db = dict(DataManagement.read_candidate_data()) - # and find candidate based on uid - uid = self.candidate_uid - candidate = db[uid] + + # initialize the candidate dictionary with new values + cand_data = {} + # capture data from fields - candidate.pscid = self.text_pscid.get() - candidate.status = self.text_status.get() - candidate.firstname = self.text_firstname.get() - candidate.lastname = self.text_lastname.get() - candidate.phone = self.text_phone.get() + cand_data['Identifier'] = self.text_pscid.get() + cand_data['FirstName'] = self.text_firstname.get() + cand_data['LastName'] = self.text_lastname.get() + cand_data['Gender'] = self.text_gender.get() + cand_data['CandStatus'] = self.text_status.get() + cand_data['Phone'] = self.text_phone.get() + + if not cand_data['Identifier'] or not cand_data['FirstName'] \ + or not cand_data['LastName'] or not cand_data['Gender']: + return False + + if not cand_data['CandStatus'] or not cand_data['Phone']: + cand_data['CandStatus'] = " " + cand_data['Phone'] = " " + # save data - DataManagement.save_candidate_data(db) \ No newline at end of file + DataManagement.save_candidate_data(cand_data) + + return True diff --git a/dicat/ui/dialogbox.py b/dicat/ui/dialogbox.py index 182e830..d8e37bf 100644 --- a/dicat/ui/dialogbox.py +++ b/dicat/ui/dialogbox.py @@ -39,10 +39,11 @@ def buttonbox(self, button1, button2): box = Frame(self) b1 = Button(box, text=button1, width=12, command=self.button1, default=ACTIVE) b1.pack(side=LEFT, padx=4, pady=4) - b2 = Button(box, text=button2, width=12, command=self.button2, default=ACTIVE) - b2.pack(side=LEFT, padx=4, pady=4) self.bind("", self.button1) - self.bind("", self.button2) + if button2: + b2 = Button(box, text=button2, width=12, command=self.button2, default=ACTIVE) + b2.pack(side=LEFT, padx=4, pady=4) + self.bind("", self.button2) box.pack() def button1(self, event=None): @@ -68,11 +69,14 @@ def validate(self): ######################################################################################### class ConfirmYesNo(DialogBox): def __init__(self, parent, message): - title = MultiLanguage.dialog_title_confirm + title = MultiLanguage.dialog_title_confirm button1 = MultiLanguage.dialog_yes button2 = MultiLanguage.dialog_no DialogBox.__init__(self, parent, title, message, button1, button2) - - +class ErrorMessage(DialogBox): + def __init__(self, parent, message): + title = MultiLanguage.dialog_title_error + button = MultiLanguage.dialog_ok + DialogBox.__init__(self, parent, title, message, button, None) From 60a2335c973de9d86ef16bd7053d8236f3a0b6f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Mon, 25 Jul 2016 18:18:10 -0400 Subject: [PATCH 58/89] Modified ID Mapper to be reading same input as scheduler. --- dicat/IDMapper.py | 246 +++++++++-------- dicat/lib/datamanagement.py | 44 ++- dicat/lib/multilanguage.py | 2 + dicat/new_data_test.xml | 474 +++++++++++++++++---------------- dicat/scheduler_application.py | 33 ++- dicat/ui/datatable.py | 1 + dicat/ui/datawindow.py | 356 ++++++++++++++----------- dicat/ui/menubar.py | 20 -- dicat/ui/newcandidatewindow.py | 4 +- 9 files changed, 661 insertions(+), 519 deletions(-) diff --git a/dicat/IDMapper.py b/dicat/IDMapper.py index 7388fa1..36e0847 100644 --- a/dicat/IDMapper.py +++ b/dicat/IDMapper.py @@ -91,7 +91,6 @@ def initialize(self): self.InitUI() - def InitUI(self): self.frame = Frame(self.parent) @@ -104,9 +103,10 @@ def InitUI(self): for i in range(3, 4): self.frame.rowconfigure(i, weight=1) - self.labelID = Label(self.frame, text=u'Identifier') - self.labelName = Label(self.frame, text=u'Real Name') - self.labelDoB = Label(self.frame, text=u'Date of Birth (YYYY-MM-DD)') + self.labelID = Label(self.frame, text=u'Identifier') + self.labelFirstName = Label(self.frame, text=u'First Name') + self.labelLastName = Label(self.frame, text=u'Last Name') + self.labelDoB = Label(self.frame, text=u'Date of Birth (YYYY-MM-DD)') self.buttonAdd = Button(self.frame, width=12, text=u'Add candidate', @@ -132,11 +132,17 @@ def InitUI(self): ) self.candidateid.focus_set() - self.textCandName = StringVar() - self.candidatename = Entry(self.frame, - textvariable=self.textCandName, - width=20 - ) + self.textCandFirstName = StringVar() + self.candidateFirstName = Entry( self.frame, + textvariable=self.textCandFirstName, + width=20 + ) + + self.textCandLastName = StringVar() + self.candidateLastName = Entry( self.frame, + textvariable=self.textCandLastName, + width=20 + ) self.textCandDoB = StringVar() self.candidateDoB = Entry(self.frame, @@ -144,40 +150,59 @@ def InitUI(self): width=20 ) - self.tableColumns = ("Identifier", "Real Name", "Date of Birth") + self.tableColumns = ( "Identifier", "First Name", + "Last Name", "Date of Birth" + ) self.datatable = ttk.Treeview(self.frame, selectmode='browse', columns=self.tableColumns, show="headings") for col in self.tableColumns: - self.datatable.heading(col, text=col.title(), - command=lambda c=col: sortby(self.datatable, c, 0)) + self.datatable.heading( col, text=col.title(), + command=lambda c=col: sortby(self.datatable, + c, + 0 + ) + ) self.datatable.bind("<>", self.OnRowClick) self.ErrorMessage = StringVar() self.error = Label(self.frame, textvariable=self.ErrorMessage, fg='red') - self.labelID.grid(row=0, column=0, padx=(0,4), sticky=E+W) - self.labelName.grid(row=0, column=1, padx=(4,4), sticky=E+W) - self.labelDoB.grid(row=0, column=2, padx=(4,4), sticky=E+W) - - self.candidateid.grid(row=1, column=0, padx=(0,4), pady=(0,10), sticky=E+W) - self.candidatename.grid(row=1, column=1, padx=(4,4), pady=(0,10), sticky=E+W) - self.candidateDoB.grid(row=1, column=2, padx=(4,4), pady=(0,10), sticky=E+W) + self.labelID.grid( row=0, column=0, padx=(0,4), sticky=E+W) + self.labelFirstName.grid(row=0, column=1, padx=(4,4), sticky=E+W) + self.labelLastName.grid( row=0, column=2, padx=(4,4), sticky=E+W) + self.labelDoB.grid( row=0, column=3, padx=(4,4), sticky=E+W) + + self.candidateid.grid( row=1, column=0, + padx=(0,4), pady=(0,10), + sticky=E+W + ) + self.candidateFirstName.grid( row=1, column=1, + padx=(4,4), pady=(0,10), + sticky=E+W + ) + self.candidateLastName.grid( row=1, column=2, + padx=(4,4), pady=(0,10), + sticky=E+W + ) + self.candidateDoB.grid( row=1, column=3, + padx=(4,4), pady=(0,10), + sticky=E+W + ) - self.buttonAdd.grid(row=2, column=1, padx=(4,0), sticky=E+W) - self.buttonClear.grid(row=1, column=3, padx=(4,0), sticky=E+W) - self.buttonSearch.grid(row=2, column=0, padx=(4,0), sticky=E+W) - self.buttonEdit.grid(row=2, column=2, padx=(4,0), sticky=E+W) + self.buttonClear.grid( row=2, column=0, padx=(4,0), sticky=E+W) + self.buttonSearch.grid(row=2, column=1, padx=(4,0), sticky=E+W) + self.buttonEdit.grid( row=2, column=2, padx=(4,0), sticky=E+W) + self.buttonAdd.grid( row=2, column=3, padx=(4,0), sticky=E+W) - self.datatable.grid(row=3, column=0, columnspan=3, pady=10, sticky='nsew') - self.error.grid(row=3, column=3) + self.datatable.grid(row=3, column=0, columnspan=4, pady=10, sticky='nsew') + self.error.grid( row=4, column=0) def LoadXML(self): - global xmlitemlist - global xmldoc + global data # empty the datatable and data dictionary before loading new file self.datatable.delete(*self.datatable.get_children()) @@ -185,16 +210,16 @@ def LoadXML(self): """Parses the XML file and loads the data into the current window""" try: - xmldoc = DataManagement.read_xmlfile(Config.xmlfile) - #xmldata = xmldoc.getElementsByTagName('data')[0] - xmlitemlist = xmldoc.getElementsByTagName('Candidate') - for s in xmlitemlist: - identifier = s.getElementsByTagName("Identifier")[0].firstChild.nodeValue - realname = s.getElementsByTagName("RealName")[0].firstChild.nodeValue - #lastname = s.getElementsByTagName("LastName")[0].firstChild.nodeValue - #realname = firstname + " " + lastname - dob = s.getElementsByTagName("DateOfBirth")[0].firstChild.nodeValue - self.AddIdentifierAction(identifier, realname, dob, False) + data = DataManagement.read_candidate_data() + for key in data: + identifier = data[key]["Identifier"] + firstname = data[key]["FirstName"] + lastname = data[key]["LastName"] + dob = data[key]["DateOfBirth"] + self.AddIdentifierAction( identifier, firstname, + lastname, dob, + False + ) except Exception as e: print str(e) #TODO add error login (in case a candidate data file does not exist) @@ -220,13 +245,14 @@ def SaveMapEvent(self, event): def AddIdentifierEvent(self): - name = self.candidatename.get() - candid = self.candidateid.get() - dob = self.candidateDoB.get() - self.AddIdentifierAction(candid, name, dob) + firstname = self.candidateFirstName.get() + lastname = self.candidateLastName.get() + candid = self.candidateid.get() + dob = self.candidateDoB.get() + self.AddIdentifierAction(candid, firstname, lastname, dob) - def AddIdentifierAction(self, candid, realname, dob, save=True): + def AddIdentifierAction(self, candid, firstname, lastname, dob, save=True): """ Adds the given identifier and real name to the mapping. If the "save" parameter is true, this also triggers the saving @@ -236,7 +262,7 @@ def AddIdentifierAction(self, candid, realname, dob, save=True): self.ErrorMessage.set("") # check that all fields are set - if not candid or not realname or not dob: + if not candid or not firstname or not lastname or not dob: message = "ERROR:\nAll fields are\nrequired to add\na candidate" self.ErrorMessage.set(message) return @@ -255,15 +281,24 @@ def AddIdentifierAction(self, candid, realname, dob, save=True): self.ErrorMessage.set(message) return - mapList = [candid, realname, dob] + mapList = [candid, firstname, lastname, dob] self.IDMap[candid] = mapList - insertedList = [(candid, realname, dob)] + insertedList = [(candid, firstname, lastname, dob)] for item in insertedList: self.datatable.insert('', 'end', values=item) if(save): - self.SaveMapAction() + cand_data = {} + cand_data["Identifier"] = candid + cand_data["FirstName"] = firstname + cand_data["LastName"] = lastname + cand_data["DateOfBirth"] = dob + cand_data["Gender"] = " " + cand_data["PhoneNumber"] = " " + cand_data["CandidateStatus"] = " " + #self.SaveMapAction() + DataManagement.save_candidate_data(cand_data) def OnRowClick(self, event): @@ -273,14 +308,16 @@ def OnRowClick(self, event): item = self.datatable.item(item_id)['values'] self.textCandId.set(item[0]) - self.textCandName.set(item[1]) - self.textCandDoB.set(item[2]) + self.textCandFirstName.set(item[1]) + self.textCandLastName.set(item[2]) + self.textCandDoB.set(item[3]) def clear(self): self.textCandId.set("") - self.textCandName.set("") + self.textCandFirstName.set("") + self.textCandLastName.set("") self.textCandDoB.set("") self.candidateid.focus_set() @@ -288,35 +325,47 @@ def clear(self): def search(self): # Find a candidate based on its ID if it is set in text box if self.textCandId.get(): - (candid, name, dob) = self.FindCandidate("candid", - self.textCandId.get() - ) + (candid, firstname, + lastname, dob) = self.FindCandidate( "candid", + self.textCandId.get() + ) # or based on its name if it is set in text box - elif self.textCandName.get(): - (candid, name, dob) = self.FindCandidate("name", - self.textCandName.get() - ) + elif self.textCandFirstName.get(): + (candid, firstname, + lastname, dob) = self.FindCandidate( "firstname", + self.textCandFirstName.get() + ) + elif self.textCandLastName.get(): + (candid, firstname, + lastname, dob) = self.FindCandidate( "lastname", + self.textCandLastName.get() + ) + # print the values in the text box self.textCandId.set(candid) - self.textCandName.set(name) + self.textCandFirstName.set(firstname) + self.textCandLastName.set(lastname) self.textCandDoB.set(dob) def FindCandidate(self, key, value): - global xmlitemlist + global data # Loop through the candidate tree and return the candid, name and dob # that matches a given value - for s in xmlitemlist: - candid = s.getElementsByTagName("Identifier")[0].firstChild.nodeValue - name = s.getElementsByTagName("RealName")[0].firstChild.nodeValue - dob = s.getElementsByTagName("DateOfBirth")[0].firstChild.nodeValue + for cand_key in data: + candid = data[cand_key]["Identifier"] + firstname = data[cand_key]["FirstName"] + lastname = data[cand_key]["LastName"] + dob = data[cand_key]["DateOfBirth"] if (key == "candid" and value == candid): - return (candid, name, dob) - elif (key == "name" and value == name): - return (candid, name, dob) + return (candid, firstname, lastname, dob) + elif (key == "firstname" and value == firstname): + return (candid, firstname, lastname, dob) + elif (key == "lastname" and value == lastname): + return (candid, firstname, lastname, dob) elif (key == "dob" and value == dob): - return (candid, name, dob) + return (candid, firstname, lastname, dob) else: continue # if candidate was not found, return empty strings @@ -325,48 +374,34 @@ def FindCandidate(self, key, value): def edit(self): self.EditIdentifierAction(self.textCandId.get(), - self.textCandName.get(), + self.textCandFirstName.get(), + self.textCandLastName.get(), self.textCandDoB.get() ) - def EditIdentifierAction(self, identifier, realname, realdob, edit=True): - global xmlitemlist - # Loop through the candidate tree, find a candidate based on its ID - # and check if name or DoB needs to be updated - for s in xmlitemlist: - # initialize update variable - update = False - - # get the candid, name and dob stored in the XML file - candid = s.getElementsByTagName("Identifier")[0].firstChild.nodeValue - name = s.getElementsByTagName("RealName")[0].firstChild.nodeValue - dob = s.getElementsByTagName("DateOfBirth")[0].firstChild.nodeValue - - # if name of candidate is changed - if (candid == identifier) and not (realname == name): - # update in the XML file - s.getElementsByTagName("RealName")[0].firstChild.nodeValue = realname - update = True # set update to True - - # if candidate's date of birth is changed - if (candid == identifier) and not (realdob == dob): - # update in the XML file - s.getElementsByTagName("DateOfBirth")[0].firstChild.nodeValue = realdob - update = True # set update to True - - if (update): - # update the XML file - f = open(self.filename, "w") - xmldoc.writexml(f) - - # update IDMap dictionary - mapList = [candid, realname, realdob] - self.IDMap[candid] = mapList - - # update datatable - item = self.datatable.selection() - updatedList = (candid, realname, realdob) - self.datatable.item(item, values=updatedList) + + def EditIdentifierAction(self, identifier, firstname, lastname, dob, edit=True): + + # save data in the XML file + cand_data = {} + cand_data["Identifier"] = identifier + cand_data["FirstName"] = firstname + cand_data["LastName"] = lastname + cand_data["DateOfBirth"] = dob + cand_data["Gender"] = " " + cand_data["PhoneNumber"] = " " + cand_data["CandidateStatus"] = " " + DataManagement.save_candidate_data(cand_data) + + # update the IDMap dictionary + mapList = [identifier, firstname, lastname, dob] + self.IDMap[identifier] = mapList + + # update datatable + item = self.datatable.selection() + updatedList = (identifier, firstname, lastname, dob) + self.datatable.item(item, values=updatedList) + def openfilename(self): @@ -383,6 +418,7 @@ def openfilename(self): return self.filename + def createfilename(self): self.filename = tkFileDialog.asksaveasfilename( diff --git a/dicat/lib/datamanagement.py b/dicat/lib/datamanagement.py index fd5834f..bb8db2d 100644 --- a/dicat/lib/datamanagement.py +++ b/dicat/lib/datamanagement.py @@ -98,6 +98,7 @@ def save_candidate_data(cand_data): if os.path.isfile(Config.xmlfile) and xmldoc: # read the xml file (xmldata, xmlcandlist) = read_data(Config.xmlfile) + updated = False for cand in xmlcandlist: for elem in cand.childNodes: tag = elem.localName @@ -108,22 +109,38 @@ def save_candidate_data(cand_data): xml_firstname = cand.getElementsByTagName("FirstName")[0] xml_lastname = cand.getElementsByTagName("LastName")[0] xml_gender = cand.getElementsByTagName("Gender")[0] + xml_dob = cand.getElementsByTagName("DateOfBirth")[0] xml_phone = cand.getElementsByTagName("PhoneNumber")[0] xml_status = cand.getElementsByTagName("CandidateStatus")[0] xml_firstname.firstChild.nodeValue = cand_data['FirstName'] xml_lastname.firstChild.nodeValue = cand_data['LastName'] - xml_gender.firstChild.nodeValue = cand_data['Gender'] - xml_status.firstChild.nodeValue = cand_data['CandStatus'] - xml_phone.firstChild.nodeValue = cand_data['Phone'] + xml_gender.firstChild.nodeValue = cand_data['Gender'] + xml_dob.firstChild.nodeValue = cand_data['DateOfBirth'] + xml_status.firstChild.nodeValue = cand_data['CandidateStatus'] + xml_phone.firstChild.nodeValue = cand_data['PhoneNumber'] + + updated = True break - print cand.getElementsByTagName("FirstName")[0].firstChild.nodeValue + # if no candidate was updated, insert a new candidate + if not updated: + # Create a new Candidate element + cand = xmldoc.createElement("Candidate") + xmldata.appendChild(cand) + + for key in cand_data: + xml_elem = xmldoc.createElement(key) + cand.appendChild(xml_elem) + txt = xmldoc.createTextNode( cand_data[key] ) + xml_elem.appendChild(txt) # update the xml file with the correct values f = open(Config.xmlfile, "w") - xmldoc.writexml(f) - + xmldoc.writexml(f, addindent=" ", newl="\n") + f.close() + # remove the empty lines inserted by writexml + remove_empty_lines_from_file(Config.xmlfile) def read_visitset_data(): """ @@ -208,6 +225,21 @@ def save_study_data(data): pickle.dump(data, open("studydata", "wb")) +def remove_empty_lines_from_file(file): + """ + This function allows to remove empty lines that are inserted by writexml + function from minidom. + + :param file: file that need empty lines to be removed + :type file: str + """ + + # grep all lines that are not empty into lines + with open(file) as f: + lines = [line for line in f if line.strip() is not ""] + # write lines into the file + with open(file, "w") as f: + f.writelines(lines) #self-test "module" TODO remove if __name__ == '__main__': diff --git a/dicat/lib/multilanguage.py b/dicat/lib/multilanguage.py index 684ac0f..0010705 100644 --- a/dicat/lib/multilanguage.py +++ b/dicat/lib/multilanguage.py @@ -109,6 +109,7 @@ candidate_pane = u"Candidat" candidate_firstname = u"Prénom" candidate_lastname = u"Nom de famille" + candidate_dob = u"Date de naissance" candidate_phone = u"Téléphone" candidate_pscid = u"ID" candidate_status = u"Status" @@ -220,6 +221,7 @@ candidate_pane = u"Candidate" candidate_firstname = u"Firstname" candidate_lastname = u"Lastname" + candidate_dob = u"Date of Birth" candidate_phone = u"Phone" candidate_pscid = u"ID" candidate_status = u"Status" diff --git a/dicat/new_data_test.xml b/dicat/new_data_test.xml index f49a231..5f52f7b 100644 --- a/dicat/new_data_test.xml +++ b/dicat/new_data_test.xml @@ -1,228 +1,246 @@ - - - DICAT test - This is a test project. - - 0 - 100 - 2000 - 2050 - - - Project 1 - - Subproject 1 - - V0 - 1 - 25-35 - - V1 - 2 - 55-65 - - V2 - 3 - 95-105 - - - V3 - 4 - - - - Subproject 2 - - V0 - 1 - - - - - - - - MTL0002 - Sepia - Calamari - Female - 2015-06-14 - 444-555-6666 - active - - V0 - completed - - 2016-01-06 13:15:00 - 2016-01-06 14:15:00 - Douglas - Annie - - - V1 - completed - 2016-04-05 13:15:00 - 2016-04-05 14:15:00 - Douglas - Jennifer - - - V2 - scheduled - 2016-06-05 13:15:00 - 2016-06-05 14:15:00 - Douglas - Jennifer - - - - MTL0003 - Bibi - LaPraline - Female - 2010-08-12 - 450-345-6789 - excluded - - V0 - completed - - 2016-01-06 13:15:00 - 2016-01-06 14:15:00 - Douglas - Annie - - - - MTL0001 - Blues - Singer - Male - 2015-06-14 - 444-555-6666 - active - - V0 - completed - - 2016-01-06 13:15:00 - 2016-01-06 14:15:00 - Douglas - Annie - - - V1 - completed - 2016-04-05 13:15:00 - 2016-04-05 14:15:00 - Douglas - Jennifer - - - V2 - scheduled - 2016-06-05 13:15:00 - 2016-06-05 14:15:00 - Douglas - Jennifer - - - - MTL0006 - Ali - Gator - Male - 2014-02-05 - 514-758-8903 - active - - V0 - completed - - 2016-01-06 13:15:00 - 2016-01-06 14:15:00 - Douglas - Annie - - - V1 - scheduled - 2016-04-05 13:15:00 - 2016-04-05 14:15:00 - Douglas - Jennifer - - - - MTL0004 - Pikachu - Pokemon - Male - 1996-01-01 - 543-453-5432 - withdrawn - - V0 - completed - - 2016-01-06 13:15:00 - 2016-01-06 14:15:00 - Douglas - Annie - - - - MTL0005 - Bilou - Doudou - Female - 2014-02-03 - 450-758-9385 - withdrawn - - V0 - completed - - 2016-01-06 13:15:00 - 2016-01-06 14:15:00 - Douglas - Annie - - - - MTL9999 - Lego - Phantom - Male - 2012-04-29 - 432-654-9093 - active - - V0 - completed - - 2016-01-06 13:15:00 - 2016-01-06 14:15:00 - Douglas - Annie - - - V1 - scheduled - 2016-04-05 13:15:00 - 2016-04-05 14:15:00 - Douglas - Jennifer - - - - MTL0025 - Santa - Claus - Male - 2015-12-25 - - - - - \ No newline at end of file + + + + DICAT test + This is a test project. + + 0 + 100 + 2000 + 2050 + + Project 1 + + Subproject 1 + + V0 + 1 + + 25-35 + + V1 + 2 + 55-65 + + V2 + 3 + 95-105 + + + V3 + 4 + + + + Subproject 2 + + V0 + 1 + + + + + + + + MTL0002 + + Sepia + Calamari + Female + 2015-06-14 + 444-555-6666 + active + + V0 + completed + + 2016-01-06 13:15:00 + 2016-01-06 14:15:00 + Douglas + Annie + + + V1 + completed + 2016-04-05 13:15:00 + 2016-04-05 14:15:00 + Douglas + Jennifer + + + V2 + scheduled + 2016-06-05 13:15:00 + 2016-06-05 14:15:00 + Douglas + Jennifer + + + + MTL0003 + Bibi + LaPraline + Female + 2010-08-12 + 450-345-6789 + excluded + + V0 + completed + + 2016-01-06 13:15:00 + 2016-01-06 14:15:00 + Douglas + Annie + + + + MTL0001 + Blues + Singer + Male + 2015-06-14 + 444-555-6666 + active + + V0 + completed + + 2016-01-06 13:15:00 + 2016-01-06 14:15:00 + Douglas + Annie + + + V1 + completed + 2016-04-05 13:15:00 + 2016-04-05 14:15:00 + Douglas + Jennifer + + + V2 + scheduled + 2016-06-05 13:15:00 + 2016-06-05 14:15:00 + Douglas + Jennifer + + + + MTL0006 + Ali + Gator + Male + 2014-02-05 + 514-758-8903 + active + + V0 + completed + + 2016-01-06 13:15:00 + 2016-01-06 14:15:00 + Douglas + Annie + + + V1 + scheduled + 2016-04-05 13:15:00 + 2016-04-05 14:15:00 + Douglas + Jennifer + + + + MTL0004 + Pikachu + Pokemon + Male + 1996-01-01 + 543-453-5432 + withdrawn + + V0 + completed + + 2016-01-06 13:15:00 + 2016-01-06 14:15:00 + Douglas + Annie + + + + MTL0005 + Bilou + Doudou + Female + 2014-02-03 + 450-758-9385 + withdrawn + + V0 + completed + + 2016-01-06 13:15:00 + 2016-01-06 14:15:00 + Douglas + Annie + + + + MTL9999 + Lego + Phantom + Male + 2012-04-29 + 432-654-9093 + active + + V0 + completed + + 2016-01-06 13:15:00 + 2016-01-06 14:15:00 + Douglas + Annie + + + V1 + scheduled + 2016-04-05 13:15:00 + 2016-04-05 14:15:00 + Douglas + Jennifer + + + + MTL0025 + Santa + Claus + Male + 2015-12-25 + + + + James + Bond + 1977-07-07 + + Male + MTL0007 + + + + Bugs + Bunny + 1938-04-30 + + Male + MTL0008 + + + diff --git a/dicat/scheduler_application.py b/dicat/scheduler_application.py index 43cc893..74c46fb 100644 --- a/dicat/scheduler_application.py +++ b/dicat/scheduler_application.py @@ -5,6 +5,7 @@ from ttk import * #import internal packages import ui.datatable as DataTable +import ui.datawindow as DataWindow import lib.multilanguage as MultiLanguage import lib.config as Config @@ -53,7 +54,26 @@ def initialize(self): self.data_pane.add(self.candidate_pane) self.data_pane.add(self.visit_pane) + # plot the button action in the pane frame + # candidate pane frame buttons + self.buttonNewCandidate = Button( self.candidate_pane, + width=12, + text=MultiLanguage.candidate_add, + command=self.add_candidate + ) + self.buttonNewCandidate.pack(side=TOP, anchor=W) + # create data tables (treeview) + # candidate table + candidate_column_headers = ( 'identifier', 'firstname', 'lastname', + 'date of birth', 'gender', 'phone', + 'status' + ) + self.cand_table = DataTable.ParticipantsList( self.candidate_pane, + candidate_column_headers + ) + self.cand_table.pack(side=BOTTOM, expand=YES, fill=BOTH) + # calendar table visit_column_headers = ( 'identifier', 'candidate', 'visitlabel', 'when', 'where', 'status' ) @@ -61,13 +81,6 @@ def initialize(self): visit_column_headers ) self.visit_table.pack(side=BOTTOM, expand=YES, fill=BOTH) - candidate_column_headers = ( 'identifier', 'firstname', 'lastname', - 'gender', 'phone', 'status' - ) - self.data_table = DataTable.ParticipantsList( self.candidate_pane, - candidate_column_headers - ) - self.data_table.pack(side=BOTTOM, expand=YES, fill=BOTH) """ #create a filter section in each data_pane(not implemented yet) @@ -81,3 +94,9 @@ def initialize(self): self.filter_candidate_label = Label(self.filter_visit, text='Filters for Active / Tentative / Closed ...') self.filter_candidate_label.pack(side=TOP, expand=NO, fill=BOTH) """ + + def add_candidate(self): + #TODO implement add_candidate() + DataWindow.DataWindow(self, "new") + print 'running add_candidate' + pass diff --git a/dicat/ui/datatable.py b/dicat/ui/datatable.py index 364acaa..4c7b33a 100644 --- a/dicat/ui/datatable.py +++ b/dicat/ui/datatable.py @@ -147,6 +147,7 @@ def load_data(self): values=[ data[key]["Identifier"], data[key]["FirstName"], data[key]["LastName"], + data[key]["DateOfBirth"], data[key]["Gender"], phone, status diff --git a/dicat/ui/datawindow.py b/dicat/ui/datawindow.py index 59c038d..be7c6b2 100644 --- a/dicat/ui/datawindow.py +++ b/dicat/ui/datawindow.py @@ -16,14 +16,14 @@ class DataWindow(Toplevel): - def __init__(self, parent, candidate_uuid='new'): + def __init__(self, parent, candidate='new'): Toplevel.__init__(self, parent) # create a transient window on top of parent window - self.transient(parent) + self.transient(parent) self.parent = parent self.title(MultiLanguage.data_window_title) #TODO find a better title for the thing body = Frame(self) - self.initial_focus = self.body(body, candidate_uuid) + self.initial_focus = self.body(body, candidate) body.pack(padx=5, pady=5) self.button_box() @@ -43,8 +43,8 @@ def body(self, master, candidate): try: cand_data = DataManagement.read_candidate_data() # TODO better way to do this visit_data = DataManagement.read_visitset_data() - visitset = {} - cand_info = {} + visitset = {} + cand_info = {} for cand_key in cand_data: if cand_data[cand_key]["Identifier"] == candidate: cand_info = cand_data[cand_key] @@ -56,7 +56,7 @@ def body(self, master, candidate): except Exception as e: print "datawindow.body ", str(e) # TODO manage exceptions - # Candidate section + ## Candidate section self.candidate_pane = Labelframe( self, text=MultiLanguage.candidate_pane, width=250, @@ -67,136 +67,168 @@ def body(self, master, candidate): padx=5, pady=5 ) - # object unique id - does not appear on gui but needed to keep track of this candidate - #self.candidate_uid = candidate["Identifier"] - - # PSCID - self.label_pscid = Label( self.candidate_pane, - text=MultiLanguage.candidate_pscid - ) - self.label_pscid.grid(column=0, row=0, padx=10, pady=5, sticky=N+S+E+W) - self.text_pscid_var = StringVar() - self.text_pscid_var.set(cand_info["Identifier"]) - self.text_pscid = Entry( self.candidate_pane, - textvariable=self.text_pscid_var, - state='disable' - ) - self.text_pscid.grid(column=0, row=1, padx=10, pady=5, sticky=N+S+E+W) - - # firstname - self.label_firstname = Label( self.candidate_pane, - text=MultiLanguage.candidate_firstname - ) - self.label_firstname.grid( column=1, row=0, - padx=10, pady=5, - sticky=N+S+E+W - ) + # initialize text variables that will contain the field values + self.text_pscid_var = StringVar() self.text_firstname_var = StringVar() - self.text_firstname_var.set(cand_info["FirstName"]) - self.text_firstname = Entry( self.candidate_pane, - textvariable=self.text_firstname_var - ) - self.text_firstname.grid( column=1, row=1, - padx=10, pady=5, - sticky=N+S+E+W - ) - - # lastname - self.label_lastname = Label( self.candidate_pane, - text=MultiLanguage.candidate_lastname - ) - self.label_lastname.grid(column=2, row=0, padx=10, pady=5, sticky=N+S+E+W) - self.text_lastname_var = StringVar() - self.text_lastname_var.set(cand_info["LastName"]) - self.text_lastname = Entry( self.candidate_pane, - textvariable=self.text_lastname_var - ) - self.text_lastname.grid(column=2, row=1, padx=10, pady=5, sticky=N+S+E+W) - - # gender - self.label_gender = Label( self.candidate_pane, - text=MultiLanguage.candidate_gender - ) - self.label_gender.grid(column=0, row=2, padx=10, pady=5, sticky=N+S+E+W) - self.text_gender_var = StringVar() - self.text_gender_var.set(cand_info["Gender"]) - self.text_gender = Entry( self.candidate_pane, - textvariable=self.text_gender_var - ) - self.text_gender.grid(column=0, row=3, padx=10, pady=5, sticky=N+S+E+W) - - # status - self.label_status = Label( self.candidate_pane, - text=MultiLanguage.candidate_status - ) - self.label_status.grid( column=1, row=2, - padx=10, pady=5, - sticky=N+S+E+W - ) - self.text_status_var = StringVar() - self.text_status_var.set(cand_info["CandidateStatus"]) - self.text_status = Entry( self.candidate_pane, - textvariable=self.text_status_var - ) - self.text_status.grid(column=1, row=3, padx=10, pady=5, sticky=N+S+E+W) - - # phone number - self.label_phone = Label( self.candidate_pane, - text=MultiLanguage.candidate_phone - ) - self.label_phone.grid(column=2, row=2, padx=10, pady=5, sticky=N+S+E+W) - self.text_phone_var = StringVar() - self.text_phone_var.set(cand_info["PhoneNumber"]) - self.text_phone = Entry( self.candidate_pane, - textvariable=self.text_phone_var - ) - self.text_phone.grid(column=2, row=3, padx=10, pady=5, sticky=N+S+E+W) - - # Schedule Section - displayed as a table - self.schedule_pane = Labelframe( self, - text=MultiLanguage.schedule_pane, - width=250, - height=350, - borderwidth=10 - ) + self.text_lastname_var = StringVar() + self.text_dob_var = StringVar() + self.text_gender_var = StringVar() + self.text_status_var = StringVar() + self.text_phone_var = StringVar() + + # if candidate="new" populate the field with an empty string + # otherwise populate with the values available in cand_info dictionary + if candidate == "new": + self.text_pscid_var = "" + self.text_firstname_var = "" + self.text_lastname_var = "" + self.text_dob_var = "" + self.text_gender_var = "NA" + self.text_status_var = "" + self.text_phone_var = "" + else: + self.text_pscid_var.set(cand_info["Identifier"]) + self.text_firstname_var.set(cand_info["FirstName"]) + self.text_lastname_var.set(cand_info["LastName"]) + self.text_dob_var.set(cand_info["DateOfBirth"]) + self.text_gender_var.set(cand_info["Gender"]) + self.text_status_var.set(cand_info["CandidateStatus"]) + self.text_phone_var.set(cand_info["PhoneNumber"]) + + print cand_info["Gender"] + print self.text_gender_var.get() + # Create widgets to be displayed + # (typically a label with a text box underneath per variable to display) + self.label_pscid = Label( # identifier label + self.candidate_pane, text=MultiLanguage.candidate_pscid + ) + self.text_pscid = Entry( # identifier text box + self.candidate_pane, textvariable=self.text_pscid_var + ) + self.label_firstname = Label( # firstname label + self.candidate_pane, text=MultiLanguage.candidate_firstname + ) + self.text_firstname = Entry( # firstname text box + self.candidate_pane, textvariable=self.text_firstname_var + ) + self.label_lastname = Label( # lastname label + self.candidate_pane, text=MultiLanguage.candidate_lastname + ) + self.text_lastname = Entry( # lastname text box + self.candidate_pane, textvariable=self.text_lastname_var + ) + self.label_dob = Label( # date of birth label + self.candidate_pane, text=MultiLanguage.candidate_dob + ) + self.text_dob = Entry( # date of birth text box + self.candidate_pane, textvariable=self.text_dob_var + ) + self.label_gender = Label( # gender label + self.candidate_pane, text=MultiLanguage.candidate_gender + ) + self.text_gender = OptionMenu( # gender selected from a drop down menu + self.candidate_pane, + self.text_gender_var, # variable in which to store the selection + self.text_gender_var.get(), # default value to be used at display + *("NA", "Male", "Female") # list of drop down options + ) + self.label_status = Label( # candidate status label + self.candidate_pane, text=MultiLanguage.candidate_status + ) + self.text_status = Entry( # candidate status text box + self.candidate_pane, textvariable=self.text_status_var + ) + self.label_phone = Label( # phone number label + self.candidate_pane, text=MultiLanguage.candidate_phone + ) + self.text_phone = Entry( # phone number text box + self.candidate_pane, textvariable=self.text_phone_var + ) + + # Draw widgets in the candidate pane section + self.label_pscid.grid( # draw identifier label + column=0, row=0, padx=10, pady=5, sticky=N+S+E+W + ) + self.text_pscid.grid( # draw identifier text box + column=0, row=1, padx=10, pady=5, sticky=N+S+E+W + ) + self.label_firstname.grid( # draw firstname label + column=1, row=0, padx=10, pady=5, sticky=N+S+E+W + ) + self.text_firstname.grid( # draw firstname text box + column=1, row=1, padx=10, pady=5, sticky=N+S+E+W + ) + self.label_lastname.grid( # draw lastname label + column=2, row=0, padx=10, pady=5, sticky=N+S+E+W + ) + self.text_lastname.grid( # draw lastname text box + column=2, row=1, padx=10, pady=5, sticky=N+S+E+W + ) + self.label_dob.grid( # draw date of birth label + column=3, row=0, padx=10, pady=5, sticky=N+S+E+W + ) + self.text_dob.grid( # draw date of birth text box + column=3, row=1, padx=10, pady=5, sticky=N+S+E+W + ) + self.label_gender.grid( # draw gender label + column=0, row=2, padx=10, pady=5, sticky=N+S+E+W + ) + self.text_gender.grid( # draw gender text box + column=0, row=3, padx=10, pady=5, sticky=N+S+E+W + ) + self.label_status.grid( # draw candidate status label + column=1, row=2, padx=10, pady=5, sticky=N+S+E+W + ) + self.text_status.grid( # draw candidate status text box + column=1, row=3, padx=10, pady=5, sticky=N+S+E+W + ) + self.label_phone.grid( # draw phone number label + column=2, row=2, padx=10, pady=5, sticky=N+S+E+W + ) + self.text_phone.grid( # draw phone number text box + column=2, row=3, padx=10, pady=5, sticky=N+S+E+W + ) + + + ## Calendar Section - displayed as a table + self.schedule_pane = Labelframe( + self, text=MultiLanguage.schedule_pane, + width=250, height=350, + borderwidth=10 + ) self.schedule_pane.pack(side=TOP, expand=YES, fill=BOTH, padx=5, pady=5) # top row (header) - self.label_visit_label = Label( self.schedule_pane, - text=MultiLanguage.col_visitlabel - ) - self.label_visit_label.grid( column=1, row=0, - padx=5, pady=5, - sticky=N+S+E+W - ) - self.label_visit_when = Label( self.schedule_pane, - text=MultiLanguage.col_when - ) - self.label_visit_when.grid( column=2, row=0, - padx=5, pady=5, - sticky=NSEW - ) - self.label_visit_status = Label( self.schedule_pane, - text=MultiLanguage.col_where - ) - self.label_visit_status.grid( column=3, row=0, - padx=5, pady=5, - sticky=N+S+E+W - ) - self.label_visit_status = Label( self.schedule_pane, - text=MultiLanguage.col_withwhom - ) - self.label_visit_status.grid( column=4, row=0, - padx=5, pady=5, - sticky=N+S+E+W - ) - self.label_visit_status = Label( self.schedule_pane, - text=MultiLanguage.col_status - ) - self.label_visit_status.grid( column=5, row=0, - padx=5, pady=5, - sticky=N+S+E+W - ) + self.label_visit_label = Label( + self.schedule_pane, text=MultiLanguage.col_visitlabel + ) + self.label_visit_when = Label( + self.schedule_pane, text=MultiLanguage.col_when + ) + self.label_visit_label.grid( + column=1, row=0, padx=5, pady=5, sticky=N+S+E+W + ) + self.label_visit_when.grid( + column=2, row=0, padx=5, pady=5, sticky=N+S+E+W + ) + self.label_visit_status = Label( + self.schedule_pane, text=MultiLanguage.col_where + ) + self.label_visit_status.grid( + column=3, row=0, padx=5, pady=5, sticky=N+S+E+W + ) + self.label_visit_status = Label( + self.schedule_pane, text=MultiLanguage.col_withwhom + ) + self.label_visit_status.grid( + column=4, row=0, padx=5, pady=5, sticky=N+S+E+W + ) + self.label_visit_status = Label( + self.schedule_pane, text=MultiLanguage.col_status + ) + self.label_visit_status.grid( + column=5, row=0, padx=5, pady=5, sticky=N+S+E+W + ) """ PSEUDOCODE @@ -289,31 +321,50 @@ def body(self, master, candidate): def button_box(self): + # add standard button box box = Frame(self) - w = Button( box, text="OK", - width=10, command=self.ok_button, - default=ACTIVE - ) - w.pack(side=LEFT, padx=5, pady=5) - w = Button(box, text="Cancel", width=10, command=self.cancel_button) - w.pack(side=LEFT, padx=5, pady=5) + + # initialize buttons + ok = Button( box, text="OK", + width=10, command=self.ok_button, + default=ACTIVE + ) + cancel = Button( box, text="Cancel", + width=10, command=self.cancel_button + ) + + # draw the buttons + ok.pack(side=LEFT, padx=5, pady=5) + cancel.pack(side=LEFT, padx=5, pady=5) + + # bind key handlers to button functions self.bind("", self.ok_button) self.bind("", self.closedialog) + + # draw the button box box.pack() def ok_button(self, event=None): + print "saving data and closing" # TODO remove when done + message = self.capture_data() + if not message: parent = Frame(self) - newwin = DialogBox.ErrorMessage(parent, MultiLanguage.dialog_missing_candidate_info) + newwin = DialogBox.ErrorMessage( + parent, + MultiLanguage.dialog_missing_candidate_info + ) if newwin.buttonvalue == 1: return # to stay on the candidate pop up page after clicking OK + if not self.validate(): self.initial_focus.focus_set() # put focus back return + #need to call treeview update here self.withdraw() self.closedialog() @@ -330,7 +381,8 @@ def cancel_button(self, event=None): def closedialog(self, event=None): - self.parent.focus_set() # put focus back to parent window before destroying the window + # put focus back to parent window before destroying the window + self.parent.focus_set() self.destroy() @@ -353,20 +405,22 @@ def capture_data(self): cand_data = {} # capture data from fields - cand_data['Identifier'] = self.text_pscid.get() - cand_data['FirstName'] = self.text_firstname.get() - cand_data['LastName'] = self.text_lastname.get() - cand_data['Gender'] = self.text_gender.get() - cand_data['CandStatus'] = self.text_status.get() - cand_data['Phone'] = self.text_phone.get() + cand_data['Identifier'] = self.text_pscid.get() + cand_data['FirstName'] = self.text_firstname.get() + cand_data['LastName'] = self.text_lastname.get() + cand_data['DateOfBirth'] = self.text_dob.get() + cand_data['Gender'] = self.text_gender.get() + cand_data['PhoneNumber'] = self.text_phone.get() + cand_data['CandidateStatus'] = self.text_status.get() if not cand_data['Identifier'] or not cand_data['FirstName'] \ - or not cand_data['LastName'] or not cand_data['Gender']: + or not cand_data['LastName'] or not cand_data['Gender'] \ + or not cand_data['DateOfBirth']: return False - if not cand_data['CandStatus'] or not cand_data['Phone']: - cand_data['CandStatus'] = " " - cand_data['Phone'] = " " + if not cand_data['CandidateStatus'] or not cand_data['PhoneNumber']: + cand_data['CandidateStatus'] = " " + cand_data['PhoneNumber'] = " " # save data DataManagement.save_candidate_data(cand_data) diff --git a/dicat/ui/menubar.py b/dicat/ui/menubar.py index a54cbeb..6a27000 100644 --- a/dicat/ui/menubar.py +++ b/dicat/ui/menubar.py @@ -18,11 +18,6 @@ def __init__(self, parent): self.add_cascade(label=MultiLanguage.candidate_menu, underline=0, menu=candidate_menu) candidate_menu.add_command(label=MultiLanguage.candidate_add, command=self.add_candidate) candidate_menu.add_command(label=MultiLanguage.candidate_find, command=self.find_candidate) - candidate_menu.add_command(label=MultiLanguage.candidate_update, command=self.update_candidate) - candidate_menu.add_separator() - candidate_menu.add_command(label=MultiLanguage.candidate_get_id, command=self.get_candidate_id) - candidate_menu.add_separator() - candidate_menu.add_command(label=MultiLanguage.candidate_exclude_include_toggle, command=self.exclude_candidate) # create a CALENDAR pulldown menu calendar_menu = Tkinter.Menu(self, tearoff=False) self.add_cascade(label=MultiLanguage.calendar_menu, underline=0, menu=calendar_menu) @@ -65,21 +60,6 @@ def find_candidate(self): print 'running find_candidate' pass - def update_candidate(self): - #TODO implement update_candidate() - print 'running update_candidate' - pass - - def get_candidate_id(self): - #TODO get_candidate_id() - print 'running get_candidate_id' - pass - - def exclude_candidate(self): #need renaming - #TODO exclude_candidate() - print 'running incativate_candidate' - pass - def open_help(self): #TODO open_help() print 'running open_help' diff --git a/dicat/ui/newcandidatewindow.py b/dicat/ui/newcandidatewindow.py index 6619719..516acfe 100644 --- a/dicat/ui/newcandidatewindow.py +++ b/dicat/ui/newcandidatewindow.py @@ -66,8 +66,8 @@ def body(self, master, candidate): self.label_phone.grid(column=2, row=2, padx=10, pady=5, sticky=N+S+E+W) self.text_phone_var = StringVar() self.text_phone_var.set(candidate.phone) - self.text_pone = Entry(self.candidate_pane, textvariable=self.text_phone_var) - self.text_pone.grid(column=2, row=3, padx=10, pady=5, sticky=N+S+E+W) + self.text_phone = Entry(self.candidate_pane, textvariable=self.text_phone_var) + self.text_phone.grid(column=2, row=3, padx=10, pady=5, sticky=N+S+E+W) #pscid #self.label_pscid = Label(self.candidate_pane, textvariable=self.pscid_var) From 5fb5f28528573c055a9e0ad4e700a2f0eff3c566 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Tue, 26 Jul 2016 14:22:43 -0400 Subject: [PATCH 59/89] Cleaned up some TODO statements. --- dicat/scheduler_application.py | 4 ++-- dicat/ui/datatable.py | 6 +++--- dicat/ui/datawindow.py | 4 +--- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/dicat/scheduler_application.py b/dicat/scheduler_application.py index 74c46fb..2c5c124 100644 --- a/dicat/scheduler_application.py +++ b/dicat/scheduler_application.py @@ -96,7 +96,7 @@ def initialize(self): """ def add_candidate(self): - #TODO implement add_candidate() DataWindow.DataWindow(self, "new") + # TODO: might need to refresh data table to include new candidate? print 'running add_candidate' - pass + diff --git a/dicat/ui/datatable.py b/dicat/ui/datatable.py index 4c7b33a..ee986c6 100644 --- a/dicat/ui/datatable.py +++ b/dicat/ui/datatable.py @@ -112,13 +112,13 @@ def onrowclick(self, event): """Not used yet""" item_id = str(self.datatable.focus()) item = self.datatable.item(item_id)['values'] - # print item_id, item #TODO remove when done class ParticipantsList(DataTable): """ - class ParticipantsList(DataTable) takes care of the data table holding the list of participants - That list is comprised of all participants (even those that have not been called once. + Class ParticipantsList(DataTable) takes care of the data table holding the + list of participants. That list contains all participants (even those that + have never been called). """ def __init__(self, parent, colheaders): # expected is dataset diff --git a/dicat/ui/datawindow.py b/dicat/ui/datawindow.py index be7c6b2..537db8a 100644 --- a/dicat/ui/datawindow.py +++ b/dicat/ui/datawindow.py @@ -41,7 +41,7 @@ def __init__(self, parent, candidate='new'): def body(self, master, candidate): """Creates the body of 'datawindow'. param candidate is the candidate.uuid""" try: - cand_data = DataManagement.read_candidate_data() # TODO better way to do this + cand_data = DataManagement.read_candidate_data() visit_data = DataManagement.read_visitset_data() visitset = {} cand_info = {} @@ -348,8 +348,6 @@ def button_box(self): def ok_button(self, event=None): - print "saving data and closing" # TODO remove when done - message = self.capture_data() if not message: From 73c6f5951d59b0d9d01eaa4c4e7195592b1b814c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Tue, 26 Jul 2016 14:49:25 -0400 Subject: [PATCH 60/89] Fixed issue #58 - Edition of a candidate in the ID mapper does not erase data specific to the scheduler (such as PhoneNumber etc...) from the XML file. --- dicat/IDMapper.py | 25 ------------------------- dicat/lib/datamanagement.py | 22 +++++++++++++++++----- dicat/new_data_test.xml | 9 +++++++++ 3 files changed, 26 insertions(+), 30 deletions(-) diff --git a/dicat/IDMapper.py b/dicat/IDMapper.py index 36e0847..87ca01b 100644 --- a/dicat/IDMapper.py +++ b/dicat/IDMapper.py @@ -224,25 +224,6 @@ def LoadXML(self): print str(e) #TODO add error login (in case a candidate data file does not exist) - def SaveMapAction(self): - - """Function which performs the action of writing the XML file""" - f = open(self.filename, "w") - f.write("\n\n") - for key in self.IDMap: - f.write("\t\n") - f.write("\t\t%s\n" % key) - f.write("\t\t%s\n" % self.IDMap[key][1]) - f.write("\t\t%s\n" % self.IDMap[key][2]) - f.write("\t\n") - f.write("") - - - def SaveMapEvent(self, event): - """Handles any wxPython event which should trigger a save action""" - self.SaveMapAction() - - def AddIdentifierEvent(self): firstname = self.candidateFirstName.get() @@ -294,9 +275,6 @@ def AddIdentifierAction(self, candid, firstname, lastname, dob, save=True): cand_data["FirstName"] = firstname cand_data["LastName"] = lastname cand_data["DateOfBirth"] = dob - cand_data["Gender"] = " " - cand_data["PhoneNumber"] = " " - cand_data["CandidateStatus"] = " " #self.SaveMapAction() DataManagement.save_candidate_data(cand_data) @@ -388,9 +366,6 @@ def EditIdentifierAction(self, identifier, firstname, lastname, dob, edit=True): cand_data["FirstName"] = firstname cand_data["LastName"] = lastname cand_data["DateOfBirth"] = dob - cand_data["Gender"] = " " - cand_data["PhoneNumber"] = " " - cand_data["CandidateStatus"] = " " DataManagement.save_candidate_data(cand_data) # update the IDMap dictionary diff --git a/dicat/lib/datamanagement.py b/dicat/lib/datamanagement.py index bb8db2d..fb6a7bb 100644 --- a/dicat/lib/datamanagement.py +++ b/dicat/lib/datamanagement.py @@ -115,10 +115,14 @@ def save_candidate_data(cand_data): xml_firstname.firstChild.nodeValue = cand_data['FirstName'] xml_lastname.firstChild.nodeValue = cand_data['LastName'] - xml_gender.firstChild.nodeValue = cand_data['Gender'] - xml_dob.firstChild.nodeValue = cand_data['DateOfBirth'] - xml_status.firstChild.nodeValue = cand_data['CandidateStatus'] - xml_phone.firstChild.nodeValue = cand_data['PhoneNumber'] + xml_dob.firstChild.nodeValue = cand_data['DateOfBirth'] + if 'Gender' in cand_data: + print "in cand_data gender" + xml_gender.firstChild.nodeValue = cand_data['Gender'] + if 'CandidateStatus' in cand_data: + xml_status.firstChild.nodeValue = cand_data['CandidateStatus'] + if 'PhoneNumber' in cand_data: + xml_phone.firstChild.nodeValue = cand_data['PhoneNumber'] updated = True break @@ -132,9 +136,17 @@ def save_candidate_data(cand_data): for key in cand_data: xml_elem = xmldoc.createElement(key) cand.appendChild(xml_elem) - txt = xmldoc.createTextNode( cand_data[key] ) + txt = xmldoc.createTextNode(cand_data[key]) xml_elem.appendChild(txt) + optional_fields = ['Gender', 'CandidateStatus', 'PhoneNumber'] + for key in optional_fields: + if key not in cand_data: + xml_elem = xmldoc.createElement(key) + cand.appendChild(xml_elem) + txt = xmldoc.createTextNode(" ") + xml_elem.appendChild(txt) + # update the xml file with the correct values f = open(Config.xmlfile, "w") xmldoc.writexml(f, addindent=" ", newl="\n") diff --git a/dicat/new_data_test.xml b/dicat/new_data_test.xml index 5f52f7b..170a93b 100644 --- a/dicat/new_data_test.xml +++ b/dicat/new_data_test.xml @@ -242,5 +242,14 @@ Male MTL0008 + + Gonzales + MTL0009 + Speedy + 1953-08-29 + + + + From 43515d5dfd8f21466d31c00ff924b1abba310f8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Tue, 26 Jul 2016 15:01:43 -0400 Subject: [PATCH 61/89] Fixed the way self.text_pscid_var are initialized --- dicat/ui/datawindow.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/dicat/ui/datawindow.py b/dicat/ui/datawindow.py index 537db8a..2608fb0 100644 --- a/dicat/ui/datawindow.py +++ b/dicat/ui/datawindow.py @@ -79,13 +79,13 @@ def body(self, master, candidate): # if candidate="new" populate the field with an empty string # otherwise populate with the values available in cand_info dictionary if candidate == "new": - self.text_pscid_var = "" - self.text_firstname_var = "" - self.text_lastname_var = "" - self.text_dob_var = "" - self.text_gender_var = "NA" - self.text_status_var = "" - self.text_phone_var = "" + self.text_pscid_var.set("") + self.text_firstname_var.set("") + self.text_lastname_var.set("") + self.text_dob_var.set("") + self.text_gender_var.set("NA") + self.text_status_var.set("") + self.text_phone_var.set("") else: self.text_pscid_var.set(cand_info["Identifier"]) self.text_firstname_var.set(cand_info["FirstName"]) @@ -95,8 +95,6 @@ def body(self, master, candidate): self.text_status_var.set(cand_info["CandidateStatus"]) self.text_phone_var.set(cand_info["PhoneNumber"]) - print cand_info["Gender"] - print self.text_gender_var.get() # Create widgets to be displayed # (typically a label with a text box underneath per variable to display) self.label_pscid = Label( # identifier label From f84921f155a063065fa35be2be66aa0c09d50564 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Tue, 26 Jul 2016 15:40:20 -0400 Subject: [PATCH 62/89] Fixed issue #54 - Refresh candidate's list when adding a new candidate --- dicat/new_data_test.xml | 9 +++++++++ dicat/scheduler_application.py | 23 ++++++++++++++--------- dicat/ui/datawindow.py | 2 +- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/dicat/new_data_test.xml b/dicat/new_data_test.xml index 170a93b..0b98e72 100644 --- a/dicat/new_data_test.xml +++ b/dicat/new_data_test.xml @@ -251,5 +251,14 @@ + + + Lucky + Luke + 1946-12-07 + + Male + MTL0010 + diff --git a/dicat/scheduler_application.py b/dicat/scheduler_application.py index 2c5c124..b71989e 100644 --- a/dicat/scheduler_application.py +++ b/dicat/scheduler_application.py @@ -24,6 +24,7 @@ def initialize(self): self.frame = Frame(self.parent) self.frame.pack(side=TOP, expand=YES, fill=BOTH, padx=10, pady=10) + #TODO implement button to be able to choose the XML file Config.xmlfile = "new_data_test.xml" # This area (datapane) is one Panedwindow containing 3 Labelframes @@ -65,18 +66,18 @@ def initialize(self): # create data tables (treeview) # candidate table - candidate_column_headers = ( 'identifier', 'firstname', 'lastname', - 'date of birth', 'gender', 'phone', - 'status' - ) + candidate_column_headers = ( + 'identifier', 'firstname', 'lastname', 'date of birth', + 'gender', 'phone', 'status' + ) self.cand_table = DataTable.ParticipantsList( self.candidate_pane, candidate_column_headers ) self.cand_table.pack(side=BOTTOM, expand=YES, fill=BOTH) # calendar table - visit_column_headers = ( 'identifier', 'candidate', 'visitlabel', - 'when', 'where', 'status' - ) + visit_column_headers = ( + 'identifier', 'candidate', 'visitlabel', 'when', 'where', 'status' + ) self.visit_table = DataTable.VisitList( self.visit_pane, visit_column_headers ) @@ -97,6 +98,10 @@ def initialize(self): def add_candidate(self): DataWindow.DataWindow(self, "new") - # TODO: might need to refresh data table to include new candidate? - print 'running add_candidate' + self.update_data(self.cand_table) + + def update_data(self, table): + for i in table.datatable.get_children(): + table.datatable.delete(i) + DataTable.ParticipantsList.load_data(table) diff --git a/dicat/ui/datawindow.py b/dicat/ui/datawindow.py index 2608fb0..475eae3 100644 --- a/dicat/ui/datawindow.py +++ b/dicat/ui/datawindow.py @@ -405,7 +405,7 @@ def capture_data(self): cand_data['FirstName'] = self.text_firstname.get() cand_data['LastName'] = self.text_lastname.get() cand_data['DateOfBirth'] = self.text_dob.get() - cand_data['Gender'] = self.text_gender.get() + cand_data['Gender'] = self.text_gender_var.get() cand_data['PhoneNumber'] = self.text_phone.get() cand_data['CandidateStatus'] = self.text_status.get() From 6da35bcb493a40af21bafe972886ccf33b68b437 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Tue, 26 Jul 2016 16:11:26 -0400 Subject: [PATCH 63/89] Fixed first part of issue #39 - implementation of a drop down for candidate status. --- dicat/lib/multilanguage.py | 2 +- dicat/new_data_test.xml | 2 +- dicat/ui/datawindow.py | 21 +++++++++++++++------ 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/dicat/lib/multilanguage.py b/dicat/lib/multilanguage.py index 0010705..b866dfd 100644 --- a/dicat/lib/multilanguage.py +++ b/dicat/lib/multilanguage.py @@ -230,4 +230,4 @@ schedule_visit_rank = u"#" schedule_visit_status = u"Status" schedule_visit_when = u"Date" - schedule_optional =u"Optional" \ No newline at end of file + schedule_optional = u"Optional" \ No newline at end of file diff --git a/dicat/new_data_test.xml b/dicat/new_data_test.xml index 0b98e72..303db2f 100644 --- a/dicat/new_data_test.xml +++ b/dicat/new_data_test.xml @@ -234,7 +234,7 @@ MTL0007 - + ineligible Bugs Bunny 1938-04-30 diff --git a/dicat/ui/datawindow.py b/dicat/ui/datawindow.py index 475eae3..1a1b047 100644 --- a/dicat/ui/datawindow.py +++ b/dicat/ui/datawindow.py @@ -83,8 +83,8 @@ def body(self, master, candidate): self.text_firstname_var.set("") self.text_lastname_var.set("") self.text_dob_var.set("") - self.text_gender_var.set("NA") - self.text_status_var.set("") + self.text_gender_var.set(" ") + self.text_status_var.set(" ") self.text_phone_var.set("") else: self.text_pscid_var.set(cand_info["Identifier"]) @@ -124,17 +124,26 @@ def body(self, master, candidate): self.label_gender = Label( # gender label self.candidate_pane, text=MultiLanguage.candidate_gender ) + gender_options = [' ', 'Male', 'Female'] self.text_gender = OptionMenu( # gender selected from a drop down menu self.candidate_pane, self.text_gender_var, # variable in which to store the selection self.text_gender_var.get(), # default value to be used at display - *("NA", "Male", "Female") # list of drop down options + *gender_options # list of drop down options ) self.label_status = Label( # candidate status label self.candidate_pane, text=MultiLanguage.candidate_status ) - self.text_status = Entry( # candidate status text box - self.candidate_pane, textvariable=self.text_status_var + #TODO: grep the status_options list from the project information + status_options = [ + ' ', 'active', 'withdrawn', 'excluded', + 'death', 'ineligible', 'completed' + ] + self.text_status = OptionMenu( # cand. status selected from drop down + self.candidate_pane, + self.text_status_var, # variable in which to store the selection + self.text_status_var.get(), # default value to be used at display + *status_options # list of drop down options ) self.label_phone = Label( # phone number label self.candidate_pane, text=MultiLanguage.candidate_phone @@ -407,7 +416,7 @@ def capture_data(self): cand_data['DateOfBirth'] = self.text_dob.get() cand_data['Gender'] = self.text_gender_var.get() cand_data['PhoneNumber'] = self.text_phone.get() - cand_data['CandidateStatus'] = self.text_status.get() + cand_data['CandidateStatus'] = self.text_status_var.get() if not cand_data['Identifier'] or not cand_data['FirstName'] \ or not cand_data['LastName'] or not cand_data['Gender'] \ From 9f7cf83df3b57f6e6711503fca6a5955fc2c8a7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Wed, 27 Jul 2016 11:30:51 -0400 Subject: [PATCH 64/89] Cleaned up folowing files: modified: dicat/lib/__init__.py deleted: dicat/lib/classtool.py modified: dicat/lib/config.py modified: dicat/lib/multilanguage.py modified: dicat/ui/__init__.py --- dicat/lib/__init__.py | 14 +- dicat/lib/classtool.py | 20 --- dicat/lib/config.py | 4 +- dicat/lib/multilanguage.py | 278 +++++++++++++++++++------------------ dicat/ui/__init__.py | 14 +- 5 files changed, 161 insertions(+), 169 deletions(-) delete mode 100644 dicat/lib/classtool.py diff --git a/dicat/lib/__init__.py b/dicat/lib/__init__.py index 5c9d53e..2da98fd 100644 --- a/dicat/lib/__init__.py +++ b/dicat/lib/__init__.py @@ -1,8 +1,10 @@ """ -From the documentation at https://docs.python.org/2/tutorial/modules.html#packages +From https://docs.python.org/2/tutorial/modules.html#packages documentation -The __init__.py files are required to make Python treat the directories as containing packages; this is done to prevent -directories with a common name, such as string, from unintentionally hiding valid modules that occur later on the module -search path. In the simplest case, __init__.py can just be an empty file, but it can also execute initialization code -for the package or set the __all__ variable, described later. -""" +The __init__.py files are required to make Python treat the directories as +containing packages; this is done to prevent directories with a common name, +such as string, from unintentionally hiding valid modules that occur later on +the module search path. In the simplest case, __init__.py can just be an empty +file, but it can also execute initialization code for the package or set the +__all__ variable, described later. +""" \ No newline at end of file diff --git a/dicat/lib/classtool.py b/dicat/lib/classtool.py deleted file mode 100644 index 3a3083e..0000000 --- a/dicat/lib/classtool.py +++ /dev/null @@ -1,20 +0,0 @@ -class ClassTool(): - """ - Not used! Useful for development as inheritable class MyClass(ClassTool). - """ - #no constructor - def __repr__(self): - attributes =[] - for key in sorted(self.__dict__): - attributes.append('%s=%s' %(key, getattr(self, key))) - return ', '.join(attributes) - - def gatherattributes(self): - attributes =[] - for key in sorted(self.__dict__): - attributes.append('%s' %(key)) - return ', '.join(attributes) - - def getframesize(self): - print str(self.winfo_width()) - diff --git a/dicat/lib/config.py b/dicat/lib/config.py index 4fe6616..9616884 100644 --- a/dicat/lib/config.py +++ b/dicat/lib/config.py @@ -1,6 +1,6 @@ """ -This file will contain global variables that can be used in all class, assuming -the config.py script gets imported. +This file will contain global variables that can be used in all classes, +assuming the config.py script gets imported. Access to the variable would then be config.variablename """ diff --git a/dicat/lib/multilanguage.py b/dicat/lib/multilanguage.py index b866dfd..440ccd0 100644 --- a/dicat/lib/multilanguage.py +++ b/dicat/lib/multilanguage.py @@ -11,223 +11,231 @@ ###################### TOP LEVEL MENU BAR ###################### app_title = u"Outils LORIS" #APPLICATION menu - application_menu = u"Application" + application_menu = u"Application" application_setting = u"Préferences" - application_quit = u"Quitter" + application_quit = u"Quitter" #PROJECT menu - #menuproject = u"Projet" #TODO remove? - #openproject = u"Ouvrir un projet" + #menuproject = u"Projet" #TODO remove? + #openproject = u"Ouvrir un projet" #modifyproject = u"Modifier le projet ouvert" - #newproject = u"Créer un nouveau projet" + #newproject = u"Créer un nouveau projet" #CANDIDATE menu - candidate_menu = u"Candidat" - candidate_add = u"Nouveau candidat" - candidate_find = u"Trouver candidat" + candidate_menu = u"Candidat" + candidate_add = u"Nouveau candidat" + candidate_find = u"Trouver candidat" candidate_update = u"Mettre à jour" - candidate_exclude_include_toggle = u"Inclure/Exclure un candidat" candidate_get_id = u"Obtenir l'identifiant d'un candidat" + candidate_exclude_include_toggle = u"Inclure/Exclure un candidat" #clear_all_field = u"Effacer" #ANONYMIZER menu anonymizer_menu = u"DICOM" - anonymizer_run = u"Anonymizer" + anonymizer_run = u"Anonymizer" #CALENDAR menu calendar_menu = u"Calendrier" calendar_new_appointment = u"Nouveau Rendez-vous" #HELP menu - help_menu = u"Aide" + help_menu = u"Aide" help_get_help = u"Obtenir de l'aide" help_about_window = u"A propos de ..." ###################### PROJECT INFO PANE ####################### - project_info_pane = u"Projet" + project_info_pane = u"Projet" project_detail_pane = u"Détails du Projet" - visit_detail_pane = u"Détails des Visites" - project_name = u"Projet" - project_start = u"Début" - project_end = u"Fin" - target_recruitment = u"Cible de recrutement" + visit_detail_pane = u"Détails des Visites" + target_recruitment = u"Cible de recrutement" current_recruitment = u"Recrutement actuel" - total_visit = u"Nombre de Visites" + project_name = u"Projet" + project_start = u"Début" + project_end = u"Fin" + total_visit = u"Nombre de Visites" #################### MULTI-TAB DATA SECTION ##################### - calendar_pane = u"Calendrier" + calendar_pane = u"Calendrier" candidate_pane = u"Candidats" - label_candidate_table = u"Faites un double-clic sur l'une des lignes pour remplir les champs ci-dessus" - datatable_id = u"ID" - datatable_firstname = u"Prénom" - datatable_lastname = u"Nom de famille" - datatable_dob = u"Date de Naissance" + datatable_id = u"ID" + datatable_dob = u"Date de Naissance" datatable_phone = u"Téléphone" - datatable_address = u"Adresse" - datatable_city = u"Ville" - datatable_province = u"Province" - datatable_country = u"Pays" + datatable_city = u"Ville" + datatable_firstname = u"Prénom" + datatable_lastname = u"Nom de famille" + datatable_address = u"Adresse" + datatable_province = u"Province" + datatable_country = u"Pays" datatable_postal_code = u"Code Postal" + label_candidate_table = u"Faites un double-clic sur l'une des lignes " \ + u"pour remplir les champs ci-dessus" - calendar_monday = u"Lundi" - calendar_tuesday = u"Mardi" + calendar_monday = u"Lundi" + calendar_tuesday = u"Mardi" calendar_wednesday = u"Mercredi" - calendar_thursday = u"Jeudi" - calendar_friday = u"Vendredi" - calendar_saturday = u"Samedi" - calendar_sunday = u"Dimanche" - calendar_january = u"Janvier" - calendar_february = u"Février" - calendar_march = u"Mars" - calendar_april = u"Avril" - calendar_may = u"Mai" - calendar_june = u"Juin" - calendar_jully = u"Juillet" - calendar_august = u"Août" + calendar_thursday = u"Jeudi" + calendar_friday = u"Vendredi" + calendar_saturday = u"Samedi" + calendar_sunday = u"Dimanche" + calendar_january = u"Janvier" + calendar_february = u"Février" + calendar_march = u"Mars" + calendar_april = u"Avril" + calendar_may = u"Mai" + calendar_june = u"Juin" + calendar_jully = u"Juillet" + calendar_august = u"Août" calendar_september = u"Septembre" - calendar_october = u"Octobre" - calendar_november = u"Novembre" - calendar_december = u"Décembre" + calendar_october = u"Octobre" + calendar_november = u"Novembre" + calendar_december = u"Décembre" ################ COLUMN HEADER ################## - col_candidate = u"Candidat" + col_candidate = u"Candidat" col_visitlabel = u"Visite" - col_when = u"Date/Heure" - col_where = u"Endroit" + col_withwhom = u"Avec qui" + col_when = u"Date/Heure" + col_where = u"Endroit" col_status = u"Statut" - col_withwhom = u"Avec qui" + #################### STATUS ##################### - status_active = u"actif" + status_active = u"actif" status_tentative = u"provisoire" ################# DATA WINDOWS ################## data_window_title = u"DATA WINDOW" #TODO trouver un titre français ################## DIALOGBOX #################### # very not sure what to do about that section - dialog_yes = u"Oui" - dialog_no = u"Non" + dialog_yes = u"Oui" + dialog_no = u"Non" + dialog_ok = u"OK" + dialog_close = u"Vous êtes sur le point de fermer cette fenêtre sans " \ + u"sauvegarder!\n\nVoulez-vous continuer?" dialog_title_confirm = u"Veuillez confirmer!" - dialog_close = u"Vous êtes sur le point de fermer cette fenêtre sans sauvegarder!\n\nVoulez-vous continuer?" - dialog_title_error = u"Erreur" - dialog_ok = u"OK" - dialog_missing_candidate_info = u"Les champs 'Identifiant', 'Prénom', 'Nom de famille', 'Sexe', 'Date de naissance' sont requis!" + dialog_title_error = u"Erreur" + dialog_missing_candidate_info = u"Les champs 'Identifiant', 'Prénom', " \ + u"'Nom de famille', 'Date de naissance' " \ + u"et 'Sexe' sont requis!" ################ DATA WINDOW ################### - schedule_pane = u"Calendrier" - candidate_pane = u"Candidat" - candidate_firstname = u"Prénom" - candidate_lastname = u"Nom de famille" - candidate_dob = u"Date de naissance" - candidate_phone = u"Téléphone" - candidate_pscid = u"ID" + schedule_pane = u"Calendrier" + candidate_pane = u"Candidat" + candidate_dob = u"Date de naissance" + candidate_phone = u"Téléphone" + candidate_pscid = u"ID" candidate_status = u"Status" candidate_gender = u"Sexe" + candidate_firstname = u"Prénom" + candidate_lastname = u"Nom de famille" - schedule_visit_label = u"Visite" - schedule_visit_rank = u"#" + schedule_visit_label = u"Visite" + schedule_visit_rank = u"#" schedule_visit_status = u"Status" - schedule_visit_when = u"Date" - schedule_optional =u"Optionnel" + schedule_visit_when = u"Date" + schedule_optional = u"Optionnel" elif language == "en": app_title = u"LORIS tools" #APPLICATION menu - application_menu = u"Application" + application_menu = u"Application" application_setting = u"Preferences" - application_quit = u"Quit" + application_quit = u"Quit" #PROJECT menu - #menuproject = u"Project" #TODO remove? - #openproject = u"Open project" + #menuproject = u"Project" #TODO remove? + #openproject = u"Open project" #modifyproject = u"Modify open project" - #newproject = u"New project" + #newproject = u"New project" #CANDIDATE menu - candidate_menu = u"Candidate" - candidate_add = u"New candidate" - candidate_find = u"Find a candidate" + candidate_menu = u"Candidate" + candidate_add = u"New candidate" + candidate_find = u"Find a candidate" candidate_update = u"Update" - candidate_exclude_include_toggle = u"Include/Exclude a candidate" candidate_get_id = u"Get a canditate ID" + candidate_exclude_include_toggle = u"Include/Exclude a candidate" #clear_all_field = u"Clear" #CALENDAR menu calendar_menu = u"Calendar" calendar_new_appointment = u"New appointment" #ANONYMIZER menu anonymizer_menu = u"DICOM" - anonymizer_run = u"Anonymizer" + anonymizer_run = u"Anonymizer" #HELP menu - help_menu = u"Help" + help_menu = u"Help" help_get_help = u"Get some help" help_about_window = u"About this..." ###################### PROJECT INFO PANE ####################### - project_info_pane = u"Project Information" + project_info_pane = u"Project Information" project_detail_pane = u"Project Details" - visit_detail_pane = u"Visit Details" - project_name = u"Project" - project_start = u"Start" - project_end = u"End" - target_recruitment = u"Recruitment target" + visit_detail_pane = u"Visit Details" + target_recruitment = u"Recruitment target" current_recruitment = u"Current recruitment" - total_visit = u"Total number of Visits" + project_name = u"Project" + project_start = u"Start" + project_end = u"End" + total_visit = u"Total number of Visits" #################### MULTI-TAB DATA SECTION ##################### - calendar_pane = u"Calendar" - candidate_pane = u"Candidates" - label_candidate_table = u"Double click on row to populate fields above" - datatable_id = u"ID" - datatable_firstname = u"First Name" - datatable_lastname = u"Last Name" - datatable_dob = u"Date of Birth" + calendar_pane = u"Calendar" + candidate_pane = u"Candidates" + datatable_id = u"ID" + datatable_dob = u"Date of Birth" datatable_phone = u"Phone" - datatable_address = u"Address" - datatable_city = u"City" + datatable_city = u"City" + datatable_address = u"Address" datatable_province = u"Province" - datatable_country = u"Country" + datatable_country = u"Country" + datatable_firstname = u"First Name" + datatable_lastname = u"Last Name" + label_candidate_table = u"Double click on row to populate fields above" datatable_postal_code = u"Postal Code" - calendar_monday = u"Monday" - calendar_tuesday = u"Tuesday" + calendar_monday = u"Monday" + calendar_tuesday = u"Tuesday" calendar_wednesday = u"Wednesday" - calendar_thursday = u"Thursday" - calendar_friday = u"Friday" - calendar_saturday = u"Saturday" - calendar_sunday = u"Sunday" - calendar_january = u"January" - calendar_february = u"February" - calendar_march = u"Marc" - calendar_april = u"April" - calendar_may = u"May" - calendar_june = u"June" - calendar_jully = u"Jully" - calendar_august = u"August" + calendar_thursday = u"Thursday" + calendar_friday = u"Friday" + calendar_saturday = u"Saturday" + calendar_sunday = u"Sunday" + calendar_january = u"January" + calendar_february = u"February" + calendar_march = u"Marc" + calendar_april = u"April" + calendar_may = u"May" + calendar_june = u"June" + calendar_jully = u"Jully" + calendar_august = u"August" calendar_september = u"September" - calendar_october = u"October" - calendar_november = u"November" - calendar_december = u"December" + calendar_october = u"October" + calendar_november = u"November" + calendar_december = u"December" ################ COLUMN HEADER ################## - col_candidate = u"Candidate" + col_candidate = u"Candidate" col_visitlabel = u"Visit" - col_when = u"Date/Time" - col_where = u"Place" + col_withwhom = u"Whom" + col_when = u"Date/Time" + col_where = u"Place" col_status = u"Status" - col_withwhom = u"Whom" #################### STATUS ##################### - status_active = u"active" + status_active = u"active" status_tentative = u"tentative" ################# DATA WINDOWS ################## - data_window_title =u"Data Window" + data_window_title = u"Data Window" ################## DIALOGBOX #################### # very not sure what to do about that section - dialog_yes = u"Yes" - dialog_no = u"No" + dialog_yes = u"Yes" + dialog_no = u"No" + dialog_ok = u"OK" + dialog_close = u"You are about to close this window without saving!\n\n" \ + u"Do you want to continue?" dialog_title_confirm = u"Please confirm!" - dialog_close = u"You are about to close this window without saving! \n\nDo you want to continue?" - dialog_title_error = u"Error" - dialog_ok = u"OK" - dialog_missing_candidate_info = u"'Identifier', 'Firstname', 'Lastname', 'Gender', 'Date of Birth' fields are required!" + dialog_title_error = u"Error" + dialog_missing_candidate_info = u"'Identifier', 'Firstname', 'Lastname', " \ + u"'Gender' and 'Date of Birth' fields " \ + u"are required!" ################ DATA WINDOW ################### - schedule_pane = u"Calendar" - candidate_pane = u"Candidate" - candidate_firstname = u"Firstname" - candidate_lastname = u"Lastname" - candidate_dob = u"Date of Birth" - candidate_phone = u"Phone" - candidate_pscid = u"ID" + schedule_pane = u"Calendar" + candidate_pane = u"Candidate" + candidate_dob = u"Date of Birth" + candidate_phone = u"Phone" + candidate_pscid = u"ID" candidate_status = u"Status" candidate_gender = u"Sex" - schedule_visit_label = u"Visit" - schedule_visit_rank = u"#" + candidate_firstname = u"Firstname" + candidate_lastname = u"Lastname" + schedule_visit_label = u"Visit" + schedule_visit_rank = u"#" schedule_visit_status = u"Status" - schedule_visit_when = u"Date" - schedule_optional = u"Optional" \ No newline at end of file + schedule_visit_when = u"Date" + schedule_optional = u"Optional" \ No newline at end of file diff --git a/dicat/ui/__init__.py b/dicat/ui/__init__.py index 5c9d53e..2da98fd 100644 --- a/dicat/ui/__init__.py +++ b/dicat/ui/__init__.py @@ -1,8 +1,10 @@ """ -From the documentation at https://docs.python.org/2/tutorial/modules.html#packages +From https://docs.python.org/2/tutorial/modules.html#packages documentation -The __init__.py files are required to make Python treat the directories as containing packages; this is done to prevent -directories with a common name, such as string, from unintentionally hiding valid modules that occur later on the module -search path. In the simplest case, __init__.py can just be an empty file, but it can also execute initialization code -for the package or set the __all__ variable, described later. -""" +The __init__.py files are required to make Python treat the directories as +containing packages; this is done to prevent directories with a common name, +such as string, from unintentionally hiding valid modules that occur later on +the module search path. In the simplest case, __init__.py can just be an empty +file, but it can also execute initialization code for the package or set the +__all__ variable, described later. +""" \ No newline at end of file From 6b72ab7ec1213790fa8790fe634ab773784a0fd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Wed, 27 Jul 2016 12:12:24 -0400 Subject: [PATCH 65/89] Cleaned up datamanagement.py file --- dicat/lib/datamanagement.py | 148 +++++++++++++++++++++++++----------- dicat/new_data_test.xml | 2 + 2 files changed, 104 insertions(+), 46 deletions(-) diff --git a/dicat/lib/datamanagement.py b/dicat/lib/datamanagement.py index fb6a7bb..80d7e32 100644 --- a/dicat/lib/datamanagement.py +++ b/dicat/lib/datamanagement.py @@ -1,13 +1,25 @@ -#imports from standard packages +# Imports from standard packages import os.path -import pickle from xml.dom import minidom + +# Imports from DICAT import config as Config -""" -The data_management.py file contains functions related to data management only. -Generic functions: savedata(data, datafilename) and readdata(datafile). Currently, these are not being used. -Specific functions read_candidate_data(), save_candidate_data(), read_studydata() and save_study_data() are used to get/save candidate data and study setup data respectively. +""" +This file contains functions related to data management only. + +Generic functions: + - read_data(xmlfile) + - read_xmlfile(xmlfile) + - remove_enpty_lines_from_file(file) + +Specific functions: + - read_candidate_data() + - read_visitset_data() + - read_visit_data(xmlvisitlist, cand, data) + - save_candidate_data(cand_data) + - read_study_data() + - save_study_data() """ def read_xmlfile(xmlfile): @@ -29,6 +41,7 @@ def read_xmlfile(xmlfile): message = "ERROR: could not read file " + xmlfile print message #TODO: create a log class to display the messages + def read_data(xmlfile): """ @@ -50,6 +63,7 @@ def read_data(xmlfile): return xmldata, xmlcandlist + def read_candidate_data(): """ Read and return the candidate level content of an XML file specified in the @@ -94,18 +108,24 @@ def save_candidate_data(cand_data): """ - # check to see if xmldoc global variable and file exist before saving + # Check to see if xmldoc global variable and file exist before saving if os.path.isfile(Config.xmlfile) and xmldoc: - # read the xml file + # Read the xml file (xmldata, xmlcandlist) = read_data(Config.xmlfile) updated = False + + # Loop through all the candidates that exist in the XML file for cand in xmlcandlist: for elem in cand.childNodes: tag = elem.localName if not tag: continue val = cand.getElementsByTagName(tag)[0].firstChild.nodeValue + + # If the candidate was found in the XML file, updates its info if tag == "Identifier" and val == cand_data['Identifier']: + + # Grep the XML elements xml_firstname = cand.getElementsByTagName("FirstName")[0] xml_lastname = cand.getElementsByTagName("LastName")[0] xml_gender = cand.getElementsByTagName("Gender")[0] @@ -113,6 +133,8 @@ def save_candidate_data(cand_data): xml_phone = cand.getElementsByTagName("PhoneNumber")[0] xml_status = cand.getElementsByTagName("CandidateStatus")[0] + # Replace elements' value with what has been captured in + # the cand_data dictionary xml_firstname.firstChild.nodeValue = cand_data['FirstName'] xml_lastname.firstChild.nodeValue = cand_data['LastName'] xml_dob.firstChild.nodeValue = cand_data['DateOfBirth'] @@ -120,40 +142,51 @@ def save_candidate_data(cand_data): print "in cand_data gender" xml_gender.firstChild.nodeValue = cand_data['Gender'] if 'CandidateStatus' in cand_data: - xml_status.firstChild.nodeValue = cand_data['CandidateStatus'] + key = 'CandidateStatus' + xml_status.firstChild.nodeValue = cand_data[key] if 'PhoneNumber' in cand_data: - xml_phone.firstChild.nodeValue = cand_data['PhoneNumber'] + key = 'PhoneNumber' + xml_phone.firstChild.nodeValue = cand_data[key] updated = True break - # if no candidate was updated, insert a new candidate + # If no candidate was updated, insert a new candidate if not updated: # Create a new Candidate element cand = xmldoc.createElement("Candidate") xmldata.appendChild(cand) + # Loop through cand_data keys ('Identifier', 'FirstName' ...) + # and add them to the XML handler (xmldoc) for key in cand_data: + # create the child tag ('Gender', 'DOB' etc...) and its value xml_elem = xmldoc.createElement(key) + value = xmldoc.createTextNode(cand_data[key]) + # append the child tag and value to the 'Candidate' tag cand.appendChild(xml_elem) - txt = xmldoc.createTextNode(cand_data[key]) - xml_elem.appendChild(txt) + xml_elem.appendChild(value) + # Loop through optional fields and add them to the XML handler + # with an empty string if the field was not present in cand_data optional_fields = ['Gender', 'CandidateStatus', 'PhoneNumber'] for key in optional_fields: if key not in cand_data: + # create the new tag and its value xml_elem = xmldoc.createElement(key) + value = xmldoc.createTextNode(" ") + # append the child tag and value to the 'Candidate' tag cand.appendChild(xml_elem) - txt = xmldoc.createTextNode(" ") - xml_elem.appendChild(txt) + xml_elem.appendChild(value) - # update the xml file with the correct values + # Update the xml file with the correct values f = open(Config.xmlfile, "w") xmldoc.writexml(f, addindent=" ", newl="\n") f.close() - # remove the empty lines inserted by writexml + # Remove the empty lines inserted by writexml (weird bug from writexml) remove_empty_lines_from_file(Config.xmlfile) + def read_visitset_data(): """ Read and return the visit set content of an XML file specified in the @@ -167,26 +200,38 @@ def read_visitset_data(): """ - data = {} - #check to see if file exists before loading it + data = {} # Initialize data dictionary + + # Check to see if file exists before loading it if os.path.isfile(Config.xmlfile): - # read the xml file + # Read the xml file (xmldata, xmlcandlist) = read_data(Config.xmlfile) + + # Loop through all candidates present in the XML file for cand in xmlcandlist: data[cand] = {} for cand_elem in cand.childNodes: cand_tag = cand_elem.localName - tags_to_ignore = ( "Gender", "DateOfBirth", - "PhoneNumber", "CandidateStatus" - ) + tags_to_ignore = ( + "Gender", "DateOfBirth", "PhoneNumber", "CandidateStatus" + ) + + # Continue if met a non-wanted tag if not cand_tag or cand_tag in tags_to_ignore: continue + + # If the tag is 'Visit', grep all visit information. + # This will be stored in data[cand]['VisitSet'] dictionary if cand_tag == "Visit": xmlvisitlist = cand.getElementsByTagName('Visit') read_visit_data(xmlvisitlist, cand, data) - val = cand.getElementsByTagName(cand_tag)[0].firstChild.nodeValue + # This will store candidate information (such as 'Identifier', + # 'Gender', 'Firstname' ...) into data[cand][cand_tag]. + elem = cand.getElementsByTagName(cand_tag)[0] + val = elem.firstChild.nodeValue data[cand][cand_tag] = val + else: data = "" @@ -209,32 +254,47 @@ def read_visit_data(xmlvisitlist, cand, data): """ - data[cand]["VisitSet"] = {} + data[cand]["VisitSet"] = {} # Initialize a VisitSet dictionary + + # Loop through all visits present in the XML file for a given candidate for visit in xmlvisitlist: - data[cand]["VisitSet"][visit] = {} + data[cand]["VisitSet"][visit] = {} # Initialize a Visit dictionary + + # Loop through all visit elements present under a given Visit tag for visit_elem in visit.childNodes: visit_tag = visit_elem.localName - if not visit_tag: + + if not visit_tag: # continue if no tag continue - visit_val = visit.getElementsByTagName(visit_tag)[0].firstChild.nodeValue - data[cand]["VisitSet"][visit][visit_tag] = visit_val + # Insert the visit tag and its value into the visit dictionary + elem = visit.getElementsByTagName(visit_tag)[0] + val = elem.firstChild.nodeValue + data[cand]["VisitSet"][visit][visit_tag] = val + + +def read_study_data(): #TODO: implement this function + """ + This function reads and returns the content of the XML 'projectInfo' tag. + It will return nothing if it could not find the information. + + :return: -def read_study_data(): - """Read and return the content of a file called studydata. Returns nothing if file doesn't exist""" + """ #check to see if file exists before loading it - if os.path.isfile("studydata"): - #load file - db = pickle.load(open("studydata", "rb")) - else: - db = "" - return db + pass + +def save_study_data(study_data): #TODO: implement this function + """ + Save study/project information into the XML file under the tag 'projectInfo' + + :param study_data: dictionary containing all the study/project information + :type study_data: dict -def save_study_data(data): - """Save data in a pickle file named studydata. - Will overwrite any existing file. Will create one if it doesn't exist""" - pickle.dump(data, open("studydata", "wb")) + :return: + """ + pass def remove_empty_lines_from_file(file): @@ -253,8 +313,4 @@ def remove_empty_lines_from_file(file): with open(file, "w") as f: f.writelines(lines) -#self-test "module" TODO remove -if __name__ == '__main__': - print 'testing module: datamanagement.py' - data=dict(read_visitset_data("../new_data_test.xml")); - print data; + diff --git a/dicat/new_data_test.xml b/dicat/new_data_test.xml index 303db2f..073c083 100644 --- a/dicat/new_data_test.xml +++ b/dicat/new_data_test.xml @@ -223,6 +223,8 @@ Claus Male 2015-12-25 + + ineligible From f5ee862eb85372510d074687a6abaa60583f28e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Wed, 27 Jul 2016 12:24:16 -0400 Subject: [PATCH 66/89] Cleaned up dicat/lib/dicom_anonymizer_methods.py. --- dicat/lib/dicom_anonymizer_methods.py | 36 +++++++++++++-------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/dicat/lib/dicom_anonymizer_methods.py b/dicat/lib/dicom_anonymizer_methods.py index 7dd64d6..ff2d2fe 100755 --- a/dicat/lib/dicom_anonymizer_methods.py +++ b/dicat/lib/dicom_anonymizer_methods.py @@ -1,3 +1,4 @@ +# Imports from standard packages import os import sys import xml.etree.ElementTree as ET @@ -13,17 +14,16 @@ - newer versions of the PyDICOM module are imported using "import dicom" - returns true if the PyDICOM was imported, false otherwise """ -# Create a boolean variable that returns True if PyDICOM was imported, False -# otherwise +# Create a boolean variable (use_pydicom) that returns: +# - True if PyDICOM was succesfully imported +# - False otherwise use_pydicom = False try: import pydicom as dicom - use_pydicom = True # set to true as PyDICOM was found and imported except ImportError: try: # try importing newer versions of PyDICOM import dicom - use_pydicom = True # set to true as PyDICOM was found and imported except ImportError: use_pydicom = False # set to false as PyDICOM was not found @@ -118,6 +118,7 @@ def grep_dicom_fields(xml_file): :return: dicom_fields -> dictionary of DICOM fields :rtype: dict + """ xmldoc = ET.parse(xml_file) @@ -147,7 +148,7 @@ def grep_dicom_values(dicom_folder, dicom_fields): """ # Grep first DICOM of the directory - # TODO: Need to check if file is DICOM though, otherwise go to next one + # TODO: Need to check if file is DICOM though, otherwise go to next file (dicoms_list, subdirs_list) = grep_dicoms_from_folder(dicom_folder) dicom_file = dicoms_list[0] @@ -269,7 +270,7 @@ def dicom_zapping(dicom_folder, dicom_fields): move(orig_bak_dcm, original_dcm) # Zip the de-identified and original DICOM folders - (deidentified_zip, original_zip) = zip_dicom_directories(deidentified_dir, + (deidentified_zip, original_zip) = zip_dcm_directories(deidentified_dir, original_dir, subdirs_list, dicom_folder @@ -325,7 +326,7 @@ def dcmodify_zapping(dicom_file, dicom_fields): :returns: original_zip -> Path to the zip file containing original DICOM files - deidentified_zip -> Path to the zip file containing de-identified DICOM files + deidentified_zip -> Path to the zip containing the de-identified DICOMs :rtype: str """ @@ -351,7 +352,7 @@ def dcmodify_zapping(dicom_file, dicom_fields): subprocess.call(modify_cmd, shell=True) -def zip_dicom_directories(deidentified_dir, original_dir, subdirs_list, root_dir): +def zip_dcm_directories(deidentified_dir, original_dir, subdirs_list, root_dir): """ Zip the de-identified and origin DICOM directories. @@ -374,18 +375,18 @@ def zip_dicom_directories(deidentified_dir, original_dir, subdirs_list, root_dir # If de-identified and original folders exist, zip them if os.path.exists(deidentified_dir) and os.path.exists(original_dir): - original_zip = zip_dicom(original_dir) + original_zip = zip_dicom(original_dir) deidentified_zip = zip_dicom(deidentified_dir) else: sys.exit('Failed to find original and de-identify data folders') - # If archive de-identified and original DICOMs found, remove subdirectories in - # root directory + # If archive de-identified and original DICOMs found, remove subdirectories + # in root directory if os.path.exists(deidentified_zip) and os.path.exists(original_zip): for subdir in subdirs_list: shutil.rmtree(root_dir + os.path.sep + subdir) else: - sys.exit('Failed: could not zip de-identified and original data folders') + sys.exit('Failed: could not zip de-identified & original data folders') # Return zip files return deidentified_zip, original_zip @@ -413,16 +414,15 @@ def create_directories(dicom_folder, dicom_fields, subdirs_list): # Create an original_dcm and deidentified_dcm directory in the DICOM folder, # as well as subdirectories - original_dir = dicom_folder + os.path.sep + dicom_fields['0010,0010'][ - 'Value'] - deidentified_dir = dicom_folder + os.path.sep + dicom_fields['0010,0010'][ - 'Value'] + "_deidentified" - os.mkdir(original_dir, 0755) + name = dicom_fields['0010,0010']['Value'] + original_dir = dicom_folder + os.path.sep + name + deidentified_dir = dicom_folder + os.path.sep + name + "_deidentified" + os.mkdir(original_dir, 0755) os.mkdir(deidentified_dir, 0755) # Create subdirectories in original and de-identified directory, as found in # DICOM folder for subdir in subdirs_list: - os.mkdir(original_dir + os.path.sep + subdir, 0755) + os.mkdir(original_dir + os.path.sep + subdir, 0755) os.mkdir(deidentified_dir + os.path.sep + subdir, 0755) return original_dir, deidentified_dir From 908f5ea43e29869bd9e74d15dc60ed10ee8ad489 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Wed, 27 Jul 2016 13:25:58 -0400 Subject: [PATCH 67/89] Cleaned up following files: modified: dicat/lib/resource_path_methods.py modified: dicat/lib/utilities.py modified: dicat/ui/datatable.py --- dicat/lib/resource_path_methods.py | 12 +- dicat/lib/utilities.py | 7 +- dicat/ui/datatable.py | 341 ++++++++++++++++++++--------- 3 files changed, 255 insertions(+), 105 deletions(-) diff --git a/dicat/lib/resource_path_methods.py b/dicat/lib/resource_path_methods.py index c7aee92..ac9203a 100644 --- a/dicat/lib/resource_path_methods.py +++ b/dicat/lib/resource_path_methods.py @@ -1,15 +1,25 @@ #!/usr/bin/python +# Import from standard packages import os import sys class resource_path(): + """ + This class allows to get the absolute path to the resource scripts. It works + for development installation as well as for PyInstaller builds (.app, .exe). + """ def __init__(self, relative_path): self.relative_path = relative_path def return_path(self): - """ Get absolute path to resource, works for dev and for PyInstaller """ + """ + Get absolute path to resource, works for dev and for PyInstaller + + :return: relative path to be used to find all DICAT scripts + :rtype: str + """ if hasattr(sys, '_MEIPASS'): return os.path.join(sys._MEIPASS, self.relative_path) diff --git a/dicat/lib/utilities.py b/dicat/lib/utilities.py index d3ce267..eac0025 100644 --- a/dicat/lib/utilities.py +++ b/dicat/lib/utilities.py @@ -14,6 +14,7 @@ def generate_uid(): ui = str(uuid1()) return ui + def is_unique(data, dataset): """ will verify if 'data' passed as argument is unique in a dataset @@ -34,12 +35,14 @@ def describe(something): returnvalue = objectclass, " (", objecttype, "): ", attributes print returnvalue + def get_current_date(): """ will return today's date as a string of format yyyymmdd """ return time.strftime("%Y%m%d") + def get_current_time(option): """ will return current time as a string of format hhmmss @@ -67,7 +70,6 @@ def error_log(message): print message + "\n" #TODO remove when done - def center_window(win): win.update_idletasks() width = win.winfo_width() @@ -77,9 +79,6 @@ def center_window(win): win.geometry('{}x{}+{}+{}'.format(width, height, x, y)) - - - ######################################################################################## def gather_attributes(something): """ diff --git a/dicat/ui/datatable.py b/dicat/ui/datatable.py index ee986c6..5f8ceb1 100644 --- a/dicat/ui/datatable.py +++ b/dicat/ui/datatable.py @@ -1,215 +1,356 @@ # import standard packages from Tkinter import * from ttk import * + # import internal packages import ui.datawindow as DataWindow import lib.datamanagement as DataManagement class DataTable(Frame): """ - DataTable is a base class (which inherit from Frame) defining the functionalities of - this Tkinter.ttk.treeview widget. - Child clases are ParticipantsList(DataTable) and VisitList(DataTable) + DataTable is a base class (which inherits from Frame) defining the + functionality of the Tkinter.ttk.treeview widget. + Children classes are ParticipantsList(DataTable) and VisitList(DataTable) + """ + def __init__(self, parent, colheaders): Frame.__init__(self) self.parent = parent self.init_datatable(parent, colheaders) - """ - Initialize the datatable (which is a tk.treeview & add scroll bars) - """ + def init_datatable(self, parent, colheaders): + """ + Initialize datatable (which is a tk.treeview & add scroll bars) + + :param parent: frame in which the datatable should take place + :type parent: frame + :param colheaders: array with the list of column header strings + :type colheaders: list - # initialize the treeview datatable - self.datatable = Treeview( parent, selectmode='browse', - columns=colheaders, show="headings" - ) + """ - # add the column headers to the datatable + # Initialize the Treeview datatable + self.datatable = Treeview( + parent, selectmode='browse', columns=colheaders, show="headings" + ) + + # Add column headers to datatable for col in colheaders: self.datatable.heading( col, text=col.title(), - command=lambda c=col: self.treeview_sortby(self.datatable, c, 0) - ) - self.datatable.column( col, width=100, - stretch="Yes", anchor="center" - ) - - # add vertical and horizontal scroll - self.verticalscroll = Scrollbar( parent, orient="vertical", - command=self.datatable.yview - ) - self.horizontalscroll = Scrollbar( parent, orient="horizontal", - command=self.datatable.xview - ) - self.datatable.configure( yscrollcommand=self.verticalscroll.set, - xscroll=self.horizontalscroll.set - ) - self.verticalscroll.pack(side=RIGHT, expand=NO, fill=BOTH) - self.horizontalscroll.pack(side=BOTTOM, expand=NO, fill=BOTH) - - # draw the datatable - self.datatable.pack(side=LEFT, expand=YES, fill=BOTH) - - # bind with events - #self.datatable.bind('', lambda event: self.ondoubleclick(event)) + command=lambda c=col: self.treeview_sortby( + self.datatable, c, 0 + ) + ) + self.datatable.column( + col, width=100, stretch="Yes", anchor="center" + ) + + # Add vertical and horizontal scroll bars + self.verticalscroll = Scrollbar( + parent, orient="vertical", command=self.datatable.yview + ) + self.horizontalscroll = Scrollbar( + parent, orient="horizontal", command=self.datatable.xview + ) + self.datatable.configure( + yscrollcommand=self.verticalscroll.set, + xscroll=self.horizontalscroll.set + ) + self.verticalscroll.pack( side=RIGHT, expand=NO, fill=BOTH ) + self.horizontalscroll.pack( side=BOTTOM, expand=NO, fill=BOTH ) + # Draw the datatable + self.datatable.pack( side=LEFT, expand=YES, fill=BOTH ) + + # Bind with events self.datatable.bind('', self.ondoubleclick) self.datatable.bind("<>", self.onrowclick ) self.datatable.bind('', self.onrightclik ) + def load_data(self): - """Should be overriden in child class""" + """ + Should be overriden in child's class + + """ pass + def update_data(self): + """ + Delete everything in datatable and reload its content with the updated + data coming from the XML file. + + """ + for i in self.datatable.get_children(): - self.datatable.delete(i) - self.load_data() + self.datatable.delete(i) # delete all data from the datatable + self.load_data() # reload all data with updated values + def treeview_sortby(self, tree, column, descending): """ Sort treeview contents when a column is clicked on. - Taken from Dave's IDmapper - From: https://code.google.com/p/python-ttk/source/browse/trunk/pyttk-samples/treeview_multicolumn.py?r=21 + + :param tree: treview table + :type tree: + :param column: column to sort by + :type column: + :param descending: descending sorting + :type descending: + """ + # grab values to sort - data = [(tree.set(child, column), child) for child in tree.get_children('')] + data = \ + [(tree.set(child, column), child) for child in tree.get_children('')] + # reorder data data.sort(reverse=descending) for index, item in enumerate(data): tree.move(item[1], '', index) + # switch the heading so that it will sort in the opposite direction - tree.heading(column, command=lambda column=column: self.treeview_sortby(tree, column, int(not descending))) + tree.heading( + column, + command=lambda column=column: self.treeview_sortby( + tree, column, int(not descending) + ) + ) + def ondoubleclick(self, event): """ - Double clicking on a treeview line opens a 'data window' - and refresh the treeview data when the 'data window' is closed + Double clicking on a treeview line opens a 'data window'. + Treeview data will be reloaded once the 'data window' has been closed. + + :param event: + :type event: + """ - # double clicking on blank space of the treeview when no valid line is selected generates a + # Double click on a blank line of the Treeview datatable generates an # IndexOutOfRange error which is taken care of by this try:except block try: itemID = self.datatable.selection()[0] - item = self.datatable.item(itemID)['tags'] + item = self.datatable.item(itemID)['tags'] parent = self.parent candidate_id = item[1] DataWindow.DataWindow(parent, candidate_id) self.update_data() + except Exception as e: - print "Datatable ondoubleclick ", str(e) # TODO deal with exception or not!?! + # TODO: deal with exceptions + print "Datatable ondoubleclick ", str(e) + + + def onrightclik(self, event): #TODO: to implement this or not? + """ + Not used yet + + :param event: + :type event: + + """ - def onrightclik(self, event): - """Not used yet""" print 'contextual menu for this item' pass - def onrowclick(self, event): - """Not used yet""" + + def onrowclick(self, event): #TODO: to implement this or not? + """ + Not used yet + + :param event: + :type event: + + """ + item_id = str(self.datatable.focus()) item = self.datatable.item(item_id)['values'] + + class ParticipantsList(DataTable): """ Class ParticipantsList(DataTable) takes care of the data table holding the list of participants. That list contains all participants (even those that have never been called). + """ def __init__(self, parent, colheaders): # expected is dataset + """ + __init__ function of ParticipantsList class + + :param parent: frame in which to insert the candidate datatable + :type parent: frame + :param colheaders: array of column headers to use in the datatable + :type colheaders: list + + """ + DataTable.__init__(self, parent, colheaders) - self.colheaders = colheaders - self.load_data() - # TODO add these color settings in a 'settings and preferences section of the app' - self.datatable.tag_configure('active', background='#F1F8FF') # TODO replace active tag by status variable value + + self.colheaders = colheaders # initialize the column headers + self.load_data() # load the data in the datatable + + # TODO add color settings in a 'settings & preferences' section + # TODO replace 'active' tag by status variable value + self.datatable.tag_configure('active', background='#F1F8FF') + def load_data(self): - data = DataManagement.read_candidate_data() + """ + Load candidates information into the candidate datatable. + + """ + + # Read candidates information into a cand_data dictionary + cand_data = DataManagement.read_candidate_data() try: - for key in data: + # Loop through all candidates + for key in cand_data: - if "CandidateStatus" not in data[key].keys(): + # Deal with occurences where CandidateStatus is not set + if "CandidateStatus" not in cand_data[key].keys(): status = "" else: - status = data[key]["CandidateStatus"] + status = cand_data[key]["CandidateStatus"] - if "PhoneNumber" not in data[key].keys(): + # Deal with occurences where PhoneNumber is not set + if "PhoneNumber" not in cand_data[key].keys(): phone = "" else: - phone = data[key]["PhoneNumber"] - self.datatable.insert( '', 'end', - values=[ data[key]["Identifier"], - data[key]["FirstName"], - data[key]["LastName"], - data[key]["DateOfBirth"], - data[key]["Gender"], - phone, - status - ], - tags=(status, data[key]["Identifier"]) - ) - except Exception as e: - print "datatable.ParticipantsList.load_data ", str(e) # TODO proper exception handling + phone = cand_data[key]["PhoneNumber"] + + # Insert a given candidate into the datatable + self.datatable.insert( + '', + 'end', + values=[ + cand_data[key]["Identifier"], + cand_data[key]["FirstName"], + cand_data[key]["LastName"], + cand_data[key]["DateOfBirth"], + cand_data[key]["Gender"], + phone, + status + ], + tags=(status, cand_data[key]["Identifier"]) + ) + + except Exception as e: # TODO proper exception handling + print "datatable.ParticipantsList.load_data ", str(e) pass + + class VisitList(DataTable): """ - class VisitList(DataTable) takes care of the data table holding the list of all appointments - even those that have not been confirmed yet. + This class takes care of the data table holding the list of all + appointments, even those that have not been confirmed yet. + """ def __init__(self, parent, colheaders): + """ + __init__ function of ParticipantsList class + + :param parent: frame in which to insert the visit list datatable + :type parent: frame + :param colheaders: array of column headers to use in the datatable + :type colheaders: list + + """ + DataTable.__init__(self, parent, colheaders) - self.colheaders = colheaders - self.load_data() - # TODO add these color settings in a 'settings and preferences section of the app' - self.datatable.tag_configure('active', background='#F1F8FF') # TODO change for non-language parameter - self.datatable.tag_configure('tentative', background='#F0F0F0') # TODO change for non-language parameter + + self.colheaders = colheaders # initialize the column headers + self.load_data() # load the data in the datatable + + # TODO add color settings in a 'settings and preferences' section + # TODO change 'active' & 'tentative' for non-language parameter + self.datatable.tag_configure('active', background='#F1F8FF') + self.datatable.tag_configure('tentative', background='#F0F0F0') + def load_data(self): - data = DataManagement.read_visitset_data() - for cand_key, value in data.iteritems(): - if "VisitSet" in data[cand_key].keys(): # skip the search if visitset = None - current_visitset = data[cand_key]["VisitSet"] # set this candidate.visitset for the next step + """ + Load the visit list into the datatable. + + """ + + # Read visit list and visit information into a visit_data dictionary + visit_data = DataManagement.read_visitset_data() + + # Loop through candidates + for cand_key, value in visit_data.iteritems(): + + # Skip the search if visitset == None for that candidate + if "VisitSet" in visit_data[cand_key].keys(): + + # set this candidate.visitset for the next step + current_visitset = visit_data[cand_key]["VisitSet"] + # gather information about the candidate - candidate_id = data[cand_key]["Identifier"] - candidate_firstname = data[cand_key]["FirstName"] - candidate_lastname = data[cand_key]["LastName"] - candidate_fullname = str( candidate_firstname - + ' ' - + candidate_lastname - ) + candidate_id = visit_data[cand_key]["Identifier"] + candidate_firstname = visit_data[cand_key]["FirstName"] + candidate_lastname = visit_data[cand_key]["LastName"] + candidate_fullname = str( + candidate_firstname + ' ' + candidate_lastname + ) + + # Loop through all visits for that candidate for visit_key, value in current_visitset.iteritems(): + if "VisitStatus" in current_visitset[visit_key].keys(): + # Set visit status and label status = current_visitset[visit_key]["VisitStatus"] visit_label = current_visitset[visit_key]["VisitLabel"] - if "VisitStartWhen" not in current_visitset[visit_key].keys(): - when = '' #TODO check what would be the next visit and set status to "to_schedule" + + # Check at what time the visit has been scheduled + field = 'VisitStartWhen' + if field not in current_visitset[visit_key].keys(): + when = '' + #TODO check what would be next visit + #TODO & set its status to "to_schedule" #when = current_visitset[visit_key].whenearliest + else: when = current_visitset[visit_key]["VisitStartWhen"] - if "VisitWhere" not in current_visitset[visit_key].keys(): + + # Check if the location of the visit has been set + field = 'VisitWhere' + if field not in current_visitset[visit_key].keys(): where = '' + else: where = current_visitset[visit_key]["VisitWhere"] + + # Check that all values could be found try: - row_values = [ candidate_id, candidate_fullname, - visit_label, when, - where, status - ] + row_values = [ + candidate_id, candidate_fullname, visit_label, + when, where, status + ] row_tags = (status, candidate_id, visit_label) self.datatable.insert('', 'end', values = row_values, tags = row_tags ) + except Exception as e: - print "datatable.VisitList.load_data ", str(e) # TODO add proper error handling + # TODO deal with exception + # TODO (add proper error handling) + print "datatable.VisitList.load_data ", str(e) pass From 98ef4f41158cdb58e12f2ee4a7d0673a23a90e96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Wed, 27 Jul 2016 14:06:19 -0400 Subject: [PATCH 68/89] Cleaned up the following files: modified: dicat/DicAT_application.py modified: dicat/scheduler_application.py modified: dicat/ui/dialogbox.py modified: dicat/ui/menubar.py deleted: dicat/ui/newcandidatewindow.py modified: dicat/ui/projectpane.py --- dicat/DicAT_application.py | 44 ++++++++-- dicat/scheduler_application.py | 144 +++++++++++++++++++++------------ dicat/ui/dialogbox.py | 41 +++++++--- dicat/ui/menubar.py | 82 +++++++++++++++---- dicat/ui/newcandidatewindow.py | 74 ----------------- dicat/ui/projectpane.py | 16 ++++ 6 files changed, 245 insertions(+), 156 deletions(-) delete mode 100644 dicat/ui/newcandidatewindow.py diff --git a/dicat/DicAT_application.py b/dicat/DicAT_application.py index a7b38fe..3efe45f 100644 --- a/dicat/DicAT_application.py +++ b/dicat/DicAT_application.py @@ -1,20 +1,33 @@ #!/usr/bin/python +# Import from standard packages import ttk from Tkinter import * +# Import DICAT libraries from dicom_anonymizer_frame import dicom_deidentifier_frame_gui from IDMapper import IDMapper_frame_gui from scheduler_application import UserInterface from welcome_frame import welcome_frame_gui import ui.menubar as MenuBar + + class DicAT_application(): - # Constructor of the class DicAT called with a parent widget ("master") - # to which we will add a number of child widgets. The constructor starts - # by creating a "Frame" widget. A frame is a simple container. + def __init__(self, master, side=LEFT): + """ + Constructor of the class DICAT called with a parent widget ("master") + to which we will add a number of child widgets. The constructor starts + by creating a "Frame" widget. A frame is a simple container. + + :param master: + :type master: object + :param side: + :type side: + + """ self.dir_opt = {} @@ -39,7 +52,7 @@ def __init__(self, master, side=LEFT): # Add the pages to the notebook self.nb.add(self.page1, text='Welcome to DICAT!') self.nb.add(self.page2, text='DICOM de-identifier') - self.nb.add(self.page3, text='Scheduler') # hide scheduler for now + self.nb.add(self.page3, text='Scheduler') self.nb.add(self.page4, text='ID key') # Draw @@ -53,23 +66,41 @@ def __init__(self, master, side=LEFT): def dicom_deidentifier_tab(self): + """ + Start the DICOM de-identifier frame. + + """ # start dicom_anonymizer_frame_gui method dicom_deidentifier_frame_gui(self.page2) def id_key_frame(self): + """ + Start the ID Mapper frame. + + """ # start the ID mapper frame gui IDMapper_frame_gui(self.page4) def welcome_page(self): + """ + Start the Welcome frame. + + """ # start the Welcome page welcome_frame_gui(self.page1) def scheduler_page(self, master): + """ + Start the scheduler frame. + + :param master: + :type master: + """ # initialize the menu bar and start the scheduler frame menu = MenuBar.SchedulerMenuBar(master) @@ -87,6 +118,7 @@ def scheduler_page(self, master): # The program will stay in the event loop until we close the window. root.mainloop() - # Some development environments won't terminate the Python process unless it is - # explicitly mentioned to destroy the main window when the loop is terminated. + # Some development environments won't terminate the Python process unless + # it is explicitly mentioned to destroy the main window when the loop is + # terminated. # root.destroy() diff --git a/dicat/scheduler_application.py b/dicat/scheduler_application.py index b71989e..ae73b66 100644 --- a/dicat/scheduler_application.py +++ b/dicat/scheduler_application.py @@ -13,95 +13,139 @@ class UserInterface(Frame): def __init__(self, parent): + """ + Initialize the UserInterface class. + + :param parent: parent widget in which to insert the UserInterface + :type parent: object + + """ + Frame.__init__(self) self.parent = parent #self.parent.title(MultiLanguage.app_title) self.initialize() + def initialize(self): + """ + Creates the paned window with the project information, candidate and + calendar panes. + + """ - # initialize frame + # Initialize frame self.frame = Frame(self.parent) self.frame.pack(side=TOP, expand=YES, fill=BOTH, padx=10, pady=10) #TODO implement button to be able to choose the XML file Config.xmlfile = "new_data_test.xml" + ## Create the PanedWindow + # This area (datapane) is one Panedwindow containing 3 Labelframes - self.data_pane = Panedwindow( self.frame, width=1000, - height=500, orient=HORIZONTAL - ) # TODO add dynamic resize + # TODO add dynamic resize + self.data_pane = Panedwindow( + self.frame, width=1000, height=500, orient=HORIZONTAL + ) self.data_pane.pack(side=RIGHT, expand=YES, fill=BOTH) + + ## Create the 3 LabelFrames that will be part of the PanedWindow + + # Project info pane # TODO create class for project info pane - self.project_infopane = Labelframe(self.data_pane, - text=MultiLanguage.project_info_pane, - width=250, - height=350, - borderwidth=10 - ) # TODO add dynamic resize - self.candidate_pane = Labelframe( self.data_pane, - text=MultiLanguage.candidate_pane, - width=100, - height=450, - borderwidth=10 - ) # TODO add dynamic resize - self.visit_pane = Labelframe( self.data_pane, - text=MultiLanguage.calendar_pane, - width=100, - height=350, - borderwidth=10 - ) # TODO add dynamic resize + # TODO add dynamic resize + self.project_infopane = Labelframe( + self.data_pane, text=MultiLanguage.project_info_pane, width=250, + height=350, borderwidth=10 + ) + + # Candidate pane + # TODO add dynamic resize + self.candidate_pane = Labelframe( + self.data_pane, text=MultiLanguage.candidate_pane, width=100, + height=450, borderwidth=10 + ) + + # Visit pane + # TODO add dynamic resize + self.visit_pane = Labelframe( + self.data_pane, text=MultiLanguage.calendar_pane, width=100, + height=350, borderwidth=10 + ) self.data_pane.add(self.project_infopane) self.data_pane.add(self.candidate_pane) self.data_pane.add(self.visit_pane) - # plot the button action in the pane frame - # candidate pane frame buttons - self.buttonNewCandidate = Button( self.candidate_pane, - width=12, - text=MultiLanguage.candidate_add, - command=self.add_candidate - ) + ## Plot the button action in the pane frame + + # Candidate pane frame buttons + self.buttonNewCandidate = Button( + self.candidate_pane, + width=12, + text=MultiLanguage.candidate_add, + command=self.add_candidate + ) self.buttonNewCandidate.pack(side=TOP, anchor=W) - # create data tables (treeview) - # candidate table + ## Create data tables (using Treeview) + + # Candidate datatable candidate_column_headers = ( 'identifier', 'firstname', 'lastname', 'date of birth', 'gender', 'phone', 'status' ) - self.cand_table = DataTable.ParticipantsList( self.candidate_pane, - candidate_column_headers - ) + self.cand_table = DataTable.ParticipantsList( + self.candidate_pane, candidate_column_headers + ) self.cand_table.pack(side=BOTTOM, expand=YES, fill=BOTH) - # calendar table + + # Calendar datatable visit_column_headers = ( 'identifier', 'candidate', 'visitlabel', 'when', 'where', 'status' ) - self.visit_table = DataTable.VisitList( self.visit_pane, - visit_column_headers - ) + self.visit_table = DataTable.VisitList( + self.visit_pane, visit_column_headers + ) self.visit_table.pack(side=BOTTOM, expand=YES, fill=BOTH) + #TODO This section to be replaced by REAL CODE actively filtering data """ - #create a filter section in each data_pane(not implemented yet) - #TODO This whole section needs to be replaced by REAL CODE actively filtering the data - self.filter_candidate = Labelframe(self.candidate_pane, text='Filters', width=220, height=50, borderwidth=10) + #Create a filter section in each data_pane (not implemented yet) + + + self.filter_candidate = Labelframe( + self.candidate_pane, text='Filters', width=220, height=50, + borderwidth=10 + ) self.filter_candidate.pack(side=TOP, expand=NO, fill=BOTH, pady=5) - self.filter_candidate_label = Label(self.filter_candidate, text='Filter for Non-Active / Active / Excluded / Group...') + self.filter_candidate_label = Label( + self.filter_candidate, + text='Filter for Non-Active / Active / Excluded / Group...' + ) self.filter_candidate_label.pack(side=TOP, expand=NO, fill=BOTH) - self.filter_visit = Labelframe(self.visit_pane, text='Filters', width=220, height=50, borderwidth=10) + self.filter_visit = Labelframe( + self.visit_pane, text='Filters', width=220, height=50, + borderwidth=10 + ) self.filter_visit.pack(side=TOP, expand=NO, fill=BOTH, pady=5) - self.filter_candidate_label = Label(self.filter_visit, text='Filters for Active / Tentative / Closed ...') + self.filter_candidate_label = Label( + self.filter_visit, + text='Filters for Active / Tentative / Closed ...' + ) self.filter_candidate_label.pack(side=TOP, expand=NO, fill=BOTH) """ + def add_candidate(self): - DataWindow.DataWindow(self, "new") - self.update_data(self.cand_table) + """ + This function will allow functionality to add a new candidate using + the same data window as when editing a subject. + + """ + # Open the datawindow + DataWindow.DataWindow(self, "new") - def update_data(self, table): - for i in table.datatable.get_children(): - table.datatable.delete(i) - DataTable.ParticipantsList.load_data(table) + # Update the candidate datatable when save the new candidate + self.cand_table.update_data() \ No newline at end of file diff --git a/dicat/ui/dialogbox.py b/dicat/ui/dialogbox.py index d8e37bf..53ee1bf 100644 --- a/dicat/ui/dialogbox.py +++ b/dicat/ui/dialogbox.py @@ -1,14 +1,19 @@ #import standard packages from Tkinter import * + #import internal packages import lib.utilities as Utilities import lib.multilanguage as MultiLanguage class DialogBox(Toplevel): """ - This class was created mainly because the native dialog box don't work as expected when called from a top-level window. - This class (although it could be improved in many aspects) insure that the parent window cannot get focus while a dialog box is still active. + This class was created mainly because the native dialog box don't work as + expected when called from a top-level window. + This class (although it could be improved in many aspects) insure that the + parent window cannot get focus while a dialog box is still active. + """ + def __init__(self,parent, title, message, button1, button2): Toplevel.__init__(self,parent) self.transient(parent) @@ -28,31 +33,40 @@ def __init__(self,parent, title, message, button1, button2): self.initial_focus.focus_set() self.deiconify() self.wait_window(self) - + + def body(self, parent, message): label = Label(self, text=message) label.pack(padx=4, pady=4) pass - + + def buttonbox(self, button1, button2): #add a standard button box box = Frame(self) - b1 = Button(box, text=button1, width=12, command=self.button1, default=ACTIVE) + b1 = Button( + box, text=button1, width=12, command=self.button1, default=ACTIVE + ) b1.pack(side=LEFT, padx=4, pady=4) self.bind("", self.button1) if button2: - b2 = Button(box, text=button2, width=12, command=self.button2, default=ACTIVE) + b2 = Button( + box, text=button2, width=12, command=self.button2, + default=ACTIVE + ) b2.pack(side=LEFT, padx=4, pady=4) self.bind("", self.button2) box.pack() - + + def button1(self, event=None): if not self.validate(): self.initial_focus.focus_set() #put focus on Button return self.buttonvalue = 1 self.closedialog() - + + def button2(self, event=None): self.buttonvalue = 2 self.closedialog() @@ -62,18 +76,23 @@ def closedialog(self, event=None): #put focus back to parent window before destroying the window self.parent.focus_set() self.destroy() - + + def validate(self): return 1 -######################################################################################### + + + class ConfirmYesNo(DialogBox): def __init__(self, parent, message): title = MultiLanguage.dialog_title_confirm button1 = MultiLanguage.dialog_yes button2 = MultiLanguage.dialog_no DialogBox.__init__(self, parent, title, message, button1, button2) - + + + class ErrorMessage(DialogBox): def __init__(self, parent, message): diff --git a/dicat/ui/menubar.py b/dicat/ui/menubar.py index 6a27000..8d9b49c 100644 --- a/dicat/ui/menubar.py +++ b/dicat/ui/menubar.py @@ -1,70 +1,122 @@ #import standard packages import Tkinter + #import internal packages import lib.multilanguage as MultiLanguage import ui.datawindow as DataWindow class SchedulerMenuBar(Tkinter.Menu): + def __init__(self, parent): + """ + Initialize the Menu bar of the application + + :param parent: parent in which to put the menu bar of the application + :type parent: object + + """ + Tkinter.Menu.__init__(self, parent) - # create an APPLICATION pulldown menu + + # Create an APPLICATION pulldown menu application_menu = Tkinter.Menu(self, tearoff=False) - self.add_cascade(label=MultiLanguage.application_menu,underline=0, menu=application_menu) - application_menu.add_command(label=MultiLanguage.application_setting, underline=1, command=self.app_settings) + self.add_cascade( + label=MultiLanguage.application_menu, + underline=0, + menu=application_menu + ) + application_menu.add_command( + label=MultiLanguage.application_setting, + underline=1, + command=self.app_settings + ) application_menu.add_separator() - application_menu.add_command(label=MultiLanguage.application_quit, underline=1, command=self.quit_application) - # create a CANDIDATE pulldown menu + application_menu.add_command( + label=MultiLanguage.application_quit, + underline=1, + command=self.quit_application + ) + + # Create a CANDIDATE pulldown menu candidate_menu = Tkinter.Menu(self, tearoff=False) - self.add_cascade(label=MultiLanguage.candidate_menu, underline=0, menu=candidate_menu) - candidate_menu.add_command(label=MultiLanguage.candidate_add, command=self.add_candidate) - candidate_menu.add_command(label=MultiLanguage.candidate_find, command=self.find_candidate) - # create a CALENDAR pulldown menu + self.add_cascade( + label=MultiLanguage.candidate_menu, underline=0, menu=candidate_menu + ) + candidate_menu.add_command( + label=MultiLanguage.candidate_add, command=self.add_candidate + ) + candidate_menu.add_command( + label=MultiLanguage.candidate_find, command=self.find_candidate + ) + + # Create a CALENDAR pulldown menu calendar_menu = Tkinter.Menu(self, tearoff=False) - self.add_cascade(label=MultiLanguage.calendar_menu, underline=0, menu=calendar_menu) - calendar_menu.add_command(label=MultiLanguage.calendar_new_appointment, command=self.open_calendar) - # create a HELP pulldown menu + self.add_cascade( + label=MultiLanguage.calendar_menu, underline=0, menu=calendar_menu + ) + calendar_menu.add_command( + label=MultiLanguage.calendar_new_appointment, + command=self.open_calendar + ) + + # Create a HELP pulldown menu help_menu = Tkinter.Menu(self, tearoff=0) - self.add_cascade(label=MultiLanguage.help_menu, underline=0, menu=help_menu) - help_menu.add_command(label=MultiLanguage.help_get_help, command=self.open_help) - help_menu.add_command(label=MultiLanguage.help_about_window, command=self.about_application) + self.add_cascade( + label=MultiLanguage.help_menu, underline=0, menu=help_menu + ) + help_menu.add_command( + label=MultiLanguage.help_get_help, command=self.open_help + ) + help_menu.add_command( + label=MultiLanguage.help_about_window, + command=self.about_application + ) + def app_settings(self): #TODO implement app_settings() print 'running appsettings' pass + def quit_application(self): #TODO implement quit_application() print 'running quit_application' self.quit() pass + def open_calendar(self): #TODO implement open_calendar() print 'running open_calendar' pass + def dicom_anonymizer(self): #TODO implement dicom_anonymizer() print 'running dicom anonymizer' pass + def add_candidate(self): #TODO implement add_candidate() DataWindow.DataWindow(self, "new") print 'running add_candidate' pass + def find_candidate(self): #TODO implement find_candidate() print 'running find_candidate' pass + def open_help(self): #TODO open_help() print 'running open_help' pass + def about_application(self): #TODO about_application() print 'running about_application' diff --git a/dicat/ui/newcandidatewindow.py b/dicat/ui/newcandidatewindow.py deleted file mode 100644 index 516acfe..0000000 --- a/dicat/ui/newcandidatewindow.py +++ /dev/null @@ -1,74 +0,0 @@ -#import standard packages -from Tkinter import * -from ttk import * -#import internal packages -import ui.dialogbox as DialogBox -import lib.utilities as Utilities -import lib.multilanguage as MultiLanguage -import lib.datamanagement as DataManagement - -class NewCandidateWindow(TopLevel): - def __init__(self, parent): - Toplevel.__init__(self, parent) - #create a transient window on top of parent window - print "running NewCandidate(Toplevel) " #TODO remove when done - self.transient(parent) - self.parent = parent - self.title(MultiLanguage.new_candidate_title) - body = Frame(self) - self.initial_focus = self.body(body, candidate_uuid) - body.pack(padx=5, pady=5) - - self.button_box() - self.grab_set() - if not self.initial_focus: - self.initial_focus = self - self.protocol("WM_DELETE_WINDOW", self.closedialog) - Utilities.center_window(self) - self.initial_focus.focus_set() - #self.deiconify() - self.wait_window(self) - - def body(self, master, candidate): - #Candidate section - self.candidate_pane = Labelframe(self, text=MultiLanguage.candidate_pane, width=250, height=350, borderwidth=10) - self.candidate_pane.pack(side=TOP, expand=YES, fill=BOTH, padx=5, pady=5) - #PSCID - self.label_pscid = Label(self.candidate_pane, text=MultiLanguage.candidate_pscid) - self.label_pscid.grid(column=0, row=0, padx=10, pady=5, sticky=N+S+E+W) - self.text_pscid_var = StringVar() - self.text_pscid_var.set(candidate.pscid) - self.text_pscid = Entry(self.candidate_pane, textvariable=self.text_pscid_var) - self.text_pscid.grid(column=0, row=1, padx=10, pady=5, sticky=N+S+E+W) - #status - self.label_status = Label(self.candidate_pane, text=MultiLanguage.candidate_status) - self.label_status.grid(column=1, row=0, padx=10, pady=5, sticky=N+S+E+W) - self.text_status_var = StringVar() - self.text_status_var.set(candidate.status) - self.text_status = Entry(self.candidate_pane, textvariable=self.text_status_var) - self.text_status.grid(column=1, row=1, padx=10, pady=5, sticky=N+S+E+W) - #firstname - self.label_firstname = Label(self.candidate_pane, text=MultiLanguage.candidate_firstname) - self.label_firstname.grid(column=0, row=2, padx=10, pady=5, sticky=N+S+E+W) - self.text_firstname_var = StringVar() - self.text_firstname_var.set(candidate.firstname) - self.text_firstname = Entry(self.candidate_pane, textvariable=self.text_firstname_var) - self.text_firstname.grid(column=0, row=3, padx=10, pady=5, sticky=N+S+E+W) - #lastname - self.label_lastname = Label(self.candidate_pane, text=MultiLanguage.candidate_lastname) - self.label_lastname.grid(column=1, row=2, padx=10, pady=5, sticky=N+S+E+W) - self.text_lastname_var = StringVar() - self.text_lastname_var.set(candidate.lastname) - self.text_lastname = Entry(self.candidate_pane, textvariable=self.text_lastname_var) - self.text_lastname.grid(column=1, row=3, padx=10, pady=5, sticky=N+S+E+W) - #phone number - self.label_phone = Label(self.candidate_pane, text=MultiLanguage.candidate_phone) - self.label_phone.grid(column=2, row=2, padx=10, pady=5, sticky=N+S+E+W) - self.text_phone_var = StringVar() - self.text_phone_var.set(candidate.phone) - self.text_phone = Entry(self.candidate_pane, textvariable=self.text_phone_var) - self.text_phone.grid(column=2, row=3, padx=10, pady=5, sticky=N+S+E+W) - #pscid - #self.label_pscid = Label(self.candidate_pane, textvariable=self.pscid_var) - - diff --git a/dicat/ui/projectpane.py b/dicat/ui/projectpane.py index bed5d89..2d4ae70 100644 --- a/dicat/ui/projectpane.py +++ b/dicat/ui/projectpane.py @@ -1,6 +1,22 @@ +# Import from standard packages from Tkinter import * + +# Import from DICAT libraries import lib.multilanguage as multilanguage class ProjectPane(LabelFrame): + """ + This class will contain everything about the Project Pane. + + """ + def __init__(self, parent): + """ + Initialize the ProjectPane class + + :param parent: frame where to put the project pane + :type parent: object + + """ + LabelFrame.__init__(self, parent) \ No newline at end of file From 20ef413a24a35ebc77adf87f599b5cbfcbfca41f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Wed, 27 Jul 2016 15:49:37 -0400 Subject: [PATCH 69/89] Added check for DOB in scheduler data window. Moved the check for DOB into a function in utilities.py --- dicat/IDMapper.py | 6 +++--- dicat/lib/multilanguage.py | 11 +++++++---- dicat/lib/utilities.py | 23 ++++++++++++++++++++++- dicat/ui/datawindow.py | 29 ++++++++++++++++++++++------- 4 files changed, 54 insertions(+), 15 deletions(-) diff --git a/dicat/IDMapper.py b/dicat/IDMapper.py index 87ca01b..c73777a 100644 --- a/dicat/IDMapper.py +++ b/dicat/IDMapper.py @@ -5,6 +5,7 @@ import lib.datamanagement as DataManagement import lib.config as Config +import lib.utilities as Utilities import ttk @@ -255,9 +256,8 @@ def AddIdentifierAction(self, candid, firstname, lastname, dob, save=True): return # check dob is in format YYYY-MM-DD - try: - datetime.datetime.strptime(dob,"%Y-%m-%d") - except ValueError: + success = Utilities.check_date_format(dob) + if not success: message = "ERROR:\nDate of birth's\nformat should be\n'YYYY-MM-DD'" self.ErrorMessage.set(message) return diff --git a/dicat/lib/multilanguage.py b/dicat/lib/multilanguage.py index 440ccd0..1346619 100644 --- a/dicat/lib/multilanguage.py +++ b/dicat/lib/multilanguage.py @@ -106,13 +106,15 @@ u"sauvegarder!\n\nVoulez-vous continuer?" dialog_title_confirm = u"Veuillez confirmer!" dialog_title_error = u"Erreur" + dialog_bad_dob_format = u"La date de naissance doit être formatté en " \ + u"AAAA-MM-JJ!" dialog_missing_candidate_info = u"Les champs 'Identifiant', 'Prénom', " \ u"'Nom de famille', 'Date de naissance' " \ u"et 'Sexe' sont requis!" ################ DATA WINDOW ################### schedule_pane = u"Calendrier" candidate_pane = u"Candidat" - candidate_dob = u"Date de naissance" + candidate_dob = u"Date de naissance (AAAA-MM-JJ)" candidate_phone = u"Téléphone" candidate_pscid = u"ID" candidate_status = u"Status" @@ -219,15 +221,16 @@ dialog_ok = u"OK" dialog_close = u"You are about to close this window without saving!\n\n" \ u"Do you want to continue?" - dialog_title_confirm = u"Please confirm!" - dialog_title_error = u"Error" + dialog_title_confirm = u"Please confirm!" + dialog_title_error = u"Error" + dialog_bad_dob_format = u"Date of Birth should be in YYYY-MM-DD format!" dialog_missing_candidate_info = u"'Identifier', 'Firstname', 'Lastname', " \ u"'Gender' and 'Date of Birth' fields " \ u"are required!" ################ DATA WINDOW ################### schedule_pane = u"Calendar" candidate_pane = u"Candidate" - candidate_dob = u"Date of Birth" + candidate_dob = u"Date of Birth (YYYY-MM-DD)" candidate_phone = u"Phone" candidate_pscid = u"ID" candidate_status = u"Status" diff --git a/dicat/lib/utilities.py b/dicat/lib/utilities.py index eac0025..b0c936b 100644 --- a/dicat/lib/utilities.py +++ b/dicat/lib/utilities.py @@ -1,6 +1,6 @@ #imports from standard packages from uuid import uuid1 -import time +import time, datetime """ This file contains utility functions used throughout the application @@ -114,6 +114,27 @@ def print_object(something): print "\n\n" +def check_date_format(date): + """ + Function that checks that the date format is YYYY-MM-DD + + :param date: date to check + :type date: str + + :return: True if date is in YYYY-MM-DD format, False otherwise + :rtype: bool + """ + + try: + datetime.datetime.strptime(date,"%Y-%m-%d") + return True + + except ValueError: + return False + + + + # self-test "module" TODO remove before release if __name__ == '__main__': import lib.datamanagement as DataManagement diff --git a/dicat/ui/datawindow.py b/dicat/ui/datawindow.py index 1a1b047..2fa0f56 100644 --- a/dicat/ui/datawindow.py +++ b/dicat/ui/datawindow.py @@ -21,7 +21,8 @@ def __init__(self, parent, candidate='new'): # create a transient window on top of parent window self.transient(parent) self.parent = parent - self.title(MultiLanguage.data_window_title) #TODO find a better title for the thing + #TODO find a better title for the data window + self.title(MultiLanguage.data_window_title) body = Frame(self) self.initial_focus = self.body(body, candidate) body.pack(padx=5, pady=5) @@ -39,7 +40,7 @@ def __init__(self, parent, candidate='new'): def body(self, master, candidate): - """Creates the body of 'datawindow'. param candidate is the candidate.uuid""" + """Creates the body of 'datawindow'. """ try: cand_data = DataManagement.read_candidate_data() visit_data = DataManagement.read_visitset_data() @@ -406,10 +407,10 @@ def capture_data(self): """ - # initialize the candidate dictionary with new values + # Initialize the candidate dictionary with new values cand_data = {} - # capture data from fields + # Capture data from fields cand_data['Identifier'] = self.text_pscid.get() cand_data['FirstName'] = self.text_firstname.get() cand_data['LastName'] = self.text_lastname.get() @@ -418,13 +419,27 @@ def capture_data(self): cand_data['PhoneNumber'] = self.text_phone.get() cand_data['CandidateStatus'] = self.text_status_var.get() - if not cand_data['Identifier'] or not cand_data['FirstName'] \ + # Check that all required fields are set (a.k.a. 'Identifier', + # 'FirstName', 'LastName', 'Gender' and 'DateOfBirth'), if not, return + # an error. (Error message stored in + # MultiLanguage.dialog_missing_candidate_info variable) + if not cand_data['Identifier'] or not cand_data['FirstName'] \ or not cand_data['LastName'] or not cand_data['Gender'] \ or not cand_data['DateOfBirth']: - return False + return MultiLanguage.dialog_missing_candidate_info - if not cand_data['CandidateStatus'] or not cand_data['PhoneNumber']: + # If Date of Birth does not match YYYY-MM-DD, return an error + # (Error message is stored in MultiLanguage.dialog_bad_dob_format) + success = Utilities.check_date_format(cand_data['DateOfBirth']) + if not success: + return MultiLanguage.dialog_bad_dob_format + + # Set CandidateStatus to space string if not defined in cand_data + if not cand_data['CandidateStatus']: cand_data['CandidateStatus'] = " " + + # Set PhoneNumber to space string if not defined in cand_data + if not cand_data['PhoneNumber']: cand_data['PhoneNumber'] = " " # save data From 3f7a6fd29192dc84ad578b3bb1cd0fcb8f710366 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Wed, 27 Jul 2016 16:27:55 -0400 Subject: [PATCH 70/89] Added error checking when entering/editing candidate's informations (no duplicate identifier, date format check, fields required to save data). See Issue #40. --- dicat/lib/datamanagement.py | 26 ++++++++++++++ dicat/lib/multilanguage.py | 2 ++ dicat/ui/datawindow.py | 68 +++++++++++++++++++++++++++++-------- 3 files changed, 81 insertions(+), 15 deletions(-) diff --git a/dicat/lib/datamanagement.py b/dicat/lib/datamanagement.py index 80d7e32..1287b3e 100644 --- a/dicat/lib/datamanagement.py +++ b/dicat/lib/datamanagement.py @@ -96,6 +96,32 @@ def read_candidate_data(): return data +def grep_list_of_candidate_IDs(): + """ + Read the XML file and grep all candidate IDs into an array candIDs_array. + + :return: candIDs_array, or False if could not find the XML file + + """ + + candIDs_array = [] + + if os.path.isfile(Config.xmlfile): + # read the xml file + (xmldata, xmlcandlist) = read_data(Config.xmlfile) + + for cand in xmlcandlist: + for elem in cand.childNodes: + tag = elem.localName + if tag == 'Identifier': + cand_elem = cand.getElementsByTagName(tag)[0] + candID = cand_elem.firstChild.nodeValue + candIDs_array.append(candID) + else: + return False + + return candIDs_array + def save_candidate_data(cand_data): """ Save the updated candidate information into the xml file (defined by the diff --git a/dicat/lib/multilanguage.py b/dicat/lib/multilanguage.py index 1346619..9a1f44b 100644 --- a/dicat/lib/multilanguage.py +++ b/dicat/lib/multilanguage.py @@ -108,6 +108,7 @@ dialog_title_error = u"Erreur" dialog_bad_dob_format = u"La date de naissance doit être formatté en " \ u"AAAA-MM-JJ!" + dialog_candID_already_exists = u"L'identifiant existe déjà!" dialog_missing_candidate_info = u"Les champs 'Identifiant', 'Prénom', " \ u"'Nom de famille', 'Date de naissance' " \ u"et 'Sexe' sont requis!" @@ -224,6 +225,7 @@ dialog_title_confirm = u"Please confirm!" dialog_title_error = u"Error" dialog_bad_dob_format = u"Date of Birth should be in YYYY-MM-DD format!" + dialog_candID_already_exists = u"Identifier already exists!" dialog_missing_candidate_info = u"'Identifier', 'Firstname', 'Lastname', " \ u"'Gender' and 'Date of Birth' fields " \ u"are required!" diff --git a/dicat/ui/datawindow.py b/dicat/ui/datawindow.py index 2fa0f56..812bbbd 100644 --- a/dicat/ui/datawindow.py +++ b/dicat/ui/datawindow.py @@ -17,16 +17,31 @@ class DataWindow(Toplevel): def __init__(self, parent, candidate='new'): + """ + Initialize the DataWindow class. + + :param parent: parent frame of the data window + :type parent: object + :param candidate: candidate ID or 'new' for a new candidate + :type candidate: str + + """ + Toplevel.__init__(self, parent) - # create a transient window on top of parent window + + # Create a transient window on top of parent window self.transient(parent) - self.parent = parent + self.parent = parent + self.candidate = candidate #TODO find a better title for the data window self.title(MultiLanguage.data_window_title) body = Frame(self) - self.initial_focus = self.body(body, candidate) + + # Draw the body of the data window + self.initial_focus = self.body(body) body.pack(padx=5, pady=5) + # Draw the button box of the data window self.button_box() self.grab_set() @@ -35,25 +50,42 @@ def __init__(self, parent, candidate='new'): self.protocol("WM_DELETE_WINDOW", self.closedialog) Utilities.center_window(self) self.initial_focus.focus_set() - # self.deiconify() self.wait_window(self) - def body(self, master, candidate): - """Creates the body of 'datawindow'. """ + def body(self, master): + """ + Creates the body of the 'datawindow'. + + :param master: frame in which to draw the body of the datawindow + :type master: object + :param candidate: candidate ID or 'new' for a new candidate + :type candidate: str + + """ + try: + # Read candidate information cand_data = DataManagement.read_candidate_data() + # Read visit information visit_data = DataManagement.read_visitset_data() - visitset = {} - cand_info = {} + visitset = {} # Create a visitset dictionary + cand_info = {} # Create a candidate information dictionary + + # Loop through all candidates for cand_key in cand_data: - if cand_data[cand_key]["Identifier"] == candidate: + # Grep candidate's information from cand_data dictionary + if cand_data[cand_key]["Identifier"] == self.candidate: cand_info = cand_data[cand_key] break + + # Loop through candidates' visit data for cand_key in visit_data: - if visit_data[cand_key]["Identifier"] == candidate: + # Grep candidate's visit set information from visit_data + if visit_data[cand_key]["Identifier"] == self.candidate: visitset = visit_data[cand_key]["VisitSet"] break + except Exception as e: print "datawindow.body ", str(e) # TODO manage exceptions @@ -68,7 +100,7 @@ def body(self, master, candidate): padx=5, pady=5 ) - # initialize text variables that will contain the field values + # Initialize text variables that will contain the field values self.text_pscid_var = StringVar() self.text_firstname_var = StringVar() self.text_lastname_var = StringVar() @@ -77,9 +109,9 @@ def body(self, master, candidate): self.text_status_var = StringVar() self.text_phone_var = StringVar() - # if candidate="new" populate the field with an empty string + # If candidate="new" populate the fields with an empty string # otherwise populate with the values available in cand_info dictionary - if candidate == "new": + if self.candidate == "new": self.text_pscid_var.set("") self.text_firstname_var.set("") self.text_lastname_var.set("") @@ -358,11 +390,11 @@ def ok_button(self, event=None): message = self.capture_data() - if not message: + if message: parent = Frame(self) newwin = DialogBox.ErrorMessage( parent, - MultiLanguage.dialog_missing_candidate_info + message ) if newwin.buttonvalue == 1: return # to stay on the candidate pop up page after clicking OK @@ -428,6 +460,12 @@ def capture_data(self): or not cand_data['DateOfBirth']: return MultiLanguage.dialog_missing_candidate_info + # If candidate is new, check that the 'Identifier' used is unique + candIDs_array = DataManagement.grep_list_of_candidate_IDs() + if self.candidate == 'new' and cand_data['Identifier'] in candIDs_array: + print "Candidate Exists" + return MultiLanguage.dialog_candID_already_exists + # If Date of Birth does not match YYYY-MM-DD, return an error # (Error message is stored in MultiLanguage.dialog_bad_dob_format) success = Utilities.check_date_format(cand_data['DateOfBirth']) From 6b09e3af8254eca1889025375df7041faab0f784 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Wed, 27 Jul 2016 18:58:15 -0400 Subject: [PATCH 71/89] Can now open an XML database from the Welcome tab. Selecting the IDMapper tab or the scheduler tab will automatically refresh the displayed data based on the latest version of the XML file. --- dicat/DicAT_application.py | 31 +++++++- dicat/IDMapper.py | 87 ++-------------------- dicat/lib/utilities.py | 2 + dicat/scheduler_application.py | 17 ++++- dicat/ui/datatable.py | 112 ++++++++++++++-------------- dicat/ui/datawindow.py | 2 - dicat/welcome_frame.py | 130 +++++++++++++++++++++++++++++++-- 7 files changed, 231 insertions(+), 150 deletions(-) diff --git a/dicat/DicAT_application.py b/dicat/DicAT_application.py index 3efe45f..dc35288 100644 --- a/dicat/DicAT_application.py +++ b/dicat/DicAT_application.py @@ -64,6 +64,9 @@ def __init__(self, master, side=LEFT): self.welcome_page() self.scheduler_page(master) + # refresh tab when selecting it + self.page3.bind("", self.update_scheduler) + self.page4.bind("", self.update_IDMapper) def dicom_deidentifier_tab(self): """ @@ -82,7 +85,7 @@ def id_key_frame(self): """ # start the ID mapper frame gui - IDMapper_frame_gui(self.page4) + self.IDMapper = IDMapper_frame_gui(self.page4) def welcome_page(self): @@ -105,7 +108,31 @@ def scheduler_page(self, master): # initialize the menu bar and start the scheduler frame menu = MenuBar.SchedulerMenuBar(master) master.config(menu=menu) - UserInterface(self.page3) + self.scheduler = UserInterface(self.page3) + + def update_scheduler(self, event): + """ + Reload the scheduler tables when the tab is selected + + :param event: event + :type event: object + + """ + + # reload the scheduler + self.scheduler.LoadXML() + + def update_IDMapper(self, event): + """ + Reload the IDMapper table when the tab is selected + + :param event: event + :type event: object + + """ + + # reload the IDMapper + self.IDMapper.LoadXML() if __name__ == "__main__": diff --git a/dicat/IDMapper.py b/dicat/IDMapper.py index c73777a..2ef647f 100644 --- a/dicat/IDMapper.py +++ b/dicat/IDMapper.py @@ -41,54 +41,7 @@ def __init__(self, parent): def initialize(self): - # initialize Frame - self.frame = Frame(self.parent) - self.frame.pack(expand=1, fill='both') - self.frame.columnconfigure(0, weight=1) - self.frame.columnconfigure(1, weight=1) - self.frame.columnconfigure(2, weight=6) - - - # select an existing candidate.xml file - # Initialize default text that will be in self.entry - self.entryVariable = Tkinter.StringVar() - self.entryVariable.set("Open an XML file with candidate's key") - - # Create an entry with a default text that will be replaced by the path - # to the XML file once directory selected - self.entry = Entry(self.frame, - width=40, - textvariable=self.entryVariable - ) - self.entry.focus_set() - self.entry.selection_range(0, Tkinter.END) - - # Create an open button to use to select an XML file with candidate's - # key info - self.buttonOpen = Button(self.frame, - text=u"Open an existing file", - command=self.openfilename - ) - - self.buttonCreate = Button(self.frame, - text=u"Create a new file", - command=self.createfilename - ) - - self.buttonCreate.grid(row=0, - column=0, - padx=(0, 15), - pady=10, - sticky=E + W - ) - self.buttonOpen.grid(row=0, - column=1, - padx=(0, 15), - pady=10, - sticky=E + W - ) - self.entry.grid(row=0, column=2, padx=15, pady=10, sticky=E + W) - + # Initialize GUI self.InitUI() @@ -198,8 +151,10 @@ def InitUI(self): self.buttonEdit.grid( row=2, column=2, padx=(4,0), sticky=E+W) self.buttonAdd.grid( row=2, column=3, padx=(4,0), sticky=E+W) - self.datatable.grid(row=3, column=0, columnspan=4, pady=10, sticky='nsew') - self.error.grid( row=4, column=0) + self.datatable.grid( + row=3, column=0, columnspan=4, pady=10, sticky='nsew' + ) + self.error.grid(row=4, column=0) def LoadXML(self): @@ -378,38 +333,6 @@ def EditIdentifierAction(self, identifier, firstname, lastname, dob, edit=True): self.datatable.item(item, values=updatedList) - def openfilename(self): - - """Returns a selected file name.""" - self.filename = tkFileDialog.askopenfilename( - filetypes=[("XML files", "*.xml")] - ) - self.entryVariable.set(self.filename) - - if self.filename: - # Load the data - Config.xmlfile = self.filename - self.LoadXML() - - return self.filename - - - def createfilename(self): - - self.filename = tkFileDialog.asksaveasfilename( - defaultextension=[("*.xml")], - filetypes=[("XML files", "*.xml")] - ) - self.entryVariable.set(self.filename) - - if self.filename: - open(self.filename, 'w') - - # Load the data - Config.xmlfile = self.filename - self.LoadXML() - - return self.filename def main(): diff --git a/dicat/lib/utilities.py b/dicat/lib/utilities.py index b0c936b..f8a3ac6 100644 --- a/dicat/lib/utilities.py +++ b/dicat/lib/utilities.py @@ -135,6 +135,8 @@ def check_date_format(date): + + # self-test "module" TODO remove before release if __name__ == '__main__': import lib.datamanagement as DataManagement diff --git a/dicat/scheduler_application.py b/dicat/scheduler_application.py index ae73b66..2b6b25a 100644 --- a/dicat/scheduler_application.py +++ b/dicat/scheduler_application.py @@ -39,7 +39,7 @@ def initialize(self): self.frame.pack(side=TOP, expand=YES, fill=BOTH, padx=10, pady=10) #TODO implement button to be able to choose the XML file - Config.xmlfile = "new_data_test.xml" + #Config.xmlfile = "new_data_test.xml" ## Create the PanedWindow @@ -148,4 +148,17 @@ def add_candidate(self): DataWindow.DataWindow(self, "new") # Update the candidate datatable when save the new candidate - self.cand_table.update_data() \ No newline at end of file + self.cand_table.update_data() + + def LoadXML(self): + """ + Update candidate and calendar/visit datatables with data extracted from + XML file stored in Config.xmlfile. + + """ + + # Load XML data into candidate datatable + self.cand_table.update_data() + + # Load XML data into visit datatable + self.visit_table.update_data() \ No newline at end of file diff --git a/dicat/ui/datatable.py b/dicat/ui/datatable.py index 5f8ceb1..d342ead 100644 --- a/dicat/ui/datatable.py +++ b/dicat/ui/datatable.py @@ -292,65 +292,63 @@ def load_data(self): # Read visit list and visit information into a visit_data dictionary visit_data = DataManagement.read_visitset_data() - # Loop through candidates - for cand_key, value in visit_data.iteritems(): - - # Skip the search if visitset == None for that candidate - if "VisitSet" in visit_data[cand_key].keys(): - - # set this candidate.visitset for the next step - current_visitset = visit_data[cand_key]["VisitSet"] - - # gather information about the candidate - candidate_id = visit_data[cand_key]["Identifier"] - candidate_firstname = visit_data[cand_key]["FirstName"] - candidate_lastname = visit_data[cand_key]["LastName"] - candidate_fullname = str( - candidate_firstname + ' ' + candidate_lastname - ) - - # Loop through all visits for that candidate - for visit_key, value in current_visitset.iteritems(): - - if "VisitStatus" in current_visitset[visit_key].keys(): - # Set visit status and label - status = current_visitset[visit_key]["VisitStatus"] - visit_label = current_visitset[visit_key]["VisitLabel"] - - # Check at what time the visit has been scheduled - field = 'VisitStartWhen' - if field not in current_visitset[visit_key].keys(): - when = '' - #TODO check what would be next visit - #TODO & set its status to "to_schedule" - #when = current_visitset[visit_key].whenearliest - - else: - when = current_visitset[visit_key]["VisitStartWhen"] - - # Check if the location of the visit has been set - field = 'VisitWhere' - if field not in current_visitset[visit_key].keys(): - where = '' - - else: - where = current_visitset[visit_key]["VisitWhere"] - - # Check that all values could be found - try: + try: + # Loop through candidates + for cand_key, value in visit_data.iteritems(): + + # Skip the search if visitset == None for that candidate + if "VisitSet" in visit_data[cand_key].keys(): + + # set this candidate.visitset for the next step + current_visitset = visit_data[cand_key]["VisitSet"] + + # gather information about the candidate + candidate_id = visit_data[cand_key]["Identifier"] + candidate_firstname = visit_data[cand_key]["FirstName"] + candidate_lastname = visit_data[cand_key]["LastName"] + candidate_fullname = str( + candidate_firstname + ' ' + candidate_lastname + ) + + # Loop through all visits for that candidate + for visit_key, value in current_visitset.iteritems(): + + if "VisitStatus" in current_visitset[visit_key].keys(): + # Set visit status and label + status = current_visitset[visit_key]["VisitStatus"] + visit_label = current_visitset[visit_key]["VisitLabel"] + + # Check at what time the visit has been scheduled + field = 'VisitStartWhen' + if field not in current_visitset[visit_key].keys(): + when = '' + #TODO check what would be next visit + #TODO & set its status to "to_schedule" + #when = current_visitset[visit_key].whenearliest + + else: + when_key = 'VisitStartWhen' + when = current_visitset[visit_key][when_key] + + # Check if the location of the visit has been set + field = 'VisitWhere' + if field not in current_visitset[visit_key].keys(): + where = '' + + else: + where = current_visitset[visit_key]["VisitWhere"] + # Check that all values could be found row_values = [ candidate_id, candidate_fullname, visit_label, when, where, status ] row_tags = (status, candidate_id, visit_label) - self.datatable.insert('', - 'end', - values = row_values, - tags = row_tags - ) - - except Exception as e: - # TODO deal with exception - # TODO (add proper error handling) - print "datatable.VisitList.load_data ", str(e) - pass + self.datatable.insert( + '', 'end', values = row_values, tags = row_tags + ) + + except Exception as err: + #TODO deal with exception + #TODO add proper error handling + print "Could not load visits" + pass diff --git a/dicat/ui/datawindow.py b/dicat/ui/datawindow.py index 812bbbd..fdaada1 100644 --- a/dicat/ui/datawindow.py +++ b/dicat/ui/datawindow.py @@ -482,5 +482,3 @@ def capture_data(self): # save data DataManagement.save_candidate_data(cand_data) - - return True diff --git a/dicat/welcome_frame.py b/dicat/welcome_frame.py index 1fa5cdd..4c4c374 100644 --- a/dicat/welcome_frame.py +++ b/dicat/welcome_frame.py @@ -1,6 +1,13 @@ #!/usr/bin/python +# import from standard packages from Tkinter import * +import tkFileDialog + +# import from DICAT libraries +import lib.config as Config +from IDMapper import IDMapper_frame_gui + ''' lib.resource_path_methods has been created for Pyinstaller. @@ -14,17 +21,18 @@ class welcome_frame_gui(Frame): def __init__(self, parent): self.parent = parent self.initialize() + self.openDatabase() def initialize(self): - self.frame = Frame(self.parent) - self.frame.pack(expand=1, fill='both') + frame = Frame(self.parent) + frame.pack(expand=1, fill='both') # Insert DICAT logo on the right side of the screen load_img = PathMethods.resource_path("images/DICAT_logo.gif") imgPath = load_img.return_path() logo = PhotoImage(file = imgPath) - logo_image = Label(self.frame, + logo_image = Label(frame, image = logo, bg='white' ) @@ -33,8 +41,8 @@ def initialize(self): logo_image.pack(side='left', fill='both') # Create the Welcome to DICAT text variable - text = Text(self.frame, padx=40, wrap='word') - scroll = Scrollbar(self.frame, command=text.yview) + text = Text(frame, padx=40, wrap='word') + scroll = Scrollbar(frame, command=text.yview) text.configure(yscrollcommand=scroll.set) text.tag_configure('title', font=('Verdana', 20, 'bold', 'italic'), @@ -76,8 +84,120 @@ def initialize(self): ''' text.insert(END, IDkey, 'default') + # Insert explanation of the scheduler tab into the text variable + tab3 = "\n\nThe scheduler tab allows to:\n" + text.insert(END, tab3, 'bold') + + # TODO: develop on the functionality of scheduler + Sched = ''' + 1) Store patient information + 2) Schedule visits + 3) View the schedule + 4) blablabla + ''' + text.insert(END, Sched, 'default') + + # Display the text variable text.pack(side='left', fill='both', expand=1) scroll.pack(side="right", fill='y') # Disable the edit functionality of the displayed text text.config(state='disabled') + + + def openDatabase(self): + """ + + :return: + """ + + frame = Frame(self.parent, bd=5, relief='groove') + frame.pack(expand=0, fill='both') + + # Label + label = Label( + frame, + text=u"Open a DICAT database (.xml file)", + font=('Verdana', 15, 'bold', 'italic') + ) + + # select an existing candidate.xml file + # Initialize default text that will be in self.entry + self.entryVariable = StringVar() + self.entryVariable.set("Open a DICAT database (.xml file)") + + # Create an entry with a default text that will be replaced by the path + # to the XML file once directory selected + entry = Entry( + frame, width=60, textvariable=self.entryVariable + ) + entry.focus_set() + entry.selection_range(0, END) + + # Create an open button to use to select an XML file with candidate's + # key info + buttonOpen = Button( + frame, + text=u"Open an existing database", + command=self.open_database + ) + + buttonCreate = Button( + frame, + text=u"Create a new database", + command=self.create_database + ) + + label.grid( + row=0, column=0, columnspan=3, padx=(0,15), pady=0, sticky=E+W + ) + buttonCreate.grid( + row=1, column=0, padx=(15,15), pady=10, sticky=E+W + ) + buttonOpen.grid( + row=1, column=1, padx=(0,15), pady=10, sticky=E+W + ) + entry.grid( + row=1, column=2, padx=(0,15), pady=10, sticky=E+W + ) + + + def open_database(self): + """ + Opens and loads the selected XML database in DICAT. + + """ + + self.filename = tkFileDialog.askopenfilename( + filetypes=[("XML files", "*.xml")] + ) + self.entryVariable.set(self.filename) + + if self.filename: + # Set the database xmlfile in Config.xmlfile to self.filename + Config.xmlfile = self.filename + else: + #TODO: proper error handling + print "file could not be opened." + + print Config.xmlfile + + def create_database(self): + """ + Uses a template XML file to create a new database and saves it. + + """ + #TODO: use a template XML and save it as new name + self.filename = tkFileDialog.asksaveasfilename( + defaultextension=[("*.xml")], + filetypes=[("XML files", "*.xml")] + ) + self.entryVariable.set(self.filename) + + if self.filename: + open(self.filename, 'w') + + # Load the data + Config.xmlfile = self.filename + + return self.filename From e22be0f6b29c35c958e487ead4d0ec9ef00f2235 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Wed, 27 Jul 2016 19:17:27 -0400 Subject: [PATCH 72/89] Allowed creation of a new database base on a database template file (data/database_template.xml) --- dicat/data/database_template.xml | 266 +++++++++++++++++++++++++++++++ dicat/welcome_frame.py | 18 ++- 2 files changed, 278 insertions(+), 6 deletions(-) create mode 100644 dicat/data/database_template.xml diff --git a/dicat/data/database_template.xml b/dicat/data/database_template.xml new file mode 100644 index 0000000..073c083 --- /dev/null +++ b/dicat/data/database_template.xml @@ -0,0 +1,266 @@ + + + + DICAT test + This is a test project. + + 0 + 100 + 2000 + 2050 + + Project 1 + + Subproject 1 + + V0 + 1 + + 25-35 + + V1 + 2 + 55-65 + + V2 + 3 + 95-105 + + + V3 + 4 + + + + Subproject 2 + + V0 + 1 + + + + + + + + MTL0002 + + Sepia + Calamari + Female + 2015-06-14 + 444-555-6666 + active + + V0 + completed + + 2016-01-06 13:15:00 + 2016-01-06 14:15:00 + Douglas + Annie + + + V1 + completed + 2016-04-05 13:15:00 + 2016-04-05 14:15:00 + Douglas + Jennifer + + + V2 + scheduled + 2016-06-05 13:15:00 + 2016-06-05 14:15:00 + Douglas + Jennifer + + + + MTL0003 + Bibi + LaPraline + Female + 2010-08-12 + 450-345-6789 + excluded + + V0 + completed + + 2016-01-06 13:15:00 + 2016-01-06 14:15:00 + Douglas + Annie + + + + MTL0001 + Blues + Singer + Male + 2015-06-14 + 444-555-6666 + active + + V0 + completed + + 2016-01-06 13:15:00 + 2016-01-06 14:15:00 + Douglas + Annie + + + V1 + completed + 2016-04-05 13:15:00 + 2016-04-05 14:15:00 + Douglas + Jennifer + + + V2 + scheduled + 2016-06-05 13:15:00 + 2016-06-05 14:15:00 + Douglas + Jennifer + + + + MTL0006 + Ali + Gator + Male + 2014-02-05 + 514-758-8903 + active + + V0 + completed + + 2016-01-06 13:15:00 + 2016-01-06 14:15:00 + Douglas + Annie + + + V1 + scheduled + 2016-04-05 13:15:00 + 2016-04-05 14:15:00 + Douglas + Jennifer + + + + MTL0004 + Pikachu + Pokemon + Male + 1996-01-01 + 543-453-5432 + withdrawn + + V0 + completed + + 2016-01-06 13:15:00 + 2016-01-06 14:15:00 + Douglas + Annie + + + + MTL0005 + Bilou + Doudou + Female + 2014-02-03 + 450-758-9385 + withdrawn + + V0 + completed + + 2016-01-06 13:15:00 + 2016-01-06 14:15:00 + Douglas + Annie + + + + MTL9999 + Lego + Phantom + Male + 2012-04-29 + 432-654-9093 + active + + V0 + completed + + 2016-01-06 13:15:00 + 2016-01-06 14:15:00 + Douglas + Annie + + + V1 + scheduled + 2016-04-05 13:15:00 + 2016-04-05 14:15:00 + Douglas + Jennifer + + + + MTL0025 + Santa + Claus + Male + 2015-12-25 + + ineligible + + + + James + Bond + 1977-07-07 + + Male + MTL0007 + + + ineligible + Bugs + Bunny + 1938-04-30 + + Male + MTL0008 + + + Gonzales + MTL0009 + Speedy + 1953-08-29 + + + + + + + Lucky + Luke + 1946-12-07 + + Male + MTL0010 + + + diff --git a/dicat/welcome_frame.py b/dicat/welcome_frame.py index 4c4c374..86a6535 100644 --- a/dicat/welcome_frame.py +++ b/dicat/welcome_frame.py @@ -182,22 +182,28 @@ def open_database(self): print Config.xmlfile + def create_database(self): """ Uses a template XML file to create a new database and saves it. """ - #TODO: use a template XML and save it as new name + self.filename = tkFileDialog.asksaveasfilename( defaultextension=[("*.xml")], filetypes=[("XML files", "*.xml")] ) self.entryVariable.set(self.filename) - if self.filename: - open(self.filename, 'w') + # Fetch the database template file + load_template = PathMethods.resource_path("data/database_template.xml") + template_file = load_template.return_path() - # Load the data + # If both the new file and the template file exists, copy the template + # lines in the new file + if self.filename and template_file: + # Set the database xmlfile in Config.xmlfile to self.filename Config.xmlfile = self.filename - - return self.filename + with open(Config.xmlfile, 'a') as f1: + for line in open(template_file, 'r'): + f1.write(line) From d23207c95128a72c6da9f6d46afa48066f3353b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Thu, 28 Jul 2016 11:30:56 -0400 Subject: [PATCH 73/89] Moved the candidate info checks into the lib/candidate.py library and updated IDmapper and Scheduler code to call this function. Also cleaned up a few more scripts. --- dicat/DicAT_application.py | 2 +- dicat/IDMapper.py | 343 ++++++++++-------- dicat/__init__.py | 1 + dicat/data/database_template.xml | 220 ----------- dicat/dicom_anonymizer_frame.py | 2 +- .../candidate.py} | 8 +- dicat/lib/multilanguage.py | 19 +- dicat/lib/resource_path_methods.py | 14 + dicat/lib/utilities.py | 9 - dicat/scheduler_application.py | 5 +- dicat/ui/datatable.py | 4 +- dicat/ui/datawindow.py | 57 ++- dicat/welcome_frame.py | 51 ++- 13 files changed, 284 insertions(+), 451 deletions(-) create mode 100644 dicat/__init__.py rename dicat/{scheduler_candidate.py => lib/candidate.py} (98%) diff --git a/dicat/DicAT_application.py b/dicat/DicAT_application.py index dc35288..691dbad 100644 --- a/dicat/DicAT_application.py +++ b/dicat/DicAT_application.py @@ -105,7 +105,7 @@ def scheduler_page(self, master): :type master: """ - # initialize the menu bar and start the scheduler frame + # description_frame_gui the menu bar and start the scheduler frame menu = MenuBar.SchedulerMenuBar(master) master.config(menu=menu) self.scheduler = UserInterface(self.page3) diff --git a/dicat/IDMapper.py b/dicat/IDMapper.py index 2ef647f..6a17ac0 100644 --- a/dicat/IDMapper.py +++ b/dicat/IDMapper.py @@ -1,14 +1,11 @@ #!/usr/bin/python -import Tkinter, Tkconstants, tkFileDialog, tkMessageBox, re, datetime from Tkinter import * +import ttk import lib.datamanagement as DataManagement -import lib.config as Config -import lib.utilities as Utilities - +from lib.candidate import Candidate -import ttk def sortby(tree, col, descending): """Sort tree contents when a column is clicked on.""" @@ -29,7 +26,11 @@ def sortby(tree, col, descending): class IDMapper_frame_gui(Frame): def __init__(self, parent): - """Initialize the application""" + """ + Initialize the ID Mapper frame. + + """ + self.parent = parent # Set up the dictionary map @@ -40,12 +41,20 @@ def __init__(self, parent): def initialize(self): + """ + Initialize the ID Mapper GUI by calling self.InitUI(). + + """ # Initialize GUI self.InitUI() def InitUI(self): + """ + Draws the ID Mapper GUI. + + """ self.frame = Frame(self.parent) self.frame.pack(expand=1, fill='both') @@ -60,64 +69,62 @@ def InitUI(self): self.labelID = Label(self.frame, text=u'Identifier') self.labelFirstName = Label(self.frame, text=u'First Name') self.labelLastName = Label(self.frame, text=u'Last Name') - self.labelDoB = Label(self.frame, text=u'Date of Birth (YYYY-MM-DD)') - - self.buttonAdd = Button(self.frame, width=12, - text=u'Add candidate', - command=self.AddIdentifierEvent - ) - self.buttonClear = Button(self.frame, width=12, - text=u'Clear fields', - command=self.clear - ) - self.buttonSearch = Button(self.frame, width=12, - text=u'Search candidate', - command=self.search - ) - self.buttonEdit = Button(self.frame, width=12, - text=u'Edit candidate', - command=self.edit - ) + self.labelDoB = Label( + self.frame, text=u'Date of Birth (YYYY-MM-DD)' + ) + + self.buttonAdd = Button( + self.frame, + width=12, + text=u'Add candidate', + command=self.AddIdentifierEvent + ) + self.buttonClear = Button( + self.frame, width=12, text=u'Clear fields', command=self.clear + ) + self.buttonSearch = Button( + self.frame, width=12, text=u'Search candidate', command=self.search + ) + self.buttonEdit = Button( + self.frame, width=12, text=u'Edit candidate', command=self.edit + ) self.textCandId = StringVar() - self.candidateid = Entry(self.frame, - textvariable=self.textCandId, - width=20 - ) + self.candidateid = Entry( + self.frame, textvariable=self.textCandId, width=20 + ) self.candidateid.focus_set() self.textCandFirstName = StringVar() - self.candidateFirstName = Entry( self.frame, - textvariable=self.textCandFirstName, - width=20 - ) + self.candidateFirstName = Entry( + self.frame, textvariable=self.textCandFirstName, width=20 + ) self.textCandLastName = StringVar() - self.candidateLastName = Entry( self.frame, - textvariable=self.textCandLastName, - width=20 - ) + self.candidateLastName = Entry( + self.frame, textvariable=self.textCandLastName, width=20 + ) self.textCandDoB = StringVar() - self.candidateDoB = Entry(self.frame, - textvariable=self.textCandDoB, - width=20 - ) - - self.tableColumns = ( "Identifier", "First Name", - "Last Name", "Date of Birth" - ) - self.datatable = ttk.Treeview(self.frame, - selectmode='browse', - columns=self.tableColumns, - show="headings") + self.candidateDoB = Entry( + self.frame, textvariable=self.textCandDoB, width=20 + ) + + self.tableColumns = ( + "Identifier", "First Name", "Last Name", "Date of Birth" + ) + self.datatable = ttk.Treeview( + self.frame, + selectmode='browse', + columns=self.tableColumns, + show="headings" + ) for col in self.tableColumns: - self.datatable.heading( col, text=col.title(), - command=lambda c=col: sortby(self.datatable, - c, - 0 - ) - ) + self.datatable.heading( + col, + text=col.title(), + command=lambda c=col: sortby(self.datatable, c, 0) + ) self.datatable.bind("<>", self.OnRowClick) @@ -129,22 +136,18 @@ def InitUI(self): self.labelLastName.grid( row=0, column=2, padx=(4,4), sticky=E+W) self.labelDoB.grid( row=0, column=3, padx=(4,4), sticky=E+W) - self.candidateid.grid( row=1, column=0, - padx=(0,4), pady=(0,10), - sticky=E+W - ) - self.candidateFirstName.grid( row=1, column=1, - padx=(4,4), pady=(0,10), - sticky=E+W - ) - self.candidateLastName.grid( row=1, column=2, - padx=(4,4), pady=(0,10), - sticky=E+W - ) - self.candidateDoB.grid( row=1, column=3, - padx=(4,4), pady=(0,10), - sticky=E+W - ) + self.candidateid.grid( + row=1, column=0, padx=(0,4), pady=(0,10), sticky=E+W + ) + self.candidateFirstName.grid( + row=1, column=1, padx=(4,4), pady=(0,10), sticky=E+W + ) + self.candidateLastName.grid( + row=1, column=2, padx=(4,4), pady=(0,10), sticky=E+W + ) + self.candidateDoB.grid( + row=1, column=3, padx=(4,4), pady=(0,10), sticky=E+W + ) self.buttonClear.grid( row=2, column=0, padx=(4,0), sticky=E+W) self.buttonSearch.grid(row=2, column=1, padx=(4,0), sticky=E+W) @@ -154,17 +157,23 @@ def InitUI(self): self.datatable.grid( row=3, column=0, columnspan=4, pady=10, sticky='nsew' ) - self.error.grid(row=4, column=0) + self.error.grid(row=4, column=0, columnspan=4) def LoadXML(self): + """ + Parses the XML file and loads the data into the IDMapper. + Calls check_and_save_data with option action=False as we don't want to + save the candidates to be added to the datatable back in the XML. + + """ + global data # empty the datatable and data dictionary before loading new file self.datatable.delete(*self.datatable.get_children()) self.IDMap = {} - """Parses the XML file and loads the data into the current window""" try: data = DataManagement.read_candidate_data() for key in data: @@ -172,71 +181,34 @@ def LoadXML(self): firstname = data[key]["FirstName"] lastname = data[key]["LastName"] dob = data[key]["DateOfBirth"] - self.AddIdentifierAction( identifier, firstname, - lastname, dob, - False - ) + self.check_and_save_data( + identifier, firstname, lastname, dob, False + ) except Exception as e: - print str(e) #TODO add error login (in case a candidate data file does not exist) + #TODO add error login (in case a candidate data file does not exist) + print str(e) def AddIdentifierEvent(self): + """ + Event handler for the 'Add new candidate' button. Will call + check_and_save_data on what has been entered in the Entry boxes. + + """ firstname = self.candidateFirstName.get() lastname = self.candidateLastName.get() candid = self.candidateid.get() dob = self.candidateDoB.get() - self.AddIdentifierAction(candid, firstname, lastname, dob) + self.check_and_save_data(candid, firstname, lastname, dob, 'save') - def AddIdentifierAction(self, candid, firstname, lastname, dob, save=True): - """ - Adds the given identifier and real name to the mapping. If - the "save" parameter is true, this also triggers the saving - of the XML file. - This is set to False on initial load. + def OnRowClick(self, event): """ - self.ErrorMessage.set("") - - # check that all fields are set - if not candid or not firstname or not lastname or not dob: - message = "ERROR:\nAll fields are\nrequired to add\na candidate" - self.ErrorMessage.set(message) - return - - # check candid does not already exist - if candid in self.IDMap: - message = "ERROR:\nCandidate ID\nalready exists" - self.ErrorMessage.set(message) - return - - # check dob is in format YYYY-MM-DD - success = Utilities.check_date_format(dob) - if not success: - message = "ERROR:\nDate of birth's\nformat should be\n'YYYY-MM-DD'" - self.ErrorMessage.set(message) - return - - mapList = [candid, firstname, lastname, dob] - self.IDMap[candid] = mapList - - insertedList = [(candid, firstname, lastname, dob)] - for item in insertedList: - self.datatable.insert('', 'end', values=item) - - if(save): - cand_data = {} - cand_data["Identifier"] = candid - cand_data["FirstName"] = firstname - cand_data["LastName"] = lastname - cand_data["DateOfBirth"] = dob - #self.SaveMapAction() - DataManagement.save_candidate_data(cand_data) + Update the text boxes' data on row click. + """ - def OnRowClick(self, event): - - """Update the text boxes' data on row click""" item_id = str(self.datatable.focus()) item = self.datatable.item(item_id)['values'] @@ -247,7 +219,11 @@ def OnRowClick(self, event): def clear(self): - + """ + Event handler for the clear button. Will clear all the Entry boxes. + + """ + self.textCandId.set("") self.textCandFirstName.set("") self.textCandLastName.set("") @@ -256,23 +232,27 @@ def clear(self): def search(self): + """ + Event handler for the search button. Will call FindCandidate function + to find the proper candidate matching what has been filled in the Entry + boxes. + + """ + # Find a candidate based on its ID if it is set in text box if self.textCandId.get(): - (candid, firstname, - lastname, dob) = self.FindCandidate( "candid", - self.textCandId.get() - ) + (candid, firstname, lastname, dob) = self.FindCandidate( + "candid", self.textCandId.get() + ) # or based on its name if it is set in text box elif self.textCandFirstName.get(): - (candid, firstname, - lastname, dob) = self.FindCandidate( "firstname", - self.textCandFirstName.get() - ) + (candid, firstname, lastname, dob) = self.FindCandidate( + "firstname", self.textCandFirstName.get() + ) elif self.textCandLastName.get(): - (candid, firstname, - lastname, dob) = self.FindCandidate( "lastname", - self.textCandLastName.get() - ) + (candid, firstname, lastname, dob) = self.FindCandidate( + "lastname", self.textCandLastName.get() + ) # print the values in the text box self.textCandId.set(candid) @@ -282,6 +262,19 @@ def search(self): def FindCandidate(self, key, value): + """ + Find a candidate based on one of the fields entered in the Entry boxes. + + :param key: the name of the Entry type (a.k.a. 'candid', 'firstname'...) + :type key: str + :param value: the value stored in the Entry box (a.k.a. 'MTL0001' ... ) + :type value: str + + :return candid, firstname, lastname, dob: found candidate + :rtype candid, firstname, lastname, dob: str + + """ + global data # Loop through the candidate tree and return the candid, name and dob @@ -306,31 +299,79 @@ def FindCandidate(self, key, value): def edit(self): - self.EditIdentifierAction(self.textCandId.get(), - self.textCandFirstName.get(), - self.textCandLastName.get(), - self.textCandDoB.get() - ) + """ + Edit event of the Edit button. Will call check_and_save_date with + data entered in the Entry boxes and action='edit'. + """ + + self.check_and_save_data( + self.textCandId.get(), + self.textCandFirstName.get(), + self.textCandLastName.get(), + self.textCandDoB.get(), + 'edit' + ) - def EditIdentifierAction(self, identifier, firstname, lastname, dob, edit=True): - # save data in the XML file + def check_and_save_data(self, candid, firstname, lastname, dob, action=False): + """ + Grep the candidate data and check them before saving and updating the + XML file and the datatale. + + :param candid: identifier of the candidate + :type candid: str + :param firstname: firstname of the candidate + :type firstname: str + :param lastname: lastname of the candidate + :type lastname: str + :param dob: date of birth of the candidate + :type dob: str + :param action: whether to 'save' a new candidate or 'edit' a candidate + :type action: bool/str + + :return: + + """ + + # Check all required data are available cand_data = {} - cand_data["Identifier"] = identifier + cand_data["Identifier"] = candid cand_data["FirstName"] = firstname cand_data["LastName"] = lastname cand_data["DateOfBirth"] = dob + candidate = Candidate(cand_data) + + # Initialize message to False. Will be replaced by the appropriate error + # message if checks failed. + message = False + if action == 'save': + message = candidate.check_candidate_data('IDmapper', False) + elif action == 'edit': + message = candidate.check_candidate_data('IDmapper', candid) + + # If message contains an error message, display it and return + if message: + self.ErrorMessage.set(message) + return + + # Save the candidate data in the XML DataManagement.save_candidate_data(cand_data) - # update the IDMap dictionary - mapList = [identifier, firstname, lastname, dob] - self.IDMap[identifier] = mapList + # Update the IDMap dictionary + mapList = [candid, firstname, lastname, dob] + self.IDMap[candid] = mapList + + # Update datatable + if action == 'edit': + item = self.datatable.selection() + updatedList = (candid, firstname, lastname, dob) + self.datatable.item(item, values=updatedList) + else: + insertedList = [(candid, firstname, lastname, dob)] + for item in insertedList: + self.datatable.insert('', 'end', values=item) - # update datatable - item = self.datatable.selection() - updatedList = (identifier, firstname, lastname, dob) - self.datatable.item(item, values=updatedList) diff --git a/dicat/__init__.py b/dicat/__init__.py new file mode 100644 index 0000000..1e5dbd8 --- /dev/null +++ b/dicat/__init__.py @@ -0,0 +1 @@ +__author__ = 'cmadjar' diff --git a/dicat/data/database_template.xml b/dicat/data/database_template.xml index 073c083..c0ecdbf 100644 --- a/dicat/data/database_template.xml +++ b/dicat/data/database_template.xml @@ -42,225 +42,5 @@ - - MTL0002 - - Sepia - Calamari - Female - 2015-06-14 - 444-555-6666 - active - - V0 - completed - - 2016-01-06 13:15:00 - 2016-01-06 14:15:00 - Douglas - Annie - - - V1 - completed - 2016-04-05 13:15:00 - 2016-04-05 14:15:00 - Douglas - Jennifer - - - V2 - scheduled - 2016-06-05 13:15:00 - 2016-06-05 14:15:00 - Douglas - Jennifer - - - - MTL0003 - Bibi - LaPraline - Female - 2010-08-12 - 450-345-6789 - excluded - - V0 - completed - - 2016-01-06 13:15:00 - 2016-01-06 14:15:00 - Douglas - Annie - - - - MTL0001 - Blues - Singer - Male - 2015-06-14 - 444-555-6666 - active - - V0 - completed - - 2016-01-06 13:15:00 - 2016-01-06 14:15:00 - Douglas - Annie - - - V1 - completed - 2016-04-05 13:15:00 - 2016-04-05 14:15:00 - Douglas - Jennifer - - - V2 - scheduled - 2016-06-05 13:15:00 - 2016-06-05 14:15:00 - Douglas - Jennifer - - - - MTL0006 - Ali - Gator - Male - 2014-02-05 - 514-758-8903 - active - - V0 - completed - - 2016-01-06 13:15:00 - 2016-01-06 14:15:00 - Douglas - Annie - - - V1 - scheduled - 2016-04-05 13:15:00 - 2016-04-05 14:15:00 - Douglas - Jennifer - - - - MTL0004 - Pikachu - Pokemon - Male - 1996-01-01 - 543-453-5432 - withdrawn - - V0 - completed - - 2016-01-06 13:15:00 - 2016-01-06 14:15:00 - Douglas - Annie - - - - MTL0005 - Bilou - Doudou - Female - 2014-02-03 - 450-758-9385 - withdrawn - - V0 - completed - - 2016-01-06 13:15:00 - 2016-01-06 14:15:00 - Douglas - Annie - - - - MTL9999 - Lego - Phantom - Male - 2012-04-29 - 432-654-9093 - active - - V0 - completed - - 2016-01-06 13:15:00 - 2016-01-06 14:15:00 - Douglas - Annie - - - V1 - scheduled - 2016-04-05 13:15:00 - 2016-04-05 14:15:00 - Douglas - Jennifer - - - - MTL0025 - Santa - Claus - Male - 2015-12-25 - - ineligible - - - - James - Bond - 1977-07-07 - - Male - MTL0007 - - - ineligible - Bugs - Bunny - 1938-04-30 - - Male - MTL0008 - - - Gonzales - MTL0009 - Speedy - 1953-08-29 - - - - - - - Lucky - Luke - 1946-12-07 - - Male - MTL0010 - diff --git a/dicat/dicom_anonymizer_frame.py b/dicat/dicom_anonymizer_frame.py index db18876..b2bb820 100755 --- a/dicat/dicom_anonymizer_frame.py +++ b/dicat/dicom_anonymizer_frame.py @@ -38,7 +38,7 @@ def __init__(self, parent): def initialize(self): - # initialize main Frame + # description_frame_gui main Frame self.frame = Frame(self.parent) self.frame.pack(expand=1, fill='both') diff --git a/dicat/scheduler_candidate.py b/dicat/lib/candidate.py similarity index 98% rename from dicat/scheduler_candidate.py rename to dicat/lib/candidate.py index 3a35b84..491e45c 100644 --- a/dicat/scheduler_candidate.py +++ b/dicat/lib/candidate.py @@ -1,12 +1,12 @@ # import standard packages import datetime -from scheduler_visit import Visit +from dicat.scheduler_visit import Visit # import internal packages -import lib.datamanagement as DataManagement -import lib.multilanguage as MultiLanguage -import lib.utilities as Utilities +import dicat.lib.datamanagement as DataManagement +import dicat.lib.multilanguage as MultiLanguage +import dicat.lib.utilities as Utilities class Candidate: diff --git a/dicat/lib/multilanguage.py b/dicat/lib/multilanguage.py index 9a1f44b..60e9cab 100644 --- a/dicat/lib/multilanguage.py +++ b/dicat/lib/multilanguage.py @@ -109,9 +109,12 @@ dialog_bad_dob_format = u"La date de naissance doit être formatté en " \ u"AAAA-MM-JJ!" dialog_candID_already_exists = u"L'identifiant existe déjà!" - dialog_missing_candidate_info = u"Les champs 'Identifiant', 'Prénom', " \ - u"'Nom de famille', 'Date de naissance' " \ - u"et 'Sexe' sont requis!" + dialog_missing_cand_info_schedul = u"Les champs 'Identifiant', 'Prénom', " \ + u"'Nom de famille', 'Sexe' et " \ + u"'Date de naissance' sont requis!" + dialog_missing_cand_info_IDmapper = u"Les champs 'Identifiant', " \ + u"'Prénom', 'Nom de famille' " \ + u"et 'Date de naissance' sont requis!" ################ DATA WINDOW ################### schedule_pane = u"Calendrier" candidate_pane = u"Candidat" @@ -226,9 +229,13 @@ dialog_title_error = u"Error" dialog_bad_dob_format = u"Date of Birth should be in YYYY-MM-DD format!" dialog_candID_already_exists = u"Identifier already exists!" - dialog_missing_candidate_info = u"'Identifier', 'Firstname', 'Lastname', " \ - u"'Gender' and 'Date of Birth' fields " \ - u"are required!" + dialog_missing_cand_info_schedul = u"'Identifier', 'Firstname', " \ + u"'Lastname', 'Date of Birth' and " \ + u"'Gender' fields are required!" + dialog_missing_cand_info_IDmapper = u"'Identifier', 'Firstname', " \ + u"'Lastname' and 'Date of Birth' " \ + u"fields are required!" + ################ DATA WINDOW ################### schedule_pane = u"Calendar" candidate_pane = u"Candidate" diff --git a/dicat/lib/resource_path_methods.py b/dicat/lib/resource_path_methods.py index ac9203a..d093512 100644 --- a/dicat/lib/resource_path_methods.py +++ b/dicat/lib/resource_path_methods.py @@ -8,11 +8,25 @@ class resource_path(): """ This class allows to get the absolute path to the resource scripts. It works for development installation as well as for PyInstaller builds (.app, .exe). + + It has been created for Pyinstaller. Linked images or external files will be + loaded using these methods, otherwise the created application (.app, .exe) + would not find them. + """ def __init__(self, relative_path): + """ + Initialize resource_path class. + + :param relative_path: relative path to the file from the dicat root dir + :type relative_path: str + + """ + self.relative_path = relative_path + def return_path(self): """ Get absolute path to resource, works for dev and for PyInstaller diff --git a/dicat/lib/utilities.py b/dicat/lib/utilities.py index f8a3ac6..c2f5848 100644 --- a/dicat/lib/utilities.py +++ b/dicat/lib/utilities.py @@ -6,15 +6,6 @@ This file contains utility functions used throughout the application """ -def generate_uid(): - """ - will generate a random UUID. - see python documentation https://docs.python.org/2/library/uuid.html - """ - ui = str(uuid1()) - return ui - - def is_unique(data, dataset): """ will verify if 'data' passed as argument is unique in a dataset diff --git a/dicat/scheduler_application.py b/dicat/scheduler_application.py index 2b6b25a..081111c 100644 --- a/dicat/scheduler_application.py +++ b/dicat/scheduler_application.py @@ -144,8 +144,9 @@ def add_candidate(self): """ - # Open the datawindow - DataWindow.DataWindow(self, "new") + # Open the datawindow with candidate=False as no existing ID associated + # yet for the new candidate + DataWindow.DataWindow(self, False) # Update the candidate datatable when save the new candidate self.cand_table.update_data() diff --git a/dicat/ui/datatable.py b/dicat/ui/datatable.py index d342ead..aff3e92 100644 --- a/dicat/ui/datatable.py +++ b/dicat/ui/datatable.py @@ -198,7 +198,7 @@ def __init__(self, parent, colheaders): # expected is dataset DataTable.__init__(self, parent, colheaders) - self.colheaders = colheaders # initialize the column headers + self.colheaders = colheaders # description_frame_gui the column headers self.load_data() # load the data in the datatable # TODO add color settings in a 'settings & preferences' section @@ -274,7 +274,7 @@ def __init__(self, parent, colheaders): DataTable.__init__(self, parent, colheaders) - self.colheaders = colheaders # initialize the column headers + self.colheaders = colheaders # description_frame_gui the column headers self.load_data() # load the data in the datatable # TODO add color settings in a 'settings and preferences' section diff --git a/dicat/ui/datawindow.py b/dicat/ui/datawindow.py index fdaada1..606de0b 100644 --- a/dicat/ui/datawindow.py +++ b/dicat/ui/datawindow.py @@ -7,7 +7,7 @@ import lib.utilities as Utilities import lib.multilanguage as MultiLanguage import lib.datamanagement as DataManagement - +from lib.candidate import Candidate # ref: http://effbot.org/tkinterbook/tkinter-newDialog-windows.htm @@ -16,7 +16,7 @@ class DataWindow(Toplevel): - def __init__(self, parent, candidate='new'): + def __init__(self, parent, candidate=False): """ Initialize the DataWindow class. @@ -109,17 +109,9 @@ def body(self, master): self.text_status_var = StringVar() self.text_phone_var = StringVar() - # If candidate="new" populate the fields with an empty string - # otherwise populate with the values available in cand_info dictionary - if self.candidate == "new": - self.text_pscid_var.set("") - self.text_firstname_var.set("") - self.text_lastname_var.set("") - self.text_dob_var.set("") - self.text_gender_var.set(" ") - self.text_status_var.set(" ") - self.text_phone_var.set("") - else: + # If candidate is populated with candID populate the fields with values + # available in cand_info dictionary, otherwise populate with empty str + if self.candidate: self.text_pscid_var.set(cand_info["Identifier"]) self.text_firstname_var.set(cand_info["FirstName"]) self.text_lastname_var.set(cand_info["LastName"]) @@ -127,6 +119,14 @@ def body(self, master): self.text_gender_var.set(cand_info["Gender"]) self.text_status_var.set(cand_info["CandidateStatus"]) self.text_phone_var.set(cand_info["PhoneNumber"]) + else: + self.text_pscid_var.set("") + self.text_firstname_var.set("") + self.text_lastname_var.set("") + self.text_dob_var.set("") + self.text_gender_var.set(" ") + self.text_status_var.set(" ") + self.text_phone_var.set("") # Create widgets to be displayed # (typically a label with a text box underneath per variable to display) @@ -365,7 +365,7 @@ def button_box(self): # add standard button box box = Frame(self) - # initialize buttons + # description_frame_gui buttons ok = Button( box, text="OK", width=10, command=self.ok_button, default=ACTIVE @@ -451,27 +451,6 @@ def capture_data(self): cand_data['PhoneNumber'] = self.text_phone.get() cand_data['CandidateStatus'] = self.text_status_var.get() - # Check that all required fields are set (a.k.a. 'Identifier', - # 'FirstName', 'LastName', 'Gender' and 'DateOfBirth'), if not, return - # an error. (Error message stored in - # MultiLanguage.dialog_missing_candidate_info variable) - if not cand_data['Identifier'] or not cand_data['FirstName'] \ - or not cand_data['LastName'] or not cand_data['Gender'] \ - or not cand_data['DateOfBirth']: - return MultiLanguage.dialog_missing_candidate_info - - # If candidate is new, check that the 'Identifier' used is unique - candIDs_array = DataManagement.grep_list_of_candidate_IDs() - if self.candidate == 'new' and cand_data['Identifier'] in candIDs_array: - print "Candidate Exists" - return MultiLanguage.dialog_candID_already_exists - - # If Date of Birth does not match YYYY-MM-DD, return an error - # (Error message is stored in MultiLanguage.dialog_bad_dob_format) - success = Utilities.check_date_format(cand_data['DateOfBirth']) - if not success: - return MultiLanguage.dialog_bad_dob_format - # Set CandidateStatus to space string if not defined in cand_data if not cand_data['CandidateStatus']: cand_data['CandidateStatus'] = " " @@ -480,5 +459,11 @@ def capture_data(self): if not cand_data['PhoneNumber']: cand_data['PhoneNumber'] = " " - # save data + # Check fields format and required fields + candidate = Candidate(cand_data) + message = candidate.check_candidate_data('scheduler', self.candidate) + if message: + return message + + # Save candidate data DataManagement.save_candidate_data(cand_data) diff --git a/dicat/welcome_frame.py b/dicat/welcome_frame.py index 86a6535..f8a8b3a 100644 --- a/dicat/welcome_frame.py +++ b/dicat/welcome_frame.py @@ -6,24 +6,34 @@ # import from DICAT libraries import lib.config as Config -from IDMapper import IDMapper_frame_gui - - -''' -lib.resource_path_methods has been created for Pyinstaller. -Need to load images or external files using these methods, otherwise the -created application would not find them. -''' -import lib.resource_path_methods as PathMethods +import lib.resource_path_methods as PathMethods # needed for PyInstaller builds class welcome_frame_gui(Frame): + """ + Welcome frame GUI class. + + """ def __init__(self, parent): + """ + Initialization of the welcome frame gui class. + + :param parent: parent widget in which to display the welcome frame + :type parent: object + + """ + self.parent = parent - self.initialize() - self.openDatabase() + self.description_frame_gui() + self.load_database_gui() + + + def description_frame_gui(self): + """ + Draws the description frame with the LOGO image. + + """ - def initialize(self): frame = Frame(self.parent) frame.pack(expand=1, fill='both') @@ -105,10 +115,13 @@ def initialize(self): text.config(state='disabled') - def openDatabase(self): + def load_database_gui(self): """ + Load database GUI including: + - a button to create a new database based on a template file + - a button to select and open an existing database + - an entry where the path to the loaded database file is displayed - :return: """ frame = Frame(self.parent, bd=5, relief='groove') @@ -139,13 +152,13 @@ def openDatabase(self): buttonOpen = Button( frame, text=u"Open an existing database", - command=self.open_database + command=self.open_existing_database ) buttonCreate = Button( frame, text=u"Create a new database", - command=self.create_database + command=self.create_new_database ) label.grid( @@ -162,7 +175,7 @@ def openDatabase(self): ) - def open_database(self): + def open_existing_database(self): """ Opens and loads the selected XML database in DICAT. @@ -183,12 +196,12 @@ def open_database(self): print Config.xmlfile - def create_database(self): + def create_new_database(self): """ Uses a template XML file to create a new database and saves it. """ - + self.filename = tkFileDialog.asksaveasfilename( defaultextension=[("*.xml")], filetypes=[("XML files", "*.xml")] From d07203d7c05e6e6ebff67e153a73241af9811c88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Thu, 28 Jul 2016 11:35:09 -0400 Subject: [PATCH 74/89] Forgot to add this file to the last commit --- dicat/lib/candidate.py | 91 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 74 insertions(+), 17 deletions(-) diff --git a/dicat/lib/candidate.py b/dicat/lib/candidate.py index 491e45c..024f596 100644 --- a/dicat/lib/candidate.py +++ b/dicat/lib/candidate.py @@ -1,15 +1,15 @@ # import standard packages import datetime -from dicat.scheduler_visit import Visit +#from dicat.scheduler_visit import Visit # import internal packages -import dicat.lib.datamanagement as DataManagement -import dicat.lib.multilanguage as MultiLanguage -import dicat.lib.utilities as Utilities +import lib.datamanagement as DataManagement +import lib.multilanguage as MultiLanguage +import lib.utilities as Utilities -class Candidate: +class Candidate(): """ The Candidate class defines the candidates/participants of the study @@ -31,18 +31,75 @@ class Candidate: candidatedb[candidatedata.uid] = candidatedata #add candidate to dict DataManagement.save_candidate_data(candidatedb) #save data to file """ - def __init__(self, firstname, lastname, phone, uid=None, visitset = None, status = None, pscid=None, **kwargs): #TODO *kwarg - self.uid = Utilities.generate_uid() - self.firstname = firstname - self.lastname = lastname - self.visitset = visitset - self.phone = phone - self.status = status - self.pscid = pscid - #...many other attributes - if kwargs is not None: - for key, value in kwargs.iteritems(): - setattr(self, key, value) + def __init__(self, cand_data): + # Required fields in cand_data + self.pscid = cand_data['Identifier'] + self.dob = cand_data['DateOfBirth'] + self.firstname = cand_data['FirstName'] + self.lastname = cand_data['LastName'] + + # Optional fields + if 'Gender' in cand_data: + self.gender = cand_data['Gender'] + else: + self.gender = " " + if 'PhoneNumber' in cand_data: + self.phone = cand_data['PhoneNumber'] + else: + self.phone = "" + if 'CandidateStatus' in cand_data: + self.cand_status = cand_data['CandidateStatus'] + else: + self.cand_status = " " + + #TODO check if VisitSet necessary here. Commenting it for now. + #self.visitset = cand_data['VisitSet'] + + + def check_candidate_data(self, tab, candidate=False): + """ + Check that the data entered in the data window for a given candidate is + as expected. If not, will return an error message that can be displayed. + + :param tab: IDMapper or Scheduler tab + :type tab: str + + :return: error message determined by the checks + :rtype: str + + """ + + # Check that all required fields are set (a.k.a. 'Identifier', + # 'FirstName', 'LastName', 'Gender' and 'DateOfBirth'), if not, return + # an error. (Error message stored in + # MultiLanguage.dialog_missing_candidate_info variable) + if not self.pscid or not self.firstname or not self.lastname \ + or not self.dob or (tab == 'scheduler' and self.gender == " "): + # If it is the scheduler capturing the data, gender must be set + if tab == 'scheduler' and self.gender == " ": + return MultiLanguage.dialog_missing_cand_info_schedul + return MultiLanguage.dialog_missing_cand_info_IDmapper + + # If candidate is new, check that the 'Identifier' used is unique + candIDs_array = DataManagement.grep_list_of_candidate_IDs() + # if candidate not populated with a candID, it means we are creating a + # new candidate so we need to check if the PSCID entered is unique. + if not candidate and self.pscid in candIDs_array: + return MultiLanguage.dialog_candID_already_exists + + # If Date of Birth does not match YYYY-MM-DD, return an error + # (Error message is stored in MultiLanguage.dialog_bad_dob_format) + date_ok = Utilities.check_date_format(self.dob) + if not date_ok: + return MultiLanguage.dialog_bad_dob_format + + # If we get there, it means all the data is good so no message needs + # to be displayed. Return False so that no message is displayed + return False + + + + def setup_visitset(self): """ From 08263ba9b7d2bb438aa40eb89f0797a55f053be2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Thu, 28 Jul 2016 11:53:04 -0400 Subject: [PATCH 75/89] Cleaned up datawindow.py --- dicat/ui/datawindow.py | 132 ++++++++++++++++++++++++----------------- 1 file changed, 79 insertions(+), 53 deletions(-) diff --git a/dicat/ui/datawindow.py b/dicat/ui/datawindow.py index 606de0b..4652244 100644 --- a/dicat/ui/datawindow.py +++ b/dicat/ui/datawindow.py @@ -90,15 +90,16 @@ def body(self, master): print "datawindow.body ", str(e) # TODO manage exceptions ## Candidate section - self.candidate_pane = Labelframe( self, - text=MultiLanguage.candidate_pane, - width=250, - height=350, - borderwidth=10 - ) - self.candidate_pane.pack( side=TOP, expand=YES, fill=BOTH, - padx=5, pady=5 - ) + self.candidate_pane = Labelframe( + self, + text=MultiLanguage.candidate_pane, + width=250, + height=350, + borderwidth=10 + ) + self.candidate_pane.pack( + side=TOP, expand=YES, fill=BOTH, padx=5, pady=5 + ) # Initialize text variables that will contain the field values self.text_pscid_var = StringVar() @@ -295,21 +296,20 @@ def body(self, master): visit_list.append(visitset[key]) # 2- Sort list on visit.rank - visit_list = sorted( visit_list, - key=lambda visit: visit["VisitStartWhen"] - ) + visit_list = sorted( + visit_list, key=lambda visit: visit["VisitStartWhen"] + ) # 3- 'print' values on ui x = 0 for x in range(len(visit_list)): # visitlabel - label_visit_label = Label( self.schedule_pane, - text=visit_list[x]["VisitLabel"] - ) - label_visit_label.grid( column=1, row=x+1, - padx=5, pady=5, - sticky=N+S+E+W - ) + label_visit_label = Label( + self.schedule_pane, text=visit_list[x]["VisitLabel"] + ) + label_visit_label.grid( + column=1, row=x+1, padx=5, pady=5, sticky=N+S+E+W + ) # when visit_when = "" if "VisitStartWhen" not in visit_list[x].keys(): @@ -320,59 +320,58 @@ def body(self, master): else: visit_when = visit_list[x]["VisitStartWhen"] label_visit_when = Label(self.schedule_pane, text=visit_when) - label_visit_when.grid( column=2, row=x+1, - padx=5, pady=5, - sticky=N+S+E+W - ) + label_visit_when.grid( + column=2, row=x+1, padx=5, pady=5, sticky=N+S+E+W + ) # where visit_where = "" if "VisitWhere" in visit_list[x].keys(): visit_where = visit_list[x]["VisitWhere"] label_visit_where = Label(self.schedule_pane, text=visit_where) - label_visit_where.grid( column=3, row=x+1, - padx=5, pady=5, - sticky=N+S+E+W - ) + label_visit_where.grid( + column=3, row=x+1, padx=5, pady=5, sticky=N+S+E+W + ) # withwhom visit_with_whom = "" if "VisitWithWhom" in visit_list[x].keys(): visit_with_whom = visit_list[x]["VisitWithWhom"] - label_visit_with_whom = Label( self.schedule_pane, - text=visit_with_whom - ) - label_visit_with_whom.grid( column=4, row=x+1, - padx=5, pady=5, - sticky=N+S+E+W - ) + label_visit_with_whom = Label( + self.schedule_pane, text=visit_with_whom + ) + label_visit_with_whom.grid( + column=4, row=x+1, padx=5, pady=5, sticky=N+S+E+W + ) # status visit_status = '' if "VisitStatus" in visit_list[x].keys(): visit_status = visit_list[x]["VisitStatus"] - label_visit_status = Label( self.schedule_pane, - text=visit_status - ) - label_visit_status.grid( column=5, row=x+1, - padx=5, pady=5, - sticky=N+S+E+W - ) + label_visit_status = Label( + self.schedule_pane, text=visit_status + ) + label_visit_status.grid( + column=5, row=x+1, padx=5, pady=5, sticky=N+S+E+W + ) def button_box(self): + """ + Draws the button box at the bottom of the data window. + + """ # add standard button box box = Frame(self) # description_frame_gui buttons - ok = Button( box, text="OK", - width=10, command=self.ok_button, - default=ACTIVE - ) - cancel = Button( box, text="Cancel", - width=10, command=self.cancel_button - ) + ok = Button( + box, text="OK", width=10, command=self.ok_button, default=ACTIVE + ) + cancel = Button( + box, text="Cancel", width=10, command=self.cancel_button + ) # draw the buttons ok.pack(side=LEFT, padx=5, pady=5) @@ -387,15 +386,23 @@ def button_box(self): def ok_button(self, event=None): + """ + Event handler for the OK button. If something was missing in the data + and it could not be saved, it will pop up an error message with the + appropriate error message. + + :param event: + :type event: + + :return: + + """ message = self.capture_data() if message: parent = Frame(self) - newwin = DialogBox.ErrorMessage( - parent, - message - ) + newwin = DialogBox.ErrorMessage(parent, message) if newwin.buttonvalue == 1: return # to stay on the candidate pop up page after clicking OK @@ -409,7 +416,18 @@ def ok_button(self, event=None): def cancel_button(self, event=None): - print "close without saving" + """ + Event handler for the cancel button. Will ask confirmation if want to + cancel, if yes put focus back to the datatable without saving, else put + focus back to the data window. + + :param event: + :type event: + + :return: + + """ + parent = Frame(self) newwin = DialogBox.ConfirmYesNo(parent, MultiLanguage.dialog_close) if newwin.buttonvalue == 1: @@ -419,6 +437,14 @@ def cancel_button(self, event=None): def closedialog(self, event=None): + """ + Close dialog handler: will put focus back to the parent window. + + :param event: + :return: + + """ + # put focus back to parent window before destroying the window self.parent.focus_set() self.destroy() From c7a51ac36917a3e586855b8b3579a9531c409b0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Thu, 28 Jul 2016 11:53:32 -0400 Subject: [PATCH 76/89] deleted: dicat/__init__.py --- dicat/__init__.py | 1 - 1 file changed, 1 deletion(-) delete mode 100644 dicat/__init__.py diff --git a/dicat/__init__.py b/dicat/__init__.py deleted file mode 100644 index 1e5dbd8..0000000 --- a/dicat/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__author__ = 'cmadjar' From f7a2e0bec8296c01c9b6ee90fae78bbef9e7fcd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Thu, 28 Jul 2016 12:57:36 -0400 Subject: [PATCH 77/89] Cleaned up dialogbox.py --- dicat/ui/dialogbox.py | 92 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 90 insertions(+), 2 deletions(-) diff --git a/dicat/ui/dialogbox.py b/dicat/ui/dialogbox.py index 53ee1bf..b5accc5 100644 --- a/dicat/ui/dialogbox.py +++ b/dicat/ui/dialogbox.py @@ -15,6 +15,22 @@ class DialogBox(Toplevel): """ def __init__(self,parent, title, message, button1, button2): + """ + Initialize the dialog box window. + + :param parent: parent window to the dialog window + :type parent: object + :param title: title to give to the dialog window + :type title: str + :param message: message to be displayed in the dialog window + :type message: str + :param button1: what should be written on button 1 of the dialog window + :type button1: str + :param button2: what should be written on button 2 of the dialog window + :type button2: str + + """ + Toplevel.__init__(self,parent) self.transient(parent) self.parent = parent @@ -36,12 +52,32 @@ def __init__(self,parent, title, message, button1, button2): def body(self, parent, message): + """ + Draw the body of the dialog box + + :param parent: parent window of the dialog box + :type parent: object + :param message: message to be drawn on the dialog box + :type message: str + + """ + + # Draw the message in the dialog box label = Label(self, text=message) label.pack(padx=4, pady=4) - pass def buttonbox(self, button1, button2): + """ + Draws the button box at the bottom of the dialog box. + + :param button1: button 1 of the button box + :type button1: str + :param button2: button 2 of the button box + :type button2: str + + """ + #add a standard button box box = Frame(self) b1 = Button( @@ -60,6 +96,14 @@ def buttonbox(self, button1, button2): def button1(self, event=None): + """ + Event handler for button1. + + :param event: + :type event: + + """ + if not self.validate(): self.initial_focus.focus_set() #put focus on Button return @@ -68,11 +112,26 @@ def button1(self, event=None): def button2(self, event=None): + """ + Event handler for button2. + + :param event: + :type event: + + """ self.buttonvalue = 2 self.closedialog() def closedialog(self, event=None): + """ + Event handler to close the dialog box. + + :param event: + :type event: + + """ + #put focus back to parent window before destroying the window self.parent.focus_set() self.destroy() @@ -85,7 +144,21 @@ def validate(self): class ConfirmYesNo(DialogBox): + """ + Confirmation on closing a window class -> Yes or No. + + """ + def __init__(self, parent, message): + """ + Initialization of the confirmation window class. + + :param parent: parent of the confirmation window + :type parent: object + :param message: message to print in the confirmation window. + :type message: str + + """ title = MultiLanguage.dialog_title_confirm button1 = MultiLanguage.dialog_yes button2 = MultiLanguage.dialog_no @@ -93,9 +166,24 @@ def __init__(self, parent, message): - + class ErrorMessage(DialogBox): + """ + Error Message pop up window. + + """ + def __init__(self, parent, message): + """ + Initialization of the error message window. + + :param parent: parent of the error message window to be displayed + :type parent: object + :param message: message to be displayed on the error window. + :type message: str + + """ + title = MultiLanguage.dialog_title_error button = MultiLanguage.dialog_ok DialogBox.__init__(self, parent, title, message, button, None) From 1a1f218b1c4ab3b22ffbc1c3afd0385c7d970a9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Thu, 28 Jul 2016 14:23:58 -0400 Subject: [PATCH 78/89] Added filtering functionality to IDMapper and renamed IDMapper functions to conventional lower_case_separated_by_underscores. --- dicat/DicAT_application.py | 2 +- dicat/IDMapper.py | 184 ++++++++++++++++++++------------ dicat/dicom_anonymizer_frame.py | 2 +- dicat/lib/candidate.py | 4 +- dicat/lib/multilanguage.py | 18 ++-- dicat/ui/datatable.py | 2 +- dicat/welcome_frame.py | 2 +- 7 files changed, 132 insertions(+), 82 deletions(-) diff --git a/dicat/DicAT_application.py b/dicat/DicAT_application.py index 691dbad..ee95978 100644 --- a/dicat/DicAT_application.py +++ b/dicat/DicAT_application.py @@ -132,7 +132,7 @@ def update_IDMapper(self, event): """ # reload the IDMapper - self.IDMapper.LoadXML() + self.IDMapper.load_xml() if __name__ == "__main__": diff --git a/dicat/IDMapper.py b/dicat/IDMapper.py index 6a17ac0..c63a599 100644 --- a/dicat/IDMapper.py +++ b/dicat/IDMapper.py @@ -2,9 +2,11 @@ from Tkinter import * import ttk +import re import lib.datamanagement as DataManagement from lib.candidate import Candidate +import lib.multilanguage as MultiLanguage def sortby(tree, col, descending): @@ -42,15 +44,15 @@ def __init__(self, parent): def initialize(self): """ - Initialize the ID Mapper GUI by calling self.InitUI(). + Initialize the ID Mapper GUI by calling self.init_ui(). """ # Initialize GUI - self.InitUI() + self.init_ui() - def InitUI(self): + def init_ui(self): """ Draws the ID Mapper GUI. @@ -77,16 +79,19 @@ def InitUI(self): self.frame, width=12, text=u'Add candidate', - command=self.AddIdentifierEvent + command=self.add_identifier_event ) self.buttonClear = Button( - self.frame, width=12, text=u'Clear fields', command=self.clear + self.frame, + width=12, + text=u'Clear fields and filters', + command=self.clear_event ) self.buttonSearch = Button( - self.frame, width=12, text=u'Search candidate', command=self.search + self.frame, width=12, text=u'Search candidate', command=self.search_event ) self.buttonEdit = Button( - self.frame, width=12, text=u'Edit candidate', command=self.edit + self.frame, width=12, text=u'Edit candidate', command=self.edit_search ) self.textCandId = StringVar() @@ -126,7 +131,7 @@ def InitUI(self): command=lambda c=col: sortby(self.datatable, c, 0) ) - self.datatable.bind("<>", self.OnRowClick) + self.datatable.bind("<>", self.on_row_click_event) self.ErrorMessage = StringVar() self.error = Label(self.frame, textvariable=self.ErrorMessage, fg='red') @@ -160,7 +165,7 @@ def InitUI(self): self.error.grid(row=4, column=0, columnspan=4) - def LoadXML(self): + def load_xml(self): """ Parses the XML file and loads the data into the IDMapper. Calls check_and_save_data with option action=False as we don't want to @@ -189,7 +194,7 @@ def LoadXML(self): print str(e) - def AddIdentifierEvent(self): + def add_identifier_event(self): """ Event handler for the 'Add new candidate' button. Will call check_and_save_data on what has been entered in the Entry boxes. @@ -203,7 +208,7 @@ def AddIdentifierEvent(self): self.check_and_save_data(candid, firstname, lastname, dob, 'save') - def OnRowClick(self, event): + def on_row_click_event(self, event): """ Update the text boxes' data on row click. @@ -218,9 +223,9 @@ def OnRowClick(self, event): self.textCandDoB.set(item[3]) - def clear(self): + def clear_event(self): """ - Event handler for the clear button. Will clear all the Entry boxes. + Event handler for the clear_event button. Will clear_event all the Entry boxes. """ @@ -230,48 +235,52 @@ def clear(self): self.textCandDoB.set("") self.candidateid.focus_set() + self.load_xml() # Reload the entire dataset. + - def search(self): + def search_event(self): """ - Event handler for the search button. Will call FindCandidate function + Event handler for the search_event button. Will call find_candidate function to find the proper candidate matching what has been filled in the Entry boxes. """ - # Find a candidate based on its ID if it is set in text box + # Grep the data from the Entry fields + data_captured = {} if self.textCandId.get(): - (candid, firstname, lastname, dob) = self.FindCandidate( - "candid", self.textCandId.get() - ) - # or based on its name if it is set in text box - elif self.textCandFirstName.get(): - (candid, firstname, lastname, dob) = self.FindCandidate( - "firstname", self.textCandFirstName.get() - ) - elif self.textCandLastName.get(): - (candid, firstname, lastname, dob) = self.FindCandidate( - "lastname", self.textCandLastName.get() - ) + data_captured['Identifier'] = self.textCandId.get() + if self.textCandFirstName.get(): + data_captured['FirstName'] = self.textCandFirstName.get() + if self.textCandLastName.get(): + data_captured['LastName'] = self.textCandLastName.get() + if self.textCandDoB.get(): + data_captured['DateOfBirth'] = self.textCandDoB.get() + + # If no data entered, write a message saying at least one field should + # be entered and return + if not data_captured: + message = MultiLanguage.dialog_no_data_entered + self.ErrorMessage.set(message) + return + + # Use the function find_candidate to find all matching candidates and + # return them in the filtered data dictionary + filtered_data = self.find_candidate(data_captured) - # print the values in the text box - self.textCandId.set(candid) - self.textCandFirstName.set(firstname) - self.textCandLastName.set(lastname) - self.textCandDoB.set(dob) + # Display only the filtered data using DisplayCandidates(filtered_data) + self.display_filtered_data(filtered_data) - def FindCandidate(self, key, value): + def find_candidate(self, data_captured): """ Find a candidate based on one of the fields entered in the Entry boxes. - :param key: the name of the Entry type (a.k.a. 'candid', 'firstname'...) - :type key: str - :param value: the value stored in the Entry box (a.k.a. 'MTL0001' ... ) - :type value: str + :param data_captured: dictionary of the data captured in the Entry boxes + :type data_captured: dict - :return candid, firstname, lastname, dob: found candidate - :rtype candid, firstname, lastname, dob: str + :return filtered_data: dictionary of the matching candidates + :rtype filtered_data: dict """ @@ -279,29 +288,66 @@ def FindCandidate(self, key, value): # Loop through the candidate tree and return the candid, name and dob # that matches a given value + # Create a filtered_data dictionary that will store all matching candidates + filtered_data = {} + # Create an 'add' boolean on whether should add a candidate to the + # filtered list and set it to False + add = False for cand_key in data: + # Grep the candidate information from data candid = data[cand_key]["Identifier"] firstname = data[cand_key]["FirstName"] lastname = data[cand_key]["LastName"] dob = data[cand_key]["DateOfBirth"] - if (key == "candid" and value == candid): - return (candid, firstname, lastname, dob) - elif (key == "firstname" and value == firstname): - return (candid, firstname, lastname, dob) - elif (key == "lastname" and value == lastname): - return (candid, firstname, lastname, dob) - elif (key == "dob" and value == dob): - return (candid, firstname, lastname, dob) - else: - continue - # if candidate was not found, return empty strings - return ("", "", "") - - - def edit(self): + if 'DateOfBirth' in data_captured \ + and re.match(data_captured['DateOfBirth'], dob): + add = True # set the 'add' boolean to true to add candidate + if 'FirstName' in data_captured \ + and re.match(data_captured['FirstName'], firstname): + add = True # set the 'add' boolean to true to add candidate + if 'LastName' in data_captured \ + and re.match(data_captured['LastName'], lastname): + add = True # set the 'add' boolean to true to add candidate + if 'Identifier' in data_captured \ + and re.match(data_captured['Identifier'], candid): + add = True # set the 'add' boolean to true to add candidate + + # If add is set to True, add the candidate to the filtered list. + if add: + filtered_data[cand_key] = data[cand_key] + add = False # reset the 'add' boolean to false for next cand_key + + return filtered_data + + + def display_filtered_data(self, filtered_data): + """ + Displays only the filtered data matching the search_event. + + :param filtered_data: dictionary of the matching candidates + :type filtered_data: dict + + """ + + # Empty the datatable and data dictionary before loading filtered data + self.datatable.delete(*self.datatable.get_children()) + self.IDMap = {} + + # Loop through the data and display them in the datatable + for key in filtered_data: + identifier = filtered_data[key]["Identifier"] + firstname = filtered_data[key]["FirstName"] + lastname = filtered_data[key]["LastName"] + dob = filtered_data[key]["DateOfBirth"] + self.check_and_save_data( + identifier, firstname, lastname, dob, False + ) + + + def edit_search(self): """ Edit event of the Edit button. Will call check_and_save_date with - data entered in the Entry boxes and action='edit'. + data entered in the Entry boxes and action='edit_search'. """ @@ -310,24 +356,24 @@ def edit(self): self.textCandFirstName.get(), self.textCandLastName.get(), self.textCandDoB.get(), - 'edit' + 'edit_search' ) - def check_and_save_data(self, candid, firstname, lastname, dob, action=False): + def check_and_save_data(self, id, firstname, lastname, dob, action=False): """ Grep the candidate data and check them before saving and updating the XML file and the datatale. - :param candid: identifier of the candidate - :type candid: str + :param id: identifier of the candidate + :type id: str :param firstname: firstname of the candidate :type firstname: str :param lastname: lastname of the candidate :type lastname: str :param dob: date of birth of the candidate :type dob: str - :param action: whether to 'save' a new candidate or 'edit' a candidate + :param action: whether to 'save' a new candidate or 'edit_search' a candidate :type action: bool/str :return: @@ -336,7 +382,7 @@ def check_and_save_data(self, candid, firstname, lastname, dob, action=False): # Check all required data are available cand_data = {} - cand_data["Identifier"] = candid + cand_data["Identifier"] = id cand_data["FirstName"] = firstname cand_data["LastName"] = lastname cand_data["DateOfBirth"] = dob @@ -347,8 +393,8 @@ def check_and_save_data(self, candid, firstname, lastname, dob, action=False): message = False if action == 'save': message = candidate.check_candidate_data('IDmapper', False) - elif action == 'edit': - message = candidate.check_candidate_data('IDmapper', candid) + elif action == 'edit_search': + message = candidate.check_candidate_data('IDmapper', id) # If message contains an error message, display it and return if message: @@ -359,16 +405,16 @@ def check_and_save_data(self, candid, firstname, lastname, dob, action=False): DataManagement.save_candidate_data(cand_data) # Update the IDMap dictionary - mapList = [candid, firstname, lastname, dob] - self.IDMap[candid] = mapList + mapList = [id, firstname, lastname, dob] + self.IDMap[id] = mapList # Update datatable - if action == 'edit': + if action == 'edit_search': item = self.datatable.selection() - updatedList = (candid, firstname, lastname, dob) + updatedList = (id, firstname, lastname, dob) self.datatable.item(item, values=updatedList) else: - insertedList = [(candid, firstname, lastname, dob)] + insertedList = [(id, firstname, lastname, dob)] for item in insertedList: self.datatable.insert('', 'end', values=item) diff --git a/dicat/dicom_anonymizer_frame.py b/dicat/dicom_anonymizer_frame.py index b2bb820..cf15b94 100755 --- a/dicat/dicom_anonymizer_frame.py +++ b/dicat/dicom_anonymizer_frame.py @@ -103,7 +103,7 @@ def askdirectory(self): def deidentify(self): - # clear edit table if it exists + # clear_event edit_search table if it exists if hasattr(self, 'field_edit_win'): self.field_edit_win.destroy() diff --git a/dicat/lib/candidate.py b/dicat/lib/candidate.py index 024f596..ffe1a8b 100644 --- a/dicat/lib/candidate.py +++ b/dicat/lib/candidate.py @@ -175,7 +175,7 @@ def set_visit_date(self, visitlabel, visitdate, visittime, visitwhere, visitwhom """ def set_next_visit_window(self, candidate, current_visit): - #get the current visit object as argument. Will search and look for the next visit (visit where previousvisit == current_visit_label) + #get the current visit object as argument. Will search_event and look for the next visit (visit where previousvisit == current_visit_label) next_visit_searchset = candidate.visitset current_visit_label = current_visit.visitlabel next_visit = "" @@ -208,7 +208,7 @@ def set_next_visit_window(self, candidate, current_visit): """ - #get the current visit object as argument. Will search and look for the next visit (visit where previousvisit == current_visitlabel) + #get the current visit object as argument. Will search_event and look for the next visit (visit where previousvisit == current_visitlabel) #1- Get Candidate.visitset and current_visit / next_visit will == Visit(VisitSetup) of the next visit (relative to current_visit) visit_searchset = candidate.visitset diff --git a/dicat/lib/multilanguage.py b/dicat/lib/multilanguage.py index 60e9cab..9b8d9a7 100644 --- a/dicat/lib/multilanguage.py +++ b/dicat/lib/multilanguage.py @@ -104,10 +104,12 @@ dialog_ok = u"OK" dialog_close = u"Vous êtes sur le point de fermer cette fenêtre sans " \ u"sauvegarder!\n\nVoulez-vous continuer?" - dialog_title_confirm = u"Veuillez confirmer!" - dialog_title_error = u"Erreur" - dialog_bad_dob_format = u"La date de naissance doit être formatté en " \ - u"AAAA-MM-JJ!" + dialog_title_confirm = u"Veuillez confirmer!" + dialog_title_error = u"Erreur" + dialog_bad_dob_format = u"La date de naissance doit être formatté en " \ + u"AAAA-MM-JJ!" + dialog_no_data_entered = u"Au moins un des champs doit être entré pour " \ + u"chercher un candidat." dialog_candID_already_exists = u"L'identifiant existe déjà!" dialog_missing_cand_info_schedul = u"Les champs 'Identifiant', 'Prénom', " \ u"'Nom de famille', 'Sexe' et " \ @@ -225,9 +227,11 @@ dialog_ok = u"OK" dialog_close = u"You are about to close this window without saving!\n\n" \ u"Do you want to continue?" - dialog_title_confirm = u"Please confirm!" - dialog_title_error = u"Error" - dialog_bad_dob_format = u"Date of Birth should be in YYYY-MM-DD format!" + dialog_title_confirm = u"Please confirm!" + dialog_title_error = u"Error" + dialog_bad_dob_format = u"Date of Birth should be in YYYY-MM-DD format!" + dialog_no_data_entered = u"At least one of the fields needs to be entered " \ + u"to search_event a candidate." dialog_candID_already_exists = u"Identifier already exists!" dialog_missing_cand_info_schedul = u"'Identifier', 'Firstname', " \ u"'Lastname', 'Date of Birth' and " \ diff --git a/dicat/ui/datatable.py b/dicat/ui/datatable.py index aff3e92..dd82862 100644 --- a/dicat/ui/datatable.py +++ b/dicat/ui/datatable.py @@ -296,7 +296,7 @@ def load_data(self): # Loop through candidates for cand_key, value in visit_data.iteritems(): - # Skip the search if visitset == None for that candidate + # Skip the search_event if visitset == None for that candidate if "VisitSet" in visit_data[cand_key].keys(): # set this candidate.visitset for the next step diff --git a/dicat/welcome_frame.py b/dicat/welcome_frame.py index f8a8b3a..2fe9309 100644 --- a/dicat/welcome_frame.py +++ b/dicat/welcome_frame.py @@ -111,7 +111,7 @@ def description_frame_gui(self): # Display the text variable text.pack(side='left', fill='both', expand=1) scroll.pack(side="right", fill='y') - # Disable the edit functionality of the displayed text + # Disable the edit_search functionality of the displayed text text.config(state='disabled') From 19fea5681017bdd96b3442171583ff46b103302f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Thu, 28 Jul 2016 14:26:06 -0400 Subject: [PATCH 79/89] Renamed function names of datamanagement.py to fit python convention function_naming --- dicat/lib/datamanagement.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dicat/lib/datamanagement.py b/dicat/lib/datamanagement.py index 1287b3e..d37fa84 100644 --- a/dicat/lib/datamanagement.py +++ b/dicat/lib/datamanagement.py @@ -96,7 +96,7 @@ def read_candidate_data(): return data -def grep_list_of_candidate_IDs(): +def grep_list_of_candidate_ids(): """ Read the XML file and grep all candidate IDs into an array candIDs_array. From d9104569462e59ad53ad2d70a366d6a4d50c753c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Thu, 28 Jul 2016 14:32:29 -0400 Subject: [PATCH 80/89] Renamed functions in following files to match Python convention. modified: dicat/DicAT_application.py modified: dicat/scheduler_application.py modified: dicat/ui/datawindow.py --- dicat/DicAT_application.py | 6 +++--- dicat/scheduler_application.py | 2 +- dicat/ui/datawindow.py | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/dicat/DicAT_application.py b/dicat/DicAT_application.py index ee95978..080316b 100644 --- a/dicat/DicAT_application.py +++ b/dicat/DicAT_application.py @@ -66,7 +66,7 @@ def __init__(self, master, side=LEFT): # refresh tab when selecting it self.page3.bind("", self.update_scheduler) - self.page4.bind("", self.update_IDMapper) + self.page4.bind("", self.update_id_mapper) def dicom_deidentifier_tab(self): """ @@ -120,9 +120,9 @@ def update_scheduler(self, event): """ # reload the scheduler - self.scheduler.LoadXML() + self.scheduler.load_xml() - def update_IDMapper(self, event): + def update_id_mapper(self, event): """ Reload the IDMapper table when the tab is selected diff --git a/dicat/scheduler_application.py b/dicat/scheduler_application.py index 081111c..a09e2fa 100644 --- a/dicat/scheduler_application.py +++ b/dicat/scheduler_application.py @@ -151,7 +151,7 @@ def add_candidate(self): # Update the candidate datatable when save the new candidate self.cand_table.update_data() - def LoadXML(self): + def load_xml(self): """ Update candidate and calendar/visit datatables with data extracted from XML file stored in Config.xmlfile. diff --git a/dicat/ui/datawindow.py b/dicat/ui/datawindow.py index 4652244..f530055 100644 --- a/dicat/ui/datawindow.py +++ b/dicat/ui/datawindow.py @@ -47,7 +47,7 @@ def __init__(self, parent, candidate=False): self.grab_set() if not self.initial_focus: self.initial_focus = self - self.protocol("WM_DELETE_WINDOW", self.closedialog) + self.protocol("WM_DELETE_WINDOW", self.close_dialog) Utilities.center_window(self) self.initial_focus.focus_set() self.wait_window(self) @@ -379,7 +379,7 @@ def button_box(self): # bind key handlers to button functions self.bind("", self.ok_button) - self.bind("", self.closedialog) + self.bind("", self.close_dialog) # draw the button box box.pack() @@ -412,7 +412,7 @@ def ok_button(self, event=None): #need to call treeview update here self.withdraw() - self.closedialog() + self.close_dialog() def cancel_button(self, event=None): @@ -431,12 +431,12 @@ def cancel_button(self, event=None): parent = Frame(self) newwin = DialogBox.ConfirmYesNo(parent, MultiLanguage.dialog_close) if newwin.buttonvalue == 1: - self.closedialog() + self.close_dialog() else: return - def closedialog(self, event=None): + def close_dialog(self, event=None): """ Close dialog handler: will put focus back to the parent window. From 059b9857224f26ed8a7e0bdd704955a47c88845f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Thu, 28 Jul 2016 14:41:21 -0400 Subject: [PATCH 81/89] Finalized renaming function names to Python convention. --- dicat/IDMapper.py | 34 ++++++++++++++++++++------------- dicat/dicom_anonymizer_frame.py | 2 +- dicat/lib/candidate.py | 2 +- dicat/welcome_frame.py | 2 +- 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/dicat/IDMapper.py b/dicat/IDMapper.py index c63a599..73438ff 100644 --- a/dicat/IDMapper.py +++ b/dicat/IDMapper.py @@ -88,10 +88,16 @@ def init_ui(self): command=self.clear_event ) self.buttonSearch = Button( - self.frame, width=12, text=u'Search candidate', command=self.search_event + self.frame, + width=12, + text=u'Search candidate', + command=self.search_event ) self.buttonEdit = Button( - self.frame, width=12, text=u'Edit candidate', command=self.edit_search + self.frame, + width=12, + text=u'Edit candidate', + command=self.edit_event ) self.textCandId = StringVar() @@ -225,7 +231,8 @@ def on_row_click_event(self, event): def clear_event(self): """ - Event handler for the clear_event button. Will clear_event all the Entry boxes. + Event handler for the clear_event button. Will clear_event all the + Entry boxes. """ @@ -240,9 +247,9 @@ def clear_event(self): def search_event(self): """ - Event handler for the search_event button. Will call find_candidate function - to find the proper candidate matching what has been filled in the Entry - boxes. + Event handler for the search_event button. Will call find_candidate + function to find the proper candidate matching what has been filled in + the Entry boxes. """ @@ -288,7 +295,8 @@ def find_candidate(self, data_captured): # Loop through the candidate tree and return the candid, name and dob # that matches a given value - # Create a filtered_data dictionary that will store all matching candidates + # Create a filtered_data dictionary that will store all matching + # candidates filtered_data = {} # Create an 'add' boolean on whether should add a candidate to the # filtered list and set it to False @@ -344,10 +352,10 @@ def display_filtered_data(self, filtered_data): ) - def edit_search(self): + def edit_event(self): """ Edit event of the Edit button. Will call check_and_save_date with - data entered in the Entry boxes and action='edit_search'. + data entered in the Entry boxes and action='edit_event'. """ @@ -356,7 +364,7 @@ def edit_search(self): self.textCandFirstName.get(), self.textCandLastName.get(), self.textCandDoB.get(), - 'edit_search' + 'edit' ) @@ -373,7 +381,7 @@ def check_and_save_data(self, id, firstname, lastname, dob, action=False): :type lastname: str :param dob: date of birth of the candidate :type dob: str - :param action: whether to 'save' a new candidate or 'edit_search' a candidate + :param action: either 'save' new candidate or 'edit' a candidate :type action: bool/str :return: @@ -393,7 +401,7 @@ def check_and_save_data(self, id, firstname, lastname, dob, action=False): message = False if action == 'save': message = candidate.check_candidate_data('IDmapper', False) - elif action == 'edit_search': + elif action == 'edit': message = candidate.check_candidate_data('IDmapper', id) # If message contains an error message, display it and return @@ -409,7 +417,7 @@ def check_and_save_data(self, id, firstname, lastname, dob, action=False): self.IDMap[id] = mapList # Update datatable - if action == 'edit_search': + if action == 'edit': item = self.datatable.selection() updatedList = (id, firstname, lastname, dob) self.datatable.item(item, values=updatedList) diff --git a/dicat/dicom_anonymizer_frame.py b/dicat/dicom_anonymizer_frame.py index cf15b94..803cc0e 100755 --- a/dicat/dicom_anonymizer_frame.py +++ b/dicat/dicom_anonymizer_frame.py @@ -103,7 +103,7 @@ def askdirectory(self): def deidentify(self): - # clear_event edit_search table if it exists + # clear_event edit_event table if it exists if hasattr(self, 'field_edit_win'): self.field_edit_win.destroy() diff --git a/dicat/lib/candidate.py b/dicat/lib/candidate.py index ffe1a8b..9bde764 100644 --- a/dicat/lib/candidate.py +++ b/dicat/lib/candidate.py @@ -81,7 +81,7 @@ def check_candidate_data(self, tab, candidate=False): return MultiLanguage.dialog_missing_cand_info_IDmapper # If candidate is new, check that the 'Identifier' used is unique - candIDs_array = DataManagement.grep_list_of_candidate_IDs() + candIDs_array = DataManagement.grep_list_of_candidate_ids() # if candidate not populated with a candID, it means we are creating a # new candidate so we need to check if the PSCID entered is unique. if not candidate and self.pscid in candIDs_array: diff --git a/dicat/welcome_frame.py b/dicat/welcome_frame.py index 2fe9309..1820188 100644 --- a/dicat/welcome_frame.py +++ b/dicat/welcome_frame.py @@ -111,7 +111,7 @@ def description_frame_gui(self): # Display the text variable text.pack(side='left', fill='both', expand=1) scroll.pack(side="right", fill='y') - # Disable the edit_search functionality of the displayed text + # Disable the edit_event functionality of the displayed text text.config(state='disabled') From 4053fa674ce4f75834fb4b458b93a2314e3a5e3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Thu, 28 Jul 2016 15:26:52 -0400 Subject: [PATCH 82/89] Show a warning at the bottom of the ID mapper when filters are in function --- dicat/IDMapper.py | 4 ++++ dicat/lib/multilanguage.py | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/dicat/IDMapper.py b/dicat/IDMapper.py index 73438ff..5c8153a 100644 --- a/dicat/IDMapper.py +++ b/dicat/IDMapper.py @@ -278,6 +278,10 @@ def search_event(self): # Display only the filtered data using DisplayCandidates(filtered_data) self.display_filtered_data(filtered_data) + # Show a message warning to say that filters are set + message = MultiLanguage.warning_filters_set + self.ErrorMessage.set(message) + def find_candidate(self, data_captured): """ diff --git a/dicat/lib/multilanguage.py b/dicat/lib/multilanguage.py index 9b8d9a7..dbc0716 100644 --- a/dicat/lib/multilanguage.py +++ b/dicat/lib/multilanguage.py @@ -110,6 +110,9 @@ u"AAAA-MM-JJ!" dialog_no_data_entered = u"Au moins un des champs doit être entré pour " \ u"chercher un candidat." + warning_filters_set = u"ATTENTION: des filtres sont en fonction. " \ + u"Seuls les candidats correspondant aux filtres " \ + u"sont montrés" dialog_candID_already_exists = u"L'identifiant existe déjà!" dialog_missing_cand_info_schedul = u"Les champs 'Identifiant', 'Prénom', " \ u"'Nom de famille', 'Sexe' et " \ @@ -232,6 +235,8 @@ dialog_bad_dob_format = u"Date of Birth should be in YYYY-MM-DD format!" dialog_no_data_entered = u"At least one of the fields needs to be entered " \ u"to search_event a candidate." + warning_filters_set = u"WARNING: filters are set. Only matching " \ + u"candidates are shown." dialog_candID_already_exists = u"Identifier already exists!" dialog_missing_cand_info_schedul = u"'Identifier', 'Firstname', " \ u"'Lastname', 'Date of Birth' and " \ From 885f3fd9263a2d908386c64e5aba4f315f2c82aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Thu, 28 Jul 2016 16:35:22 -0400 Subject: [PATCH 83/89] Set the flag for new candidates back to 'new' and added a 'search' flag to open the data window and search for a candidate. --- dicat/lib/candidate.py | 4 +-- dicat/lib/multilanguage.py | 4 +-- dicat/scheduler_application.py | 51 ++++++++++++++++++++++++++-------- dicat/ui/datawindow.py | 20 ++++++------- dicat/ui/menubar.py | 2 +- 5 files changed, 55 insertions(+), 26 deletions(-) diff --git a/dicat/lib/candidate.py b/dicat/lib/candidate.py index 9bde764..b164f07 100644 --- a/dicat/lib/candidate.py +++ b/dicat/lib/candidate.py @@ -56,7 +56,7 @@ def __init__(self, cand_data): #self.visitset = cand_data['VisitSet'] - def check_candidate_data(self, tab, candidate=False): + def check_candidate_data(self, tab, candidate=''): """ Check that the data entered in the data window for a given candidate is as expected. If not, will return an error message that can be displayed. @@ -84,7 +84,7 @@ def check_candidate_data(self, tab, candidate=False): candIDs_array = DataManagement.grep_list_of_candidate_ids() # if candidate not populated with a candID, it means we are creating a # new candidate so we need to check if the PSCID entered is unique. - if not candidate and self.pscid in candIDs_array: + if candidate == 'new' and self.pscid in candIDs_array: return MultiLanguage.dialog_candID_already_exists # If Date of Birth does not match YYYY-MM-DD, return an error diff --git a/dicat/lib/multilanguage.py b/dicat/lib/multilanguage.py index dbc0716..dd8fb92 100644 --- a/dicat/lib/multilanguage.py +++ b/dicat/lib/multilanguage.py @@ -22,7 +22,7 @@ #CANDIDATE menu candidate_menu = u"Candidat" candidate_add = u"Nouveau candidat" - candidate_find = u"Trouver candidat" + candidate_search = u"Trouver candidat" candidate_update = u"Mettre à jour" candidate_get_id = u"Obtenir l'identifiant d'un candidat" candidate_exclude_include_toggle = u"Inclure/Exclure un candidat" @@ -151,7 +151,7 @@ #CANDIDATE menu candidate_menu = u"Candidate" candidate_add = u"New candidate" - candidate_find = u"Find a candidate" + candidate_search = u"Search candidate" candidate_update = u"Update" candidate_get_id = u"Get a canditate ID" candidate_exclude_include_toggle = u"Include/Exclude a candidate" diff --git a/dicat/scheduler_application.py b/dicat/scheduler_application.py index a09e2fa..a205c07 100644 --- a/dicat/scheduler_application.py +++ b/dicat/scheduler_application.py @@ -3,11 +3,11 @@ #import standard packages from Tkinter import * from ttk import * + #import internal packages import ui.datatable as DataTable import ui.datawindow as DataWindow import lib.multilanguage as MultiLanguage -import lib.config as Config class UserInterface(Frame): @@ -77,33 +77,49 @@ def initialize(self): self.data_pane.add(self.candidate_pane) self.data_pane.add(self.visit_pane) - ## Plot the button action in the pane frame + ## Plot the button actions in the candidate pane frame + + # Create a frame that will contain the buttons + self.buttonBox = Frame(self.candidate_pane) + self.buttonBox.pack(side=TOP, anchor=W) - # Candidate pane frame buttons + # Create a 'new candidate' button to be added to self.buttonBox self.buttonNewCandidate = Button( - self.candidate_pane, - width=12, + self.buttonBox, + width=15, text=MultiLanguage.candidate_add, command=self.add_candidate ) - self.buttonNewCandidate.pack(side=TOP, anchor=W) + self.buttonSearchCandidate = Button( + self.buttonBox, + width=15, + text=MultiLanguage.candidate_search, + command=self.search_candidate + ) + # Draw the buttons + self.buttonNewCandidate.grid( + row=0, column=0, padx=(5,0), pady=(0,5), sticky=E+W + ) + self.buttonSearchCandidate.grid( + row=0, column=1, padx=(5,0), pady=(0,5), sticky=E+W + ) ## Create data tables (using Treeview) # Candidate datatable - candidate_column_headers = ( + candidate_column_headers = [ 'identifier', 'firstname', 'lastname', 'date of birth', 'gender', 'phone', 'status' - ) + ] self.cand_table = DataTable.ParticipantsList( self.candidate_pane, candidate_column_headers ) self.cand_table.pack(side=BOTTOM, expand=YES, fill=BOTH) # Calendar datatable - visit_column_headers = ( + visit_column_headers = [ 'identifier', 'candidate', 'visitlabel', 'when', 'where', 'status' - ) + ] self.visit_table = DataTable.VisitList( self.visit_pane, visit_column_headers ) @@ -146,11 +162,24 @@ def add_candidate(self): # Open the datawindow with candidate=False as no existing ID associated # yet for the new candidate - DataWindow.DataWindow(self, False) + DataWindow.DataWindow(self, 'new') # Update the candidate datatable when save the new candidate self.cand_table.update_data() + def search_candidate(self): + """ + This function will allow functionality to search for candidates using + the same data window as when editing a subject. + + """ + + # Open the datawindow with candidate=False as want to fill in the + # options to search for a candidate + DataWindow.DataWindow(self, 'search') + + # + def load_xml(self): """ Update candidate and calendar/visit datatables with data extracted from diff --git a/dicat/ui/datawindow.py b/dicat/ui/datawindow.py index f530055..fb2c89b 100644 --- a/dicat/ui/datawindow.py +++ b/dicat/ui/datawindow.py @@ -16,7 +16,7 @@ class DataWindow(Toplevel): - def __init__(self, parent, candidate=False): + def __init__(self, parent, candidate=''): """ Initialize the DataWindow class. @@ -112,15 +112,7 @@ def body(self, master): # If candidate is populated with candID populate the fields with values # available in cand_info dictionary, otherwise populate with empty str - if self.candidate: - self.text_pscid_var.set(cand_info["Identifier"]) - self.text_firstname_var.set(cand_info["FirstName"]) - self.text_lastname_var.set(cand_info["LastName"]) - self.text_dob_var.set(cand_info["DateOfBirth"]) - self.text_gender_var.set(cand_info["Gender"]) - self.text_status_var.set(cand_info["CandidateStatus"]) - self.text_phone_var.set(cand_info["PhoneNumber"]) - else: + if self.candidate == 'new' or self.candidate == 'search': self.text_pscid_var.set("") self.text_firstname_var.set("") self.text_lastname_var.set("") @@ -128,6 +120,14 @@ def body(self, master): self.text_gender_var.set(" ") self.text_status_var.set(" ") self.text_phone_var.set("") + else: + self.text_pscid_var.set(cand_info["Identifier"]) + self.text_firstname_var.set(cand_info["FirstName"]) + self.text_lastname_var.set(cand_info["LastName"]) + self.text_dob_var.set(cand_info["DateOfBirth"]) + self.text_gender_var.set(cand_info["Gender"]) + self.text_status_var.set(cand_info["CandidateStatus"]) + self.text_phone_var.set(cand_info["PhoneNumber"]) # Create widgets to be displayed # (typically a label with a text box underneath per variable to display) diff --git a/dicat/ui/menubar.py b/dicat/ui/menubar.py index 8d9b49c..5b40279 100644 --- a/dicat/ui/menubar.py +++ b/dicat/ui/menubar.py @@ -46,7 +46,7 @@ def __init__(self, parent): label=MultiLanguage.candidate_add, command=self.add_candidate ) candidate_menu.add_command( - label=MultiLanguage.candidate_find, command=self.find_candidate + label=MultiLanguage.candidate_search, command=self.find_candidate ) # Create a CALENDAR pulldown menu From 73c8440b70678c57fb365ae4607ffbe65f366f0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Thu, 28 Jul 2016 18:22:26 -0400 Subject: [PATCH 84/89] Restructuration of datawindow.py --- dicat/ui/datawindow.py | 162 +++++++++++++++++++++++++++-------------- 1 file changed, 106 insertions(+), 56 deletions(-) diff --git a/dicat/ui/datawindow.py b/dicat/ui/datawindow.py index fb2c89b..1e94768 100644 --- a/dicat/ui/datawindow.py +++ b/dicat/ui/datawindow.py @@ -55,41 +55,20 @@ def __init__(self, parent, candidate=''): def body(self, master): """ - Creates the body of the 'datawindow'. + Creates the body of the 'data window'. - :param master: frame in which to draw the body of the datawindow + :param master: frame in which to draw the body of the data window :type master: object - :param candidate: candidate ID or 'new' for a new candidate - :type candidate: str """ - try: - # Read candidate information - cand_data = DataManagement.read_candidate_data() - # Read visit information - visit_data = DataManagement.read_visitset_data() - visitset = {} # Create a visitset dictionary - cand_info = {} # Create a candidate information dictionary - - # Loop through all candidates - for cand_key in cand_data: - # Grep candidate's information from cand_data dictionary - if cand_data[cand_key]["Identifier"] == self.candidate: - cand_info = cand_data[cand_key] - break - - # Loop through candidates' visit data - for cand_key in visit_data: - # Grep candidate's visit set information from visit_data - if visit_data[cand_key]["Identifier"] == self.candidate: - visitset = visit_data[cand_key]["VisitSet"] - break - - except Exception as e: - print "datawindow.body ", str(e) # TODO manage exceptions + # Load the candidate and visitset data + cand_info = [] + visitset = [] + if not self.candidate in ['new', 'search']: + (cand_info, visitset) = self.load_data() - ## Candidate section + ## Create a candidate section in the data window self.candidate_pane = Labelframe( self, text=MultiLanguage.candidate_pane, @@ -101,6 +80,36 @@ def body(self, master): side=TOP, expand=YES, fill=BOTH, padx=5, pady=5 ) + # Draw in the candidate section of the data window + self.candidate_pane_ui(cand_info) + + # Draw the visit section if self.candidate is not 'new' or 'search' + if not self.candidate in ['new', 'search']: + # Create a calendar section in the data window + self.schedule_pane = Labelframe( + self, + text=MultiLanguage.schedule_pane, + width=250, + height=350, + borderwidth=10 + ) + self.schedule_pane.pack( + side=TOP, expand=YES, fill=BOTH, padx=5, pady=5 + ) + # Draw in the calendar section of the data window + self.schedule_pane_ui(visitset) + + + def candidate_pane_ui(self, cand_info): + """ + Draws the candidate section of the datawindow and populates it fields + based on what is store in cand_info + + :param cand_info: dictionary with the candidate's information + :type cand_info: dict + + """ + # Initialize text variables that will contain the field values self.text_pscid_var = StringVar() self.text_firstname_var = StringVar() @@ -110,8 +119,9 @@ def body(self, master): self.text_status_var = StringVar() self.text_phone_var = StringVar() - # If candidate is populated with candID populate the fields with values - # available in cand_info dictionary, otherwise populate with empty str + # If self.candidate is populated with a candID populate the fields with + # values available in cand_info dictionary, otherwise populate with + # empty str or " " in the case of drop down menus if self.candidate == 'new' or self.candidate == 'search': self.text_pscid_var.set("") self.text_firstname_var.set("") @@ -231,43 +241,40 @@ def body(self, master): ) - ## Calendar Section - displayed as a table - self.schedule_pane = Labelframe( - self, text=MultiLanguage.schedule_pane, - width=250, height=350, - borderwidth=10 - ) - self.schedule_pane.pack(side=TOP, expand=YES, fill=BOTH, padx=5, pady=5) + def schedule_pane_ui(self, visitset): - # top row (header) - self.label_visit_label = Label( + + # Create top row (header) widgets + self.label_visit_label = Label( # create visit label widget self.schedule_pane, text=MultiLanguage.col_visitlabel ) - self.label_visit_when = Label( + self.label_visit_when = Label( # create visit when widget self.schedule_pane, text=MultiLanguage.col_when ) - self.label_visit_label.grid( + self.label_visit_where = Label( # create visit where widget + self.schedule_pane, text=MultiLanguage.col_where + ) + self.label_visit_withwhom = Label( # create visit withwhom widget + self.schedule_pane, text=MultiLanguage.col_withwhom + ) + self.label_visit_status = Label( # create visit status widget + self.schedule_pane, text=MultiLanguage.col_status + ) + + # Draw the top row (header) widgets + self.label_visit_label.grid( # draw visit label widget column=1, row=0, padx=5, pady=5, sticky=N+S+E+W ) - self.label_visit_when.grid( + self.label_visit_when.grid( # draw visit when widget column=2, row=0, padx=5, pady=5, sticky=N+S+E+W ) - self.label_visit_status = Label( - self.schedule_pane, text=MultiLanguage.col_where - ) - self.label_visit_status.grid( + self.label_visit_where.grid( # draw visit where widget column=3, row=0, padx=5, pady=5, sticky=N+S+E+W ) - self.label_visit_status = Label( - self.schedule_pane, text=MultiLanguage.col_withwhom - ) - self.label_visit_status.grid( + self.label_visit_withwhom.grid( # draw visit withwhom widget column=4, row=0, padx=5, pady=5, sticky=N+S+E+W ) - self.label_visit_status = Label( - self.schedule_pane, text=MultiLanguage.col_status - ) - self.label_visit_status.grid( + self.label_visit_status.grid( # draw visit status widget column=5, row=0, padx=5, pady=5, sticky=N+S+E+W ) @@ -289,7 +296,8 @@ def body(self, master): # TODO add logic "foreach" to create a table showing each visit # 1- Get candidate visitset and parse into a list visit_list = [] - if len(visitset.keys()) == 0: + #if len(visitset.keys()) == 0: + if not visitset: print 'no visit yet' else: for key, value in visitset.iteritems(): @@ -356,6 +364,48 @@ def body(self, master): ) + def load_data(self): + """ + Read the XML data and return the candidate's (self.candidate) + information as well as its visit information. + + :return cand_data: data dictionary with candidate information + :rtype cand_data: dict + :return visit_data: data dictionary with visit information + :rtype visit_data: dict + + """ + + try: + # Read candidate information + cand_data = DataManagement.read_candidate_data() + # Read visit information + visit_data = DataManagement.read_visitset_data() + visitset = {} # Create a visitset dictionary + cand_info = {} # Create a candidate information dictionary + + # Loop through all candidates + for cand_key in cand_data: + # Grep candidate's information from cand_data dictionary + if cand_data[cand_key]["Identifier"] == self.candidate: + cand_info = cand_data[cand_key] + break + + # Loop through candidates' visit data + for cand_key in visit_data: + # Grep candidate's visit set information from visit_data + if visit_data[cand_key]["Identifier"] == self.candidate \ + and 'VisitSet' in visit_data[cand_key]: + visitset = visit_data[cand_key]["VisitSet"] + break + + except Exception as e: + print "datawindow.body ", str(e) # TODO manage exceptions + return + + return cand_info, visitset + + def button_box(self): """ Draws the button box at the bottom of the data window. From 2958e30a68d2addf16e83b4763265102a32166e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Fri, 29 Jul 2016 09:45:41 -0400 Subject: [PATCH 85/89] Finished cleaning up datawindow.py and added a text to be displayed in datawindow calendar pane when no visits are scheduled yet for a candidate. --- dicat/lib/datamanagement.py | 23 ++++++ dicat/lib/multilanguage.py | 7 +- dicat/ui/datawindow.py | 158 +++++++++++++++++------------------- 3 files changed, 102 insertions(+), 86 deletions(-) diff --git a/dicat/lib/datamanagement.py b/dicat/lib/datamanagement.py index d37fa84..5f1674a 100644 --- a/dicat/lib/datamanagement.py +++ b/dicat/lib/datamanagement.py @@ -340,3 +340,26 @@ def remove_empty_lines_from_file(file): f.writelines(lines) +def sort_candidate_visit_list(visitset): + """ + Sort a candidate's visit set and return it into sorted visit_list. + + :param visitset: visit set for a given candidate + :type visitset: dict + + :return visit_list: list of sorted visits for that candidate + :rtype visit_list: list + + """ + + # 1- Grep candidate's visitset and parse into a list + visit_list = [] + for key, value in visitset.iteritems(): + visit_list.append(visitset[key]) + + # 2- Sort list on visit.rank + visit_list = sorted( + visit_list, key=lambda visit: visit["VisitStartWhen"] + ) + + return visit_list diff --git a/dicat/lib/multilanguage.py b/dicat/lib/multilanguage.py index dd8fb92..4971a90 100644 --- a/dicat/lib/multilanguage.py +++ b/dicat/lib/multilanguage.py @@ -136,6 +136,7 @@ schedule_visit_status = u"Status" schedule_visit_when = u"Date" schedule_optional = u"Optionnel" + schedule_no_visit_yet = u"Aucune visite de programmé pour ce candidat" elif language == "en": app_title = u"LORIS tools" @@ -218,11 +219,14 @@ col_when = u"Date/Time" col_where = u"Place" col_status = u"Status" + #################### STATUS ##################### status_active = u"active" status_tentative = u"tentative" + ################# DATA WINDOWS ################## data_window_title = u"Data Window" + ################## DIALOGBOX #################### # very not sure what to do about that section dialog_yes = u"Yes" @@ -259,4 +263,5 @@ schedule_visit_rank = u"#" schedule_visit_status = u"Status" schedule_visit_when = u"Date" - schedule_optional = u"Optional" \ No newline at end of file + schedule_optional = u"Optional" + schedule_no_visit_yet = u"No visit scheduled for that candidate yet" \ No newline at end of file diff --git a/dicat/ui/datawindow.py b/dicat/ui/datawindow.py index 1e94768..7d2e1c2 100644 --- a/dicat/ui/datawindow.py +++ b/dicat/ui/datawindow.py @@ -243,6 +243,16 @@ def candidate_pane_ui(self, cand_info): def schedule_pane_ui(self, visitset): + # If the candidate has not visit set, display a message on the calendar + # section to say that no visit has been scheduled yet for that candidate + if not visitset: + self.label_no_visit = Label( + self.schedule_pane, text=MultiLanguage.schedule_no_visit_yet + ) + self.label_no_visit.grid( + row=0, column=1, columnspan=4, padx=5, sticky=N+S+E+W + ) + return # Create top row (header) widgets self.label_visit_label = Label( # create visit label widget @@ -263,105 +273,83 @@ def schedule_pane_ui(self, visitset): # Draw the top row (header) widgets self.label_visit_label.grid( # draw visit label widget - column=1, row=0, padx=5, pady=5, sticky=N+S+E+W + row=0, column=1, padx=5, pady=5, sticky=N+S+E+W ) self.label_visit_when.grid( # draw visit when widget - column=2, row=0, padx=5, pady=5, sticky=N+S+E+W + row=0, column=2, padx=5, pady=5, sticky=N+S+E+W ) self.label_visit_where.grid( # draw visit where widget - column=3, row=0, padx=5, pady=5, sticky=N+S+E+W + row=0, column=3, padx=5, pady=5, sticky=N+S+E+W ) self.label_visit_withwhom.grid( # draw visit withwhom widget - column=4, row=0, padx=5, pady=5, sticky=N+S+E+W + row=0, column=4, padx=5, pady=5, sticky=N+S+E+W ) self.label_visit_status.grid( # draw visit status widget - column=5, row=0, padx=5, pady=5, sticky=N+S+E+W + row=0, column=5, padx=5, pady=5, sticky=N+S+E+W ) - """ - PSEUDOCODE - 1. Get candidate.visitset - 2. Parse into a sorted list (sorted on visit.rank) - 3. Print cand_data on screen - - - visit_set = candidate.visitset - for key, value in study_setup.iteritems(): - visit_list.append(study_setup[key]) - visit_list = sorted(visit_list, key=lambda visit: visit.rank) - - for key, value in visit_list.iteritems(): - - """ # TODO add logic "foreach" to create a table showing each visit - # 1- Get candidate visitset and parse into a list - visit_list = [] - #if len(visitset.keys()) == 0: - if not visitset: - print 'no visit yet' - else: - for key, value in visitset.iteritems(): - visit_list.append(visitset[key]) - # 2- Sort list on visit.rank - visit_list = sorted( - visit_list, key=lambda visit: visit["VisitStartWhen"] + # Sort visit list based on the VisitStartWhen field + visit_list = DataManagement.sort_candidate_visit_list(visitset) + + # Show values on ui + row_number=1 + for visit in visit_list: + + # Check if values are set for VisitStartWhen, VisitWhere, + # VisitWindow & VisitStatus keys. If not, set it to empty string as + # we need a text to display in the corresponding label widgets. + visit_when = "" + visit_where = "" + visit_status = "" + visit_with_whom = "" + if "VisitStartWhen" in visit.keys(): + #TODO: implement automatic range for next visit + visit_when = visit["VisitStartWhen"] + if "VisitWhere" in visit.keys(): + visit_where = visit["VisitWhere"] + if "VisitWithWhom" in visit.keys(): + visit_with_whom = visit["VisitWithWhom"] + if "VisitStatus" in visit.keys(): + visit_status = visit["VisitStatus"] + + # Create the visit row widgets + label_visit_label = Label( # visit label widget + self.schedule_pane, text=visit["VisitLabel"] + ) + label_visit_when = Label( # visit when widget + self.schedule_pane, text=visit_when + ) + label_visit_where = Label( # visit where widget + self.schedule_pane, text=visit_where + ) + label_visit_with_whom = Label( # visit with whom widget + self.schedule_pane, text=visit_with_whom + ) + label_visit_status = Label( # visit status widget + self.schedule_pane, text=visit_status + ) + + # Draw the visit row widget + label_visit_label.grid( + row=row_number+1, column=1, padx=5, pady=5, sticky=N+S+E+W + ) + label_visit_when.grid( + row=row_number+1, column=2, padx=5, pady=5, sticky=N+S+E+W + ) + label_visit_where.grid( + row=row_number+1, column=3, padx=5, pady=5, sticky=N+S+E+W + ) + label_visit_with_whom.grid( + row=row_number+1, column=4, padx=5, pady=5, sticky=N+S+E+W + ) + label_visit_status.grid( + row=row_number+1, column=5, padx=5, pady=5, sticky=N+S+E+W ) - # 3- 'print' values on ui - x = 0 - for x in range(len(visit_list)): - # visitlabel - label_visit_label = Label( - self.schedule_pane, text=visit_list[x]["VisitLabel"] - ) - label_visit_label.grid( - column=1, row=x+1, padx=5, pady=5, sticky=N+S+E+W - ) - # when - visit_when = "" - if "VisitStartWhen" not in visit_list[x].keys(): - #visit = visit_list[x]["VisitLabel"] - #date_range = visit.visit_date_range() - #TODO: implement automatic range for next visit - visit_when = "" - else: - visit_when = visit_list[x]["VisitStartWhen"] - label_visit_when = Label(self.schedule_pane, text=visit_when) - label_visit_when.grid( - column=2, row=x+1, padx=5, pady=5, sticky=N+S+E+W - ) - - # where - visit_where = "" - if "VisitWhere" in visit_list[x].keys(): - visit_where = visit_list[x]["VisitWhere"] - label_visit_where = Label(self.schedule_pane, text=visit_where) - label_visit_where.grid( - column=3, row=x+1, padx=5, pady=5, sticky=N+S+E+W - ) - - # withwhom - visit_with_whom = "" - if "VisitWithWhom" in visit_list[x].keys(): - visit_with_whom = visit_list[x]["VisitWithWhom"] - label_visit_with_whom = Label( - self.schedule_pane, text=visit_with_whom - ) - label_visit_with_whom.grid( - column=4, row=x+1, padx=5, pady=5, sticky=N+S+E+W - ) - - # status - visit_status = '' - if "VisitStatus" in visit_list[x].keys(): - visit_status = visit_list[x]["VisitStatus"] - label_visit_status = Label( - self.schedule_pane, text=visit_status - ) - label_visit_status.grid( - column=5, row=x+1, padx=5, pady=5, sticky=N+S+E+W - ) + # Increment row_number for the next visit to be displayed + row_number += 1 def load_data(self): From 47e65f6116b26be786a6da078718d8eea3e7495f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Fri, 29 Jul 2016 12:14:04 -0400 Subject: [PATCH 86/89] Implemented a search filter for the scheduler (#42). Also replaced regex re.match by re.search (re.match only search str that start with pattern) --- dicat/IDMapper.py | 8 ++--- dicat/lib/datamanagement.py | 25 +++++++++++++- dicat/lib/multilanguage.py | 2 +- dicat/scheduler_application.py | 62 ++++++++++++---------------------- dicat/ui/datatable.py | 42 ++++++++++++++++------- dicat/ui/datawindow.py | 9 ++--- 6 files changed, 83 insertions(+), 65 deletions(-) diff --git a/dicat/IDMapper.py b/dicat/IDMapper.py index 5c8153a..363021c 100644 --- a/dicat/IDMapper.py +++ b/dicat/IDMapper.py @@ -312,16 +312,16 @@ def find_candidate(self, data_captured): lastname = data[cand_key]["LastName"] dob = data[cand_key]["DateOfBirth"] if 'DateOfBirth' in data_captured \ - and re.match(data_captured['DateOfBirth'], dob): + and re.search(data_captured['DateOfBirth'], dob): add = True # set the 'add' boolean to true to add candidate if 'FirstName' in data_captured \ - and re.match(data_captured['FirstName'], firstname): + and re.search(data_captured['FirstName'], firstname): add = True # set the 'add' boolean to true to add candidate if 'LastName' in data_captured \ - and re.match(data_captured['LastName'], lastname): + and re.search(data_captured['LastName'], lastname): add = True # set the 'add' boolean to true to add candidate if 'Identifier' in data_captured \ - and re.match(data_captured['Identifier'], candid): + and re.search(data_captured['Identifier'], candid): add = True # set the 'add' boolean to true to add candidate # If add is set to True, add the candidate to the filtered list. diff --git a/dicat/lib/datamanagement.py b/dicat/lib/datamanagement.py index 5f1674a..d8600e7 100644 --- a/dicat/lib/datamanagement.py +++ b/dicat/lib/datamanagement.py @@ -1,5 +1,5 @@ # Imports from standard packages -import os.path +import os.path, re from xml.dom import minidom # Imports from DICAT @@ -338,6 +338,7 @@ def remove_empty_lines_from_file(file): # write lines into the file with open(file, "w") as f: f.writelines(lines) + f.writelines(lines) def sort_candidate_visit_list(visitset): @@ -363,3 +364,25 @@ def sort_candidate_visit_list(visitset): ) return visit_list + + +def dict_match(pattern, data_dict): + """ + Function that will return True if the pattern was matching one value of the + data dictionary (data_dict). False otherwise. + + :param pattern: pattern to be used in the regular expression + :type pattern: str + :param data_dict: data dictionary to look for matches + :type data_dict: dict + + :return: True if found a match, False otherwise + :rtype: bool + + """ + + for key in data_dict: + if re.search(pattern, data_dict[key]): + return True + + return False \ No newline at end of file diff --git a/dicat/lib/multilanguage.py b/dicat/lib/multilanguage.py index 4971a90..1fd686a 100644 --- a/dicat/lib/multilanguage.py +++ b/dicat/lib/multilanguage.py @@ -152,7 +152,7 @@ #CANDIDATE menu candidate_menu = u"Candidate" candidate_add = u"New candidate" - candidate_search = u"Search candidate" + candidate_search = u"Search:" candidate_update = u"Update" candidate_get_id = u"Get a canditate ID" candidate_exclude_include_toggle = u"Include/Exclude a candidate" diff --git a/dicat/scheduler_application.py b/dicat/scheduler_application.py index a205c07..a5ba39a 100644 --- a/dicat/scheduler_application.py +++ b/dicat/scheduler_application.py @@ -84,25 +84,34 @@ def initialize(self): self.buttonBox.pack(side=TOP, anchor=W) # Create a 'new candidate' button to be added to self.buttonBox - self.buttonNewCandidate = Button( + self.buttonNewCandidate = Button( # new candidate button widget self.buttonBox, width=15, text=MultiLanguage.candidate_add, command=self.add_candidate ) - self.buttonSearchCandidate = Button( + self.labelSearchCandidate = Label( # search candidate Label widget self.buttonBox, - width=15, + width=8, text=MultiLanguage.candidate_search, - command=self.search_candidate + justify=RIGHT, + anchor=E + ) + self.textSearchCandValue = StringVar() + self.textSearchCandValue.trace('w', self.find_matching_candidates) + self.entrySearchCandidate = Entry( # search candidate entry widget + self.buttonBox, text=self.textSearchCandValue, width=20, ) # Draw the buttons self.buttonNewCandidate.grid( - row=0, column=0, padx=(5,0), pady=(0,5), sticky=E+W + row=0, column=0, padx=(0,10), pady=(0,5), sticky=E+W ) - self.buttonSearchCandidate.grid( + self.labelSearchCandidate.grid( row=0, column=1, padx=(5,0), pady=(0,5), sticky=E+W ) + self.entrySearchCandidate.grid( + row=0, column=2, padx=(0,0), pady=(0,5), sticky=E+W + ) ## Create data tables (using Treeview) @@ -125,32 +134,17 @@ def initialize(self): ) self.visit_table.pack(side=BOTTOM, expand=YES, fill=BOTH) - #TODO This section to be replaced by REAL CODE actively filtering data + + def find_matching_candidates(self, *args): """ - #Create a filter section in each data_pane (not implemented yet) + Updates data table with matching candidates. + :param args: + :type args: list - self.filter_candidate = Labelframe( - self.candidate_pane, text='Filters', width=220, height=50, - borderwidth=10 - ) - self.filter_candidate.pack(side=TOP, expand=NO, fill=BOTH, pady=5) - self.filter_candidate_label = Label( - self.filter_candidate, - text='Filter for Non-Active / Active / Excluded / Group...' - ) - self.filter_candidate_label.pack(side=TOP, expand=NO, fill=BOTH) - self.filter_visit = Labelframe( - self.visit_pane, text='Filters', width=220, height=50, - borderwidth=10 - ) - self.filter_visit.pack(side=TOP, expand=NO, fill=BOTH, pady=5) - self.filter_candidate_label = Label( - self.filter_visit, - text='Filters for Active / Tentative / Closed ...' - ) - self.filter_candidate_label.pack(side=TOP, expand=NO, fill=BOTH) """ + pattern = self.textSearchCandValue.get() + self.cand_table.update_data(pattern) def add_candidate(self): @@ -167,18 +161,6 @@ def add_candidate(self): # Update the candidate datatable when save the new candidate self.cand_table.update_data() - def search_candidate(self): - """ - This function will allow functionality to search for candidates using - the same data window as when editing a subject. - - """ - - # Open the datawindow with candidate=False as want to fill in the - # options to search for a candidate - DataWindow.DataWindow(self, 'search') - - # def load_xml(self): """ diff --git a/dicat/ui/datatable.py b/dicat/ui/datatable.py index dd82862..1fcef8b 100644 --- a/dicat/ui/datatable.py +++ b/dicat/ui/datatable.py @@ -73,7 +73,7 @@ def init_datatable(self, parent, colheaders): self.datatable.bind('', self.onrightclik ) - def load_data(self): + def load_data(self, pattern=False): """ Should be overriden in child's class @@ -81,7 +81,7 @@ def load_data(self): pass - def update_data(self): + def update_data(self, pattern=False): """ Delete everything in datatable and reload its content with the updated data coming from the XML file. @@ -90,7 +90,7 @@ def update_data(self): for i in self.datatable.get_children(): self.datatable.delete(i) # delete all data from the datatable - self.load_data() # reload all data with updated values + self.load_data(pattern) # reload all data with updated values def treeview_sortby(self, tree, column, descending): @@ -206,9 +206,13 @@ def __init__(self, parent, colheaders): # expected is dataset self.datatable.tag_configure('active', background='#F1F8FF') - def load_data(self): + def load_data(self, pattern=False): """ - Load candidates information into the candidate datatable. + Load candidates information into the candidate datatable. If pattern is + set, will only load candidates that have info that matches the pattern. + + :param pattern: pattern to use to find matching candidates + :type pattern: str """ @@ -217,11 +221,23 @@ def load_data(self): try: # Loop through all candidates + matching_cand = {} for key in cand_data: + # If pattern, check if found its match in cand_data[key] dict + # DataManagement.dict_match function will return: + # - True if found a match, + # - False otherwise + if pattern and \ + not DataManagement.dict_match(pattern, cand_data[key]): + continue # continue to the following candidate + + # Populate the matching_cand dictionary with the candidate info + matching_cand[key] = cand_data[key] + # Deal with occurences where CandidateStatus is not set if "CandidateStatus" not in cand_data[key].keys(): - status = "" + status = " " else: status = cand_data[key]["CandidateStatus"] @@ -236,15 +252,15 @@ def load_data(self): '', 'end', values=[ - cand_data[key]["Identifier"], - cand_data[key]["FirstName"], - cand_data[key]["LastName"], - cand_data[key]["DateOfBirth"], - cand_data[key]["Gender"], + matching_cand[key]["Identifier"], + matching_cand[key]["FirstName"], + matching_cand[key]["LastName"], + matching_cand[key]["DateOfBirth"], + matching_cand[key]["Gender"], phone, status ], - tags=(status, cand_data[key]["Identifier"]) + tags=(status, matching_cand[key]["Identifier"]) ) except Exception as e: # TODO proper exception handling @@ -283,7 +299,7 @@ def __init__(self, parent, colheaders): self.datatable.tag_configure('tentative', background='#F0F0F0') - def load_data(self): + def load_data(self, pattern=False): """ Load the visit list into the datatable. diff --git a/dicat/ui/datawindow.py b/dicat/ui/datawindow.py index 7d2e1c2..efcdf79 100644 --- a/dicat/ui/datawindow.py +++ b/dicat/ui/datawindow.py @@ -2,7 +2,6 @@ from Tkinter import * from ttk import * # import internal packages -from scheduler_visit import Visit import ui.dialogbox as DialogBox import lib.utilities as Utilities import lib.multilanguage as MultiLanguage @@ -65,7 +64,7 @@ def body(self, master): # Load the candidate and visitset data cand_info = [] visitset = [] - if not self.candidate in ['new', 'search']: + if not self.candidate == 'new': (cand_info, visitset) = self.load_data() ## Create a candidate section in the data window @@ -84,7 +83,7 @@ def body(self, master): self.candidate_pane_ui(cand_info) # Draw the visit section if self.candidate is not 'new' or 'search' - if not self.candidate in ['new', 'search']: + if not self.candidate == 'new': # Create a calendar section in the data window self.schedule_pane = Labelframe( self, @@ -122,7 +121,7 @@ def candidate_pane_ui(self, cand_info): # If self.candidate is populated with a candID populate the fields with # values available in cand_info dictionary, otherwise populate with # empty str or " " in the case of drop down menus - if self.candidate == 'new' or self.candidate == 'search': + if self.candidate == 'new': self.text_pscid_var.set("") self.text_firstname_var.set("") self.text_lastname_var.set("") @@ -288,8 +287,6 @@ def schedule_pane_ui(self, visitset): row=0, column=5, padx=5, pady=5, sticky=N+S+E+W ) - # TODO add logic "foreach" to create a table showing each visit - # Sort visit list based on the VisitStartWhen field visit_list = DataManagement.sort_candidate_visit_list(visitset) From e29c98c5baa980dbdd7a095af14ed7ffee2ba23d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Fri, 29 Jul 2016 15:04:53 -0400 Subject: [PATCH 87/89] Removed candidate and visit menus --- dicat/ui/menubar.py | 47 --------------------------------------------- 1 file changed, 47 deletions(-) diff --git a/dicat/ui/menubar.py b/dicat/ui/menubar.py index 5b40279..dbd98ef 100644 --- a/dicat/ui/menubar.py +++ b/dicat/ui/menubar.py @@ -37,28 +37,6 @@ def __init__(self, parent): command=self.quit_application ) - # Create a CANDIDATE pulldown menu - candidate_menu = Tkinter.Menu(self, tearoff=False) - self.add_cascade( - label=MultiLanguage.candidate_menu, underline=0, menu=candidate_menu - ) - candidate_menu.add_command( - label=MultiLanguage.candidate_add, command=self.add_candidate - ) - candidate_menu.add_command( - label=MultiLanguage.candidate_search, command=self.find_candidate - ) - - # Create a CALENDAR pulldown menu - calendar_menu = Tkinter.Menu(self, tearoff=False) - self.add_cascade( - label=MultiLanguage.calendar_menu, underline=0, menu=calendar_menu - ) - calendar_menu.add_command( - label=MultiLanguage.calendar_new_appointment, - command=self.open_calendar - ) - # Create a HELP pulldown menu help_menu = Tkinter.Menu(self, tearoff=0) self.add_cascade( @@ -86,31 +64,6 @@ def quit_application(self): pass - def open_calendar(self): - #TODO implement open_calendar() - print 'running open_calendar' - pass - - - def dicom_anonymizer(self): - #TODO implement dicom_anonymizer() - print 'running dicom anonymizer' - pass - - - def add_candidate(self): - #TODO implement add_candidate() - DataWindow.DataWindow(self, "new") - print 'running add_candidate' - pass - - - def find_candidate(self): - #TODO implement find_candidate() - print 'running find_candidate' - pass - - def open_help(self): #TODO open_help() print 'running open_help' From 01f3f3ffa7fc26c83071fb9798f2976d04e7e764 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Fri, 29 Jul 2016 16:03:36 -0400 Subject: [PATCH 88/89] Fixed a bug that did not write correctly in the XML file. --- dicat/lib/datamanagement.py | 2 -- dicat/new_data_test.xml | 18 ++++++++++++++++++ dicat/ui/datawindow.py | 2 +- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/dicat/lib/datamanagement.py b/dicat/lib/datamanagement.py index d8600e7..e96b7fd 100644 --- a/dicat/lib/datamanagement.py +++ b/dicat/lib/datamanagement.py @@ -165,7 +165,6 @@ def save_candidate_data(cand_data): xml_lastname.firstChild.nodeValue = cand_data['LastName'] xml_dob.firstChild.nodeValue = cand_data['DateOfBirth'] if 'Gender' in cand_data: - print "in cand_data gender" xml_gender.firstChild.nodeValue = cand_data['Gender'] if 'CandidateStatus' in cand_data: key = 'CandidateStatus' @@ -338,7 +337,6 @@ def remove_empty_lines_from_file(file): # write lines into the file with open(file, "w") as f: f.writelines(lines) - f.writelines(lines) def sort_candidate_visit_list(visitset): diff --git a/dicat/new_data_test.xml b/dicat/new_data_test.xml index 073c083..917b744 100644 --- a/dicat/new_data_test.xml +++ b/dicat/new_data_test.xml @@ -262,5 +262,23 @@ Male MTL0010 + + + Hopie + Chipmunk + 2008-04-20 + + Female + MTL0011 + + + death + Chatran + Cat + 1992-04-05 + + Male + MTL0012 + diff --git a/dicat/ui/datawindow.py b/dicat/ui/datawindow.py index efcdf79..b5a75fc 100644 --- a/dicat/ui/datawindow.py +++ b/dicat/ui/datawindow.py @@ -522,7 +522,7 @@ def capture_data(self): # Check fields format and required fields candidate = Candidate(cand_data) - message = candidate.check_candidate_data('scheduler', self.candidate) + message = candidate.check_candidate_data('scheduler', self.candidate) if message: return message From 208da4743d24b6ec1de98ef219986b95eeece3ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9cile=20Madjar?= Date: Fri, 29 Jul 2016 17:43:36 -0400 Subject: [PATCH 89/89] Included a date picker for the date of birth using the code made available on https://github.com/moshekaplan/tkinter_components --- dicat/CalendarDialog/CalendarDialog.py | 41 +++++ dicat/CalendarDialog/__init__.py | 7 + dicat/CalendarDialog/tkSimpleDialog.py | 94 ++++++++++ dicat/CalendarDialog/ttkcalendar.py | 236 +++++++++++++++++++++++++ dicat/ui/datawindow.py | 34 +++- 5 files changed, 409 insertions(+), 3 deletions(-) create mode 100755 dicat/CalendarDialog/CalendarDialog.py create mode 100644 dicat/CalendarDialog/__init__.py create mode 100755 dicat/CalendarDialog/tkSimpleDialog.py create mode 100755 dicat/CalendarDialog/ttkcalendar.py diff --git a/dicat/CalendarDialog/CalendarDialog.py b/dicat/CalendarDialog/CalendarDialog.py new file mode 100755 index 0000000..79b0e3c --- /dev/null +++ b/dicat/CalendarDialog/CalendarDialog.py @@ -0,0 +1,41 @@ +import Tkinter +import ttkcalendar + +import tkSimpleDialog + + +class CalendarDialog(tkSimpleDialog.Dialog): + """Dialog box that displays a calendar and returns the selected date""" + def body(self, master): + self.calendar = ttkcalendar.Calendar(master) + self.calendar.pack() + + def apply(self): + self.result = self.calendar.selection + +# Demo code: + + +class CalendarFrame(Tkinter.LabelFrame): + def __init__(self, master): + Tkinter.LabelFrame.__init__(self, master, text="CalendarDialog Demo") + + def getdate(): + cd = CalendarDialog(self) + result = cd.result + self.selected_date.set(result.strftime("%m/%d/%Y")) + + self.selected_date = Tkinter.StringVar() + + Tkinter.Entry(self, textvariable=self.selected_date).pack(side=Tkinter.LEFT) + Tkinter.Button(self, text="Choose a date", command=getdate).pack(side=Tkinter.LEFT) + + +def main(): + root = Tkinter.Tk() + root.wm_title("CalendarDialog Demo") + CalendarFrame(root).pack() + root.mainloop() + +if __name__ == "__main__": + main() diff --git a/dicat/CalendarDialog/__init__.py b/dicat/CalendarDialog/__init__.py new file mode 100644 index 0000000..77e44d0 --- /dev/null +++ b/dicat/CalendarDialog/__init__.py @@ -0,0 +1,7 @@ +__author__ = 'cmadjar' + +""" +These scripts have been downloaded from the following GitHub repository and +allows to have date picker widgets and maybe some calendar functionality? +https://github.com/moshekaplan/tkinter_components +""" \ No newline at end of file diff --git a/dicat/CalendarDialog/tkSimpleDialog.py b/dicat/CalendarDialog/tkSimpleDialog.py new file mode 100755 index 0000000..29b055e --- /dev/null +++ b/dicat/CalendarDialog/tkSimpleDialog.py @@ -0,0 +1,94 @@ +from Tkinter import * +from ttk import * + +class Dialog(Toplevel): + """Sourced from http://effbot.org/tkinterbook/tkinter-dialog-windows.htm""" + def __init__(self, parent, title = None): + + Toplevel.__init__(self, parent) + self.transient(parent) + + if title: + self.title(title) + + self.parent = parent + + self.result = None + + body = Frame(self) + self.initial_focus = self.body(body) + body.pack(padx=5, pady=5) + + self.buttonbox() + + self.grab_set() + + if not self.initial_focus: + self.initial_focus = self + + self.protocol("WM_DELETE_WINDOW", self.cancel) + + self.geometry("+%d+%d" % (parent.winfo_rootx()+50, + parent.winfo_rooty()+50)) + + self.initial_focus.focus_set() + + self.wait_window(self) + + # + # construction hooks + + def body(self, master): + # create dialog body. return widget that should have + # initial focus. this method should be overridden + + pass + + def buttonbox(self): + # add standard button box. override if you don't want the + # standard buttons + + box = Frame(self) + + w = Button(box, text="OK", width=10, command=self.ok, default=ACTIVE) + w.pack(side=LEFT, padx=5, pady=5) + w = Button(box, text="Cancel", width=10, command=self.cancel) + w.pack(side=LEFT, padx=5, pady=5) + + self.bind("", self.ok) + self.bind("", self.cancel) + + box.pack() + + # + # standard button semantics + + def ok(self, event=None): + + if not self.validate(): + self.initial_focus.focus_set() # put focus back + return + + self.withdraw() + self.update_idletasks() + + self.apply() + + self.cancel() + + def cancel(self, event=None): + + # put focus back to the parent window + self.parent.focus_set() + self.destroy() + + # + # command hooks + + def validate(self): + + return 1 # override + + def apply(self): + + pass # override diff --git a/dicat/CalendarDialog/ttkcalendar.py b/dicat/CalendarDialog/ttkcalendar.py new file mode 100755 index 0000000..14c289c --- /dev/null +++ b/dicat/CalendarDialog/ttkcalendar.py @@ -0,0 +1,236 @@ +# Source: http://svn.python.org/projects/sandbox/trunk/ttk-gsoc/samples/ttkcalendar.py + +""" +Simple calendar using ttk Treeview together with calendar and datetime +classes. +""" +import calendar + +try: + import Tkinter + import tkFont +except ImportError: # py3k + import tkinter as Tkinter + import tkinter.font as tkFont + +import ttk + +def get_calendar(locale, fwday): + # instantiate proper calendar class + if locale is None: + return calendar.TextCalendar(fwday) + else: + return calendar.LocaleTextCalendar(fwday, locale) + +class Calendar(ttk.Frame): + # XXX ToDo: cget and configure + + datetime = calendar.datetime.datetime + timedelta = calendar.datetime.timedelta + + def __init__(self, master=None, **kw): + """ + WIDGET-SPECIFIC OPTIONS + + locale, firstweekday, year, month, selectbackground, + selectforeground + """ + # remove custom options from kw before initializating ttk.Frame + fwday = kw.pop('firstweekday', calendar.MONDAY) + year = kw.pop('year', self.datetime.now().year) + month = kw.pop('month', self.datetime.now().month) + locale = kw.pop('locale', None) + sel_bg = kw.pop('selectbackground', '#ecffc4') + sel_fg = kw.pop('selectforeground', '#05640e') + + self._date = self.datetime(year, month, 1) + self._selection = None # no date selected + + ttk.Frame.__init__(self, master, **kw) + + self._cal = get_calendar(locale, fwday) + + self.__setup_styles() # creates custom styles + self.__place_widgets() # pack/grid used widgets + self.__config_calendar() # adjust calendar columns and setup tags + # configure a canvas, and proper bindings, for selecting dates + self.__setup_selection(sel_bg, sel_fg) + + # store items ids, used for insertion later + self._items = [self._calendar.insert('', 'end', values='') + for _ in range(6)] + # insert dates in the currently empty calendar + self._build_calendar() + + def __setitem__(self, item, value): + if item in ('year', 'month'): + raise AttributeError("attribute '%s' is not writeable" % item) + elif item == 'selectbackground': + self._canvas['background'] = value + elif item == 'selectforeground': + self._canvas.itemconfigure(self._canvas.text, item=value) + else: + ttk.Frame.__setitem__(self, item, value) + + def __getitem__(self, item): + if item in ('year', 'month'): + return getattr(self._date, item) + elif item == 'selectbackground': + return self._canvas['background'] + elif item == 'selectforeground': + return self._canvas.itemcget(self._canvas.text, 'fill') + else: + r = ttk.tclobjs_to_py({item: ttk.Frame.__getitem__(self, item)}) + return r[item] + + def __setup_styles(self): + # custom ttk styles + style = ttk.Style(self.master) + arrow_layout = lambda dir: ( + [('Button.focus', {'children': [('Button.%sarrow' % dir, None)]})] + ) + style.layout('L.TButton', arrow_layout('left')) + style.layout('R.TButton', arrow_layout('right')) + + def __place_widgets(self): + # header frame and its widgets + hframe = ttk.Frame(self) + lbtn = ttk.Button(hframe, style='L.TButton', command=self._prev_month) + rbtn = ttk.Button(hframe, style='R.TButton', command=self._next_month) + self._header = ttk.Label(hframe, width=15, anchor='center') + # the calendar + self._calendar = ttk.Treeview(self, show='', selectmode='none', height=7) + + # pack the widgets + hframe.pack(in_=self, side='top', pady=4, anchor='center') + lbtn.grid(in_=hframe) + self._header.grid(in_=hframe, column=1, row=0, padx=12) + rbtn.grid(in_=hframe, column=2, row=0) + self._calendar.pack(in_=self, expand=1, fill='both', side='bottom') + + def __config_calendar(self): + cols = self._cal.formatweekheader(3).split() + self._calendar['columns'] = cols + self._calendar.tag_configure('header', background='grey90') + self._calendar.insert('', 'end', values=cols, tag='header') + # adjust its columns width + font = tkFont.Font() + maxwidth = max(font.measure(col) for col in cols) + for col in cols: + self._calendar.column(col, width=maxwidth, minwidth=maxwidth, + anchor='e') + + def __setup_selection(self, sel_bg, sel_fg): + self._font = tkFont.Font() + self._canvas = canvas = Tkinter.Canvas(self._calendar, + background=sel_bg, borderwidth=0, highlightthickness=0) + canvas.text = canvas.create_text(0, 0, fill=sel_fg, anchor='w') + + canvas.bind('', lambda evt: canvas.place_forget()) + self._calendar.bind('', lambda evt: canvas.place_forget()) + self._calendar.bind('', self._pressed) + + def __minsize(self, evt): + width, height = self._calendar.master.geometry().split('x') + height = height[:height.index('+')] + self._calendar.master.minsize(width, height) + + def _build_calendar(self): + year, month = self._date.year, self._date.month + + # update header text (Month, YEAR) + header = self._cal.formatmonthname(year, month, 0) + self._header['text'] = header.title() + + # update calendar shown dates + cal = self._cal.monthdayscalendar(year, month) + for indx, item in enumerate(self._items): + week = cal[indx] if indx < len(cal) else [] + fmt_week = [('%02d' % day) if day else '' for day in week] + self._calendar.item(item, values=fmt_week) + + def _show_selection(self, text, bbox): + """Configure canvas for a new selection.""" + x, y, width, height = bbox + + textw = self._font.measure(text) + + canvas = self._canvas + canvas.configure(width=width, height=height) + canvas.coords(canvas.text, width - textw, height / 2 - 1) + canvas.itemconfigure(canvas.text, text=text) + canvas.place(in_=self._calendar, x=x, y=y) + + # Callbacks + + def _pressed(self, evt): + """Clicked somewhere in the calendar.""" + x, y, widget = evt.x, evt.y, evt.widget + item = widget.identify_row(y) + column = widget.identify_column(x) + + if not column or not item in self._items: + # clicked in the weekdays row or just outside the columns + return + + item_values = widget.item(item)['values'] + if not len(item_values): # row is empty for this month + return + + text = item_values[int(column[1]) - 1] + if not text: # date is empty + return + + bbox = widget.bbox(item, column) + if not bbox: # calendar not visible yet + return + + # update and then show selection + text = '%02d' % text + self._selection = (text, item, column) + self._show_selection(text, bbox) + + def _prev_month(self): + """Updated calendar to show the previous month.""" + self._canvas.place_forget() + + self._date = self._date - self.timedelta(days=1) + self._date = self.datetime(self._date.year, self._date.month, 1) + self._build_calendar() # reconstuct calendar + + def _next_month(self): + """Update calendar to show the next month.""" + self._canvas.place_forget() + + year, month = self._date.year, self._date.month + self._date = self._date + self.timedelta( + days=calendar.monthrange(year, month)[1] + 1) + self._date = self.datetime(self._date.year, self._date.month, 1) + self._build_calendar() # reconstruct calendar + + # Properties + + @property + def selection(self): + """Return a datetime representing the current selected date.""" + if not self._selection: + return None + + year, month = self._date.year, self._date.month + return self.datetime(year, month, int(self._selection[0])) + +def test(): + import sys + root = Tkinter.Tk() + root.title('Ttk Calendar') + ttkcal = Calendar(firstweekday=calendar.SUNDAY) + ttkcal.pack(expand=1, fill='both') + + if 'win' not in sys.platform: + style = ttk.Style() + style.theme_use('clam') + + root.mainloop() + +if __name__ == '__main__': + test() diff --git a/dicat/ui/datawindow.py b/dicat/ui/datawindow.py index b5a75fc..a73586f 100644 --- a/dicat/ui/datawindow.py +++ b/dicat/ui/datawindow.py @@ -1,6 +1,10 @@ # import standard packages from Tkinter import * from ttk import * + +# import external package from https://github.com/moshekaplan/tkinter_components +import CalendarDialog.CalendarDialog as CalendarDialog + # import internal packages import ui.dialogbox as DialogBox import lib.utilities as Utilities @@ -162,7 +166,12 @@ def candidate_pane_ui(self, cand_info): self.candidate_pane, text=MultiLanguage.candidate_dob ) self.text_dob = Entry( # date of birth text box - self.candidate_pane, textvariable=self.text_dob_var + self.candidate_pane, textvariable=self.text_dob_var, width=15 + ) + self.dob_picker = Button( # date of birth date picker + self.candidate_pane, + text='Date Picker', + command=self.date_picker_event ) self.label_gender = Label( # gender label self.candidate_pane, text=MultiLanguage.candidate_gender @@ -215,10 +224,13 @@ def candidate_pane_ui(self, cand_info): column=2, row=1, padx=10, pady=5, sticky=N+S+E+W ) self.label_dob.grid( # draw date of birth label - column=3, row=0, padx=10, pady=5, sticky=N+S+E+W + column=3, row=0, columnspan=2, padx=10, pady=5, sticky=N+S+E+W ) self.text_dob.grid( # draw date of birth text box - column=3, row=1, padx=10, pady=5, sticky=N+S+E+W + column=3, row=1, padx=(10, 0), pady=5, sticky=N+S+E+W + ) + self.dob_picker.grid( + column=4, row=1, padx=(0,10), pady=5, sticky=N+S+E+W ) self.label_gender.grid( # draw gender label column=0, row=2, padx=10, pady=5, sticky=N+S+E+W @@ -240,6 +252,22 @@ def candidate_pane_ui(self, cand_info): ) + def date_picker_event(self): + """ + Date picker event. Once a date have been chosen from CalendarDialog, + will print the result into the self.text_dob_var entry variable with + the proper format. + + """ + + # Initialize the calendar dialog window + cd = CalendarDialog.CalendarDialog(self.candidate_pane) + + # Grep the date picked and print it in self.text_dob_var entry variable + date_picked = cd.result + self.text_dob_var.set(date_picked.strftime("%Y-%m-%d")) + + def schedule_pane_ui(self, visitset): # If the candidate has not visit set, display a message on the calendar