| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326 |
- import copy
- from datetime import datetime, timedelta
- import math
- from django import forms
- from django.contrib.auth.decorators import login_required
- from django.contrib.auth.models import User
- from django.core.mail import EmailMessage
- from django.core.urlresolvers import reverse
- from django.db import IntegrityError
- from django.db.models import Prefetch, Count
- from django.http import HttpResponseRedirect
- from django.shortcuts import render
- from django.template.loader import render_to_string
- from django.utils import timezone
- from django.utils.translation import ugettext as _, get_language
- import arrow
- from babel.dates import format_timedelta, format_datetime
- from graphos.renderers import gchart
- from graphos.sources.model import ModelDataSource
- from graphos.sources.simple import SimpleDataSource
- import numpy as np
- import pandas as pd
- from counter.models import *
- from counter.utils import parseSeumReason
- # Number of counters displayed on the home page's best seumeurs graph
- bestSeumeursNumber = 15
- @login_required
- def index(request):
- # Used later to keep track of the maximum JSS
- lastResets = []
- no_seum_delta = timedelta.max
- # First select our counter
- try:
- myCounter = Counter.objects.get(user__id=request.user.id)
- myLastReset = Reset.objects.select_related('who').filter(counter=myCounter).order_by('-timestamp').first()
- if myLastReset is None:
- # This person never had the seum
- myCounter.lastReset = Reset()
- myCounter.lastReset.delta = no_seum_delta
- myCounter.lastReset.formatted_delta = format_timedelta(myCounter.lastReset.delta, locale=get_language(), threshold=1)
- myCounter.lastReset.noSeum = True
- else:
- myCounter.lastReset = myLastReset
- myCounter.lastReset.noSeum = False
- if myCounter.lastReset.who is None or myCounter.lastReset.who.id == myCounter.id:
- myCounter.lastReset.selfSeum = True
- else:
- myCounter.lastReset.selfSeum = False
- likesMe = list(Like.objects.select_related('liker').filter(reset=myCounter.lastReset))
- myCounter.likeCount = len(likesMe)
- if myCounter.likeCount > 0:
- myCounter.likersString = ", ".join([like.liker.trigramme for like in likesMe])
- myCounter.lastReset.formatted_delta = arrow.Arrow.fromdatetime(myCounter.lastReset.timestamp).humanize(locale=get_language())
- except Counter.DoesNotExist:
- return HttpResponseRedirect(reverse('login'))
- # Building data for counters display
- counters = Counter.objects.prefetch_related(
- 'resets__likes',
- Prefetch(
- 'resets',
- queryset=Reset.objects.prefetch_related('who', Prefetch('likes', queryset=Like.objects.select_related('liker'))).order_by('-timestamp'),
- to_attr='lastReset'
- )
- )
- for counter in counters:
- # Only the last reset is displayed
- lastReset = list(counter.lastReset)
- if len(lastReset) == 0: # This person never had the seum
- counter.lastReset = Reset()
- counter.lastReset.delta = no_seum_delta
- counter.lastReset.formatted_delta = format_timedelta(counter.lastReset.delta, locale=get_language(), threshold=1)
- counter.lastReset.noSeum = True
- counter.lastReset.likes_count = -1
- counter.CSSclass = "warning"
- else: # This person already had the seum
- counter.lastReset = lastReset[0]
- # To display the last seum we have to know if it is self-inflicted
- if counter.lastReset.who is None or counter.lastReset.who == counter:
- counter.lastReset.selfSeum = True
- else:
- counter.lastReset.selfSeum = False
- # Now we compute the duration since the reset
- counter.lastReset.noSeum = False
- counter.lastReset.delta = datetime.now() - counter.lastReset.timestamp.replace(tzinfo=None)
- # Defining CSS attributes for the counter
- counter.CSSclass = 'primary' if counter == myCounter else 'default'
- # Computing the total number of likes for this counter
- likesMe = list(counter.lastReset.likes.all())
- counter.lastReset.likes_count = len(likesMe)
- counter.alreadyLiked = myCounter in likesMe
- if counter.lastReset.likes_count > 0:
- counter.likersString = ", ".join([like.liker.trigramme for like in likesMe])
- counter.lastReset.formatted_delta = arrow.Arrow.fromdatetime(counter.lastReset.timestamp).humanize(locale=get_language())
- counter.likeCount = counter.lastReset.likes_count
- counter.isHidden = 'hidden'
- if myCounter.sort_by_score:
- # Now we sort the counters according to a reddit-like ranking formula
- # We take into account the number of likes of a reset and recentness
- # The log on the score will give increased value to the first likes
- # The counters with no seum have a like count of -1 by convention
- sorting_key = lambda t: - (math.log(t.lastReset.likes_count + 2) / (1 + (t.lastReset.delta.total_seconds()) / (24 * 3600)))
- counters = sorted(counters, key=sorting_key)
- else:
- counters = sorted(counters, key=lambda t: + t.lastReset.delta.total_seconds())
- # ### GRAPHS ###
- resets_raw = list(Reset.objects.select_related('who', 'counter').annotate(likes_count=Count('likes')))
- likes_raw = list(Like.objects.select_related('liker', 'reset__counter').all())
- hashtags_raw = list(Hashtag.objects.select_related('keyword').all())
- # Prepare pandas.DataFrames to efficiently process the data
- # About the counters
- resets_cols = ['date', 'counter', 'counter_trigram', 'who', 'who_trigram', 'reason', 'likes_count']
- resets_data = [[r.timestamp, r.counter.id, r.counter.trigramme, r.who, r.who, r.reason, r.likes_count] for r in resets_raw]
- for r in resets_data:
- r[3] = 0 if r[3] is None else r[3].id
- r[4] = '' if r[4] is None else r[4].trigramme
- resets_df = pd.DataFrame(resets_data, columns=resets_cols)
- resets_df['timestamp'] = resets_df.date.map(lambda d: d.timestamp())
- resets_df['self_seum'] = (resets_df.who.eq(np.zeros(resets_df.shape[0])) | resets_df.who.eq(resets_df.counter)).map(float)
- resets_df['formatted_delta'] = resets_df.date.map(lambda d: arrow.Arrow.fromdatetime(d).humanize(locale=get_language()))
- # About the likes
- likes_cols = ['liker', 'liker_trigram', 'counter', 'counter_trigram']
- likes_data = [[l.liker.id, l.liker.trigramme, l.reset.counter.id, l.reset.counter.trigramme] for l in likes_raw]
- likes_df = pd.DataFrame(likes_data, columns=likes_cols)
- # About the hashtags
- hashtags_cols = ['keyword']
- hashtags_data = [[h.keyword.text] for h in hashtags_raw]
- hashtags_df = pd.DataFrame(hashtags_data, columns=hashtags_cols)
- # Timeline graph
- timeline_resets = resets_df[resets_df.date > (datetime.now() - timedelta(days=1))].copy().reset_index()
- if timeline_resets.shape[0] == 0:
- noTimeline = True
- line_chart = None
- else:
- noTimeline = False
- # Construct legend for timeline dots
- legend_ = np.zeros(timeline_resets.shape[0], dtype=np.object)
- for i in range(timeline_resets.shape[0]):
- row = timeline_resets.iloc[i]
- if row['self_seum'] == 1:
- legend_[i] = _('%(counter)s: %(reason)s') % {'counter': row['counter_trigram'], 'reason': row['reason']}
- else:
- legend_[i] = _('%(who)s to %(counter)s: %(reason)s') % {'who': row['who_trigram'], 'counter': row['counter_trigram'], 'reason': row['reason']}
- timeline_resets['legend'] = legend_
- # Generate graph
- resets_ = [['', _('Seum')]]
- for i in range(timeline_resets.shape[0]):
- r = timeline_resets.iloc[i]
- resets_.append([{'v': r.timestamp, 'f': r.formatted_delta}, {'v': 0, 'f': r.legend}])
- # resets_.append({
- # 'timestamp': {'v': r.date.timestamp(), 'f': r.formatted_delta},
- # 'Seum': {'v': 0, 'f': r.legend},
- # })
- line_data = SimpleDataSource(resets_)
- line_chart = gchart.LineChart(line_data, options={
- 'lineWidth': 0,
- 'pointSize': 10,
- 'title': '',
- 'vAxis': {'ticks': []},
- 'hAxis': {
- 'ticks': [
- {'v': (datetime.now() - timedelta(days=1)
- ).timestamp(), 'f': _('24h ago')},
- {'v': datetime.now().timestamp(), 'f': _('Now')}
- ]
- },
- 'legend': 'none',
- 'height': 90
- })
- # Graph of greatest seumers
- seum_counts_df = resets_df[['counter_trigram', 'self_seum']].copy()
- seum_counts_df['seum_count'] = np.ones(seum_counts_df.shape[0], dtype=np.float32)
- seum_counts_df = seum_counts_df.groupby(['counter_trigram']).sum().reset_index()
- # TODO: Add the ratio self_seum / seum_count
- if (seum_counts_df.shape[0] == 0):
- noBestSeum = True
- best_chart = None
- else:
- noBestSeum = False
- seum_counts_data = seum_counts_df.sort_values(by='seum_count', ascending=False)[['counter_trigram', 'seum_count']].values.tolist()
- seum_counts_data.insert(0, [_('Trigram'), _('Number of seums')])
- best_data = SimpleDataSource(seum_counts_data[:bestSeumeursNumber])
- best_chart = gchart.ColumnChart(best_data, options={
- 'title': '',
- 'legend': 'none',
- 'vAxis': {'title': _('Number of seums')},
- 'hAxis': {'title': _('Trigram')},
- })
- # Graph of seum activity
- resets_act = resets_df[resets_df.date > (timezone.now() - timedelta(days=365))][['date']].copy()
- resets_act['year'] = resets_df.date.map(lambda d: d.year)
- resets_act['month'] = resets_df.date.map(lambda d: d.month)
- resets_act = resets_act.drop(['date'], axis=1)
- resets_act['month_counts'] = np.ones(resets_act.shape[0], dtype=int)
- resets_act = resets_act.groupby(['year', 'month']).sum().reset_index()
- if resets_act.shape[0] == 0:
- noSeumActivity = True
- activity_chart = None
- else:
- noSeumActivity = False
- seumActivity = [
- [arrow.Arrow(a[0], a[1], 1).format("MMM YYYY", locale=get_language()).capitalize(), a[2]]
- for a in resets_act.values.tolist()
- ]
- seumActivity.insert(0, [_('Month'), _('Number of seums')])
- activity_data = SimpleDataSource(seumActivity)
- activity_chart = gchart.ColumnChart(activity_data, options={
- 'title': '',
- 'legend': 'none',
- 'vAxis': {'title': _('Number of seums')},
- 'hAxis': {'title': _('Month')},
- })
- # Graph of best likers
- best_likers_df = likes_df.drop(['liker', 'counter', 'counter_trigram'], axis=1)
- best_likers_df['count'] = np.ones(best_likers_df.shape[0], dtype=int)
- best_likers_df = best_likers_df.groupby(['liker_trigram']).sum().reset_index()
- if best_likers_df.shape[0] == 0:
- noBestLikers = True
- likers_chart = None
- else:
- noBestLikers = False
- likersCounts = best_likers_df.sort_values(by='count', ascending=False).values.tolist()
- likersCounts.insert(0, [_('Trigram'), _('Number of given likes')])
- likers_data = SimpleDataSource(likersCounts[:bestSeumeursNumber])
- likers_chart = gchart.ColumnChart(likers_data, options={
- 'title': '',
- 'legend': 'none',
- 'vAxis': {'title': _('Number of given likes')},
- 'hAxis': {'title': _('Trigram')},
- })
- # Graph of popular hashtags
- hashtags_df['count'] = np.ones(hashtags_df.shape[0], dtype=int)
- hashtags_df = hashtags_df.groupby(['keyword']).sum().reset_index()
- hashtags_df['keyword'] = hashtags_df.keyword.map(lambda x: '#' + x)
- if hashtags_df.shape[0] == 0:
- noBestHashtags = True
- hashtags_chart = None
- else:
- noBestHashtags = False
- hashtags_data = hashtags_df.sort_values(by='count', ascending=False).values.tolist()
- hashtags_data.insert(0, [_('Hashtag'), _('Number of seums containing the hashtag')])
- hashtags_data = SimpleDataSource(hashtags_data[:bestSeumeursNumber])
- hashtags_chart = gchart.ColumnChart(hashtags_data, options={
- 'title': '',
- 'legend': 'none',
- 'vAxis': {'title': _('Number of seums containing the hashtag')},
- 'hAxis': {'title': _('Hashtag')},
- })
- # Graph of best likee
- best_likees_df = likes_df.drop(['counter', 'liker', 'liker_trigram'], axis=1)
- best_likees_df['count'] = np.ones(best_likees_df.shape[0], dtype=int)
- best_likees_df = best_likees_df.groupby(['counter_trigram']).sum().reset_index()
- if best_likees_df.shape[0] == 0:
- noBestLikees = True
- likees_chart = None
- else:
- noBestLikees = False
- likeesCounts = best_likees_df.sort_values(by='count', ascending=False).values.tolist()
- likeesCounts.insert(0, [_('Trigram'), _('Number of received likes')])
- likees_data = SimpleDataSource(likeesCounts[:bestSeumeursNumber])
- likees_chart = gchart.ColumnChart(likees_data, options={
- 'title': '',
- 'legend': 'none',
- 'vAxis': {'title': _('Number of received likes')},
- 'hAxis': {'title': _('Trigram')},
- })
- # At last we render the page
- return render(request, 'homeTemplate.html', {
- 'counters': counters,
- 'line_chart': line_chart,
- 'best_chart': best_chart,
- 'likers_chart': likers_chart,
- 'likees_chart': likees_chart,
- 'hashtags_chart': hashtags_chart,
- 'activity_chart': activity_chart,
- 'noTimeline': noTimeline,
- 'noBestSeum': noBestSeum,
- 'noBestLikers': noBestLikers,
- 'noBestLikees': noBestLikees,
- 'noBestHashtags': noBestHashtags,
- 'noSeumActivity': noSeumActivity,
- 'myCounter': myCounter,
- })
- @login_required
- def toggleEmailNotifications(request):
- counter = Counter.objects.get(user=request.user)
- counter.email_notifications = not counter.email_notifications
- counter.save()
- return HttpResponseRedirect(reverse('home'))
- @login_required
- def toggleScoreSorting(request):
- counter = Counter.objects.get(user=request.user)
- counter.sort_by_score = not counter.sort_by_score
- counter.save()
- return HttpResponseRedirect(reverse('home'))
|