def endpoint_get_leaderboard(self, request):
        """get leaderboard"""
        # fetch desired no. of users max 100
        if request.fetch > 100:
            users = get_all_users(100)
        else:
            users = get_all_users(request.fetch)

        # get leaderboard
        leaderboard = sorted(users, key=TrueSkill().expose, reverse=True)

        return GetUserRankingResponseList(
            rankings=[self._copy_ranking_response_to_list(user, leaderboard) for user in leaderboard]
        )
Example #2
0
def users(request):
    """
    人名选择器,获取当前注册用户
    """
    if not request.method == 'POST':
        return HttpResponse()
    q = request.POST.get('q', '')
    cc_name = request.POST.get('cc_name', '')
    if not cc_name:
        users_list = get_all_users(request.COOKIES['bk_token'])
        data_list = [{
            'id': user,
            'text': user
        } for user in users_list if not q or q in user]
        return HttpResponse(json.dumps(data_list))
    try:
        user_list = Application.objects.get(
            cc_name=cc_name).operator.all().values('username').values_list(
                'username', flat=True).distinct()
    except ObjectDoesNotExist:
        return render_json([])
    if not user_list:
        return HttpResponse([])
    data_list = [{
        'id': user,
        'text': user
    } for user in user_list if not q or q in user]
    return HttpResponse(json.dumps(data_list))
def user_similarity_db():
    """

    :return: calculate all users similarity and insert this information in dabase
    """

    deleted_rows = db_session.query(UserSimilarity).delete()
    db_session.commit()
    print("Deleted %i rows" % deleted_rows)

    local_ = utils.pre_get_movies_by_user()

    all_users = utils.get_all_users()

    for i in all_users:
        print("Calculating similarity of user %i" % i)

        for j in all_users:

            if i < j:
                value = similarity_value(i, j, local_)
                similarity_instance = UserSimilarity(i, j, value)
                db_session.add(similarity_instance)

    db_session.commit()
Example #4
0
def get_project_file(request, pid):
    """
    获取项目下文件
    :param request:
    :return:
    """
    # 获取所有用户
    user_info = get_all_users(request)

    files = ProjectFile.objects.filter(project_id=pid)
    file_list = []
    for file in files:
        user = user_info[int(file.user)]
        name = user['name']
        ext = file.path.split('.')[-1]
        file_list.append({
            'fid': file.pk,
            'name': file.name,
            'size': file.size,
            'user': name,
            'path': file.path,
            'ext': ext,
            'create_time': file.create_time.strftime('%Y-%m-%d')
        })
    return render_json({'result': True, 'data': file_list})
Example #5
0
def get_project_user(request):
    """
    获取项目所有用户
    :param request:
    :return:
    """
    pid = request.GET.get('pid', '')
    try:
        project = Project.objects.get(pk=pid)
    except Exception as e:
        logger.error(u"获取项目用户列表失败", e)
        return render_json({'result': False})

    uids = project.participant.split(",")
    uids.append(project.owner)
    # 获取所有用户
    user_info = get_all_users(request)
    user_list = []
    # 过滤出当前项目下的用户
    for uid in uids:
        user = user_info[int(uid)]
        name = user['name']

        # for id, user in user_info.items():
        user_list.append({'id': uid, 'text': name})

    return render_json({'result': True, 'message': user_list})
Example #6
0
    def process_ticket(self, req):
        """process a request to /hours/<ticket number>"""

        # get the ticket
        path = req.path_info.rstrip('/')
        ticket_id = int(path.split('/')[-1])  # matches a ticket number
        ticket = Ticket(self.env, ticket_id)

        if req.method == 'POST':
            if 'addhours' in req.args:
                return self.do_ticket_change(req, ticket)
            if 'edithours' in req.args:
                return self.edit_ticket_hours(req, ticket)

        # XXX abstract date stuff as this is used multiple places
        now = datetime.now()
        months = [(i, calendar.month_name[i], i == now.month)
                  for i in range(1, 13)]
        years = range(now.year, now.year - 10, -1)
        days = [(i, i == now.day) for i in range(1, 32)]

        time_records = self.get_ticket_hours(ticket.id)
        time_records.sort(key=lambda x: x['time_started'], reverse=True)

        # add additional data for the template
        total = 0
        for record in time_records:
            record['date_started'] = self.format_date(record['time_started'])
            record['hours_worked'], record[
                'minutes_worked'] = self.format_hours_and_minutes(
                    record['seconds_worked'])
            total += record['seconds_worked']
        total = self.format_hours(total)

        data = {
            'can_add_hours': req.perm.has_permission('TICKET_ADD_HOURS'),
            'can_add_others_hours': req.perm.has_permission('TRAC_ADMIN'),
            'days': days,
            'months': months,
            'years': years,
            'users': get_all_users(self.env),
            'total': total,
            'ticket': ticket,
            'time_records': time_records
        }

        # return the rss, if requested
        if req.args.get('format') == 'rss':
            return self.tickethours2rss(req, data)

        # add rss link
        rss_href = req.href(req.path_info, format='rss')
        add_link(req, 'alternate', rss_href, _('RSS Feed'),
                 'application/rss+xml', 'rss')
        add_ctxtnav(req, 'Back to Ticket #%s' % ticket_id,
                    req.href.ticket(ticket_id))

        return ('hours_ticket.html', data, 'text/html')
Example #7
0
def test_sample_person_graph():
    # for u in [utils.get_user_by_id(3568), utils.get_user_by_id(16)]:
    # for u in [utils.get_user_by_id(16)]:
    for u in random.sample(utils.get_all_users(), 200):
        print "=" * 80
        print
        print "results for ", u, " ".join([i.text for i in u.interests])
        make_full_person_graph(u)
        print
        print
        print
Example #8
0
def test_sample_person_graph():
    #for u in [utils.get_user_by_id(3568), utils.get_user_by_id(16)]:
    #for u in [utils.get_user_by_id(16)]:
    for u in random.sample(utils.get_all_users(), 200):
        print '=' * 80
        print
        print 'results for ', u, ' '.join([i.text for i in u.interests])
        make_full_person_graph(u)
        print
        print
        print
Example #9
0
    def process_ticket(self, req):
        """process a request to /hours/<ticket number>"""

        # get the ticket
        path = req.path_info.rstrip("/")
        ticket_id = int(path.split("/")[-1])  # matches a ticket number
        ticket = Ticket(self.env, ticket_id)

        if req.method == "POST":
            if "addhours" in req.args:
                return self.do_ticket_change(req, ticket)
            if "edithours" in req.args:
                return self.edit_ticket_hours(req, ticket)

        # XXX abstract date stuff as this is used multiple places
        now = datetime.now()
        months = [(i, calendar.month_name[i], i == now.month) for i in range(1, 13)]
        years = range(now.year, now.year - 10, -1)
        days = [(i, i == now.day) for i in range(1, 32)]

        time_records = self.get_ticket_hours(ticket.id)
        time_records.sort(key=lambda x: x["time_started"], reverse=True)

        # add additional data for the template
        total = 0
        for record in time_records:
            record["date_started"] = self.format_date(record["time_started"])
            record["hours_worked"], record["minutes_worked"] = self.format_hours_and_minutes(record["seconds_worked"])
            total += record["seconds_worked"]
        total = self.format_hours(total)

        data = {
            "can_add_hours": req.perm.has_permission("TICKET_ADD_HOURS"),
            "can_add_others_hours": req.perm.has_permission("TRAC_ADMIN"),
            "days": days,
            "months": months,
            "years": years,
            "users": get_all_users(self.env),
            "total": total,
            "ticket": ticket,
            "time_records": time_records,
        }

        # return the rss, if requested
        if req.args.get("format") == "rss":
            return self.tickethours2rss(req, data)

        # add rss link
        rss_href = req.href(req.path_info, format="rss")
        add_link(req, "alternate", rss_href, _("RSS Feed"), "application/rss+xml", "rss")
        add_ctxtnav(req, "Back to Ticket #%s" % ticket_id, req.href.ticket(ticket_id))

        return ("hours_ticket.html", data, "text/html")
Example #10
0
def get_full_user(request):
    """
        获取出当前用户外所有用户
        :param request:
        :return:
        """
    user_info = get_all_users(request)
    user_list = []
    for id, user in user_info.items():
        user_list.append({'id': id, 'text': user['name']})

    return render_json({'result': True, 'message': user_list})
Example #11
0
    def process_ticket(self, req):
        """process a request to /hours/<ticket number>"""

        # get the ticket
        path = req.path_info.rstrip('/')
        ticket_id = int(path.split('/')[-1]) # matches a ticket number
        ticket = Ticket(self.env, ticket_id)

        if req.method == "POST":
            if req.args.has_key('addhours'):
                return self.do_ticket_change(req, ticket)            
            if req.args.has_key('edithours'):
                return self.edit_ticket_hours(req, ticket)            

        # XXX abstract date stuff as this is used multiple places
        now = datetime.now()
        months = [ (i, calendar.month_name[i], i == now.month) for i in range(1,13) ]
        years = range(now.year, now.year - 10, -1)
        days= [ (i, i == now.day) for i in range(1, 32) ]

        # user information and permissions
        can_add_hours = req.perm.has_permission('TICKET_ADD_HOURS')
        can_add_others_hours = req.perm.has_permission('TRAC_ADMIN')
        users = get_all_users(self.env)
        time_records = self.get_ticket_hours(ticket.id)
        time_records.sort(key=lambda x: x['time_started'], reverse=True)

        # add additional data for the template
        total = 0
        for record in time_records:
            record['date_started'] = self.format_date(record['time_started'])
            record['hours_worked'], record['minutes_worked'] = self.format_hours_and_minutes(record['seconds_worked'])
            total += record['seconds_worked']
        total = self.format_hours(total)
        ticket_link = req.href('ticket', ticket.id)
        hours_link = req.href('hours')

        # copy the locals dictionary for use in the template
        data = locals().copy()
        data.pop('self')
        
        # return the rss, if requested
        if req.args.get('format') == 'rss':
            return self.tickethours2rss(req, data)

        # add rss link
        rss_href = req.href(req.path_info, format='rss')
        add_link(req, 'alternate', rss_href, _('RSS Feed'),
                 'application/rss+xml', 'rss')

        return ('hours_ticket.html', data, 'text/html')
Example #12
0
def all_users():
    # if request.method == "POST":
        # if search == "none":
    users_list = get_all_users()
    page, per_page, offset = get_page_args(page_parameter='page', per_page_parameter='per_page')
    total = len(users_list)
    pagination_users = get_users(offset=offset, per_page=per_page)
    pagination = Pagination(page=page, per_page=per_page, total=total, css_framework='bootstrap4')
    return render_template('users.html',
                           title=title,
                           user_list=pagination_users,
                           page=page,
                           per_page=per_page,
                           pagination=pagination,
                           change_offset=get_users,
                           )
Example #13
0
 def add_new_item_to_task(self, self_channel_mode=True):
     if self_channel_mode:
         for i, item in enumerate(self.clients):
             print(i, item)
         i = int(input("choose client"))
         client = self.clients[i].client
         channels = self.run_cor(utils.get_all_users(client))
         for item, i in enumerate(channels):
             print(item, i)
         i = input('choose items:')
         channels = list(map(lambda x: channels[int(x)][1], i.split(' ')))
     else:
         i = input('Input http link to channel')
         channels = i.split(',')
     for channel in channels:
         print(channel)
         self.run_cor(self.task.taskQu.put(channel))
    def endpoint_get_user_ranking(self, request):
        """Get ranking of a user"""
        user = get_user(request.user_name)

        # fetch only top 100 users
        users = get_all_users(100)

        # get leaderboard
        leaderboard = sorted(users, key=TrueSkill().expose, reverse=True)

        if user in leaderboard:
            user_ranking = leaderboard.index(user) + 1
        else:
            user_ranking = 0

        return GetUserRankingResponse(user_name=user.user_name,
                                      user_ranking=user_ranking,
                                      user_performance=user.mu)
Example #15
0
def get_gantt_project(request, pid):
    """
    获取项目gantt图数据
    :param request:
    :return:
    """
    # 获取所有用户
    user_info = get_all_users(request)
    try:
        tasks = Task.objects.filter(project_id=pid).filter(status=0)
    except Exception as e:
        logger.error(u"获取项目甘特图失败", e)
        return render_json({"result": False})

    COLORS = ['ganttRed', "ganttGreen", "ganttBlue", "ganttOrange"]
    data = []

    for task in tasks:
        user_names = []
        for id in task.participant.split(','):
            user = user_info[int(id)]
            user_names.append(user['name'])
        begin = "/Date(" + str(
            int(time.mktime(task.begin_time.timetuple()) * 1000)) + ")/"
        finish = "/Date(" + str(
            int(time.mktime(task.finish_time.timetuple()) * 1000)) + ")/"
        data.append({
            'name':
            task.name,
            "desc":
            task.introduction,
            "values": [{
                "id": task.pk,
                "from": begin,
                "to": finish,
                "label": (",").join(user_names),
                "customClass": COLORS[task.pk % len(COLORS)]
            }]
        })
    if not data:
        empty = True
    else:
        empty = False
    return render_json({'result': True, 'data': data, 'empty': empty})
    def get_email_list():
        email_list = []

        timestamp = datetime.datetime.now() - datetime.timedelta(hours=24)

        users = get_all_users(100)

        for user in users:
            all_games = get_user_games(user.user_name)
            # filter for active games
            active_game_filter = ndb.query.FilterNode('game_status', '=', GameStatus.IN_SESSION.number)
            # filter for timestamp
            timestamp_filter = ndb.query.FilterNode('timestamp', '<', timestamp)
            # fetch filtered games of this user
            filtered_games = all_games.filter(active_game_filter, timestamp_filter).fetch()
            if filtered_games:
                email_list.append(user.email)

        return email_list
def users_similarity_local():
    """

    :return: a map with all similarities of database calculated
    """

    local_ = utils.pre_get_movies_by_user()

    all_users = utils.get_all_users()

    map_similarity = {}

    for i in all_users:
        for j in all_users:

            if i < j:
                map_similarity[(i, j)] = similarity_value(i, j, local_)

    return map_similarity
Example #18
0
    def display_html(self, req, query):
        """returns the HTML according to a query for /hours view"""
        db = self.env.get_db_cnx()

        # The most recent query is stored in the user session;
        orig_list = None
        orig_time = datetime.now(utc)
        query_time = int(req.session.get('query_time', 0))
        query_time = datetime.fromtimestamp(query_time, utc)
        query_constraints = unicode(query.constraints)
        if query_constraints != req.session.get('query_constraints') \
                or query_time < orig_time - timedelta(hours=1):
            tickets = query.execute(req, db)
            # New or outdated query, (re-)initialize session vars
            req.session['query_constraints'] = query_constraints
            req.session['query_tickets'] = ' '.join([str(t['id'])
                                                     for t in tickets])
        else:
            orig_list = [int(id) for id
                         in req.session.get('query_tickets', '').split()]
            tickets = query.execute(req, db, orig_list)
            orig_time = query_time

        context = Context.from_request(req, 'query')
        ticket_data = query.template_data(context, tickets, orig_list, orig_time, req)

        # For clients without JavaScript, we add a new constraint here if
        # requested
        constraints = ticket_data['constraints']
        if 'add' in req.args:
            field = req.args.get('add_filter')
            if field:
                constraint = constraints.setdefault(field, {})
                constraint.setdefault('values', []).append('')
                # FIXME: '' not always correct (e.g. checkboxes)

        req.session['query_href'] = query.get_href(context.href)
        req.session['query_time'] = to_timestamp(orig_time)
        req.session['query_tickets'] = ' '.join([str(t['id'])
                                                 for t in tickets])

        # data dictionary for genshi
        data = {}

        # get data for saved queries
        query_id = req.args.get('query_id')
        if query_id:
            try:
                query_id = int(query_id)
            except ValueError:
                add_warning(req, "query_id should be an integer, you put '%s'" % query_id)
                query_id = None
        if query_id:
            data['query_id'] = query_id
            query_data = self.get_query(query_id)

            data['query_title'] = query_data['title']
            data['query_description'] = query_data['description']

        data.setdefault('report', None)
        data.setdefault('description', None)

        data['all_columns'] = query.get_all_columns() + self.get_columns()
        # Don't allow the user to remove the id column        
        data['all_columns'].remove('id')
        data['all_textareas'] = query.get_all_textareas()

        # need to re-get the cols because query will remove our fields
        cols = req.args.get('col')
        if isinstance(cols, basestring):
            cols = [cols]
        if not cols:
            cols = query.get_columns() + self.get_default_columns()
        data['col'] = cols

        now = datetime.now()
        # get the date range for the query
        if 'from_year' in req.args:
            from_date = get_date(req.args['from_year'], 
                                 req.args.get('from_month'),
                                 req.args.get('from_day'))

        else:
            from_date = datetime(now.year, now.month, now.day)
            from_date = from_date - timedelta(days=7) # 1 week ago, by default

        if 'to_year' in req.args:
            to_date = get_date(req.args['to_year'], 
                               req.args.get('to_month'),
                               req.args.get('to_day'),
                               end_of_day=True)
        else:
            to_date = now
        
        data['prev_week'] = from_date - timedelta(days=7)
        data['months'] = [ (i, calendar.month_name[i]) for i in range(1,13) ]        
        data['years'] = range(now.year, now.year - 10, -1)
        data['days'] = range(1, 32)
        data['users'] = get_all_users(self.env)
        data['cur_worker_filter'] = req.args.get('worker_filter', '*any')

        data['from_date'] = from_date
        data['to_date'] = to_date

        ticket_ids = [t['id'] for t in tickets]

        # generate data for ticket_times
        time_records = self.get_ticket_hours(ticket_ids, from_date=from_date, to_date=to_date, worker_filter=data['cur_worker_filter'])

        data['query'] = ticket_data['query']
        data['context'] = ticket_data['context']
        data['row'] = ticket_data['row'] 
        if 'comments' in req.args.get('row', []):
            data['row'].append('comments')
        data['constraints'] = ticket_data['constraints']

        our_labels = dict([(f['name'], f['label']) for f in self.fields])
        labels = ticket_data['labels']
        labels.update(our_labels)
        data['labels'] = labels

        order = req.args.get('order')
        desc = bool(req.args.get('desc'))
        data['order'] = order
        data['desc'] = desc

        headers = [{'name': col, 
                    'label' : labels.get(col),
                    'href': self.get_href(query, req.args,
                                          context.href, 
                                          order=col,
                                          desc=(col == order and not desc)
                                          )
                    } for col in cols]

        data['headers'] = headers

        data['fields'] = ticket_data['fields']
        data['modes'] = ticket_data['modes']


        # group time records
        time_records_by_ticket = {}
        for record in time_records:
            id = record['ticket']
            if id not in time_records_by_ticket:
                time_records_by_ticket[id] = []

            time_records_by_ticket[id].append(record)

        data['extra_group_fields'] = dict(ticket = dict(name='ticket', type='select', label='Ticket'),
                                          worker = dict(name='worker', type='select', label='Worker'))

        num_items = 0
        data['groups'] = []

        # merge ticket data into ticket_time records
        for key, tickets in ticket_data['groups']:
            ticket_times = []
            total_time = 0
            total_estimated_time = 0
            for ticket in tickets:
                records = time_records_by_ticket.get(ticket['id'], [])
                [rec.update(ticket) for rec in records]
                ticket_times += records

            # sort ticket_times, if needed
            if order in our_labels:                
                ticket_times.sort(key=lambda x: x[order], reverse=desc)
            data['groups'].append((key, ticket_times))
            num_items += len(ticket_times)


        data['double_count_warning'] = ''

        # group by ticket id or other time_ticket fields if necessary
        if req.args.get('group') in data['extra_group_fields']:
            query.group = req.args.get('group')
            if not query.group == "id":
                data['double_count_warning'] = "Warning: estimated hours may be counted more than once if a ticket appears in multiple groups"

            tickets = data['groups'][0][1]
            groups = {}
            for time_rec in tickets:
                key = time_rec[query.group]
                if not key in groups:
                    groups[key] = []
                groups[key].append(time_rec)
            data['groups'] = sorted(groups.items())

        total_times = dict((k, self.format_hours(sum(rec['seconds_worked'] for rec in v))) for k, v in data['groups'])
        total_estimated_times = {}
        for key, records in data['groups']:
            seen_tickets = set()
            est = 0
            for record in records:
                # do not double-count tickets
                id = record['ticket']
                if id in seen_tickets:
                    continue
                seen_tickets.add(id)
                estimatedhours = record.get('estimatedhours') or 0
                try:
                    estimatedhours = float(estimatedhours)
                except ValueError:
                    estimatedhours = 0
                est +=  estimatedhours * 3600
            total_estimated_times[key] = self.format_hours(est)

        data['total_times'] = total_times
        data['total_estimated_times'] = total_estimated_times

        # format records
        for record in time_records:
            if 'seconds_worked' in record:
                record['seconds_worked'] = self.format_hours(record['seconds_worked']) # XXX misleading name
            if 'time_started' in record:
                record['time_started'] = self.format_date(record['time_started'])
            if 'time_submitted' in record:
                record['time_submitted'] = self.format_date(record['time_submitted'])
            

        data['query'].num_items = num_items
        data['labels'] = ticket_data['labels']
        data['labels'].update(labels)
        data['can_add_hours'] = req.perm.has_permission('TICKET_ADD_HOURS')

        data['multiproject'] = self.env.is_component_enabled(MultiprojectHours)

        from web_ui import TracUserHours
        data['user_hours'] = self.env.is_component_enabled(TracUserHours)

        # return the rss, if requested
        if req.args.get('format') == 'rss':
            return self.queryhours2rss(req, data)

        # return the csv, if requested
        if req.args.get('format') == 'csv':
            self.queryhours2csv(req, data)

        # add rss link
        rss_href = req.href(req.path_info, format='rss')
        add_link(req, 'alternate', rss_href, _('RSS Feed'),
                 'application/rss+xml', 'rss')

        # add csv link
        add_link(req, 'alternate', req.href(req.path_info, format='csv', **req.args), 'CSV', 'text/csv', 'csv')
                
        # add navigation of weeks
        prev_args = dict(req.args)        
        next_args = dict(req.args)
                
        prev_args['from_year'] = (from_date - timedelta(days=7)).year
        prev_args['from_month'] = (from_date - timedelta(days=7)).month
        prev_args['from_day'] = (from_date - timedelta(days=7)).day
        prev_args['to_year'] = from_date.year
        prev_args['to_month'] = from_date.month
        prev_args['to_day'] = from_date.day        
        
        next_args['from_year'] = to_date.year
        next_args['from_month'] = to_date.month
        next_args['from_day'] = to_date.day
        next_args['to_year'] = (to_date + timedelta(days=7)).year
        next_args['to_month'] = (to_date + timedelta(days=7)).month
        next_args['to_day'] = (to_date + timedelta(days=7)).day
        
        add_link(req, 'prev', self.get_href(query, prev_args, context.href), _('Prev Week'))
        add_link(req, 'next', self.get_href(query, next_args, context.href), _('Next Week'))                                            
        prevnext_nav(req, _('Prev Week'), _('Next Week'))
        
        add_ctxtnav(req, 'Cross-Project Hours', req.href.hours('multiproject'))
        add_ctxtnav(req, 'Hours by User', req.href.hours('user', from_day=from_date.day, 
                                                                 from_month=from_date.month, 
                                                                 from_year=from_date.year, 
                                                                 to_day=to_date.year, 
                                                                 to_month=to_date.month, 
                                                                 to_year=to_date.year))
        add_ctxtnav(req, 'Saved Queries', req.href.hours('query/list'))
        
        add_stylesheet(req, 'common/css/report.css')
        add_script(req, 'common/js/query.js')
        
        return ('hours_timeline.html', data, 'text/html')
Example #19
0
def upload_project_file(request):
    """
    上传项目文件
    :param request:
    :return:
    """
    # 获取所有用户
    user_info = get_all_users(request)

    file = request.FILES.get('file', '')
    pid = request.POST.get('pid', '')

    name = file.name
    user = request.session['id']
    import os
    try:
        # 根据时间戳存储文件
        now = int(time.time())
        ext = file.name.split('.')[-1]
        file_first_name = '.'.join(file.name.split('.')[:-1])
        file_name = file_first_name + str(now)
        file_full_name = file_name + '.' + ext
        file_path = os.path.join(os.path.dirname(__file__), 'static/appfile/',
                                 file_full_name)
        with open(file_path, 'wb') as f:
            for item in file.chunks():
                f.write(item)
    except Exception as e:
        logger.error(u'文件写入失败', e)
        return render_json({'result': False, 'message': u'文件写入失败'})

    # 获取文件大小

    try:
        size = os.path.getsize(file_path)
        new_size = formatSize(size)
    except Exception as e:
        logger.error(u"获取文件大小失败", e)
        return render_json({'result': False, 'message': u"获取文件大小失败"})
    try:
        file = ProjectFile.objects.create(project_id=pid,
                                          name=name,
                                          user=user,
                                          size=new_size,
                                          path='static/appfile/' +
                                          file_full_name)
    except Exception as e:
        logger.error(u"上传文件失败", e)
        return render_json({'result': False, 'message': u"上传文件失败"})

    file_list = []
    user = user_info[int(file.user)]
    name = user['name']
    ext = file.path.split('.')[-1]
    file_list.append({
        'fid': file.pk,
        'name': file.name,
        'size': file.size,
        'user': name,
        'path': file.path,
        'ext': ext,
        'create_time': file.create_time.strftime('%Y-%m-%d')
    })
    return render_json({'result': True, 'data': file_list})
Example #20
0
def task_index(request):
    """
    用户任务首页
    :param request:
    :return:
    """
    uid = request.session['id']
    # 获取所用用户信息
    users = get_all_users(request)
    try:
        nofinish_tasks = Task.objects.filter(status=0).filter(
            owner=uid).order_by('-level')
        finish_tasks = Task.objects.filter(status=2).filter(
            owner=uid).order_by('-level')
    except Exception as e:
        logger.error(u"查询任务失败", e)
        return render_json({'result': False})
    unfinish_task = []
    # 未完成任务
    for tasks in nofinish_tasks:
        if str(request.session['id']) in tasks.participant.split(','):
            name = tasks.name
            finish_time = tasks.finish_time.strftime('%Y-%m-%d')
            owner = tasks.owner
            user = users[int(owner)]
            avatar = user['avatar']
            level = tasks.level
            tid = tasks.pk
            unfinish_task.append({
                'name': name,
                'finish_time': finish_time,
                'avatar': avatar,
                'level': level,
                'tid': tid
            })

    finish_task = []
    # 已完成任务
    for tasks in finish_tasks:
        if str(request.session['id']) in tasks.participant.split(','):
            name = tasks.name
            finish_time = tasks.finish_time.strftime('%Y-%m-%d')
            owner = tasks.owner
            user = users[int(owner)]
            avatar = user['avatar']
            level = tasks.level
            tid = tasks.pk
            finish_task.append({
                'name': name,
                'finish_time': finish_time,
                'avatar': avatar,
                'level': level,
                'tid': tid
            })

    task_today = []
    # 今日任务
    for task in nofinish_tasks:
        if checkTime(task.begin_time, task.finish_time):
            name = task.name
            finish_time = task.finish_time.strftime('%Y-%m-%d')
            owner = task.owner
            user = users[int(owner)]
            avatar = user['avatar']
            level = task.level
            tid = task.pk
            task_today.append({
                'name': name,
                'finish_time': finish_time,
                'avatar': avatar,
                'level': level,
                'tid': tid,
                'is_finish': False
            })
    for task in finish_tasks:
        if checkTime(task.begin_time, task.finish_time):
            name = task.name
            finish_time = task.finish_time.strftime('%Y-%m-%d')
            owner = task.owner
            user = users[int(owner)]
            avatar = user['avatar']
            level = task.level
            tid = task.pk
            task_today.append({
                'name': name,
                'finish_time': finish_time,
                'avatar': avatar,
                'level': level,
                'tid': tid,
                'is_finish': True
            })

    return render(
        request, 'task_user.html', {
            'finish_task': finish_task,
            'unfinish_task': unfinish_task,
            'today_task': task_today
        })
Example #21
0
def get_project_task(request, pid):
    """
    获取项目下的任务
    :param request:
    :return:
    """
    # 获取所用用户信息
    users = get_all_users(request)

    try:
        nofinish_tasks = Task.objects.get_task_pid_unfinish(pid=pid)
        finish_tasks = Task.objects.get_task_pid_finish(pid=pid)
    except Exception as e:
        logger.error(u"查询任务失败", e)
        return render_json({'result': False})
    unfinish_task = []
    # 未完成任务
    for tasks in nofinish_tasks:
        name = tasks.name
        finish_time = tasks.finish_time.strftime('%Y-%m-%d')
        owner = tasks.owner
        user = users[int(owner)]
        avatar = user['avatar']
        level = tasks.level
        tid = tasks.pk
        unfinish_task.append({
            'name': name,
            'finish_time': finish_time,
            'avatar': avatar,
            'level': level,
            'tid': tid
        })

    finish_task = []
    # 已完成任务
    for tasks in finish_tasks:
        name = tasks.name
        finish_time = tasks.finish_time.strftime('%Y-%m-%d')
        owner = tasks.owner
        user = users[int(owner)]
        avatar = user['avatar']
        level = tasks.level
        tid = tasks.pk
        finish_task.append({
            'name': name,
            'finish_time': finish_time,
            'avatar': avatar,
            'level': level,
            'tid': tid
        })

    # 获取项目动态
    dynamic_list = []
    dynamics = get_project_dynamic(request=request, pid=pid)
    for dynamic in dynamics:
        owner = dynamic.sender_id
        user = users[int(owner)]
        avatar = user['avatar']
        dynamic_list.append({
            'content':
            dynamic.content,
            'title':
            dynamic.title,
            'create_time':
            dynamic.create_time.strftime('%Y-%m-%d'),
            'avatar':
            avatar
        })

    # 获取项目信息
    try:
        project = Project.objects.get(pk=pid)
    except Exception as e:
        logger.error(u"获取项目信息失败", e)
        finish_task = []
        unfinish_task = []
        dynamic_list = []
        project = {}

    project_info = {}
    project_info['project_name'] = project.name
    project_info['project_intro'] = project.introduction
    project_info['project_user'] = project.participant
    project_info['project_logo'] = project.logo
    if str(request.session['id']) == str(project.owner):
        project_owner = True
    else:
        project_owner = False

    return render(
        request, 'task_project.html', {
            'pid': pid,
            'project_owner': project_owner,
            'finish_task': finish_task,
            'unfinish_task': unfinish_task,
            'dynamic_list': dynamic_list,
            'project_info': project_info
        })
Example #22
0
    def display_html(self, req, query):
        """returns the HTML according to a query for /hours view"""
        db = self.env.get_db_cnx()

        # The most recent query is stored in the user session;
        orig_list = None
        orig_time = datetime.now(utc)
        query_time = int(req.session.get("query_time", 0))
        query_time = datetime.fromtimestamp(query_time, utc)
        query_constraints = unicode(query.constraints)
        if query_constraints != req.session.get("query_constraints") or query_time < orig_time - timedelta(hours=1):
            tickets = query.execute(req, db)
            # New or outdated query, (re-)initialize session vars
            req.session["query_constraints"] = query_constraints
            req.session["query_tickets"] = " ".join([str(t["id"]) for t in tickets])
        else:
            orig_list = [int(id) for id in req.session.get("query_tickets", "").split()]
            tickets = query.execute(req, db, orig_list)
            orig_time = query_time

        context = Context.from_request(req, "query")
        ticket_data = query.template_data(context, tickets, orig_list, orig_time, req)

        # For clients without JavaScript, we add a new constraint here if
        # requested
        constraints = ticket_data["clauses"][0]
        if "add" in req.args:
            field = req.args.get("add_filter")
            if field:
                constraint = constraints.setdefault(field, {})
                constraint.setdefault("values", []).append("")
                # FIXME: '' not always correct (e.g. checkboxes)

        req.session["query_href"] = query.get_href(context.href)
        req.session["query_time"] = to_timestamp(orig_time)
        req.session["query_tickets"] = " ".join([str(t["id"]) for t in tickets])

        # data dictionary for genshi
        data = {}

        # get data for saved queries
        query_id = req.args.get("query_id")
        if query_id:
            try:
                query_id = int(query_id)
            except ValueError:
                add_warning(req, "query_id should be an integer, you put '%s'" % query_id)
                query_id = None
        if query_id:
            data["query_id"] = query_id
            query_data = self.get_query(query_id)

            data["query_title"] = query_data["title"]
            data["query_description"] = query_data["description"]

        data.setdefault("report", None)
        data.setdefault("description", None)

        data["all_columns"] = query.get_all_columns() + self.get_columns()
        # Don't allow the user to remove the id column
        data["all_columns"].remove("id")
        data["all_textareas"] = query.get_all_textareas()

        # need to re-get the cols because query will remove our fields
        cols = req.args.get("col")
        if isinstance(cols, basestring):
            cols = [cols]
        if not cols:
            cols = query.get_columns() + self.get_default_columns()
        data["col"] = cols

        now = datetime.now()
        # get the date range for the query
        if "from_year" in req.args:
            from_date = get_date(req.args["from_year"], req.args.get("from_month"), req.args.get("from_day"))

        else:
            from_date = datetime(now.year, now.month, now.day)
            from_date = from_date - timedelta(days=7)  # 1 week ago, by default

        if "to_year" in req.args:
            to_date = get_date(req.args["to_year"], req.args.get("to_month"), req.args.get("to_day"), end_of_day=True)
        else:
            to_date = now

        data["prev_week"] = from_date - timedelta(days=7)
        data["months"] = list(enumerate(calendar.month_name))
        data["years"] = range(now.year, now.year - 10, -1)
        data["days"] = range(1, 32)
        data["users"] = get_all_users(self.env)
        data["cur_worker_filter"] = req.args.get("worker_filter", "*any")

        data["from_date"] = from_date
        data["to_date"] = to_date

        ticket_ids = [t["id"] for t in tickets]

        # generate data for ticket_times
        time_records = self.get_ticket_hours(
            ticket_ids, from_date=from_date, to_date=to_date, worker_filter=data["cur_worker_filter"]
        )

        data["query"] = ticket_data["query"]
        data["context"] = ticket_data["context"]
        data["row"] = ticket_data["row"]
        if "comments" in req.args.get("row", []):
            data["row"].append("comments")
        data["constraints"] = ticket_data["clauses"]

        our_labels = dict([(f["name"], f["label"]) for f in self.fields])
        labels = TicketSystem(self.env).get_ticket_field_labels()
        labels.update(our_labels)
        data["labels"] = labels

        order = req.args.get("order")
        desc = bool(req.args.get("desc"))
        data["order"] = order
        data["desc"] = desc

        headers = [
            {
                "name": col,
                "label": labels.get(col),
                "href": self.get_href(query, req.args, context.href, order=col, desc=(col == order and not desc)),
            }
            for col in cols
        ]

        data["headers"] = headers

        data["fields"] = ticket_data["fields"]
        data["modes"] = ticket_data["modes"]

        # group time records
        time_records_by_ticket = {}
        for record in time_records:
            id = record["ticket"]
            if id not in time_records_by_ticket:
                time_records_by_ticket[id] = []

            time_records_by_ticket[id].append(record)

        data["extra_group_fields"] = dict(
            ticket=dict(name="ticket", type="select", label="Ticket"),
            worker=dict(name="worker", type="select", label="Worker"),
        )

        num_items = 0
        data["groups"] = []

        # merge ticket data into ticket_time records
        for key, tickets in ticket_data["groups"]:
            ticket_times = []
            total_time = 0
            total_estimated_time = 0
            for ticket in tickets:
                records = time_records_by_ticket.get(ticket["id"], [])
                [rec.update(ticket) for rec in records]
                ticket_times += records

            # sort ticket_times, if needed
            if order in our_labels:
                ticket_times.sort(key=lambda x: x[order], reverse=desc)
            data["groups"].append((key, ticket_times))
            num_items += len(ticket_times)

        data["double_count_warning"] = ""

        # group by ticket id or other time_ticket fields if necessary
        if req.args.get("group") in data["extra_group_fields"]:
            query.group = req.args.get("group")
            if not query.group == "id":
                data[
                    "double_count_warning"
                ] = "Warning: estimated hours may be counted more than once if a ticket appears in multiple groups"

            tickets = data["groups"][0][1]
            groups = {}
            for time_rec in tickets:
                key = time_rec[query.group]
                if not key in groups:
                    groups[key] = []
                groups[key].append(time_rec)
            data["groups"] = sorted(groups.items())

        total_times = dict((k, self.format_hours(sum(rec["seconds_worked"] for rec in v))) for k, v in data["groups"])
        total_estimated_times = {}
        for key, records in data["groups"]:
            seen_tickets = set()
            est = 0
            for record in records:
                # do not double-count tickets
                id = record["ticket"]
                if id in seen_tickets:
                    continue
                seen_tickets.add(id)
                estimatedhours = record.get("estimatedhours") or 0
                try:
                    estimatedhours = float(estimatedhours)
                except ValueError:
                    estimatedhours = 0
                est += estimatedhours * 3600
            total_estimated_times[key] = self.format_hours(est)

        data["total_times"] = total_times
        data["total_estimated_times"] = total_estimated_times

        # format records
        for record in time_records:
            if "seconds_worked" in record:
                record["seconds_worked"] = self.format_hours(record["seconds_worked"])  # XXX misleading name
            if "time_started" in record:
                record["time_started"] = self.format_date(record["time_started"])
            if "time_submitted" in record:
                record["time_submitted"] = self.format_date(record["time_submitted"])

        data["query"].num_items = num_items
        data["labels"] = TicketSystem(self.env).get_ticket_field_labels()
        data["labels"].update(labels)
        data["can_add_hours"] = req.perm.has_permission("TICKET_ADD_HOURS")

        data["multiproject"] = self.env.is_component_enabled(MultiprojectHours)

        from web_ui import TracUserHours

        data["user_hours"] = self.env.is_component_enabled(TracUserHours)

        # return the rss, if requested
        if req.args.get("format") == "rss":
            return self.queryhours2rss(req, data)

        # return the csv, if requested
        if req.args.get("format") == "csv":
            self.queryhours2csv(req, data)

        # add rss link
        rss_href = req.href(req.path_info, format="rss")
        add_link(req, "alternate", rss_href, _("RSS Feed"), "application/rss+xml", "rss")

        # add csv link
        add_link(req, "alternate", req.href(req.path_info, format="csv", **req.args), "CSV", "text/csv", "csv")

        # add navigation of weeks
        prev_args = dict(req.args)
        next_args = dict(req.args)

        prev_args["from_year"] = (from_date - timedelta(days=7)).year
        prev_args["from_month"] = (from_date - timedelta(days=7)).month
        prev_args["from_day"] = (from_date - timedelta(days=7)).day
        prev_args["to_year"] = from_date.year
        prev_args["to_month"] = from_date.month
        prev_args["to_day"] = from_date.day

        next_args["from_year"] = to_date.year
        next_args["from_month"] = to_date.month
        next_args["from_day"] = to_date.day
        next_args["to_year"] = (to_date + timedelta(days=7)).year
        next_args["to_month"] = (to_date + timedelta(days=7)).month
        next_args["to_day"] = (to_date + timedelta(days=7)).day

        add_link(req, "prev", self.get_href(query, prev_args, context.href), _("Prev Week"))
        add_link(req, "next", self.get_href(query, next_args, context.href), _("Next Week"))
        prevnext_nav(req, _("Prev Week"), _("Next Week"))

        add_ctxtnav(req, "Cross-Project Hours", req.href.hours("multiproject"))
        add_ctxtnav(
            req,
            "Hours by User",
            req.href.hours(
                "user",
                from_day=from_date.day,
                from_month=from_date.month,
                from_year=from_date.year,
                to_day=to_date.year,
                to_month=to_date.month,
                to_year=to_date.year,
            ),
        )
        add_ctxtnav(req, "Saved Queries", req.href.hours("query/list"))

        add_stylesheet(req, "common/css/report.css")
        add_script(req, "common/js/query.js")

        return ("hours_timeline.html", data, "text/html")
Example #23
0
    def display_html(self, req, query):
        """returns the HTML according to a query for /hours view"""
        db = self.env.get_db_cnx()

        # The most recent query is stored in the user session;
        orig_list = None
        orig_time = datetime.now(utc)
        query_time = int(req.session.get('query_time', 0))
        query_time = datetime.fromtimestamp(query_time, utc)
        query_constraints = unicode(query.constraints)
        if query_constraints != req.session.get('query_constraints') \
                or query_time < orig_time - timedelta(hours=1):
            tickets = query.execute(req, db)
            # New or outdated query, (re-)initialize session vars
            req.session['query_constraints'] = query_constraints
            req.session['query_tickets'] = ' '.join(
                [str(t['id']) for t in tickets])
        else:
            orig_list = [
                int(id) for id in req.session.get('query_tickets', '').split()
            ]
            tickets = query.execute(req, db, orig_list)
            orig_time = query_time

        context = Context.from_request(req, 'query')
        ticket_data = query.template_data(context, tickets, orig_list,
                                          orig_time, req)

        # For clients without JavaScript, we add a new constraint here if
        # requested
        constraints = ticket_data['clauses'][0]
        if 'add' in req.args:
            field = req.args.get('add_filter')
            if field:
                constraint = constraints.setdefault(field, {})
                constraint.setdefault('values', []).append('')
                # FIXME: '' not always correct (e.g. checkboxes)

        req.session['query_href'] = query.get_href(context.href)
        req.session['query_time'] = to_timestamp(orig_time)
        req.session['query_tickets'] = ' '.join(
            [str(t['id']) for t in tickets])

        # data dictionary for genshi
        data = {}

        # get data for saved queries
        query_id = req.args.get('query_id')
        if query_id:
            try:
                query_id = int(query_id)
            except ValueError:
                add_warning(
                    req,
                    "query_id should be an integer, you put '%s'" % query_id)
                query_id = None
        if query_id:
            data['query_id'] = query_id
            query_data = self.get_query(query_id)

            data['query_title'] = query_data['title']
            data['query_description'] = query_data['description']

        data.setdefault('report', None)
        data.setdefault('description', None)

        data['all_columns'] = query.get_all_columns() + self.get_columns()
        # Don't allow the user to remove the id column
        data['all_columns'].remove('id')
        data['all_textareas'] = query.get_all_textareas()

        # need to re-get the cols because query will remove our fields
        cols = req.args.get('col')
        if isinstance(cols, basestring):
            cols = [cols]
        if not cols:
            cols = query.get_columns() + self.get_default_columns()
        data['col'] = cols

        now = datetime.now()
        # get the date range for the query
        if 'from_year' in req.args:
            from_date = get_date(req.args['from_year'],
                                 req.args.get('from_month'),
                                 req.args.get('from_day'))

        else:
            from_date = datetime(now.year, now.month, now.day)
            from_date = from_date - timedelta(days=7)  # 1 week ago, by default

        if 'to_year' in req.args:
            to_date = get_date(req.args['to_year'],
                               req.args.get('to_month'),
                               req.args.get('to_day'),
                               end_of_day=True)
        else:
            to_date = now

        data['prev_week'] = from_date - timedelta(days=7)
        data['months'] = list(enumerate(calendar.month_name))
        data['years'] = range(now.year, now.year - 10, -1)
        data['days'] = range(1, 32)
        data['users'] = get_all_users(self.env)
        data['cur_worker_filter'] = req.args.get('worker_filter', '*any')

        data['from_date'] = from_date
        data['to_date'] = to_date

        ticket_ids = [t['id'] for t in tickets]

        # generate data for ticket_times
        time_records = self.get_ticket_hours(
            ticket_ids,
            from_date=from_date,
            to_date=to_date,
            worker_filter=data['cur_worker_filter'])

        data['query'] = ticket_data['query']
        data['context'] = ticket_data['context']
        data['row'] = ticket_data['row']
        if 'comments' in req.args.get('row', []):
            data['row'].append('comments')
        data['constraints'] = ticket_data['clauses']

        our_labels = dict([(f['name'], f['label']) for f in self.fields])
        labels = TicketSystem(self.env).get_ticket_field_labels()
        labels.update(our_labels)
        data['labels'] = labels

        order = req.args.get('order')
        desc = bool(req.args.get('desc'))
        data['order'] = order
        data['desc'] = desc

        headers = [{
            'name':
            col,
            'label':
            labels.get(col),
            'href':
            self.get_href(query,
                          req.args,
                          context.href,
                          order=col,
                          desc=(col == order and not desc))
        } for col in cols]

        data['headers'] = headers

        data['fields'] = ticket_data['fields']
        data['modes'] = ticket_data['modes']

        # group time records
        time_records_by_ticket = {}
        for record in time_records:
            id = record['ticket']
            if id not in time_records_by_ticket:
                time_records_by_ticket[id] = []

            time_records_by_ticket[id].append(record)

        data['extra_group_fields'] = dict(ticket=dict(name='ticket',
                                                      type='select',
                                                      label='Ticket'),
                                          worker=dict(name='worker',
                                                      type='select',
                                                      label='Worker'))

        num_items = 0
        data['groups'] = []

        # merge ticket data into ticket_time records
        for key, tickets in ticket_data['groups']:
            ticket_times = []
            total_time = 0
            total_estimated_time = 0
            for ticket in tickets:
                records = time_records_by_ticket.get(ticket['id'], [])
                [rec.update(ticket) for rec in records]
                ticket_times += records

            # sort ticket_times, if needed
            if order in our_labels:
                ticket_times.sort(key=lambda x: x[order], reverse=desc)
            data['groups'].append((key, ticket_times))
            num_items += len(ticket_times)

        data['double_count_warning'] = ''

        # group by ticket id or other time_ticket fields if necessary
        if req.args.get('group') in data['extra_group_fields']:
            query.group = req.args.get('group')
            if not query.group == "id":
                data[
                    'double_count_warning'] = "Warning: estimated hours may be counted more than once if a ticket appears in multiple groups"

            tickets = data['groups'][0][1]
            groups = {}
            for time_rec in tickets:
                key = time_rec[query.group]
                if not key in groups:
                    groups[key] = []
                groups[key].append(time_rec)
            data['groups'] = sorted(groups.items())

        total_times = dict(
            (k, self.format_hours(sum(rec['seconds_worked'] for rec in v)))
            for k, v in data['groups'])
        total_estimated_times = {}
        for key, records in data['groups']:
            seen_tickets = set()
            est = 0
            for record in records:
                # do not double-count tickets
                id = record['ticket']
                if id in seen_tickets:
                    continue
                seen_tickets.add(id)
                estimatedhours = record.get('estimatedhours') or 0
                try:
                    estimatedhours = float(estimatedhours)
                except ValueError:
                    estimatedhours = 0
                est += estimatedhours * 3600
            total_estimated_times[key] = self.format_hours(est)

        data['total_times'] = total_times
        data['total_estimated_times'] = total_estimated_times

        # format records
        for record in time_records:
            if 'seconds_worked' in record:
                record['seconds_worked'] = self.format_hours(
                    record['seconds_worked'])  # XXX misleading name
            if 'time_started' in record:
                record['time_started'] = self.format_date(
                    record['time_started'])
            if 'time_submitted' in record:
                record['time_submitted'] = self.format_date(
                    record['time_submitted'])

        data['query'].num_items = num_items
        data['labels'] = TicketSystem(self.env).get_ticket_field_labels()
        data['labels'].update(labels)
        data['can_add_hours'] = req.perm.has_permission('TICKET_ADD_HOURS')

        data['multiproject'] = self.env.is_component_enabled(MultiprojectHours)

        from web_ui import TracUserHours
        data['user_hours'] = self.env.is_component_enabled(TracUserHours)

        # return the rss, if requested
        if req.args.get('format') == 'rss':
            return self.queryhours2rss(req, data)

        # return the csv, if requested
        if req.args.get('format') == 'csv':
            self.queryhours2csv(req, data)

        # add rss link
        rss_href = req.href(req.path_info, format='rss')
        add_link(req, 'alternate', rss_href, _('RSS Feed'),
                 'application/rss+xml', 'rss')

        # add csv link
        add_link(req, 'alternate',
                 req.href(req.path_info, format='csv', **req.args), 'CSV',
                 'text/csv', 'csv')

        # add navigation of weeks
        prev_args = dict(req.args)
        next_args = dict(req.args)

        prev_args['from_year'] = (from_date - timedelta(days=7)).year
        prev_args['from_month'] = (from_date - timedelta(days=7)).month
        prev_args['from_day'] = (from_date - timedelta(days=7)).day
        prev_args['to_year'] = from_date.year
        prev_args['to_month'] = from_date.month
        prev_args['to_day'] = from_date.day

        next_args['from_year'] = to_date.year
        next_args['from_month'] = to_date.month
        next_args['from_day'] = to_date.day
        next_args['to_year'] = (to_date + timedelta(days=7)).year
        next_args['to_month'] = (to_date + timedelta(days=7)).month
        next_args['to_day'] = (to_date + timedelta(days=7)).day

        add_link(req, 'prev', self.get_href(query, prev_args, context.href),
                 _('Prev Week'))
        add_link(req, 'next', self.get_href(query, next_args, context.href),
                 _('Next Week'))
        prevnext_nav(req, _('Prev Week'), _('Next Week'))

        add_ctxtnav(req, 'Cross-Project Hours', req.href.hours('multiproject'))
        add_ctxtnav(
            req, 'Hours by User',
            req.href.hours('user',
                           from_day=from_date.day,
                           from_month=from_date.month,
                           from_year=from_date.year,
                           to_day=to_date.year,
                           to_month=to_date.month,
                           to_year=to_date.year))
        add_ctxtnav(req, 'Saved Queries', req.href.hours('query/list'))

        add_stylesheet(req, 'common/css/report.css')
        add_script(req, 'common/js/query.js')

        return ('hours_timeline.html', data, 'text/html')