예제 #1
0
파일: taskcmd.py 프로젝트: ryanakca/yokadi
        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
예제 #2
0
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'))
예제 #3
0
파일: taskcmd.py 프로젝트: ryanakca/yokadi
    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
예제 #4
0
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)
예제 #5
0
파일: yical.py 프로젝트: mathstuf/yokadi
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
예제 #6
0
    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
예제 #8
0
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
예제 #9
0
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]
예제 #10
0
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))
예제 #11
0
    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))
예제 #12
0
파일: yokadid.py 프로젝트: ryanakca/yokadi
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)
예제 #13
0
파일: taskcmd.py 프로젝트: ryanakca/yokadi
    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)
예제 #14
0
    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))
예제 #15
0
 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"
예제 #16
0
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'))
예제 #17
0
파일: taskcmd.py 프로젝트: ryanakca/yokadi
 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"
예제 #18
0
    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)
예제 #19
0
    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)
예제 #20
0
파일: taskcmd.py 프로젝트: ryanakca/yokadi
    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))
예제 #21
0
파일: taskcmd.py 프로젝트: mathstuf/yokadi
    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"))