暫無描述

home.py 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. import copy
  2. from datetime import datetime, timedelta
  3. import functools
  4. import math
  5. import random
  6. from django import forms
  7. from django.contrib.auth.decorators import login_required
  8. from django.contrib.auth.models import User
  9. from django.core.mail import EmailMessage
  10. from django.core.urlresolvers import reverse
  11. from django.db import IntegrityError
  12. from django.db.models import Prefetch, Count
  13. from django.http import HttpResponseRedirect
  14. from django.shortcuts import render
  15. from django.template.loader import render_to_string
  16. from django.utils import timezone
  17. from django.utils.translation import ugettext as _, get_language
  18. import arrow
  19. from babel.dates import format_timedelta, format_datetime
  20. from graphos.renderers import gchart
  21. from graphos.sources.model import ModelDataSource
  22. from graphos.sources.simple import SimpleDataSource
  23. from counter.models import *
  24. from counter.utils import parseSeumReason
  25. # Number of counters displayed on the home page's best seumeurs graph
  26. bestSeumeursNumber = 15
  27. @login_required
  28. def index(request):
  29. # Used later to keep track of the maximum JSS
  30. lastResets = []
  31. no_seum_delta = timedelta.max
  32. # First select our counter
  33. try:
  34. myCounter = Counter.objects.get(user__id=request.user.id)
  35. myLastReset = Reset.objects.select_related('who').filter(counter=myCounter).order_by('-timestamp').first()
  36. if myLastReset is None:
  37. # This person never had the seum
  38. myCounter.lastReset = Reset()
  39. myCounter.lastReset.delta = no_seum_delta
  40. myCounter.lastReset.noSeum = True
  41. else:
  42. myCounter.lastReset = myLastReset
  43. myCounter.lastReset.noSeum = False
  44. if myCounter.lastReset.who is None or myCounter.lastReset.who.id == myCounter.id:
  45. myCounter.lastReset.selfSeum = True
  46. else:
  47. myCounter.lastReset.selfSeum = False
  48. likesMe = list(Like.objects.select_related('liker').filter(reset=myCounter.lastReset))
  49. myCounter.likeCount = len(likesMe)
  50. if myCounter.likeCount > 0:
  51. myCounter.likersString = ", ".join([like.liker.trigramme for like in likesMe])
  52. myCounter.lastReset.formatted_delta = arrow.Arrow.fromdatetime(myCounter.lastReset.timestamp).humanize(locale=get_language())
  53. except Counter.DoesNotExist:
  54. return HttpResponseRedirect(reverse('login'))
  55. # Building data for counters display
  56. counters = Counter.objects.prefetch_related(
  57. 'resets__likes',
  58. Prefetch(
  59. 'resets',
  60. queryset=Reset.objects.prefetch_related('who', Prefetch('likes', queryset=Like.objects.select_related('liker'))).order_by('-timestamp'),
  61. to_attr='lastReset'
  62. )
  63. )
  64. for counter in counters:
  65. # Only the last reset is displayed
  66. lastReset = list(counter.lastReset)
  67. if len(lastReset) == 0: # This person never had the seum
  68. counter.lastReset = Reset()
  69. counter.lastReset.delta = no_seum_delta
  70. counter.lastReset.noSeum = True
  71. counter.lastReset.likes_count = -1
  72. counter.CSSclass = "warning"
  73. else: # This person already had the seum
  74. counter.lastReset = lastReset[0]
  75. # To display the last seum we have to know if it is self-inflicted
  76. if counter.lastReset.who is None or counter.lastReset.who == counter:
  77. counter.lastReset.selfSeum = True
  78. else:
  79. counter.lastReset.selfSeum = False
  80. # Now we compute the duration since the reset
  81. counter.lastReset.noSeum = False
  82. counter.lastReset.delta = datetime.now(
  83. ) - counter.lastReset.timestamp.replace(tzinfo=None)
  84. # Defining CSS attributes for the counter
  85. counter.CSSclass = 'primary' if counter == myCounter else 'default'
  86. # Computing the total number of likes for this counter
  87. likesMe = list(counter.lastReset.likes.all())
  88. counter.lastReset.likes_count = len(likesMe)
  89. counter.alreadyLiked = myCounter in likesMe
  90. if counter.lastReset.likes_count > 0:
  91. counter.likersString = ", ".join([like.liker.trigramme for like in likesMe])
  92. counter.lastReset.formatted_delta = format_timedelta(
  93. counter.lastReset.delta, locale='fr', threshold=1)
  94. counter.isHidden = 'hidden'
  95. if myCounter.sort_by_score:
  96. # Now we sort the counters according to a reddit-like ranking formula
  97. # We take into account the number of likes of a reset and recentness
  98. # The log on the score will give increased value to the first likes
  99. # The counters with no seum have a like count of -1 by convention
  100. sorting_key = lambda t: - (math.log(t.lastReset.likes_count + 2) / (1 + (t.lastReset.delta.total_seconds()) / (24 * 3600)))
  101. counters = sorted(counters, key=sorting_key)
  102. else:
  103. counters = sorted(counters, key=lambda t: + t.lastReset.delta.total_seconds())
  104. # Timeline graph
  105. resets = Reset.objects.select_related('who', 'counter').filter(timestamp__gte=timezone.now() - timedelta(days=1))
  106. if resets.count() == 0:
  107. noTimeline = True
  108. line_chart = None
  109. else:
  110. noTimeline = False
  111. for reset in resets:
  112. reset.timestamp = {
  113. 'v': reset.timestamp.timestamp(),
  114. 'f': "Il y a " + format_timedelta(datetime.now() -
  115. reset.timestamp.replace(
  116. tzinfo=None),
  117. locale='fr', threshold=1)
  118. }
  119. if (reset.who is None or
  120. reset.who.id == reset.counter.id):
  121. reset.Seum = {'v': 0,
  122. 'f': reset.counter.trigramme +
  123. " : " + reset.reason}
  124. else:
  125. reset.Seum = {'v': 0,
  126. 'f': reset.who.trigramme + ' à ' +
  127. reset.counter.trigramme +
  128. " : " + reset.reason}
  129. line_data = ModelDataSource(resets, fields=['timestamp', 'Seum'])
  130. line_chart = gchart.LineChart(line_data, options={
  131. 'lineWidth': 0,
  132. 'pointSize': 10,
  133. 'title': '',
  134. 'vAxis': {'ticks': []},
  135. 'hAxis': {
  136. 'ticks': [
  137. {'v': (datetime.now() - timedelta(days=1)
  138. ).timestamp(), 'f': 'Il y a 24 h'},
  139. {'v': datetime.now().timestamp(), 'f': 'Présent'}
  140. ]
  141. },
  142. 'legend': 'none',
  143. 'height': 90
  144. })
  145. # Graph of greatest seumers
  146. seumCounts = []
  147. for counter in counters:
  148. seumCounts.append([counter.trigramme, Reset.objects.filter(
  149. counter=counter).count()])
  150. if (len(seumCounts) == 0):
  151. noBestSeum = True
  152. best_chart = None
  153. else:
  154. seumCounts.sort(key=lambda x: -x[1])
  155. noBestSeum = False
  156. seumCounts.insert(0, ['Trigramme', 'Nombre de seums'])
  157. best_data = SimpleDataSource(seumCounts[:bestSeumeursNumber])
  158. best_chart = gchart.ColumnChart(best_data, options={
  159. 'title': '',
  160. 'legend': 'none',
  161. 'vAxis': {'title': 'Nombre de seums'},
  162. 'hAxis': {'title': 'Trigramme'},
  163. })
  164. # Graph of seum activity
  165. resets = Reset.objects.filter(
  166. timestamp__gte=timezone.now() - timedelta(days=365))
  167. months = {}
  168. for reset in resets:
  169. monthDate = datetime(reset.timestamp.year, reset.timestamp.month, 1)
  170. months[monthDate] = months.get(monthDate, 0) + 1
  171. monthList = sorted(months.items(), key=lambda t: t[0])
  172. seumActivity = []
  173. for month in monthList:
  174. seumActivity.append(
  175. [format_datetime(month[0], locale='fr',
  176. format="MMM Y").capitalize(), month[1]])
  177. if (len(seumActivity) == 0):
  178. noSeumActivity = True
  179. activity_chart = None
  180. else:
  181. noSeumActivity = False
  182. seumActivity.insert(0, ['Mois', 'Nombre de seums'])
  183. activity_data = SimpleDataSource(seumActivity)
  184. activity_chart = gchart.ColumnChart(activity_data, options={
  185. 'title': '',
  186. 'legend': 'none',
  187. 'vAxis': {'title': 'Nombre de seums'},
  188. 'hAxis': {'title': 'Mois'},
  189. })
  190. # Graph of best likers
  191. likersCounts = []
  192. for counter in counters:
  193. likersCounts.append(
  194. [counter.trigramme, Like.objects.filter(liker=counter).count()])
  195. if (len(likersCounts) == 0):
  196. noBestLikers = True
  197. likers_chart = None
  198. else:
  199. likersCounts.sort(key=lambda x: -x[1])
  200. noBestLikers = False
  201. likersCounts.insert(0, ['Trigramme', 'Nombre de likes distribués'])
  202. likers_data = SimpleDataSource(likersCounts[:bestSeumeursNumber])
  203. likers_chart = gchart.ColumnChart(likers_data, options={
  204. 'title': '',
  205. 'legend': 'none',
  206. 'vAxis': {'title': 'Nombre de likes distribués'},
  207. 'hAxis': {'title': 'Trigramme'},
  208. })
  209. # Graph of popular hashtags
  210. hashtagsCounts = []
  211. keywords = Keyword.objects.all()
  212. for keyword in keywords:
  213. hashtagsCounts.append(
  214. ['#' + keyword.text,
  215. Hashtag.objects.filter(keyword=keyword).count()])
  216. if (len(hashtagsCounts) == 0):
  217. noBestHashtags = True
  218. hashtags_chart = None
  219. else:
  220. hashtagsCounts.sort(key=lambda x: -x[1])
  221. noBestHashtags = False
  222. hashtagsCounts.insert(0, ['Trigramme', 'Nombre de likes distribués'])
  223. hashtags_data = SimpleDataSource(hashtagsCounts[:bestSeumeursNumber])
  224. hashtags_chart = gchart.ColumnChart(hashtags_data, options={
  225. 'title': '',
  226. 'legend': 'none',
  227. 'vAxis': {'title': 'Nombre de seums contenant le hashtag'},
  228. 'hAxis': {'title': 'Hashtag'},
  229. })
  230. # Graph of best likee
  231. likeesCounts = []
  232. for counter in counters:
  233. likeesCounts.append(
  234. [counter.trigramme,
  235. Like.objects.filter(reset__counter=counter).count()])
  236. if (len(likeesCounts) == 0):
  237. noBestLikees = True
  238. likees_chart = None
  239. else:
  240. likeesCounts.sort(key=lambda x: -x[1])
  241. noBestLikees = False
  242. likeesCounts.insert(0, ['Trigramme', 'Nombre de likes reçus'])
  243. likees_data = SimpleDataSource(likeesCounts[:bestSeumeursNumber])
  244. likees_chart = gchart.ColumnChart(likees_data, options={
  245. 'title': '',
  246. 'legend': 'none',
  247. 'vAxis': {'title': 'Nombre de likes reçus'},
  248. 'hAxis': {'title': 'Trigramme'},
  249. })
  250. # At last we render the page
  251. return render(request, 'homeTemplate.html', {
  252. 'counters': counters,
  253. 'line_chart': line_chart,
  254. 'best_chart': best_chart,
  255. 'likers_chart': likers_chart,
  256. 'likees_chart': likees_chart,
  257. 'hashtags_chart': hashtags_chart,
  258. 'activity_chart': activity_chart,
  259. 'noTimeline': noTimeline,
  260. 'noBestSeum': noBestSeum,
  261. 'noBestLikers': noBestLikers,
  262. 'noBestLikees': noBestLikees,
  263. 'noBestHashtags': noBestHashtags,
  264. 'noSeumActivity': noSeumActivity,
  265. 'myCounter': myCounter,
  266. })
  267. @login_required
  268. def toggleEmailNotifications(request):
  269. counter = Counter.objects.get(user=request.user)
  270. counter.email_notifications = not counter.email_notifications
  271. counter.save()
  272. return HttpResponseRedirect(reverse('home'))
  273. @login_required
  274. def toggleScoreSorting(request):
  275. counter = Counter.objects.get(user=request.user)
  276. counter.sort_by_score = not counter.sort_by_score
  277. counter.save()
  278. return HttpResponseRedirect(reverse('home'))