|
1 | | -from functools import partial |
2 | | -from itertools import chain, product |
3 | | -from operator import attrgetter |
| 1 | +from pythonpro.dashboard.models import TopicInteraction |
4 | 2 |
|
5 | | -import pytz |
6 | | -from django.db.models import Count, Max, Sum |
7 | | -from django.utils.datetime_safe import datetime |
8 | | - |
9 | | -from pythonpro.dashboard.models import TopicInteraction as _TopicInteracion |
10 | | -from pythonpro.modules.facade import get_entire_content_forest |
11 | | -from pythonpro.modules.models import Topic |
| 3 | +__all__ = ['has_watched_any_topic', ] |
12 | 4 |
|
13 | 5 |
|
14 | 6 | def has_watched_any_topic(user) -> bool: |
15 | | - return _TopicInteracion.objects.filter(user=user).exists() |
16 | | - |
17 | | - |
18 | | -def calculate_topic_interaction_history(user): |
19 | 7 | """ |
20 | | - Calculate user's interaction history ordered by last topic interaction |
21 | | - It will return a list of topics annotated with: |
22 | | - 1. last_interaction: last time user interacted with topic |
23 | | - 2. max_watched_time: max video time where user stopped watching |
24 | | - 3. total_watched_time: sum of all time user spent watching video |
25 | | - 4. interactions_count: number of times user started watching video |
26 | | - 5. duration: topic duration based on the min time this class had |
27 | | - :param user: |
28 | | - :return: |
| 8 | + Indicates if a user has watched any topic |
| 9 | + :param: user |
| 10 | + :return: boolean |
29 | 11 | """ |
30 | | - |
31 | | - return Topic.objects.filter( |
32 | | - topicinteraction__user=user |
33 | | - ).annotate( |
34 | | - last_interaction=Max('topicinteraction__creation'), |
35 | | - max_watched_time=Max('topicinteraction__max_watched_time'), |
36 | | - total_watched_time=Sum('topicinteraction__total_watched_time'), |
37 | | - interactions_count=Count('topicinteraction') |
38 | | - ).order_by('-last_interaction').select_related('chapter').select_related('chapter__section').select_related( |
39 | | - 'chapter__section__module')[:20] |
40 | | - |
41 | | - |
42 | | -def calculate_module_progresses(user): |
43 | | - """ |
44 | | - Calculate the user progress on all modules |
45 | | - :param user: |
46 | | - :return: |
47 | | - """ |
48 | | - # arbitrary default value for last interaction |
49 | | - default_min_time = datetime(1970, 1, 1, tzinfo=pytz.utc) |
50 | | - topic_property_defaults = { |
51 | | - 'last_interaction': default_min_time, |
52 | | - 'max_watched_time': 0, |
53 | | - 'total_watched_time': 0, |
54 | | - 'interactions_count': 0, |
55 | | - 'topics_count': 0, |
56 | | - 'finished_topics_count': 0, |
57 | | - } |
58 | | - |
59 | | - # this is here due to bug on Heroku which is not installing Python 3.8: |
60 | | - # https://sentry.io/organizations/python-pro/issues/1471675608/?project=236278&query=is%3Aunresolved |
61 | | - def sum_with_start_0(lst): |
62 | | - lst = list(lst) |
63 | | - if len(lst) == 0: |
64 | | - return 0 |
65 | | - return sum(lst) |
66 | | - |
67 | | - aggregation_functions = { |
68 | | - 'last_interaction': partial(max, default=default_min_time), |
69 | | - 'max_watched_time': sum_with_start_0, |
70 | | - 'total_watched_time': sum_with_start_0, |
71 | | - 'interactions_count': sum_with_start_0, |
72 | | - 'topics_count': sum_with_start_0, |
73 | | - 'finished_topics_count': sum_with_start_0, |
74 | | - 'duration': sum_with_start_0, |
75 | | - } |
76 | | - |
77 | | - def _aggregate_statistics(contents, content_children_property_name): |
78 | | - for content, (property_, aggregation_function) in product(contents, aggregation_functions.items()): |
79 | | - children = getattr(content, content_children_property_name) |
80 | | - setattr(content, property_, aggregation_function(map(attrgetter(property_), children))) |
81 | | - |
82 | | - def _flaten(iterable, children_property_name): |
83 | | - for i in iterable: |
84 | | - for child in getattr(i, children_property_name): |
85 | | - yield child |
86 | | - |
87 | | - def calculate_progression(content): |
88 | | - try: |
89 | | - return min(content.max_watched_time / content.duration, 1) |
90 | | - except ZeroDivisionError: |
91 | | - return 0 |
92 | | - |
93 | | - qs = Topic.objects.filter(topicinteraction__user=user).values('id').annotate( |
94 | | - last_interaction=Max('topicinteraction__creation'), |
95 | | - interactions_count=Max('topicinteraction'), |
96 | | - max_watched_time=Max('topicinteraction__max_watched_time'), |
97 | | - total_watched_time=Sum('topicinteraction__total_watched_time'), |
98 | | - children_count=Count('topicinteraction')).all() |
99 | | - |
100 | | - user_interacted_topics = {t['id']: t for t in qs} |
101 | | - modules = get_entire_content_forest() |
102 | | - all_sections = list(_flaten(modules, 'sections')) |
103 | | - all_chapters = list(_flaten(all_sections, 'chapters')) |
104 | | - all_topics = list(_flaten(all_chapters, 'topics')) |
105 | | - for topic, (property_, default_value) in product(all_topics, topic_property_defaults.items()): |
106 | | - user_interaction_data = user_interacted_topics.get(topic.id, {}) |
107 | | - setattr(topic, property_, user_interaction_data.get(property_, default_value)) |
108 | | - for topic in all_topics: |
109 | | - topic.progress = calculate_progression(topic) |
110 | | - topic.topics_count = 1 |
111 | | - watched_to_end = topic.progress > 0.99 |
112 | | - spent_half_time_watching = topic.total_watched_time * 2 > topic.duration |
113 | | - topic.finished_topics_count = 1 if (watched_to_end and spent_half_time_watching) else 0 |
114 | | - |
115 | | - contents_with_children_property_name = [ |
116 | | - (all_chapters, 'topics'), |
117 | | - (all_sections, 'chapters'), |
118 | | - (modules, 'sections') |
119 | | - ] |
120 | | - for contents, content_children_property_name in contents_with_children_property_name: |
121 | | - _aggregate_statistics(contents, content_children_property_name) |
122 | | - |
123 | | - for content in chain(all_chapters, all_sections, modules): |
124 | | - setattr(content, 'progress', calculate_progression(content)) |
125 | | - |
126 | | - return modules |
| 12 | + return TopicInteraction.objects.filter(user=user).exists() |
0 commit comments