def _renderList(self, renderer, projectList, filters, order, limit=None, groupKeyword=None): """ Render a list using renderer, according to the restrictions set by the other parameters @param renderer: renderer class (for example: TextListRenderer) @param projectList: list of project name (as unicode string) @param filters: filters in sqlobject format (example: Task.q.status == 'done') @param order: ordering in sqlobject format (example: -Task.q.urgency) @param limit: limit number tasks (int) or None for no limit @param groupKeyword: keyword used for grouping (as unicode string) or None """ if groupKeyword: if groupKeyword.startswith("@"): groupKeyword = groupKeyword[1:] for keyword in Keyword.select(LIKE(Keyword.q.name, groupKeyword)): if unicode(keyword.name).startswith("_") and not groupKeyword.startswith("_"): # BUG: cannot filter on db side because sqlobject does not understand ESCAPE needed whith _ continue taskList = Task.select(AND(TaskKeyword.q.keywordID == keyword.id, *filters), orderBy=order, limit=limit, distinct=True, join=LEFTJOINOn(Task, TaskKeyword, Task.q.id == TaskKeyword.q.taskID)) taskList = list(taskList) if projectList: taskList = [x for x in taskList if x.project in projectList] if len(taskList) > 0: self.lastTaskIds.extend([t.id for t in taskList]) # Keep selected id for further use renderer.addTaskList(unicode(keyword), taskList) renderer.end() else: hiddenProjectNames = [] for project in projectList: if not project.active: hiddenProjectNames.append(project.name) continue taskList = Task.select(AND(Task.q.projectID == project.id, *filters), orderBy=order, limit=limit, distinct=True, join=LEFTJOINOn(Task, TaskKeyword, Task.q.id == TaskKeyword.q.taskID)) taskList = list(taskList) if len(taskList) > 0: self.lastTaskIds.extend([t.id for t in taskList]) # Keep selected id for further use renderer.addTaskList(unicode(project), taskList) renderer.end() if len(hiddenProjectNames) > 0: tui.info("hidden projects: %s" % ", ".join(hiddenProjectNames))
def __init__(self, name, filter_string): self.name = name project_name, keyword_filters = parseutils.extractKeywords( filter_string) q_filters = [x.filter() for x in keyword_filters] project_list = Project.select(LIKE(Project.q.name, project_name)) q_filters.append(IN(Task.q.project, project_list)) # Skip notes q_filters.append( parseutils.KeywordFilter("!@" + NOTE_KEYWORD).filter()) # Only list done tasks if they were done after min_date min_date = compute_min_date() q_filters.append( OR(Task.q.status != 'done', Task.q.doneDate >= min_date)) self.tasks = Task.select(AND(*q_filters), orderBy=Task.q.id, distinct=True, join=LEFTJOINOn( Task, TaskKeyword, Task.q.id == TaskKeyword.q.taskID))
def do_t_reorder(self, line): """Reorder tasks of a project. It works by starting an editor with the task list: you can then change the order of the lines and save the list. The urgency field will be updated to match the order. t_reorder <project_name>""" try: project = Project.byName(line) except SQLObjectNotFound: raise BadUsageException("You must provide a valid project name") taskList = Task.select(AND(Task.q.projectID == project.id, Task.q.status != 'done'), orderBy=-Task.q.urgency) lines = ["%d,%s" % (x.id, x.title) for x in taskList] text = tui.editText("\n".join(lines)) ids = [] for line in text.split("\n"): line = line.strip() if not "," in line: continue id = int(line.split(",")[0]) ids.append(id) ids.reverse() for urgency, id in enumerate(ids): task = Task.get(id) task.urgency = urgency
def end(self): today = datetime.now().replace(hour=0, minute=0) # Adjust idColumn maxId = Task.select().max(Task.q.id) self.idColumn.width = max(2, len(str(maxId))) # Adjust titleColumn self.titleColumn.width = self.maxTitleWidth totalWidth = sum([x.width for x in self.columns]) + len(self.columns) if totalWidth >= self.termWidth: self.titleColumn.width -= (totalWidth - self.termWidth) + len(self.columns) self.titleColumn.formater = TitleFormater(self.titleColumn.width, self.cryptoMgr) # Print table for sectionName, taskList in self.taskLists: dateSplitters = [(1, "day"), (7, "week"), (30, "month"), (30 * 4, "quarter"), (365, "year")] splitterRange, splitterName = dateSplitters.pop() splitterText = None self._renderTaskListHeader(sectionName) for task in taskList: while self.splitOnDate and task.creationDate > today - timedelta(splitterRange): splitterText = "Last %s" % splitterName if len(dateSplitters) > 0: splitterRange, splitterName = dateSplitters.pop() else: self.splitOnDate = False if splitterText: print >> self.out, C.GREEN + splitterText.center(totalWidth) + C.RESET splitterText = None self._renderTaskListRow(task)
def testAdd(self): tui.addInputAnswers("y") self.cmd.do_t_add("x t1") tui.addInputAnswers("y", "y") self.cmd.do_t_add("x @kw1 @kw2=12 t2") tui.addInputAnswers("n") self.cmd.do_t_add("notExistingProject newTask") tasks = list(Task.select()) result = [x.title for x in tasks] expected = [u"t1", u"t2"] self.assertEqual(result, expected) kwDict = Task.get(2).getKeywordDict() self.assertEqual(kwDict, dict(kw1=None, kw2=12)) for bad_input in ("", # No project "x"): # No task name self.assertRaises(BadUsageException, self.cmd.do_t_add, bad_input) # Crypto stuff tui.addInputAnswers("a Secret passphrase") self.cmd.do_t_add("-c x encrypted t1") self.assertTrue(Task.get(3).title.startswith(cryptutils.CRYPTO_PREFIX))
def taskIdCompleter(cmd, text, line, begidx, endidx): # TODO: filter on parameter position # TODO: potential performance issue with lots of tasks, find a better way to do it tasks = [x for x in Task.select(Task.q.status != 'done') if str(x.id).startswith(text)] print for task in tasks: # Move that in a renderer class ? print "%s: %s / %s" % (task.id, task.project.name, task.title) return [str(x.id) for x in tasks]
def taskIdCompleter(cmd, text, line, begidx, endidx): # TODO: filter on parameter position # TODO: potential performance issue with lots of tasks, find a better way to do it tasks = [ x for x in Task.select(Task.q.status != 'done') if str(x.id).startswith(text) ] print for task in tasks: # Move that in a renderer class ? print "%s: %s / %s" % (task.id, task.project.name, task.title) return [str(x.id) for x in tasks]
def do_p_list(self, line): """List all projects.""" for project in Project.select(): if project.active: active = "" else: active = "(inactive)" print "%s %s %s %s" % ( project.name.ljust(20), project.getKeywordsAsString().ljust(20), str(Task.select(Task.q.project == project).count()).rjust(4), active)
def eventLoop(): """Main event loop""" delta = timedelta(hours=float(getConfigKey("ALARM_DELAY"))) suspend = timedelta(hours=float(getConfigKey("ALARM_SUSPEND"))) cmdDelayTemplate = getConfigKey("ALARM_DELAY_CMD") cmdDueTemplate = getConfigKey("ALARM_DUE_CMD") # For the two following dict, task id is key, and value is (duedate, triggerdate) triggeredDelayTasks = {} triggeredDueTasks = {} activeTaskFilter = [Task.q.status != "done", Task.q.projectID == Project.q.id, Project.q.active == True] while event[0]: now = datetime.today().replace(microsecond=0) delayTasks = Task.select(AND(Task.q.dueDate < now + delta, Task.q.dueDate > now, *activeTaskFilter)) dueTasks = Task.select(AND(Task.q.dueDate < now, *activeTaskFilter)) processTasks(delayTasks, triggeredDelayTasks, cmdDelayTemplate, suspend) processTasks(dueTasks, triggeredDueTasks, cmdDueTemplate, suspend) time.sleep(DELAY)
def do_t_remove(self, line): parser = self.parser_t_remove() args = parser.parse_args(line) task = self.getTaskFromId(args.id) if not args.force: if not tui.confirm("Remove task '%s'" % task.title): return projectId = task.project.id task.destroySelf() print "Task '%s' removed" % (task.title) # Delete project with no associated tasks if Task.select(Task.q.projectID == projectId).count() == 0: Project.delete(projectId)
def eventLoop(): """Main event loop""" delta = timedelta(hours=float(getConfigKey("ALARM_DELAY"))) suspend = timedelta(hours=float(getConfigKey("ALARM_SUSPEND"))) cmdDelayTemplate = getConfigKey("ALARM_DELAY_CMD") cmdDueTemplate = getConfigKey("ALARM_DUE_CMD") # For the two following dict, task id is key, and value is (duedate, triggerdate) triggeredDelayTasks = {} triggeredDueTasks = {} activeTaskFilter = [ Task.q.status != "done", Task.q.projectID == Project.q.id, Project.q.active == True ] while event[0]: now = datetime.today().replace(microsecond=0) delayTasks = Task.select( AND(Task.q.dueDate < now + delta, Task.q.dueDate > now, *activeTaskFilter)) dueTasks = Task.select(AND(Task.q.dueDate < now, *activeTaskFilter)) processTasks(delayTasks, triggeredDelayTasks, cmdDelayTemplate, suspend) processTasks(dueTasks, triggeredDueTasks, cmdDueTemplate, suspend) time.sleep(DELAY)
def do_p_remove(self, line): parser = self.parser_p_remove() args = parser.parse_args(line) project = getProjectFromName(args.project) taskList = Task.select(Task.q.projectID == project.id) taskList = list(taskList) if not args.force: if not tui.confirm("Remove project '%s' and its %d tasks" % (project.name, len(taskList))): return print "Removing project tasks:" for task in taskList: task.delete(task.id) print "- task %(id)-3s: %(title)-30s" % dict(id=str(task.id), title=str(task.title)) project.delete(project.id) print "Project removed"
def do_t_purge(self, line): parser = self.parser_t_purge() args = parser.parse_args(line) filters = [] filters.append(Task.q.status == "done") filters.append(Task.q.doneDate < (datetime.now() - timedelta(days=args.delay))) tasks = Task.select(AND(*filters)) if tasks.count() == 0: print "No tasks need to be purged" return print "The following tasks will be removed:" print "\n".join(["%s: %s" % (task.id, task.title) for task in tasks]) if args.force or tui.confirm("Do you really want to remove those tasks (this action cannot be undone)?"): Task.deleteMany(AND(*filters)) print "Tasks deleted" else: print "Purge canceled"
def generateCal(): """Generate an ical calendar from yokadi database @return: icalendar.Calendar object""" cal = icalendar.Calendar() cal.add("prodid", "-//Yokadi calendar //yokadi.github.com//") cal.add("version", "2.0") # Add projects for project in Project.select(Project.q.active == True): vTodo = icalendar.Todo() vTodo.add("summary", project.name) vTodo["uid"] = PROJECT_UID % project.id cal.add_component(vTodo) # Add tasks for task in Task.select(Task.q.status != "done"): vTodo = createVTodoFromTask(task) cal.add_component(vTodo) return cal
def testAdd(self): tui.addInputAnswers("y", "2", "4", "123") self.cmd.do_bug_add("x t1") tui.addInputAnswers("n") self.cmd.do_bug_add("notExistingProject newBug") tasks = list(Task.select()) result = [x.title for x in tasks] expected = [u"t1"] self.assertEqual(result, expected) kwDict = Task.get(1).getKeywordDict() self.assertEqual(kwDict, dict(_severity=2, _likelihood=4, _bug=123)) for bad_input in ("", # No project "x"): # No task name self.assertRaises(YokadiException, self.cmd.do_bug_add, bad_input)
def generateCal(): """Generate an ical calendar from yokadi database @return: icalendar.Calendar object""" cal = icalendar.Calendar() cal.add("prodid", '-//Yokadi calendar //yokadi.github.com//') cal.add("version", "2.0") # Add projects for project in Project.select(Project.q.active == True): vTodo = icalendar.Todo() vTodo.add("summary", project.name) vTodo["uid"] = PROJECT_UID % project.id cal.add_component(vTodo) # Add tasks for task in Task.select(Task.q.status != "done"): vTodo = createVTodoFromTask(task) cal.add_component(vTodo) return cal
def do_t_purge(self, line): parser = self.parser_t_purge() args = parser.parse_args(line) filters = [] filters.append(Task.q.status == "done") filters.append( Task.q.doneDate < (datetime.now() - timedelta(days=args.delay))) tasks = Task.select(AND(*filters)) if tasks.count() == 0: print "No tasks need to be purged" return print "The following tasks will be removed:" print "\n".join(["%s: %s" % (task.id, task.title) for task in tasks]) if args.force or tui.confirm( "Do you really want to remove those tasks (this action cannot be undone)?" ): Task.deleteMany(AND(*filters)) print "Tasks deleted" else: print "Purge canceled"
def end(self): today = datetime.now().replace(hour=0, minute=0) # Adjust idColumn maxId = Task.select().max(Task.q.id) self.idColumn.width = max(2, len(str(maxId))) # Adjust titleColumn self.titleColumn.width = self.maxTitleWidth totalWidth = sum([x.width for x in self.columns]) + len(self.columns) if totalWidth >= self.termWidth: self.titleColumn.width -= (totalWidth - self.termWidth) + len( self.columns) self.titleColumn.formater = TitleFormater(self.titleColumn.width, self.cryptoMgr) # Print table for sectionName, taskList in self.taskLists: dateSplitters = [(1, "day"), (7, "week"), (30, "month"), (30 * 4, "quarter"), (365, "year")] splitterRange, splitterName = dateSplitters.pop() splitterText = None self._renderTaskListHeader(sectionName) for task in taskList: while self.splitOnDate and task.creationDate > today - timedelta( splitterRange): splitterText = "Last %s" % splitterName if len(dateSplitters) > 0: splitterRange, splitterName = dateSplitters.pop() else: self.splitOnDate = False if splitterText: print >> self.out, C.GREEN + splitterText.center( totalWidth) + C.RESET splitterText = None self._renderTaskListRow(task)
def _renderList(self, renderer, projectList, filters, order, limit=None, groupKeyword=None): """ Render a list using renderer, according to the restrictions set by the other parameters @param renderer: renderer class (for example: TextListRenderer) @param projectList: list of project name (as unicode string) @param filters: filters in sqlobject format (example: Task.q.status == 'done') @param order: ordering in sqlobject format (example: -Task.q.urgency) @param limit: limit number tasks (int) or None for no limit @param groupKeyword: keyword used for grouping (as unicode string) or None """ if groupKeyword: if groupKeyword.startswith("@"): groupKeyword = groupKeyword[1:] for keyword in Keyword.select(LIKE(Keyword.q.name, groupKeyword)): if unicode(keyword.name).startswith( "_") and not groupKeyword.startswith("_"): # BUG: cannot filter on db side because sqlobject does not understand ESCAPE needed whith _ continue taskList = Task.select( AND(TaskKeyword.q.keywordID == keyword.id, *filters), orderBy=order, limit=limit, distinct=True, join=LEFTJOINOn(Task, TaskKeyword, Task.q.id == TaskKeyword.q.taskID)) taskList = list(taskList) if projectList: taskList = [ x for x in taskList if x.project in projectList ] if len(taskList) > 0: self.lastTaskIds.extend([ t.id for t in taskList ]) # Keep selected id for further use renderer.addTaskList(unicode(keyword), taskList) renderer.end() else: hiddenProjectNames = [] for project in projectList: if not project.active: hiddenProjectNames.append(project.name) continue taskList = Task.select(AND(Task.q.projectID == project.id, *filters), orderBy=order, limit=limit, distinct=True, join=LEFTJOINOn( Task, TaskKeyword, Task.q.id == TaskKeyword.q.taskID)) taskList = list(taskList) if len(taskList) > 0: self.lastTaskIds.extend([ t.id for t in taskList ]) # Keep selected id for further use renderer.addTaskList(unicode(project), taskList) renderer.end() if len(hiddenProjectNames) > 0: tui.info("hidden projects: %s" % ", ".join(hiddenProjectNames))
def do_p_list(self, line): """List all projects.""" for project in Project.select(): if project.active: active = "" else: active = "(inactive)" print "%s %s %s %s" % (project.name.ljust(20), project.getKeywordsAsString().ljust(20), str(Task.select(Task.q.project == project).count()).rjust(4), active)