def get_files_path(): ''' if default path is a possiblity, give the option. Otherwise, just prompt for path. ''' if DEFAULT_PATH and os.path.exists( DEFAULT_PATH ) : menu_title = "Where are the GQueues CSV files located?" IN_DEFAULT = ''.join([ 'The files are in the default location : ', DEFAULT_PATH ]) ELSEWHERE = "I'd like to enter a path manually." ( index, choice ) = select_from_menu( menu_title, [ IN_DEFAULT, ELSEWHERE ] ) print'' ############################### ''' if they chose the default location return that path ''' if choice == IN_DEFAULT : return DEFAULT_PATH ''' otherwise, enter a path - much as if the default had not been an option ''' ################### ''' enter path to CSV files ''' FILES_PATH = raw_input( 'Full Path To File(s): ') ''' check the path is an existing directory ''' while not os.path.exists( FILES_PATH ) \ or not os.path.isdir ( FILES_PATH ) : print '' print 'you entered:', FILES_PATH print "I'm sorry, that path does not exist, or is not a folder." print '' FILES_PATH = raw_input( 'Full Path To File(s): ') ################################### ''' we got past the while, and thus have a valid path to search for CSV files ''' return FILES_PATH
def add_project_to_toggl_workspace( p, ws_name ): ''' get workspace id from name ''' ws_id = ws_ids[ ws_name ] ########################## ''' Should we flattern Queue hierarchy, or should we emulate hierarchy with a naming convention. ''' ''' if there isn't any hierarchy, don't need to ask ''' if not p.has_hierarchy() : gqueues_tasks = p.get_tasks() ################### else : ''' if there is some hierarchy, we must ask the user what to do about this ''' menu_title = '\n'.join([ 'This Queue contains some nested tasks.', 'Toggl allows only 1 level of task hierarchy.' ]) EMULATE = 'Emulate hierarchy with naming, e.g. Child [ Grandparent > Parent ]' FLATTEN = 'Flat hierarchy of leaf-names, e.g. Child' IGNORE = 'Use top-level tasks only - discarding all nested tasks.' ( index, choice ) = select_from_menu( menu_title, [ EMULATE, FLATTEN, IGNORE ] ) ############################# if choice == EMULATE : gqueues_tasks = p.get_tasks( True ) ###################### elif choice == FLATTEN : gqueues_tasks = p.get_tasks( False ) ###################### elif choice == IGNORE : ''' get root nodes only ''' gqueues_tasks = p.get_root_tasks() ############################################################### ''' see if there is a project with the same name in this workspace. start by getting all projects in this workspace... ''' projects_in_ws = toggl_API.filter_data( projects, { "workspace": { 'name' : ws_name, 'id' : ws_id } }) ''' Of the projects in the selected workspace, do any have the same name as the one we're importing? ''' existing = toggl_API.filter_data( projects_in_ws, { "name": p.project_title } ) ######################################## ''' CASE 1 : There is no pre-existing project ''' if len( existing ) == 0 : ''' make a new project ''' project_data = { 'project' : { "name" : p.project_title, "is_private" : False, "billable" : False, "workspace" : { "id": ws_id } }} ''' create project ''' toggl_project = toggl_API.send_data( "projects", data = project_data ) ############################ ''' add this new project to the rest of the toggle projects ''' projects.append( toggl_project ) ############################ ''' notifiy user ''' print '' print 'uploaded a new project to Toggl:', p.project_title print '' ########################## ''' we need to add all of the gqueues tasks, as the projct is currently empty ''' new_tasks = gqueues_tasks ########################### ''' if no tasks in this project, inform the user ''' if len( new_tasks ) == 0 : print 'This project does not contain any tasks.' ################################################################## else : ''' CASE 2 : a project with this name already exists merge the project with the existing one adding, but NOT deleting old tasks. This is because tasks that are completed may still have associated time records. ''' toggl_project = existing[0] #################### ''' notifiy user ''' print '' print p.project_title, 'is already a project on Toggl. Merging new tasks...' print '' ################################### ''' get names all tasks in the project on toggl. Match project with object like this : { 'client_project_name': 'To Do', 'name': 'To Do', 'id' : 1540962 } ''' toggl_tasks = set([ t[ 'name' ] for t in toggl_API.filter_data( tasks, { "project": { 'client_project_name' : toggl_project[ 'client_project_name' ], 'name' : toggl_project[ 'name' ], 'id' : toggl_project[ 'id' ] }} )]) ########################## ''' add only the new tasks ''' new_tasks = gqueues_tasks - toggl_tasks ################## ''' inform if none ''' if len( new_tasks ) == 0 : print 'There are no new tasks to add to this project.' ####################################### ''' add a new tasks to the toggle project (whether newly created or existing) ''' for task_name in new_tasks : task_data = { 'task' : { "name" : task_name, "is_active" : True, "project" : { "id": toggl_project[ 'id' ] } }} ''' create task ''' task = toggl_API.send_data( "tasks", data = task_data ) ''' add to local collection ''' tasks.append( task ) print 'added task: ', task_name ############### ''' space after project ''' print ''
def populate_nodes( self ): ''' throw a warning if there are more than one tasks with identical names ''' all_nodes = deepcopy( self.parent_nodes ) [ all_nodes.append( description ) for ( description, parent ) in self.child_nodes ] ################### ''' compare a list of all the nodes to a list of all unique nodes. ''' uniq_nodes = DiffList( list(set( all_nodes )) ) all_nodes = DiffList( all_nodes ) ''' get any 'leftover' nodes ''' duplicates = all_nodes - uniq_nodes ''' stern warning if there are any! ''' if len( duplicates ) != 0 : title = '\n'.join([ '!!!!!!!!!!!!!!!!!!', '###########', 'WARNING!!! ', 'WARNING!!! ', 'WARNING!!! ', '###########', '!!!!!!!!!!!!!!!!!!', '', 'This project contains duplicate task names!', 'If you continue, only one task with each name', 'will be saved in the project.', '', 'In addition, if the duplicate tasks have any', 'sub-tasks, some tasks may end up with incorrect', 'ancestors. ', '', 'You may be planning to discard the repeated tasks', 'anyhow, e.g. because you only wish to import the', 'top-level tasks, and none of those are duplicates.', '', 'But if not, you may wish to return to GQueues and', 'rename the offending duplicate tasks.', '', 'The duplicated names are :', '', '\n'.join( set( duplicates )), '', 'What would you like to do?' ]) ########################### QUIT = 'Delete this CSV file and quit.' CONTINUE = 'Continue anyway.' ''' get the user's decision ''' ( index, choice ) = select_from_menu( title, [ QUIT, CONTINUE ] ) print '' ''' if they choose quit, quit! ''' if choice == QUIT : print 'deleting CSV containing duplicate task-names' self.delete_file() raise SystemExit() ''' Otherwise, continue onwards. You have been warned! ''' ########################### ''' put root nodes straight into nodes dictionary ''' for description in self.parent_nodes : self.nodes[ description ] = [ description ] ########################################### ''' now parse the children into the nodes dictionary ''' c_nodes = deepcopy( self.child_nodes ) while len( c_nodes ) > 0 : ''' pop open a node ''' ( description, parent ) = c_nodes.popleft() ''' assume we won't find it's parent ''' found = False ################################ for n in self.nodes : if n == parent : h_map = deepcopy( self.nodes[ n ] ) h_map.append( description ) ''' add new entry to the nodes dictionary ''' self.nodes[ description ] = h_map found = True break ################################ ''' if we didn't find it's parent yet re-add to the end of temp child nodes ''' if not found : c_nodes.append( ( description, parent ) )
def import_projects(): project_files = projects_from_csv() print '' print '' for p in project_files : ''' project file header ''' print '***************************************' print '***************************************' print '' ################################## ''' which workspace to import / merge this project (or sub-projects) into ? ''' menu_title = ''.join([ 'Choose workspace for *', p.project_title, '* :' ]) ''' choose from a list of worskspace names in the command shell ''' ( index, selected_workspace ) = select_from_menu( menu_title, ws_names ) print '' print 'Importing project data to workspace: ', selected_workspace print '' ######################### ''' now the user must decide whether to import this CSV file as a single project, or whether the root nodes of this Queue are themselves projects ''' menu_title = 'Import this file as a single project, or multiple projects?' SINGLE = 'This Queue is a single project.' MULTIPLE = 'Top level nodes in this Queue each represent a project.' ''' choose in cmd ''' ( index, choice ) = select_from_menu( menu_title, [ SINGLE, MULTIPLE ] ) #################### ''' spacer before project imports ''' print '' ######################### if choice == SINGLE : ''' add the current project to the selected workspace ''' add_project_to_toggl_workspace( p, selected_workspace ) ######################### elif choice == MULTIPLE : ''' Add sub-projects to toggl one by one ''' sub_projects = p.sub_projects() for sp in sub_projects : add_project_to_toggl_workspace( sp, selected_workspace ) ############################# ''' offer to delete the original file ''' menu_title = '\n'.join([ 'File successfully exported to Toggl.', 'Would you like to delete the file?' ]) DELETE = 'Yes' DONT = 'No' ( index, choice ) = select_from_menu( menu_title, [ DELETE, DONT ] ) ########################### if choice == DELETE : p.delete_file() print '' print 'Deleted File.' ############### ''' gap before next queue file ''' print '' print '' print ''