def taskHasWantedKeywordDict(task, wantedKeywordDict): """ @param task: task object @param wantedKeywordDict: dict of name/value of wanted keyword # a task is considered a subset of wantedKeywordDict if: # 1. All wantedKeywordDict keys are in task or project keywords # 2. All wantedKeywordDict valued keywords have the same value # in task or project keyword""" for wantedKeyword, wantedValue in wantedKeywordDict.items(): taskFilters=[Task.q.id==task.id, TaskKeyword.q.taskID==task.id, TaskKeyword.q.keywordID==Keyword.q.id, LIKE(Keyword.q.name, wantedKeyword)] projectFilters=[Project.q.id==task.projectID, ProjectKeyword.q.projectID==Project.q.id, ProjectKeyword.q.keyword==Keyword.q.id, LIKE(Keyword.q.name, wantedKeyword)] if wantedValue: taskFilters.append(TaskKeyword.q.value==wantedValue) projectFilters.append(ProjectKeyword.q.value==wantedValue) if Task.select(AND(*taskFilters)).count()==0 and Task.select(AND(*projectFilters)).count()==0: return False # All critera were met, return ok return True
def api_feature(pid): user = get_user() project = Project.get(Project.id == pid) if user and request.method == 'POST' and project.can_validate: ref_and_audit = request.get_json() if ref_and_audit and len(ref_and_audit) == 2: skipped = ref_and_audit[1] is None feat = Feature.get(Feature.project == project, Feature.ref == ref_and_audit[0]) user_did_it = Task.select(Task.id).where( Task.user == user, Task.feature == feat).count() > 0 Task.create(user=user, feature=feat, skipped=skipped) if not skipped: if len(ref_and_audit[1]): new_audit = json.dumps(ref_and_audit[1], sort_keys=True, ensure_ascii=False) else: new_audit = None if feat.audit != new_audit: feat.audit = new_audit feat.validates_count = 1 elif not user_did_it: feat.validates_count += 1 feat.save() fref = request.args.get('ref') if fref: feature = Feature.get(Feature.project == project, Feature.ref == fref) elif not user or request.args.get('browse') == '1': feature = Feature.select().where(Feature.project == project).order_by(fn_Random()).get() else: try: # Maybe use a join: https://stackoverflow.com/a/35927141/1297601 task_query = Task.select(Task.id).where(Task.user == user, Task.feature == Feature.id) query = Feature.select().where( Feature.project == project, Feature.validates_count < 2).where( ~fn.EXISTS(task_query)).order_by(fn_Random()) if project.validate_modified: query = query.where(Feature.action == 'm') if user.bboxes: bboxes = BBoxes(user) feature = None for f in query: if bboxes.contains(f.lat/1e7, f.lon/1e7): feature = f break elif not feature: feature = f if not feature: raise Feature.DoesNotExist() else: feature = query.get() except Feature.DoesNotExist: return jsonify(feature={}, ref=None, audit=None) return jsonify(feature=json.loads(feature.feature), ref=feature.ref, audit=json.loads(feature.audit or 'null'))
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>""" project = Project.byName(projectName) 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 project(name): project = Project.get(Project.name == name) desc = project.description.replace('\n', '<br>') cnt = project.feature_count val1 = Feature.select(Feature.id).where(Feature.project == project, Feature.validates_count > 0) val2 = Feature.select(Feature.id).where(Feature.project == project, Feature.validates_count >= 2) if project.validate_modified: val1 = val1.where(Feature.action == 'm') val2 = val2.where(Feature.action == 'm') cnt = Feature.select(Feature.id).where(Feature.project == project, Feature.action == 'm').count() corrected = Feature.select(Feature.id).where( Feature.project == project, Feature.audit.is_null(False), Feature.audit != '').count() skipped = Feature.select(Feature.id).where( Feature.project == project, Feature.audit.contains('"skip": true')).count() user = get_user() if user: has_skipped = Task.select().join(Feature).where( Task.user == user, Task.skipped == True, Feature.project == project).count() > 0 else: has_skipped = False return render_template('project.html', project=project, admin=is_admin(user, project), count=cnt, desc=desc, val1=val1.count(), val2=val2.count(), corrected=corrected, skipped=skipped, has_skipped=has_skipped)
def generateCal(): 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): todo = icalendar.Todo() todo.add("summary", project.name) todo["uid"] = PROJECT_UID % project.id cal.add_component(todo) # Add tasks for task in Task.select(Task.q.status != "done"): todo = icalendar.Todo() todo["uid"] = TASK_UID % task.id todo["related-to"] = PROJECT_UID % task.project.id todo.add("dtstamp", task.creationDate) todo.add("priority", task.urgency) todo.add("summary", "%s (%s)" % (task.title, task.id)) todo.add("dtstart", task.creationDate) if task.dueDate: todo.add("due", task.dueDate) if task.description: todo.add("description", task.description) categories = [task.project, ] # Add project as a keyword if task.keywords: categories.extend([k.name for k in task.keywords]) todo.add("categories", categories) cal.add_component(todo) return cal
def end(self): # Adjust idColumn maxId = Task.select().max(Task.q.id) self.idColumn.width = max(2, len(str(maxId))) # Adjust dueColumn shortDateFormat = self.termWidth < 100 if shortDateFormat: self.dueColumn.width = 8 else: self.dueColumn.width = 26 self.dueColumn.formater = DueDateFormater(self.today, shortDateFormat) # Adjust titleColumn self.titleColumn.width = self.maxTitleWidth totalWidth = sum([x.width for x in self.columns]) if totalWidth > self.termWidth: self.titleColumn.width -= (totalWidth - self.termWidth) + len(self.columns) self.titleColumn.formater = TitleFormater(self.titleColumn.width) # Print table for sectionName, taskList in self.taskLists: self._renderTaskListHeader(sectionName) for task in taskList: self._renderTaskListRow(task)
def get_tasks(): arr = [] tasks = Task.select() for task in tasks: arr.append({"name": task.name, "display_name": task.display_name}) print("===SAVING {0} {1}===".format(len(arr), "Tasks")) return arr
def get_last_task_day(user): try: last_task = Task.select(Task.day).where( Task.user == user, Task.changeset.is_null(False)).order_by(Task.day.desc()).get() return last_task.day except Task.DoesNotExist: return None
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 random_task_for_user(user): tasks = set(get_tasks(user.level)) if not tasks: return None last_tasks = set([ x[0] for x in Task.select(Task.task).where(Task.user == user).order_by( Task.day.desc()).limit(int(len(tasks) * 0.7)).tuples() ]) return choice(list(tasks - last_tasks))
def testAdd(self): tui.addInputAnswers("y", "2", "4", "123") self.cmd.do_bug_add("x t1") 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))
def eventLoop(): """Main event loop""" delta=timedelta(hours=float(Config.byName("ALARM_DELAY").value)) suspend=timedelta(hours=float(Config.byName("ALARM_SUSPEND").value)) cmdDelayTemplate=Config.byName("ALARM_DELAY_CMD").value cmdDueTemplate=Config.byName("ALARM_DUE_CMD").value # 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]: time.sleep(DELAY) 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)
def do_t_remove(self, line): parser = self.parser_t_remove() options, args = parser.parse_args(line) task=dbutils.getTaskFromId(' '.join(args)) if not options.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 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") 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))
def do_p_remove(self, line): parser = self.parser_p_remove() options, args = parser.parse_args(line) project = getProjectFromName(' '.join(args)) if not options.force: if not tui.confirm("Remove project '%s' and all its tasks" % project.name): return taskList = Task.select(Task.q.projectID == project.id) taskList = list(taskList) 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 changeset(): if 'osm_token' not in session: redirect(url_for('login')) cs_data = request.args.get('changeset') if not cs_data.strip(): return redirect(url_for('front')) user = get_user() # TODO: call submit_changeset instead try: changeset = parse_changeset_id(cs_data) cs_date, conforms = validate_changeset(user, changeset, None, openstreetmap) except ValueError as e: flash(str(e)) return redirect(url_for('front')) if not cs_date or cs_date != today(): flash('Date of the changeset is wrong') return redirect(url_for('front')) task = Task.get(Task.user == user, Task.day == cs_date) try: last_task = Task.select(Task.day).where( Task.user == user, Task.changeset.is_null(False)).order_by(Task.day.desc()).get() is_streak = last_task.day == cs_date - cs_date.resolution except Task.DoesNotExist: is_streak = False task.changeset = changeset task.correct = conforms if is_streak: user.streak += 1 else: user.streak = 1 user.score += int(math.log(user.streak + 1, 2)) if conforms: flash('An extra point for completing the task') user.score += 1 if user.level < len(config.LEVELS) + 1: if user.score >= config.LEVELS[user.level - 1]: user.level += 1 flash('Congratulations on gaining a level!') with database.atomic(): task.save() user.save() flash('Changeset noted, thank you!') return redirect(url_for('front'))
def do_t_purge(self, line): parser = self.parser_t_purge() options, args = parser.parse_args(line) filters=[] filters.append(Task.q.status=="done") filters.append(Task.q.doneDate<(datetime.now()-timedelta(days=options.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 options.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 "x t1"): # Existing task self.assertRaises(YokadiException, self.cmd.do_bug_add, bad_input)
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 "x t1"): # Existing task self.assertRaises(YokadiException, self.cmd.do_t_add, bad_input)
def do_t_list(self, line): doneRangeList= ["today", "thisweek", "thismonth"] def keywordDictIsSubsetOf(taskKeywordDict, wantedKeywordDict): # Returns true if taskKeywordDict is a subset of wantedKeywordDict # taskKeywordDict is considered a subset of wantedKeywordDict if: # 1. All wantedKeywordDict keys are in taskKeywordDict # 2. All wantedKeywordDict valued keywords have the same value # in taskKeywordDict for wantedKeyword, wantedValue in wantedKeywordDict.items(): if not wantedKeyword in taskKeywordDict: return False if wantedValue and taskKeywordDict[wantedKeyword] != wantedValue: return False return True def taskHasWantedKeywordDict(task, wantedKeywordDict): """ @param task: task object @param wantedKeywordDict: dict of name/value of wanted keyword # a task is considered a subset of wantedKeywordDict if: # 1. All wantedKeywordDict keys are in task or project keywords # 2. All wantedKeywordDict valued keywords have the same value # in task or project keyword""" for wantedKeyword, wantedValue in wantedKeywordDict.items(): taskFilters=[Task.q.id==task.id, TaskKeyword.q.taskID==task.id, TaskKeyword.q.keywordID==Keyword.q.id, LIKE(Keyword.q.name, wantedKeyword)] projectFilters=[Project.q.id==task.projectID, ProjectKeyword.q.projectID==Project.q.id, ProjectKeyword.q.keyword==Keyword.q.id, LIKE(Keyword.q.name, wantedKeyword)] if wantedValue: taskFilters.append(TaskKeyword.q.value==wantedValue) projectFilters.append(ProjectKeyword.q.value==wantedValue) if Task.select(AND(*taskFilters)).count()==0 and Task.select(AND(*projectFilters)).count()==0: return False # All critera were met, return ok return True def createFilterFromRange(_range): # Parse the _range string and return an SQLObject filter minDate = date.today() if _range == "today": pass elif _range == "thisweek": minDate -= timedelta(minDate.weekday()) elif _range == "thismonth": minDate = minDate.replace(day = 1) else: raise YokadiException("Invalid range value '%s'" % _range) return Task.q.doneDate>=minDate def selectRendererClass(): if options.format != "auto": return gRendererClassDict[options.format] defaultRendererClass = TextListRenderer if not options.output: return defaultRendererClass ext = os.path.splitext(options.output)[1] if not ext: return defaultRendererClass return gRendererClassDict.get(ext[1:], defaultRendererClass) #BUG: completion based on parameter position is broken when parameter is given parser = self.parser_t_list() options, args = parser.parse_args(line) if len(args) > 0: projectName, keywordDict = parseutils.extractKeywords(u" ".join(args)) else: projectName = "" keywordDict = {} if not projectName: # Take all project if none provided projectName="%" projectList = Project.select(LIKE(Project.q.name, projectName)) if projectList.count()==0: tui.error("Found no project matching '%s'" % projectName) return # Check keywords exist for keyword in keywordDict.keys(): if Keyword.select(LIKE(Keyword.q.name, keyword)).count()==0: tui.error("Keyword %s is unknown." % keyword) # Filtering and sorting according to parameters filters=[] order=-Task.q.urgency, Task.q.creationDate limit=None if options.done: filters.append(Task.q.status=='done') if options.done != "all": filters.append(createFilterFromRange(options.done)) elif not options.all: filters.append(Task.q.status!='done') if options.topUrgent: order=-Task.q.urgency limit=5 if options.topDue: filters.append(Task.q.dueDate!=None) order=Task.q.dueDate limit=5 if options.search: for word in options.search: filters.append(OR(LIKE(Task.q.title, "%"+word+"%"), LIKE(Task.q.description, "%"+word+"%"))) # Define output if options.output: out = open(options.output, "w") else: out = tui.stdout # Instantiate renderer rendererClass = selectRendererClass() renderer = rendererClass(out) # Fill the renderer if options.keyword: if options.keyword.startswith("@"): options.keyword = options.keyword[1:] for keyword in Keyword.select(LIKE(Keyword.q.name, options.keyword)): if unicode(keyword.name).startswith("_") and not options.keyword.startswith("_"): #BUG: cannot filter on db side because sqlobject does not understand ESCAPE needed whith _ continue taskList = Task.select(AND(TaskKeyword.q.taskID == Task.q.id, TaskKeyword.q.keywordID == keyword.id, *filters), orderBy=order, limit=limit) taskList = list(taskList) if keywordDict: # FIXME: factorize (see project oriented rendering below) taskList = [x for x in taskList if taskHasWantedKeywordDict(x, keywordDict)] if projectList: taskList = [x for x in taskList if x.project in projectList] if len(taskList) == 0: continue renderer.addTaskList(unicode(keyword), taskList) # Call renderer 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) if keywordDict: taskList = [x for x in taskList if taskHasWantedKeywordDict(x, keywordDict)] else: taskList = list(taskList) if len(taskList) == 0: continue renderer.addTaskList(unicode(project), taskList) renderer.end() if len(hiddenProjectNames) > 0: tui.info("hidden projects: %s" % ", ".join(hiddenProjectNames))
def do_t_list(self, line): def selectRendererClass(): if options.format != "auto": return gRendererClassDict[options.format] defaultRendererClass = TextListRenderer if not options.output: return defaultRendererClass ext = os.path.splitext(options.output)[1] if not ext: return defaultRendererClass return gRendererClassDict.get(ext[1:], defaultRendererClass) #BUG: completion based on parameter position is broken when parameter is given parser = self.parser_t_list() options, args = parser.parse_args(line) if len(args) > 0: projectName, keywordFilters = parseutils.extractKeywords(u" ".join(args)) else: projectName = "" keywordFilters = [] if self.kFilters: # Add keyword filter keywordFilters.extend(self.kFilters) if not projectName: if self.pFilter: # If a project filter is defined, use it as none was provided projectName = self.pFilter else: # Take all project if none provided projectName = "%" if projectName.startswith("!"): projectName = projectName[1:] projectList = Project.select(NOT(LIKE(Project.q.name, projectName))) else: projectList = Project.select(LIKE(Project.q.name, projectName)) if projectList.count() == 0: tui.error("Found no project matching '%s'" % projectName) return # Check keywords exist parseutils.warnIfKeywordDoesNotExist(keywordFilters) # Filtering and sorting according to parameters filters = [] # Filter on keywords for keywordFilter in keywordFilters: filters.append(keywordFilter.filter()) order = -Task.q.urgency, Task.q.creationDate limit = None if options.done: filters.append(Task.q.status == 'done') if options.done != "all": filters.append(parseutils.createFilterFromRange(options.done)) elif not options.all: filters.append(Task.q.status != 'done') if options.topUrgent: order = -Task.q.urgency limit = 5 if options.topDue: filters.append(Task.q.dueDate != None) order = Task.q.dueDate limit = 5 if options.overdue: filters.append(Task.q.dueDate < datetime.now()) order = Task.q.dueDate if options.search: for word in options.search: filters.append(OR(LIKE(Task.q.title, "%" + word + "%"), LIKE(Task.q.description, "%" + word + "%"))) # Define output if options.output: out = open(options.output, "w") else: out = tui.stdout # Instantiate renderer rendererClass = selectRendererClass() renderer = rendererClass(out) # Fill the renderer if options.keyword: if options.keyword.startswith("@"): options.keyword = options.keyword[1:] for keyword in Keyword.select(LIKE(Keyword.q.name, options.keyword)): if unicode(keyword.name).startswith("_") and not options.keyword.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: continue renderer.addTaskList(unicode(keyword), taskList) # Call renderer 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: continue renderer.addTaskList(unicode(project), taskList) renderer.end() if len(hiddenProjectNames) > 0: tui.info("hidden projects: %s" % ", ".join(hiddenProjectNames))
def restore_tasks(json): for task in json: Task(name=task["name"], display_name=task["display_name"]) arr = Task.select()[:] print("===RESTORED {0} {1}===".format(len(arr), "Tasks"))