Skip to content

Commit a7e63a0

Browse files
renzonrenzon
authored andcommitted
Implemented topic aggregation
close #1848
1 parent 91192bf commit a7e63a0

File tree

6 files changed

+164
-45
lines changed

6 files changed

+164
-45
lines changed

pythonpro/dashboard/templates/dashboard/home.html

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,38 +12,45 @@
1212
<div class="container mt-5 mb-5">
1313
<div class="row">
1414
<div class="col">
15-
<h1 class="mb-4">Dashboard</h1>
16-
<p>Confira seus últimos acessos</p>
15+
<h1 class="mb-4">Dashboard</h1>
16+
<p>Confira os dados consolidados por tópico</p>
1717
<table class="table table-striped text-center">
1818
<thead>
1919
<tr>
2020
<th scope="col">Data</th>
21-
<th scope="col">Tópico</th>
21+
<th scope="col">Módulo</th>
22+
<th scope="col">Aula</th>
2223
<th scope="col" data-toggle="tooltip" data-placement="top"
2324
title="Tempo onde parou de ver o vídeo">Parada <span
2425
class="badge badge-pill badge-dark">?</span></th>
2526
<th scope="col" data-toggle="tooltip" data-placement="top"
2627
title="Tempo total que passou vendo o vídeo">Tempo <span
2728
class="badge badge-pill badge-dark">?</span></th>
29+
<th scope="col" data-toggle="tooltip" data-placement="top"
30+
title="Número de vezes que vc começou a assistir">Visualizações <span
31+
class="badge badge-pill badge-dark">?</span></th>
2832
</tr>
2933
</thead>
3034
<tbody>
31-
{% for interaction in interactions %}
35+
{% for topic in topics %}
3236
<tr>
33-
<td>{{ interaction.creation|date:"d/m/Y" }} {{ interaction.creation|time:"H:i:s" }}</td>
34-
<td><a href="{{ interaction.get_topic_url }}">{{ interaction.get_topic_title }}</a></td>
35-
<td>{{ interaction.max_watched_time|duration }}</td>
36-
<td>{{ interaction.total_watched_time|duration }}</td>
37+
<td>{{ topic.last_interaction|date:"d/m/Y" }} {{ topic.last_interaction|time:"H:i:s" }}</td>
38+
<td><a href="{{topic.calculated_module.get_absolute_url}}">{{topic.calculated_module.title}}</a></td>
39+
<td><a href="{{ topic.get_absolute_url }}">{{ topic.title }}</a></td>
40+
<td>{{ topic.max_watched_time|duration }}</td>
41+
<td>{{ topic.total_watched_time|duration }}</td>
42+
<td>{{ topic.interactions_count }}</td>
3743
</tr>
3844
{% empty %}
3945
<tr>
40-
<td colspan="4">Você ainda não assistiu nenhum tópico</td>
46+
<td colspan="4">Ainda não existem dados agregados</td>
4147
</tr>
4248
{% endfor %}
4349

4450
</tbody>
4551
</table>
4652
</div>
4753
</div>
54+
4855
</div>
4956
{% endblock body %}

pythonpro/dashboard/tests/test_dashboard.py

Lines changed: 0 additions & 31 deletions
This file was deleted.

pythonpro/dashboard/tests/test_dashboard/__init__.py

Whitespace-only changes.
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
from datetime import datetime
2+
from typing import List
3+
4+
import pytest
5+
import pytz
6+
from django.urls import reverse
7+
from django.utils import timezone
8+
from freezegun import freeze_time
9+
from model_mommy import mommy
10+
11+
from pythonpro.dashboard.models import TopicInteraction
12+
from pythonpro.dashboard.templatetags.dashboard_tags import duration
13+
from pythonpro.django_assertions import dj_assert_contains
14+
from pythonpro.modules.models import Topic
15+
16+
17+
@pytest.fixture
18+
def interactions(logged_user, topic):
19+
with freeze_time("2019-07-22 00:00:00"):
20+
first_interaction = mommy.make(
21+
TopicInteraction,
22+
user=logged_user,
23+
topic=topic,
24+
topic_duration=125,
25+
total_watched_time=125,
26+
max_watched_time=95
27+
)
28+
29+
with freeze_time("2019-07-22 01:00:00"):
30+
second_interaction = mommy.make(
31+
TopicInteraction,
32+
user=logged_user,
33+
topic=topic,
34+
topic_duration=125,
35+
total_watched_time=34,
36+
max_watched_time=14
37+
)
38+
with freeze_time("2019-07-22 00:30:00"):
39+
third_interaction = mommy.make(
40+
TopicInteraction,
41+
user=logged_user,
42+
topic=topic,
43+
topic_duration=125,
44+
total_watched_time=64,
45+
max_watched_time=34
46+
)
47+
return [
48+
first_interaction,
49+
second_interaction,
50+
third_interaction,
51+
]
52+
53+
54+
@pytest.fixture
55+
def resp(client_with_lead, interactions):
56+
return client_with_lead.get(
57+
reverse('dashboard:home'),
58+
secure=True
59+
)
60+
61+
62+
def test_status_code(resp):
63+
return resp.status_code == 200
64+
65+
66+
def test_topic_title_is_present(resp, topic):
67+
dj_assert_contains(resp, topic.title)
68+
69+
70+
def test_table_instructions(resp, topic):
71+
dj_assert_contains(resp, 'Confira os dados consolidados por tópico')
72+
73+
74+
def test_topic_url(resp, topic: Topic):
75+
dj_assert_contains(resp, topic.get_absolute_url())
76+
77+
78+
def test_module_table_row(resp, topic: Topic):
79+
module = topic.find_module()
80+
dj_assert_contains(resp, f'<td><a href="{module.get_absolute_url()}">{module.title}</a></td>')
81+
82+
83+
def test_max_creation(resp, interactions):
84+
tz = timezone.get_current_timezone()
85+
last_interaction_utc = datetime(2019, 7, 22, 1, 0, 0, tzinfo=pytz.utc)
86+
last_interaction_local = last_interaction_utc.astimezone(tz).strftime('%d/%m/%Y %H:%M:%S')
87+
dj_assert_contains(resp, last_interaction_local)
88+
89+
90+
def test_max_watched_time(resp, interactions: List[TopicInteraction]):
91+
max_watched_time = max(interaction.max_watched_time for interaction in interactions)
92+
max_watched_time_str = duration(max_watched_time)
93+
dj_assert_contains(resp, max_watched_time_str)
94+
95+
96+
def test_total_watched_time(resp, interactions: List[TopicInteraction]):
97+
total_watched_time = sum(interaction.total_watched_time for interaction in interactions)
98+
total_watched_time_str = duration(total_watched_time)
99+
dj_assert_contains(resp, total_watched_time_str)
100+
101+
102+
def test_interactions_count(resp, interactions: List[TopicInteraction]):
103+
interactions_count = len(interactions)
104+
dj_assert_contains(resp, f'<td>{interactions_count}</td>')
105+
106+
107+
@pytest.fixture
108+
def resp_without_interactions(client_with_lead):
109+
return client_with_lead.get(
110+
reverse('dashboard:home'),
111+
secure=True
112+
)
113+
114+
115+
def test_not_existing_aggregation_msg_is_present(resp_without_interactions, topic):
116+
dj_assert_contains(resp_without_interactions, "Ainda não existem dados agregados")

pythonpro/dashboard/views.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,42 @@
11
from django.contrib.auth.decorators import login_required
2+
from django.db.models import Count, Max, Sum
23
from django.http import JsonResponse
34
from django.shortcuts import render
45
from django.views.decorators.csrf import csrf_exempt
56

67
from pythonpro.dashboard.facade import has_watched_any_topic
78
from pythonpro.dashboard.forms import TopicInteractionForm
8-
from pythonpro.dashboard.models import TopicInteraction
99
from pythonpro.domain import user_facade
10+
from pythonpro.modules.models import Topic
1011

1112

1213
@login_required
1314
def home(request):
14-
interactions = TopicInteraction.objects.select_related('topic').filter(user=request.user).order_by('-creation')[:20]
15-
return render(request, 'dashboard/home.html', {'interactions': interactions})
15+
topics = list(
16+
Topic.objects.filter(
17+
topicinteraction__user=request.user
18+
).annotate(
19+
last_interaction=Max('topicinteraction__creation')
20+
).annotate(
21+
max_watched_time=Max('topicinteraction__max_watched_time')
22+
).annotate(
23+
total_watched_time=Sum('topicinteraction__total_watched_time')
24+
).annotate(
25+
interactions_count=Count('topicinteraction')
26+
).order_by('-last_interaction').select_related('chapter').select_related('chapter__section').select_related(
27+
'chapter__section__module')[:20]
28+
)
29+
30+
for topic in topics:
31+
topic.calculated_module = topic.find_module()
32+
33+
return render(
34+
request,
35+
'dashboard/home.html',
36+
{
37+
'topics': topics,
38+
}
39+
)
1640

1741

1842
@login_required

pythonpro/modules/models.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,12 @@ def _previous_content_query_set(self):
7575
raise NotImplementedError()
7676

7777
def module_slug(self):
78+
return self.find_module().slug
79+
80+
def find_module(self):
7881
if self.parent() is None:
79-
return self.slug
80-
return self.parent().module_slug()
82+
return self
83+
return self.parent().find_module()
8184

8285

8386
class ContentWithTitleMixin(Content):

0 commit comments

Comments
 (0)