def addTask(projectName, title, keywordDict=None, interactive=True): """Adds a task based on title and keywordDict. @param projectName: name of project as a string @param title: task title as a string @param keywordDict: dictionary of keywords (name : value) @param interactive: Ask user before creating project (this is the default) @type interactive: Bool @returns : Task instance on success, None if cancelled.""" session = db.getSession() if keywordDict is None: keywordDict = {} # Create missing keywords if not createMissingKeywords(keywordDict.keys(), interactive=interactive): return None # Create missing project project = getOrCreateProject(projectName, interactive=interactive) if not project: return None # Create task task = Task(creationDate=datetime.now().replace(second=0, microsecond=0), project=project, title=title, description="", status="new") session.add(task) task.setKeywordDict(keywordDict) session.merge(task) return task
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 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 addTask(projectName, title, keywordDict=None, interactive=True): """Adds a task based on title and keywordDict. @param projectName: name of project as a string @param title: task title as a string @param keywordDict: dictionary of keywords (name : value) @param interactive: Ask user before creating project (this is the default) @type interactive: Bool @returns : Task instance on success, None if cancelled.""" if keywordDict is None: keywordDict = {} # Create missing keywords if not createMissingKeywords(keywordDict.keys(), interactive=interactive): return None # Create missing project project = getOrCreateProject(projectName, interactive=interactive) if not project: return None # Create task try: task = Task(creationDate=datetime.now(), project=project, title=title, description="", status="new") task.setKeywordDict(keywordDict) except DuplicateEntryError: raise YokadiException("A task named %s already exists in this project. Please find another name" % title) return task
def testTApply(self): self.cmd.do_k_add("lala") for i in range(10): tui.addInputAnswers("y") self.cmd.do_t_add("x t%s" % i) ids = [1, 2, 4, 5, 6, 9] self.cmd.do_t_apply("1 2,4-6 9 t_add_keywords @lala") for taskId in range(1, 10): kwDict = Task.get(taskId).getKeywordDict() if taskId in ids: self.assertEqual(kwDict, dict(lala=None)) else: self.assertNotEqual(kwDict, dict(lala=None)) # raise error if t_list had not been called previously self.assertRaises(BadUsageException, self.cmd.do_t_apply, "__ t_add_keywords @toto") self.cmd.do_t_list("@lala") self.cmd.do_t_apply("__ t_add_keywords @toto") for taskId in range(1, 10): kwDict = Task.get(taskId).getKeywordDict() if taskId in ids: self.assertEqual(kwDict, dict(lala=None, toto=None)) else: self.assertNotEqual(kwDict, dict(lala=None, toto=None))
def testLastProjectName(self): # Using "_" with no prior project used should raise an exception self.assertRaises(YokadiException, self.cmd.do_t_add, "_ t1") tui.addInputAnswers("y") self.cmd.do_t_add("x t1") task1 = Task.get(1) self.cmd.do_t_add("_ t2") task2 = Task.get(2) self.assertEqual(task1.project, task2.project)
def testSetProject(self): tui.addInputAnswers("y") self.cmd.do_t_add("x t1") tui.addInputAnswers("y") self.cmd.do_t_project("1 y") task1 = Task.get(1) self.assertEqual(task1.project.name, "y") self.cmd.do_t_add("x t2") self.cmd.do_t_project("1 _") task1 = Task.get(1) self.assertEqual(task1.project.name, "x")
def testTlistUrgency0(self): # Given a project with two tasks, one with a negative urgency prj = Project(name="prj") self.session.add(prj) t1 = Task(project=prj, title="t1") self.session.add(t1) t2 = Task(project=prj, title="t2", urgency=-1) self.session.add(t2) self.session.flush() # When I list tasks with -u 0 renderer = testutils.TestRenderer() self.cmd.do_t_list("-u 0", renderer=renderer) # Then the task with a negative urgency is not listed self.assertEqual(renderer.tasks, [t1])
def testLastTaskId(self): # Using "_" with no prior task activity should raise an exception self.assertRaises(YokadiException, self.cmd.getTaskFromId, "_") tui.addInputAnswers("y") self.cmd.do_t_add("x t1") task1 = Task.get(1) self.assertEqual(self.cmd.getTaskFromId("_"), task1) self.cmd.do_t_add("x t2") task2 = Task.get(2) self.assertEqual(self.cmd.getTaskFromId("_"), task2) self.cmd.do_t_mark_started("1") self.assertEqual(self.cmd.getTaskFromId("_"), task1)
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 run(self): filename = self.options.filename if not filename: filename = os.path.join(os.path.expandvars("$HOME"), ".yokadi.db") print "Using default database (%s)" % filename connectDatabase(filename, createIfNeeded=False) # Basic tests : if not (Task.tableExists() and Config.tableExists()): print "Your database seems broken or not initialised properly. Start yokadi command line tool to do it" sys.exit(1) # Start ical http handler if self.options.icalserver: yokadiIcalServer = YokadiIcalServer(self.options.tcpPort, self.options.tcpListen) yokadiIcalServer.start() # Start the main event Loop try: while event[1] != "SIGTERM": eventLoop() event[0] = True except KeyboardInterrupt: print "\nExiting..."
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 testRecurs(self): tui.addInputAnswers("y") self.cmd.do_t_add("x t1") task = Task.get(1) self.cmd.do_t_recurs("1 daily 10:00") desc = str(task.recurrence) self.cmd.do_t_recurs("1 weekly FR 23:00") self.cmd.do_t_recurs("1 none") self.cmd.do_t_recurs("1 weekly fr 23:00") self.cmd.do_t_recurs("1 weekly Fr 23:00") self.cmd.do_t_recurs("1 weekly Friday 23:00") self.cmd.do_t_recurs("1 monthly 3 13:00") self.cmd.do_t_recurs("1 monthly second friday 13:00") self.cmd.do_t_recurs("1 yearly 3/07 11:20") self.cmd.do_t_recurs("1 quarterly 14 11:20") self.cmd.do_t_recurs("1 quarterly first monday 23:20") self.assertNotEqual(desc, str(task.recurrence)) self.assertEqual(task.status, "new") self.cmd.do_t_mark_done("1") self.assertEqual(task.status, "new") for bad_input in ("", # No task "1", # No recurence "1 foo", # Unknown recurrence "1 daily", # No time "1 weekly", # No day "1 weekly monday", # No time "1 monthly", # No day "1 monthly 10", # No time "1 quarterly", # No day "1 quarterly 10", # No time "1 monthly foo 12:00", # Bad date ): self.assertRaises(YokadiException, self.cmd.do_t_recurs, bad_input)
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 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 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 testMark(self): tui.addInputAnswers("y") self.cmd.do_t_add("x t1") task = Task.get(1) self.assertEqual(task.status, "new") self.cmd.do_t_mark_started("1") self.assertEqual(task.status, "started") self.cmd.do_t_mark_done("1") self.assertEqual(task.status, "done")
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 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 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 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 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 testRenderListSectionOrder(self): projectNames = "ccc", "aaa", "UPPER_CASE", "zzz", "mmm" projectList = [] for name in projectNames: prj = Project(name=name) task = Task(project=prj, title="Hello") self.session.add(prj) self.session.add(task) projectList.append(prj) self.session.flush() renderer = testutils.TestRenderer() self.cmd._renderList(renderer, projectList, filters=[], order=[]) self.assertEqual(list(renderer.taskDict.keys()), sorted(projectNames, key=lambda x: x.lower()))
def testAddKeywords(self): tui.addInputAnswers("y") self.cmd.do_t_add("x t1") task = Task.get(1) tui.addInputAnswers("y", "y") self.cmd.do_t_add_keywords("1 @kw1 @kw2=12") kwDict = task.getKeywordDict() self.assertEqual(kwDict, dict(kw1=None, kw2=12)) for bad_input in ("", # No task "1", # No keyword "1 kw1"): # No @ before kw1 self.assertRaises(YokadiException, self.cmd.do_t_add_keywords, bad_input)
def applyChanges(project, oldList, newList, interactive=True): """ Modify a project so that its task list is newList @param project: the project name @param oldList: a list of Task instances @param newList: a list of MEditEntry @param interactive: whether to confirm creation of new keywords """ session = db.getSession() # Sanity check: all ids in newList should be in oldList oldIds = set([x.id for x in oldList]) newIds = set([x.id for x in newList if x.id is not None]) unknownIds = newIds.difference(oldIds) if unknownIds: idString = ", ".join([str(x) for x in unknownIds]) raise YokadiException("Unknown id(s): %s" % idString) # Check keywords for entry in newList: for name in entry.keywords: dbutils.getOrCreateKeyword(name, interactive=interactive) # Remove tasks whose lines have been deleted for id in oldIds.difference(newIds): task = dbutils.getTaskFromId(id) session.delete(task) # Update existing tasks, add new ones nbTasks = len(newList) for pos, newEntry in enumerate(newList): if newEntry.id: task = dbutils.getTaskFromId(newEntry.id) else: task = Task(creationDate=datetime.now().replace(second=0, microsecond=0), project=project) task.title = newEntry.title task.setKeywordDict(newEntry.keywords) task.setStatus(newEntry.status) task.urgency = nbTasks - pos if newEntry.id: session.merge(task) else: session.add(task)
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 testRenderListSectionOrderKeywords(self): prj = Project(name="prj") keywordNames = ["kw_" + x for x in ("ccc", "aaa", "UPPER_CASE", "zzz", "mmm")] keywordList = [] for name in keywordNames: keyword = Keyword(name=name) task = Task(project=prj, title="Hello") TaskKeyword(task=task, keyword=keyword) self.session.add(task) keywordList.append(prj) self.session.flush() renderer = testutils.TestRenderer() self.cmd._renderList(renderer, [prj], filters=[], order=[], groupKeyword="kw_%") self.assertEqual(list(renderer.taskDict.keys()), sorted(keywordNames, key=lambda x: x.lower()))
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 getTaskFromId(tid, parameterName="id"): """Returns a task given its id, or raise a YokadiException if it does not exist. @param tid: taskId string @param parameterName: name of the parameter to mention in exception @return: Task instance or None if existingTask is False""" # We do not use line.isdigit() because it returns True if line is '¹'! try: taskId = int(tid) except ValueError: raise YokadiException("<%s> should be a number" % parameterName) try: task = Task.get(taskId) except SQLObjectNotFound: raise YokadiException("Task %s does not exist. Use t_list to see all tasks" % taskId) return task
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 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)