Commit 65a5ad4aceead32255bb3bbd04a3f0d715c63519
1 parent
0392a334
Exists in
master
and in
1 other branch
Small fixes
Showing
2 changed files
with
31 additions
and
71 deletions
Show diff stats
aprendizations/learnapp.py
| @@ -6,7 +6,6 @@ This is the main controller of the application. | @@ -6,7 +6,6 @@ This is the main controller of the application. | ||
| 6 | # python standard library | 6 | # python standard library |
| 7 | import asyncio | 7 | import asyncio |
| 8 | from collections import defaultdict | 8 | from collections import defaultdict |
| 9 | -# from contextlib import contextmanager # `with` statement in db sessions | ||
| 10 | from datetime import datetime | 9 | from datetime import datetime |
| 11 | import logging | 10 | import logging |
| 12 | from random import random | 11 | from random import random |
| @@ -45,18 +44,25 @@ class LearnApp(): | @@ -45,18 +44,25 @@ class LearnApp(): | ||
| 45 | ''' | 44 | ''' |
| 46 | LearnApp - application logic | 45 | LearnApp - application logic |
| 47 | 46 | ||
| 48 | - self.deps - networkx topic dependencies | ||
| 49 | - self.courses - dict {course_id: {'title': ..., | ||
| 50 | - 'description': ..., | ||
| 51 | - 'goals': ...,}, ...} | ||
| 52 | - self.factory = dict {qref: QFactory()} | ||
| 53 | - self.online - dict {student_id: {'number': ..., | ||
| 54 | - 'name': ..., | ||
| 55 | - 'state': StudentState(), | ||
| 56 | - 'counter': ...}, ...} | 47 | + self.deps = networkx topic dependencies |
| 48 | + self.courses = { | ||
| 49 | + course_id: { | ||
| 50 | + 'title': ..., | ||
| 51 | + 'description': ..., | ||
| 52 | + 'goals': ..., | ||
| 53 | + }, ... | ||
| 54 | + } | ||
| 55 | + self.factory = { qref: QFactory() } | ||
| 56 | + self.online = { | ||
| 57 | + student_id: { | ||
| 58 | + 'number': ..., | ||
| 59 | + 'name': ..., | ||
| 60 | + 'state': StudentState(), | ||
| 61 | + 'counter': ... | ||
| 62 | + }, ... | ||
| 63 | + } | ||
| 57 | ''' | 64 | ''' |
| 58 | 65 | ||
| 59 | - # ------------------------------------------------------------------------ | ||
| 60 | def __init__(self, | 66 | def __init__(self, |
| 61 | courses: str, # filename with course configurations | 67 | courses: str, # filename with course configurations |
| 62 | prefix: str, # path to topics | 68 | prefix: str, # path to topics |
| @@ -114,7 +120,6 @@ class LearnApp(): | @@ -114,7 +120,6 @@ class LearnApp(): | ||
| 114 | if check: | 120 | if check: |
| 115 | self._sanity_check_questions() | 121 | self._sanity_check_questions() |
| 116 | 122 | ||
| 117 | - # ------------------------------------------------------------------------ | ||
| 118 | def _sanity_check_questions(self) -> None: | 123 | def _sanity_check_questions(self) -> None: |
| 119 | ''' | 124 | ''' |
| 120 | Unit tests for all questions | 125 | Unit tests for all questions |
| @@ -159,7 +164,6 @@ class LearnApp(): | @@ -159,7 +164,6 @@ class LearnApp(): | ||
| 159 | raise LearnException('Sanity checks') | 164 | raise LearnException('Sanity checks') |
| 160 | logger.info(' 0 errors found.') | 165 | logger.info(' 0 errors found.') |
| 161 | 166 | ||
| 162 | - # ------------------------------------------------------------------------ | ||
| 163 | async def login(self, uid: str, password: str) -> bool: | 167 | async def login(self, uid: str, password: str) -> bool: |
| 164 | '''user login''' | 168 | '''user login''' |
| 165 | 169 | ||
| @@ -212,13 +216,11 @@ class LearnApp(): | @@ -212,13 +216,11 @@ class LearnApp(): | ||
| 212 | 216 | ||
| 213 | return pw_ok | 217 | return pw_ok |
| 214 | 218 | ||
| 215 | - # ------------------------------------------------------------------------ | ||
| 216 | def logout(self, uid: str) -> None: | 219 | def logout(self, uid: str) -> None: |
| 217 | '''User logout''' | 220 | '''User logout''' |
| 218 | del self.online[uid] | 221 | del self.online[uid] |
| 219 | logger.info('User "%s" logged out', uid) | 222 | logger.info('User "%s" logged out', uid) |
| 220 | 223 | ||
| 221 | - # ------------------------------------------------------------------------ | ||
| 222 | async def change_password(self, uid: str, password: str) -> bool: | 224 | async def change_password(self, uid: str, password: str) -> bool: |
| 223 | ''' | 225 | ''' |
| 224 | Change user Password. | 226 | Change user Password. |
| @@ -242,7 +244,6 @@ class LearnApp(): | @@ -242,7 +244,6 @@ class LearnApp(): | ||
| 242 | logger.info('User "%s" changed password', uid) | 244 | logger.info('User "%s" changed password', uid) |
| 243 | return True | 245 | return True |
| 244 | 246 | ||
| 245 | - # ------------------------------------------------------------------------ | ||
| 246 | async def check_answer(self, uid: str, answer) -> Question: | 247 | async def check_answer(self, uid: str, answer) -> Question: |
| 247 | ''' | 248 | ''' |
| 248 | Checks answer and update database. | 249 | Checks answer and update database. |
| @@ -271,7 +272,6 @@ class LearnApp(): | @@ -271,7 +272,6 @@ class LearnApp(): | ||
| 271 | 272 | ||
| 272 | return question | 273 | return question |
| 273 | 274 | ||
| 274 | - # ------------------------------------------------------------------------ | ||
| 275 | async def get_question(self, uid: str) -> Optional[Question]: | 275 | async def get_question(self, uid: str) -> Optional[Question]: |
| 276 | ''' | 276 | ''' |
| 277 | Get the question to show (current or new one) | 277 | Get the question to show (current or new one) |
| @@ -318,7 +318,6 @@ class LearnApp(): | @@ -318,7 +318,6 @@ class LearnApp(): | ||
| 318 | 318 | ||
| 319 | return question | 319 | return question |
| 320 | 320 | ||
| 321 | - # ------------------------------------------------------------------------ | ||
| 322 | def start_course(self, uid: str, course_id: str) -> None: | 321 | def start_course(self, uid: str, course_id: str) -> None: |
| 323 | '''Start course''' | 322 | '''Start course''' |
| 324 | 323 | ||
| @@ -331,9 +330,6 @@ class LearnApp(): | @@ -331,9 +330,6 @@ class LearnApp(): | ||
| 331 | else: | 330 | else: |
| 332 | logger.info('User "%s" course "%s"', uid, course_id) | 331 | logger.info('User "%s" course "%s"', uid, course_id) |
| 333 | 332 | ||
| 334 | - # ------------------------------------------------------------------------ | ||
| 335 | - # | ||
| 336 | - # ------------------------------------------------------------------------ | ||
| 337 | async def start_topic(self, uid: str, topic: str) -> None: | 333 | async def start_topic(self, uid: str, topic: str) -> None: |
| 338 | '''Start new topic''' | 334 | '''Start new topic''' |
| 339 | 335 | ||
| @@ -350,9 +346,6 @@ class LearnApp(): | @@ -350,9 +346,6 @@ class LearnApp(): | ||
| 350 | else: | 346 | else: |
| 351 | logger.info('User "%s" started topic "%s"', uid, topic) | 347 | logger.info('User "%s" started topic "%s"', uid, topic) |
| 352 | 348 | ||
| 353 | - # ------------------------------------------------------------------------ | ||
| 354 | - # | ||
| 355 | - # ------------------------------------------------------------------------ | ||
| 356 | def _add_missing_topics(self, topics: Iterable[str]) -> None: | 349 | def _add_missing_topics(self, topics: Iterable[str]) -> None: |
| 357 | ''' | 350 | ''' |
| 358 | Fill db table 'Topic' with topics from the graph, if new | 351 | Fill db table 'Topic' with topics from the graph, if new |
| @@ -365,7 +358,6 @@ class LearnApp(): | @@ -365,7 +358,6 @@ class LearnApp(): | ||
| 365 | session.commit() | 358 | session.commit() |
| 366 | logger.info('Added %d new topic(s) to the database', len(new)) | 359 | logger.info('Added %d new topic(s) to the database', len(new)) |
| 367 | 360 | ||
| 368 | - # ------------------------------------------------------------------------ | ||
| 369 | def _db_setup(self, database: str) -> None: | 361 | def _db_setup(self, database: str) -> None: |
| 370 | ''' | 362 | ''' |
| 371 | Setup and check database contents | 363 | Setup and check database contents |
| @@ -395,7 +387,6 @@ class LearnApp(): | @@ -395,7 +387,6 @@ class LearnApp(): | ||
| 395 | logger.info('%6d topics', count_topics) | 387 | logger.info('%6d topics', count_topics) |
| 396 | logger.info('%6d answers', count_answers) | 388 | logger.info('%6d answers', count_answers) |
| 397 | 389 | ||
| 398 | - # ------------------------------------------------------------------------ | ||
| 399 | def _populate_graph(self, config: Dict[str, Any]) -> None: | 390 | def _populate_graph(self, config: Dict[str, Any]) -> None: |
| 400 | ''' | 391 | ''' |
| 401 | Populates a digraph. | 392 | Populates a digraph. |
| @@ -437,12 +428,10 @@ class LearnApp(): | @@ -437,12 +428,10 @@ class LearnApp(): | ||
| 437 | # prefix/topic | 428 | # prefix/topic |
| 438 | topic['path'] = join(self.deps.graph['prefix'], tref) | 429 | topic['path'] = join(self.deps.graph['prefix'], tref) |
| 439 | 430 | ||
| 440 | - | ||
| 441 | - # ======================================================================== | 431 | + # ------------------------------------------------------------------------ |
| 442 | # methods that do not change state (pure functions) | 432 | # methods that do not change state (pure functions) |
| 443 | - # ======================================================================== | ||
| 444 | - | ||
| 445 | # ------------------------------------------------------------------------ | 433 | # ------------------------------------------------------------------------ |
| 434 | + | ||
| 446 | def _make_factory(self) -> Dict[str, QFactory]: | 435 | def _make_factory(self) -> Dict[str, QFactory]: |
| 447 | ''' | 436 | ''' |
| 448 | Buils dictionary of question factories | 437 | Buils dictionary of question factories |
| @@ -519,38 +508,31 @@ class LearnApp(): | @@ -519,38 +508,31 @@ class LearnApp(): | ||
| 519 | 508 | ||
| 520 | return factory | 509 | return factory |
| 521 | 510 | ||
| 522 | - # ------------------------------------------------------------------------ | ||
| 523 | def get_login_counter(self, uid: str) -> int: | 511 | def get_login_counter(self, uid: str) -> int: |
| 524 | '''login counter''' # FIXME | 512 | '''login counter''' # FIXME |
| 525 | return int(self.online[uid]['counter']) | 513 | return int(self.online[uid]['counter']) |
| 526 | 514 | ||
| 527 | - # ------------------------------------------------------------------------ | ||
| 528 | def get_student_name(self, uid: str) -> str: | 515 | def get_student_name(self, uid: str) -> str: |
| 529 | '''Get the username''' | 516 | '''Get the username''' |
| 530 | return self.online[uid].get('name', '') | 517 | return self.online[uid].get('name', '') |
| 531 | 518 | ||
| 532 | - # ------------------------------------------------------------------------ | ||
| 533 | def get_student_state(self, uid: str) -> List[Dict[str, Any]]: | 519 | def get_student_state(self, uid: str) -> List[Dict[str, Any]]: |
| 534 | '''Get the knowledge state of a given user''' | 520 | '''Get the knowledge state of a given user''' |
| 535 | return self.online[uid]['state'].get_knowledge_state() | 521 | return self.online[uid]['state'].get_knowledge_state() |
| 536 | 522 | ||
| 537 | - # ------------------------------------------------------------------------ | ||
| 538 | def get_student_progress(self, uid: str) -> float: | 523 | def get_student_progress(self, uid: str) -> float: |
| 539 | '''Get the current topic progress of a given user''' | 524 | '''Get the current topic progress of a given user''' |
| 540 | return float(self.online[uid]['state'].get_topic_progress()) | 525 | return float(self.online[uid]['state'].get_topic_progress()) |
| 541 | 526 | ||
| 542 | - # ------------------------------------------------------------------------ | ||
| 543 | def get_current_question(self, uid: str) -> Optional[Question]: | 527 | def get_current_question(self, uid: str) -> Optional[Question]: |
| 544 | '''Get the current question of a given user''' | 528 | '''Get the current question of a given user''' |
| 545 | question: Optional[Question] = self.online[uid]['state'].get_current_question() | 529 | question: Optional[Question] = self.online[uid]['state'].get_current_question() |
| 546 | return question | 530 | return question |
| 547 | 531 | ||
| 548 | - # ------------------------------------------------------------------------ | ||
| 549 | def get_current_question_id(self, uid: str) -> str: | 532 | def get_current_question_id(self, uid: str) -> str: |
| 550 | '''Get id of the current question for a given user''' | 533 | '''Get id of the current question for a given user''' |
| 551 | return str(self.online[uid]['state'].get_current_question()['qid']) | 534 | return str(self.online[uid]['state'].get_current_question()['qid']) |
| 552 | 535 | ||
| 553 | - # ------------------------------------------------------------------------ | ||
| 554 | def get_student_question_type(self, uid: str) -> str: | 536 | def get_student_question_type(self, uid: str) -> str: |
| 555 | '''Get type of the current question for a given user''' | 537 | '''Get type of the current question for a given user''' |
| 556 | return str(self.online[uid]['state'].get_current_question()['type']) | 538 | return str(self.online[uid]['state'].get_current_question()['type']) |
| @@ -559,12 +541,10 @@ class LearnApp(): | @@ -559,12 +541,10 @@ class LearnApp(): | ||
| 559 | # def get_student_topic(self, uid: str) -> str: | 541 | # def get_student_topic(self, uid: str) -> str: |
| 560 | # return str(self.online[uid]['state'].get_current_topic()) | 542 | # return str(self.online[uid]['state'].get_current_topic()) |
| 561 | 543 | ||
| 562 | - # ------------------------------------------------------------------------ | ||
| 563 | def get_student_course_title(self, uid: str) -> str: | 544 | def get_student_course_title(self, uid: str) -> str: |
| 564 | '''get the title of the current course for a given user''' | 545 | '''get the title of the current course for a given user''' |
| 565 | return str(self.online[uid]['state'].get_current_course_title()) | 546 | return str(self.online[uid]['state'].get_current_course_title()) |
| 566 | 547 | ||
| 567 | - # ------------------------------------------------------------------------ | ||
| 568 | def get_current_course_id(self, uid: str) -> Optional[str]: | 548 | def get_current_course_id(self, uid: str) -> Optional[str]: |
| 569 | '''get the current course (id) of a given user''' | 549 | '''get the current course (id) of a given user''' |
| 570 | cid: Optional[str] = self.online[uid]['state'].get_current_course_id() | 550 | cid: Optional[str] = self.online[uid]['state'].get_current_course_id() |
| @@ -574,7 +554,6 @@ class LearnApp(): | @@ -574,7 +554,6 @@ class LearnApp(): | ||
| 574 | # def get_topic_name(self, ref: str) -> str: | 554 | # def get_topic_name(self, ref: str) -> str: |
| 575 | # return str(self.deps.nodes[ref]['name']) | 555 | # return str(self.deps.nodes[ref]['name']) |
| 576 | 556 | ||
| 577 | - # ------------------------------------------------------------------------ | ||
| 578 | def get_current_public_dir(self, uid: str) -> str: | 557 | def get_current_public_dir(self, uid: str) -> str: |
| 579 | ''' | 558 | ''' |
| 580 | Get the path for the 'public' directory of the current topic of the | 559 | Get the path for the 'public' directory of the current topic of the |
| @@ -586,21 +565,18 @@ class LearnApp(): | @@ -586,21 +565,18 @@ class LearnApp(): | ||
| 586 | prefix: str = self.deps.graph['prefix'] | 565 | prefix: str = self.deps.graph['prefix'] |
| 587 | return join(prefix, topic, 'public') | 566 | return join(prefix, topic, 'public') |
| 588 | 567 | ||
| 589 | - # ------------------------------------------------------------------------ | ||
| 590 | def get_courses(self) -> Dict[str, Dict[str, Any]]: | 568 | def get_courses(self) -> Dict[str, Dict[str, Any]]: |
| 591 | ''' | 569 | ''' |
| 592 | Get dictionary with all courses {'course1': {...}, 'course2': {...}} | 570 | Get dictionary with all courses {'course1': {...}, 'course2': {...}} |
| 593 | ''' | 571 | ''' |
| 594 | return self.courses | 572 | return self.courses |
| 595 | 573 | ||
| 596 | - # ------------------------------------------------------------------------ | ||
| 597 | def get_course(self, course_id: str) -> Dict[str, Any]: | 574 | def get_course(self, course_id: str) -> Dict[str, Any]: |
| 598 | ''' | 575 | ''' |
| 599 | Get dictionary {'title': ..., 'description':..., 'goals':...} | 576 | Get dictionary {'title': ..., 'description':..., 'goals':...} |
| 600 | ''' | 577 | ''' |
| 601 | return self.courses[course_id] | 578 | return self.courses[course_id] |
| 602 | 579 | ||
| 603 | - # ------------------------------------------------------------------------ | ||
| 604 | def get_rankings(self, uid: str, cid: str) -> List[Tuple[str, str, float]]: | 580 | def get_rankings(self, uid: str, cid: str) -> List[Tuple[str, str, float]]: |
| 605 | ''' | 581 | ''' |
| 606 | Returns rankings for a certain cid (course_id). | 582 | Returns rankings for a certain cid (course_id). |
| @@ -639,5 +615,3 @@ class LearnApp(): | @@ -639,5 +615,3 @@ class LearnApp(): | ||
| 639 | for u, name in students | 615 | for u, name in students |
| 640 | if u in progress and (len(u) > 2 or len(uid) <= 2)), | 616 | if u in progress and (len(u) > 2 or len(uid) <= 2)), |
| 641 | key=lambda x: x[2], reverse=True) | 617 | key=lambda x: x[2], reverse=True) |
| 642 | - | ||
| 643 | - # ------------------------------------------------------------------------ |
aprendizations/templates/maintopics-table.html
| @@ -105,23 +105,23 @@ | @@ -105,23 +105,23 @@ | ||
| 105 | <tr> | 105 | <tr> |
| 106 | <th scope="row" class="text-muted text-center"> | 106 | <th scope="row" class="text-muted text-center"> |
| 107 | {% if t['type']=='chapter' %} | 107 | {% if t['type']=='chapter' %} |
| 108 | - <i class="bi bi-flag-fill"></i> | 108 | + <h5><i class="bi bi-flag-fill"></i></h5> |
| 109 | {% elif t['type']=='learn' %} | 109 | {% elif t['type']=='learn' %} |
| 110 | - <i class="bi bi-book"></i> | 110 | + <h5><i class="bi bi-book"></i></h5> |
| 111 | {% else %} | 111 | {% else %} |
| 112 | - <i class="bi bi-pencil"></i> | 112 | + <h5><i class="bi bi-pencil"></i></h5> |
| 113 | {% end %} | 113 | {% end %} |
| 114 | </th> | 114 | </th> |
| 115 | <td> | 115 | <td> |
| 116 | <div class="text-muted"> | 116 | <div class="text-muted"> |
| 117 | {% if t['ref'] not in course['goals'] %} | 117 | {% if t['ref'] not in course['goals'] %} |
| 118 | - <i class="bi bi-puzzle-fill"></i> | 118 | + <h5><i class="bi bi-puzzle-fill"></i></h5> |
| 119 | {% end %} | 119 | {% end %} |
| 120 | {{ t['name'] }} | 120 | {{ t['name'] }} |
| 121 | </div> | 121 | </div> |
| 122 | </td> | 122 | </td> |
| 123 | <td class="text-center"> | 123 | <td class="text-center"> |
| 124 | - <i class="bi bi-lock-fill text-muted"></i> | 124 | + <h5><i class="bi bi-lock-fill text-muted"></i></h5> |
| 125 | </td> | 125 | </td> |
| 126 | </tr> | 126 | </tr> |
| 127 | 127 | ||
| @@ -131,30 +131,22 @@ | @@ -131,30 +131,22 @@ | ||
| 131 | <th scope="row" class="text-primary text-center"> | 131 | <th scope="row" class="text-primary text-center"> |
| 132 | {% if t['type']=='chapter' %} | 132 | {% if t['type']=='chapter' %} |
| 133 | <span class="text-nowrap" data-toggle="tooltip" data-placement="bottom" title="Fim do capítulo"> | 133 | <span class="text-nowrap" data-toggle="tooltip" data-placement="bottom" title="Fim do capítulo"> |
| 134 | - <h5> | ||
| 135 | - <i class="bi bi-flag-fill"></i> | ||
| 136 | - </h5> | 134 | + <h5><i class="bi bi-flag-fill"></i></h5> |
| 137 | </span> | 135 | </span> |
| 138 | {% elif t['type']=='learn' %} | 136 | {% elif t['type']=='learn' %} |
| 139 | <span class="text-nowrap" data-toggle="tooltip" data-placement="bottom" title="Texto com matéria"> | 137 | <span class="text-nowrap" data-toggle="tooltip" data-placement="bottom" title="Texto com matéria"> |
| 140 | - <h5> | ||
| 141 | - <i class="bi bi-book"></i> | ||
| 142 | - </h5> | 138 | + <h5><i class="bi bi-book"></i></h5> |
| 143 | </span> | 139 | </span> |
| 144 | {% else %} | 140 | {% else %} |
| 145 | <span class="text-nowrap" data-toggle="tooltip" data-placement="bottom" title="Exercícios"> | 141 | <span class="text-nowrap" data-toggle="tooltip" data-placement="bottom" title="Exercícios"> |
| 146 | - <h5> | ||
| 147 | - <i class="bi bi-pencil"></i> | ||
| 148 | - </h5> | 142 | + <h5><i class="bi bi-pencil"></i></h5> |
| 149 | </span> | 143 | </span> |
| 150 | {% end %} | 144 | {% end %} |
| 151 | </th> | 145 | </th> |
| 152 | <td class="text-primary"> | 146 | <td class="text-primary"> |
| 153 | {% if t['ref'] not in course['goals'] %} | 147 | {% if t['ref'] not in course['goals'] %} |
| 154 | <span class="text-nowrap" data-toggle="tooltip" data-placement="bottom" title="Pré-requisito para este curso"> | 148 | <span class="text-nowrap" data-toggle="tooltip" data-placement="bottom" title="Pré-requisito para este curso"> |
| 155 | - <h5> | ||
| 156 | - <i class="bi bi-puzzle-fill"></i> | ||
| 157 | - </h5> | 149 | + <h5><i class="bi bi-puzzle-fill"></i></h5> |
| 158 | </span> | 150 | </span> |
| 159 | {% end %} | 151 | {% end %} |
| 160 | {{ t['name'] }} | 152 | {{ t['name'] }} |
| @@ -162,18 +154,12 @@ | @@ -162,18 +154,12 @@ | ||
| 162 | 154 | ||
| 163 | <td class="text-center"> | 155 | <td class="text-center"> |
| 164 | {% if t['level'] < 0.01 %} | 156 | {% if t['level'] < 0.01 %} |
| 165 | - <h5> | ||
| 166 | - <i class="bi bi-unlock-fill text-success"></i> | ||
| 167 | - </h5> | 157 | + <h5><i class="bi bi-unlock-fill text-success"></i></h5> |
| 168 | {% elif t['type']=='chapter' %} | 158 | {% elif t['type']=='chapter' %} |
| 169 | - <h5> | ||
| 170 | - <i class="bi bi-award-fill"></i> | ||
| 171 | - </h5> | 159 | + <h5><i class="bi bi-award-fill"></i></h5> |
| 172 | {% else %} | 160 | {% else %} |
| 173 | <span class="text-nowrap text-warning" data-toggle="tooltip" data-placement="bottom" title="{{round(t['level']*5, 3)}}"> | 161 | <span class="text-nowrap text-warning" data-toggle="tooltip" data-placement="bottom" title="{{round(t['level']*5, 3)}}"> |
| 174 | - <h5> | ||
| 175 | - {{ int(t['level']*5+0.25)*'<i class="bi bi-star-fill"></i>' }}{% if int(t['level']*5+0.25) < 5 %}{{'<i class="bi bi-star-half"></i>' if 0.25 <= t['level']*5-int(t['level']*5) < 0.75 else '<i class="bi bi-star"></i>'}}{% end %}{{ (4-int(t['level']*5+0.25))*'<i class="bi bi-star"></i>' }} | ||
| 176 | - </h5> | 162 | + <h5>{{ int(t['level']*5+0.25)*'<i class="bi bi-star-fill"></i>' }}{% if int(t['level']*5+0.25) < 5 %}{{'<i class="bi bi-star-half"></i>' if 0.25 <= t['level']*5-int(t['level']*5) < 0.75 else '<i class="bi bi-star"></i>'}}{% end %}{{ (4-int(t['level']*5+0.25))*'<i class="bi bi-star"></i>' }}</h5> |
| 177 | </span> | 163 | </span> |
| 178 | {% end %} <!-- if --> | 164 | {% end %} <!-- if --> |
| 179 | </td> | 165 | </td> |