Commit 353ca74cdf1957898ed49bd6e900f8f11709454e

Authored by Miguel Barão
1 parent 2826a601
Exists in master and in 1 other branch dev

New rankings page

aprendizations/learnapp.py
... ... @@ -378,3 +378,35 @@ class LearnApp(object):
378 378 topic = self.online[uid]['state'].get_current_topic()
379 379 prefix = self.deps.graph['prefix']
380 380 return path.join(prefix, topic, 'public')
  381 +
  382 + # ------------------------------------------------------------------------
  383 + def get_rankings(self, uid):
  384 + logger.info(f'User {uid} get rankings')
  385 + with self.db_session() as s:
  386 + students = s.query(Student.id, Student.name).all()
  387 + student_topics = s.query(StudentTopic.student_id,
  388 + StudentTopic.topic_id,
  389 + StudentTopic.level,
  390 + StudentTopic.date).all()
  391 + total_topics = s.query(Topic).count()
  392 +
  393 + rankings = {s[0]: 0.0 for s in students}
  394 + now = datetime.now()
  395 + for uid, topic, level, date in student_topics:
  396 + date = datetime.strptime(date, "%Y-%m-%d %H:%M:%S.%f")
  397 + rankings[uid] += level**(now - date).days / total_topics
  398 +
  399 + rankings = [(uid, name, rankings[uid])
  400 + for uid, name in students if uid != '0']
  401 + return sorted(rankings, key=lambda x: x[2], reverse=True)
  402 +
  403 + def get_performance(self):
  404 + perf = {}
  405 + with self.db_session() as s:
  406 + students = s.query(Student.id).all()
  407 + for uid, in students:
  408 + ans = s.query(Answer).filter_by(student_id=uid)
  409 + total = ans.count()
  410 + right = ans.filter_by(grade=1.0).count()
  411 + perf[uid] = right / total if total > 0 else 0.0
  412 + return perf
... ...
aprendizations/serve.py
... ... @@ -49,6 +49,7 @@ class WebApplication(tornado.web.Application):
49 49 (r'/logout', LogoutHandler),
50 50 (r'/change_password', ChangePasswordHandler),
51 51 (r'/question', QuestionHandler), # renders each question
  52 + (r'/rankings', RankingsHandler), # student rankings
52 53 (r'/topic/(.+)', TopicHandler), # start a topic
53 54 (r'/file/(.+)', FileHandler), # serve files
54 55 (r'/', RootHandler), # show list of topics
... ... @@ -87,6 +88,19 @@ class BaseHandler(tornado.web.RequestHandler):
87 88 return uid
88 89  
89 90  
  91 +class RankingsHandler(BaseHandler):
  92 + @tornado.web.authenticated
  93 + def get(self):
  94 + uid = self.current_user
  95 + rankings = self.learn.get_rankings(uid)
  96 + performance = self.learn.get_performance()
  97 + self.render('rankings.html',
  98 + uid=uid,
  99 + name=self.learn.get_student_name(uid),
  100 + rankings=rankings,
  101 + performance=performance)
  102 +
  103 +
90 104 # ----------------------------------------------------------------------------
91 105 # /auth/login and /auth/logout
92 106 # ----------------------------------------------------------------------------
... ...
aprendizations/templates/maintopics-table.html
... ... @@ -37,6 +37,9 @@
37 37 <li class="nav-item active">
38 38 <a class="nav-link" href="#">Tópicos<span class="sr-only">(current)</span></a>
39 39 </li>
  40 + <li class="nav-item">
  41 + <a class="nav-link" href="/rankings">Classificação</a>
  42 + </li>
40 43 </ul>
41 44  
42 45 <ul class="navbar-nav">
... ... @@ -97,6 +100,7 @@
97 100 {{ t['name'] }}
98 101 </div>
99 102 {% else %}
  103 + <i class="fas fa-pencil-alt"></i>&nbsp;
100 104 {{ t['name'] }}
101 105 {% end %}
102 106 </td>
... ...
aprendizations/templates/rankings.html 0 → 100644
... ... @@ -0,0 +1,133 @@
  1 +{% autoescape %}
  2 +
  3 +<!doctype html>
  4 +<html lang="pt-PT">
  5 +<head>
  6 + <title>iLearn</title>
  7 + <link rel="icon" href="/static/favicon.ico">
  8 +
  9 + <meta charset="utf-8">
  10 + <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  11 + <meta name="author" content="Miguel Barão">
  12 +
  13 +<!-- Styles -->
  14 + <link rel="stylesheet" href="/static/mdbootstrap/css/bootstrap.min.css">
  15 + <link rel="stylesheet" href="/static/mdbootstrap/css/mdb.min.css">
  16 + <link rel="stylesheet" href="/static/css/maintopics.css">
  17 +
  18 +<!-- Scripts -->
  19 + <script defer src="/static/mdbootstrap/js/jquery-3.3.1.min.js"></script>
  20 + <script defer src="/static/mdbootstrap/js/popper.min.js"></script>
  21 + <script defer src="/static/mdbootstrap/js/bootstrap.min.js"></script>
  22 + <script defer src="/static/mdbootstrap/js/mdb.min.js"></script>
  23 + <script defer src="/static/fontawesome-free/js/all.min.js"></script>
  24 + <script defer src="/static/js/maintopics.js"></script>
  25 +
  26 +</head>
  27 +<!-- ===================================================================== -->
  28 +<body>
  29 +<nav class="navbar navbar-expand-sm fixed-top navbar-dark bg-primary">
  30 + <img src="/static/logo_horizontal.png" height="48" width="120" class="navbar-brand" alt="UEvora">
  31 + <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarText" aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
  32 + <span class="navbar-toggler-icon"></span>
  33 + </button>
  34 +
  35 + <div class="collapse navbar-collapse" id="navbarText">
  36 + <ul class="navbar-nav mr-auto">
  37 + <li class="nav-item">
  38 + <a class="nav-link" href="/">Tópicos</a>
  39 + </li>
  40 + <li class="nav-item active">
  41 + <a class="nav-link" href="/rankings">Classificação<span class="sr-only">(current)</span></a>
  42 + </li>
  43 + </ul>
  44 +
  45 + <ul class="navbar-nav">
  46 + <li class="nav-item dropdown">
  47 + <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
  48 + <i class="fas fa-user" aria-hidden="true"></i>
  49 + <span id="name">{{ escape(name) }}</span>
  50 + <span class="caret"></span>
  51 + </a>
  52 + <div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
  53 + <a class="dropdown-item" data-toggle="modal" data-target="#password_modal">Mudar Password</a>
  54 + <div class="dropdown-divider"></div>
  55 + <a class="dropdown-item" href="/logout">Sair</a>
  56 + </div>
  57 + </li>
  58 + </ul>
  59 + </div>
  60 +</nav>
  61 +<!-- ===================================================================== -->
  62 +<div class="container">
  63 +<h4>Classificação geral</h4>
  64 +<table class="table table-hover">
  65 + <col width="100">
  66 + <thead>
  67 + <tr>
  68 + <th># Posição</th>
  69 + <th>Aluno</th>
  70 + <th>Progresso</th>
  71 + </tr>
  72 + </thead>
  73 + <tbody>
  74 + {% for i,r in enumerate(rankings) %}
  75 + {% if r[0] == uid %}
  76 + <tr class="table-primary"> <!-- this is me -->
  77 + {% elif r[2] == 0 %}
  78 + <tr class="table-secondary"> <!-- level = 0 -->
  79 + {% else %}
  80 + <tr>
  81 + {% end %}
  82 + <td class="text-center"> <!-- rank -->
  83 + <strong>
  84 + {{'<i class="fas fa-crown fa-2x text-warning"></i>' if i==0 else i+1}}
  85 + </strong>
  86 + </td>
  87 + <td> <!-- student name -->
  88 + {{ ' '.join(r[1].split()[n] for n in (0,-1)) }}
  89 + &nbsp;
  90 + {{ '<i class="fas fa-brain fa-2x text-success" title="Mais de 75% das respostas correctas"></i>' if performance[r[0]] > 0.75 else '' }}
  91 + {{ '<i class="fas fa-frown text-secondary" title="Menos de 50% das respostas correctas" ></i>' if 0.0 < performance[r[0]] < 0.5 else '' }}
  92 + </td>
  93 + <td> <!-- progress -->
  94 + <div class="progress">
  95 + <div class="progress-bar" role="progressbar" style="width: {{100*r[2]}}%;" aria-valuenow="{{round(100*r[2])}}%" aria-valuemin="0" aria-valuemax="100">{{round(100*r[2])}}%</div>
  96 + </div>
  97 + </td>
  98 + </tr>
  99 + {% end %}
  100 + </tbody>
  101 +</table>
  102 +<!-- === Change Password Modal =========================================== -->
  103 +
  104 +<div id="password_modal" class="modal fade" tabindex="-1" role="dialog">
  105 + <div class="modal-dialog" role="document">
  106 + <div class="modal-content">
  107 +<!-- header -->
  108 + <div class="modal-header">
  109 + <h5 class="modal-title">Alterar Password</h5>
  110 + </div>
  111 +<!-- body -->
  112 + <div class="modal-body">
  113 + <div class="control-group">
  114 + <label for="new_password" class="control-label">Introduza a nova password:</label>
  115 + <div class="controls">
  116 + <input type="password" id="new_password" name="new_password" autocomplete="new-password">
  117 + </div>
  118 + </div>
  119 + </div>
  120 +<!-- footer -->
  121 + <div class="modal-footer">
  122 + <button type="button" class="btn btn-default" data-dismiss="modal">Cancelar</button>
  123 + <button id="change_password" type="button" class="btn btn-danger" data-dismiss="modal">Alterar</button>
  124 + </div>
  125 +
  126 + </div><!-- /.modal-content -->
  127 + </div><!-- /.modal-dialog -->
  128 +</div>
  129 +
  130 +<!-- /.modal -->
  131 +
  132 +</body>
  133 +</html>
... ...