|
|
@@ -3,6 +3,7 @@ from datetime import datetime, timedelta
|
|
3
|
3
|
import functools
|
|
4
|
4
|
import math
|
|
5
|
5
|
import random
|
|
|
6
|
+from time import clock
|
|
6
|
7
|
|
|
7
|
8
|
from django import forms
|
|
8
|
9
|
from django.contrib.auth.decorators import login_required
|
|
|
@@ -22,6 +23,8 @@ from babel.dates import format_timedelta, format_datetime
|
|
22
|
23
|
from graphos.renderers import gchart
|
|
23
|
24
|
from graphos.sources.model import ModelDataSource
|
|
24
|
25
|
from graphos.sources.simple import SimpleDataSource
|
|
|
26
|
+import numpy as np
|
|
|
27
|
+import pandas as pd
|
|
25
|
28
|
|
|
26
|
29
|
from counter.models import *
|
|
27
|
30
|
from counter.utils import parseSeumReason
|
|
|
@@ -46,6 +49,7 @@ def index(request):
|
|
46
|
49
|
# This person never had the seum
|
|
47
|
50
|
myCounter.lastReset = Reset()
|
|
48
|
51
|
myCounter.lastReset.delta = no_seum_delta
|
|
|
52
|
+ myCounter.lastReset.formatted_delta = format_timedelta(myCounter.lastReset.delta, locale=get_language(), threshold=1)
|
|
49
|
53
|
myCounter.lastReset.noSeum = True
|
|
50
|
54
|
else:
|
|
51
|
55
|
myCounter.lastReset = myLastReset
|
|
|
@@ -58,8 +62,7 @@ def index(request):
|
|
58
|
62
|
myCounter.likeCount = len(likesMe)
|
|
59
|
63
|
if myCounter.likeCount > 0:
|
|
60
|
64
|
myCounter.likersString = ", ".join([like.liker.trigramme for like in likesMe])
|
|
61
|
|
-
|
|
62
|
|
- myCounter.lastReset.formatted_delta = arrow.Arrow.fromdatetime(myCounter.lastReset.timestamp).humanize(locale=get_language())
|
|
|
65
|
+ myCounter.lastReset.formatted_delta = arrow.Arrow.fromdatetime(myCounter.lastReset.timestamp).humanize(locale=get_language())
|
|
63
|
66
|
|
|
64
|
67
|
except Counter.DoesNotExist:
|
|
65
|
68
|
return HttpResponseRedirect(reverse('login'))
|
|
|
@@ -79,6 +82,7 @@ def index(request):
|
|
79
|
82
|
if len(lastReset) == 0: # This person never had the seum
|
|
80
|
83
|
counter.lastReset = Reset()
|
|
81
|
84
|
counter.lastReset.delta = no_seum_delta
|
|
|
85
|
+ counter.lastReset.formatted_delta = format_timedelta(counter.lastReset.delta, locale=get_language(), threshold=1)
|
|
82
|
86
|
counter.lastReset.noSeum = True
|
|
83
|
87
|
counter.lastReset.likes_count = -1
|
|
84
|
88
|
counter.CSSclass = "warning"
|
|
|
@@ -91,8 +95,7 @@ def index(request):
|
|
91
|
95
|
counter.lastReset.selfSeum = False
|
|
92
|
96
|
# Now we compute the duration since the reset
|
|
93
|
97
|
counter.lastReset.noSeum = False
|
|
94
|
|
- counter.lastReset.delta = datetime.now(
|
|
95
|
|
- ) - counter.lastReset.timestamp.replace(tzinfo=None)
|
|
|
98
|
+ counter.lastReset.delta = datetime.now() - counter.lastReset.timestamp.replace(tzinfo=None)
|
|
96
|
99
|
# Defining CSS attributes for the counter
|
|
97
|
100
|
counter.CSSclass = 'primary' if counter == myCounter else 'default'
|
|
98
|
101
|
# Computing the total number of likes for this counter
|
|
|
@@ -101,9 +104,9 @@ def index(request):
|
|
101
|
104
|
counter.alreadyLiked = myCounter in likesMe
|
|
102
|
105
|
if counter.lastReset.likes_count > 0:
|
|
103
|
106
|
counter.likersString = ", ".join([like.liker.trigramme for like in likesMe])
|
|
|
107
|
+ counter.lastReset.formatted_delta = arrow.Arrow.fromdatetime(counter.lastReset.timestamp).humanize(locale=get_language())
|
|
104
|
108
|
|
|
105
|
|
- counter.lastReset.formatted_delta = format_timedelta(
|
|
106
|
|
- counter.lastReset.delta, locale='fr', threshold=1)
|
|
|
109
|
+ counter.likeCount = counter.lastReset.likes_count
|
|
107
|
110
|
counter.isHidden = 'hidden'
|
|
108
|
111
|
|
|
109
|
112
|
if myCounter.sort_by_score:
|
|
|
@@ -116,32 +119,60 @@ def index(request):
|
|
116
|
119
|
else:
|
|
117
|
120
|
counters = sorted(counters, key=lambda t: + t.lastReset.delta.total_seconds())
|
|
118
|
121
|
|
|
|
122
|
+
|
|
|
123
|
+ # ### GRAPHS ###
|
|
|
124
|
+ resets_raw = list(Reset.objects.select_related('who', 'counter').annotate(likes_count=Count('likes')))
|
|
|
125
|
+ likes_raw = list(Like.objects.select_related('liker', 'reset__counter').all())
|
|
|
126
|
+ hashtags_raw = list(Hashtag.objects.select_related('keyword').all())
|
|
|
127
|
+ # Prepare pandas.DataFrames to efficiently process the data
|
|
|
128
|
+ # About the counters
|
|
|
129
|
+ resets_cols = ['date', 'counter', 'counter_trigram', 'who', 'who_trigram', 'reason', 'likes_count']
|
|
|
130
|
+ resets_data = [[r.timestamp, r.counter.id, r.counter.trigramme, r.who, r.who, r.reason, r.likes_count] for r in resets_raw]
|
|
|
131
|
+ for r in resets_data:
|
|
|
132
|
+ r[3] = 0 if r[3] is None else r[3].id
|
|
|
133
|
+ r[4] = '' if r[4] is None else r[4].trigramme
|
|
|
134
|
+ resets_df = pd.DataFrame(resets_data, columns=resets_cols)
|
|
|
135
|
+ resets_df['timestamp'] = resets_df.date.map(lambda d: d.timestamp())
|
|
|
136
|
+ resets_df['self_seum'] = (resets_df.who.eq(np.zeros(resets_df.shape[0])) | resets_df.who.eq(resets_df.counter)).map(float)
|
|
|
137
|
+ resets_df['formatted_delta'] = resets_df.date.map(lambda d: arrow.Arrow.fromdatetime(d).humanize(locale=get_language()))
|
|
|
138
|
+ # About the likes
|
|
|
139
|
+ likes_cols = ['liker', 'liker_trigram', 'counter', 'counter_trigram']
|
|
|
140
|
+ likes_data = [[l.liker.id, l.liker.trigramme, l.reset.counter.id, l.reset.counter.trigramme] for l in likes_raw]
|
|
|
141
|
+ likes_df = pd.DataFrame(likes_data, columns=likes_cols)
|
|
|
142
|
+ # About the hashtags
|
|
|
143
|
+ hashtags_cols = ['keyword']
|
|
|
144
|
+ hashtags_data = [[h.keyword.text] for h in hashtags_raw]
|
|
|
145
|
+ hashtags_df = pd.DataFrame(hashtags_data, columns=hashtags_cols)
|
|
|
146
|
+
|
|
|
147
|
+
|
|
119
|
148
|
# Timeline graph
|
|
120
|
|
- resets = Reset.objects.select_related('who', 'counter').filter(timestamp__gte=timezone.now() - timedelta(days=1))
|
|
121
|
|
- if resets.count() == 0:
|
|
|
149
|
+ timeline_resets = resets_df[resets_df.date > (datetime.now() - timedelta(days=1))].copy().reset_index()
|
|
|
150
|
+ if timeline_resets.shape[0] == 0:
|
|
122
|
151
|
noTimeline = True
|
|
123
|
152
|
line_chart = None
|
|
124
|
153
|
else:
|
|
125
|
154
|
noTimeline = False
|
|
126
|
|
- for reset in resets:
|
|
127
|
|
- reset.timestamp = {
|
|
128
|
|
- 'v': reset.timestamp.timestamp(),
|
|
129
|
|
- 'f': "Il y a " + format_timedelta(datetime.now() -
|
|
130
|
|
- reset.timestamp.replace(
|
|
131
|
|
- tzinfo=None),
|
|
132
|
|
- locale='fr', threshold=1)
|
|
133
|
|
- }
|
|
134
|
|
- if (reset.who is None or
|
|
135
|
|
- reset.who.id == reset.counter.id):
|
|
136
|
|
- reset.Seum = {'v': 0,
|
|
137
|
|
- 'f': reset.counter.trigramme +
|
|
138
|
|
- " : " + reset.reason}
|
|
|
155
|
+
|
|
|
156
|
+ # Construct legend for timeline dots
|
|
|
157
|
+ legend_ = np.zeros(timeline_resets.shape[0], dtype=np.object)
|
|
|
158
|
+ for i in range(timeline_resets.shape[0]):
|
|
|
159
|
+ row = timeline_resets.iloc[i]
|
|
|
160
|
+ if row['self_seum'] == 1:
|
|
|
161
|
+ legend_[i] = _('%(counter)s: %(reason)s') % {'counter': row['counter_trigram'], 'reason': row['reason']}
|
|
139
|
162
|
else:
|
|
140
|
|
- reset.Seum = {'v': 0,
|
|
141
|
|
- 'f': reset.who.trigramme + ' à ' +
|
|
142
|
|
- reset.counter.trigramme +
|
|
143
|
|
- " : " + reset.reason}
|
|
144
|
|
- line_data = ModelDataSource(resets, fields=['timestamp', 'Seum'])
|
|
|
163
|
+ legend_[i] = _('%(who)s to %(counter)s: %(reason)s') % {'who': row['who_trigram'], 'counter': row['counter_trigram'], 'reason': row['reason']}
|
|
|
164
|
+ timeline_resets['legend'] = legend_
|
|
|
165
|
+
|
|
|
166
|
+ # Generate graph
|
|
|
167
|
+ resets_ = [['', _('Seum')]]
|
|
|
168
|
+ for i in range(timeline_resets.shape[0]):
|
|
|
169
|
+ r = timeline_resets.iloc[i]
|
|
|
170
|
+ resets_.append([{'v': r.timestamp, 'f': r.formatted_delta}, {'v': 0, 'f': r.legend}])
|
|
|
171
|
+ # resets_.append({
|
|
|
172
|
+ # 'timestamp': {'v': r.date.timestamp(), 'f': r.formatted_delta},
|
|
|
173
|
+ # 'Seum': {'v': 0, 'f': r.legend},
|
|
|
174
|
+ # })
|
|
|
175
|
+ line_data = SimpleDataSource(resets_)
|
|
145
|
176
|
line_chart = gchart.LineChart(line_data, options={
|
|
146
|
177
|
'lineWidth': 0,
|
|
147
|
178
|
'pointSize': 10,
|
|
|
@@ -150,8 +181,8 @@ def index(request):
|
|
150
|
181
|
'hAxis': {
|
|
151
|
182
|
'ticks': [
|
|
152
|
183
|
{'v': (datetime.now() - timedelta(days=1)
|
|
153
|
|
- ).timestamp(), 'f': 'Il y a 24 h'},
|
|
154
|
|
- {'v': datetime.now().timestamp(), 'f': 'Présent'}
|
|
|
184
|
+ ).timestamp(), 'f': _('24h ago')},
|
|
|
185
|
+ {'v': datetime.now().timestamp(), 'f': _('Now')}
|
|
155
|
186
|
]
|
|
156
|
187
|
},
|
|
157
|
188
|
'legend': 'none',
|
|
|
@@ -159,114 +190,105 @@ def index(request):
|
|
159
|
190
|
})
|
|
160
|
191
|
|
|
161
|
192
|
# Graph of greatest seumers
|
|
162
|
|
- seumCounts = []
|
|
163
|
|
- for counter in counters:
|
|
164
|
|
- seumCounts.append([counter.trigramme, Reset.objects.filter(
|
|
165
|
|
- counter=counter).count()])
|
|
166
|
|
- if (len(seumCounts) == 0):
|
|
|
193
|
+ seum_counts_df = resets_df[['counter_trigram', 'self_seum']].copy()
|
|
|
194
|
+ seum_counts_df['seum_count'] = np.ones(seum_counts_df.shape[0], dtype=np.float32)
|
|
|
195
|
+ seum_counts_df = seum_counts_df.groupby(['counter_trigram']).sum().reset_index()
|
|
|
196
|
+ # TODO: Add the ratio self_seum / seum_count
|
|
|
197
|
+ if (seum_counts_df.shape[0] == 0):
|
|
167
|
198
|
noBestSeum = True
|
|
168
|
199
|
best_chart = None
|
|
169
|
200
|
else:
|
|
170
|
|
- seumCounts.sort(key=lambda x: -x[1])
|
|
171
|
201
|
noBestSeum = False
|
|
172
|
|
- seumCounts.insert(0, ['Trigramme', 'Nombre de seums'])
|
|
173
|
|
- best_data = SimpleDataSource(seumCounts[:bestSeumeursNumber])
|
|
|
202
|
+ seum_counts_data = seum_counts_df.sort_values(by='seum_count', ascending=False)[['counter_trigram', 'seum_count']].values.tolist()
|
|
|
203
|
+ seum_counts_data.insert(0, [_('Trigram'), _('Number of seums')])
|
|
|
204
|
+ best_data = SimpleDataSource(seum_counts_data[:bestSeumeursNumber])
|
|
174
|
205
|
best_chart = gchart.ColumnChart(best_data, options={
|
|
175
|
206
|
'title': '',
|
|
176
|
207
|
'legend': 'none',
|
|
177
|
|
- 'vAxis': {'title': 'Nombre de seums'},
|
|
178
|
|
- 'hAxis': {'title': 'Trigramme'},
|
|
|
208
|
+ 'vAxis': {'title': _('Number of seums')},
|
|
|
209
|
+ 'hAxis': {'title': _('Trigram')},
|
|
179
|
210
|
})
|
|
180
|
211
|
|
|
181
|
212
|
# Graph of seum activity
|
|
182
|
|
- resets = Reset.objects.filter(
|
|
183
|
|
- timestamp__gte=timezone.now() - timedelta(days=365))
|
|
184
|
|
- months = {}
|
|
185
|
|
- for reset in resets:
|
|
186
|
|
- monthDate = datetime(reset.timestamp.year, reset.timestamp.month, 1)
|
|
187
|
|
- months[monthDate] = months.get(monthDate, 0) + 1
|
|
188
|
|
-
|
|
189
|
|
- monthList = sorted(months.items(), key=lambda t: t[0])
|
|
190
|
|
- seumActivity = []
|
|
191
|
|
- for month in monthList:
|
|
192
|
|
- seumActivity.append(
|
|
193
|
|
- [format_datetime(month[0], locale='fr',
|
|
194
|
|
- format="MMM Y").capitalize(), month[1]])
|
|
195
|
|
- if (len(seumActivity) == 0):
|
|
|
213
|
+ resets_act = resets_df[resets_df.date > (timezone.now() - timedelta(days=365))][['date']].copy()
|
|
|
214
|
+ resets_act['year'] = resets_df.date.map(lambda d: d.year)
|
|
|
215
|
+ resets_act['month'] = resets_df.date.map(lambda d: d.month)
|
|
|
216
|
+ resets_act = resets_act.drop(['date'], axis=1)
|
|
|
217
|
+ resets_act['month_counts'] = np.ones(resets_act.shape[0], dtype=int)
|
|
|
218
|
+ resets_act = resets_act.groupby(['year', 'month']).sum().reset_index()
|
|
|
219
|
+ if resets_act.shape[0] == 0:
|
|
196
|
220
|
noSeumActivity = True
|
|
197
|
221
|
activity_chart = None
|
|
198
|
222
|
else:
|
|
199
|
223
|
noSeumActivity = False
|
|
200
|
|
- seumActivity.insert(0, ['Mois', 'Nombre de seums'])
|
|
|
224
|
+ seumActivity = [
|
|
|
225
|
+ [arrow.Arrow(a[0], a[1], 1).format("MMM YYYY", locale=get_language()).capitalize(), a[2]]
|
|
|
226
|
+ for a in resets_act.values.tolist()
|
|
|
227
|
+ ]
|
|
|
228
|
+ seumActivity.insert(0, [_('Month'), _('Number of seums')])
|
|
201
|
229
|
activity_data = SimpleDataSource(seumActivity)
|
|
202
|
230
|
activity_chart = gchart.ColumnChart(activity_data, options={
|
|
203
|
231
|
'title': '',
|
|
204
|
232
|
'legend': 'none',
|
|
205
|
|
- 'vAxis': {'title': 'Nombre de seums'},
|
|
206
|
|
- 'hAxis': {'title': 'Mois'},
|
|
|
233
|
+ 'vAxis': {'title': _('Number of seums')},
|
|
|
234
|
+ 'hAxis': {'title': _('Month')},
|
|
207
|
235
|
})
|
|
208
|
236
|
|
|
209
|
237
|
# Graph of best likers
|
|
210
|
|
- likersCounts = []
|
|
211
|
|
- for counter in counters:
|
|
212
|
|
- likersCounts.append(
|
|
213
|
|
- [counter.trigramme, Like.objects.filter(liker=counter).count()])
|
|
214
|
|
- if (len(likersCounts) == 0):
|
|
|
238
|
+ best_likers_df = likes_df.drop(['liker', 'counter', 'counter_trigram'], axis=1)
|
|
|
239
|
+ best_likers_df['count'] = np.ones(best_likers_df.shape[0], dtype=int)
|
|
|
240
|
+ best_likers_df = best_likers_df.groupby(['liker_trigram']).sum().reset_index()
|
|
|
241
|
+ if best_likers_df.shape[0] == 0:
|
|
215
|
242
|
noBestLikers = True
|
|
216
|
243
|
likers_chart = None
|
|
217
|
244
|
else:
|
|
218
|
|
- likersCounts.sort(key=lambda x: -x[1])
|
|
219
|
245
|
noBestLikers = False
|
|
220
|
|
- likersCounts.insert(0, ['Trigramme', 'Nombre de likes distribués'])
|
|
|
246
|
+ likersCounts = best_likers_df.sort_values(by='count', ascending=False).values.tolist()
|
|
|
247
|
+ likersCounts.insert(0, [_('Trigram'), _('Number of given likes')])
|
|
221
|
248
|
likers_data = SimpleDataSource(likersCounts[:bestSeumeursNumber])
|
|
222
|
249
|
likers_chart = gchart.ColumnChart(likers_data, options={
|
|
223
|
250
|
'title': '',
|
|
224
|
251
|
'legend': 'none',
|
|
225
|
|
- 'vAxis': {'title': 'Nombre de likes distribués'},
|
|
226
|
|
- 'hAxis': {'title': 'Trigramme'},
|
|
|
252
|
+ 'vAxis': {'title': _('Number of given likes')},
|
|
|
253
|
+ 'hAxis': {'title': _('Trigram')},
|
|
227
|
254
|
})
|
|
228
|
255
|
|
|
229
|
256
|
# Graph of popular hashtags
|
|
230
|
|
- hashtagsCounts = []
|
|
231
|
|
- keywords = Keyword.objects.all()
|
|
232
|
|
- for keyword in keywords:
|
|
233
|
|
- hashtagsCounts.append(
|
|
234
|
|
- ['#' + keyword.text,
|
|
235
|
|
- Hashtag.objects.filter(keyword=keyword).count()])
|
|
236
|
|
- if (len(hashtagsCounts) == 0):
|
|
|
257
|
+ hashtags_df['count'] = np.ones(hashtags_df.shape[0], dtype=int)
|
|
|
258
|
+ hashtags_df = hashtags_df.groupby(['keyword']).sum().reset_index()
|
|
|
259
|
+ hashtags_df['keyword'] = hashtags_df.keyword.map(lambda x: '#' + x)
|
|
|
260
|
+ if hashtags_df.shape[0] == 0:
|
|
237
|
261
|
noBestHashtags = True
|
|
238
|
262
|
hashtags_chart = None
|
|
239
|
263
|
else:
|
|
240
|
|
- hashtagsCounts.sort(key=lambda x: -x[1])
|
|
241
|
264
|
noBestHashtags = False
|
|
242
|
|
- hashtagsCounts.insert(0, ['Trigramme', 'Nombre de likes distribués'])
|
|
243
|
|
- hashtags_data = SimpleDataSource(hashtagsCounts[:bestSeumeursNumber])
|
|
|
265
|
+ hashtags_data = hashtags_df.sort_values(by='count', ascending=False).values.tolist()
|
|
|
266
|
+ hashtags_data.insert(0, [_('Hashtag'), _('Number of seums containing the hashtag')])
|
|
|
267
|
+ hashtags_data = SimpleDataSource(hashtags_data[:bestSeumeursNumber])
|
|
244
|
268
|
hashtags_chart = gchart.ColumnChart(hashtags_data, options={
|
|
245
|
269
|
'title': '',
|
|
246
|
270
|
'legend': 'none',
|
|
247
|
|
- 'vAxis': {'title': 'Nombre de seums contenant le hashtag'},
|
|
248
|
|
- 'hAxis': {'title': 'Hashtag'},
|
|
|
271
|
+ 'vAxis': {'title': _('Number of seums containing the hashtag')},
|
|
|
272
|
+ 'hAxis': {'title': _('Hashtag')},
|
|
249
|
273
|
})
|
|
250
|
274
|
|
|
251
|
275
|
# Graph of best likee
|
|
252
|
|
- likeesCounts = []
|
|
253
|
|
- for counter in counters:
|
|
254
|
|
- likeesCounts.append(
|
|
255
|
|
- [counter.trigramme,
|
|
256
|
|
- Like.objects.filter(reset__counter=counter).count()])
|
|
257
|
|
- if (len(likeesCounts) == 0):
|
|
|
276
|
+ best_likees_df = likes_df.drop(['counter', 'liker', 'liker_trigram'], axis=1)
|
|
|
277
|
+ best_likees_df['count'] = np.ones(best_likees_df.shape[0], dtype=int)
|
|
|
278
|
+ best_likees_df = best_likees_df.groupby(['counter_trigram']).sum().reset_index()
|
|
|
279
|
+ if best_likees_df.shape[0] == 0:
|
|
258
|
280
|
noBestLikees = True
|
|
259
|
281
|
likees_chart = None
|
|
260
|
282
|
else:
|
|
261
|
|
- likeesCounts.sort(key=lambda x: -x[1])
|
|
262
|
283
|
noBestLikees = False
|
|
263
|
|
- likeesCounts.insert(0, ['Trigramme', 'Nombre de likes reçus'])
|
|
|
284
|
+ likeesCounts = best_likees_df.sort_values(by='count', ascending=False).values.tolist()
|
|
|
285
|
+ likeesCounts.insert(0, [_('Trigram'), _('Number of received likes')])
|
|
264
|
286
|
likees_data = SimpleDataSource(likeesCounts[:bestSeumeursNumber])
|
|
265
|
287
|
likees_chart = gchart.ColumnChart(likees_data, options={
|
|
266
|
288
|
'title': '',
|
|
267
|
289
|
'legend': 'none',
|
|
268
|
|
- 'vAxis': {'title': 'Nombre de likes reçus'},
|
|
269
|
|
- 'hAxis': {'title': 'Trigramme'},
|
|
|
290
|
+ 'vAxis': {'title': _('Number of received likes')},
|
|
|
291
|
+ 'hAxis': {'title': _('Trigram')},
|
|
270
|
292
|
})
|
|
271
|
293
|
|
|
272
|
294
|
# At last we render the page
|