Nenhuma descrição

views.py 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551
  1. from django.shortcuts import render
  2. from counter.models import Counter, Reset, Like, Keyword, Hashtag
  3. from django.contrib.auth.models import User
  4. from babel.dates import format_timedelta, format_datetime
  5. from datetime import datetime, timedelta
  6. from django import forms
  7. from django.http import HttpResponseRedirect
  8. from django.core.mail import EmailMessage
  9. from django.contrib.auth.decorators import login_required
  10. from django.core.urlresolvers import reverse
  11. from django.db import IntegrityError
  12. from graphos.renderers import gchart
  13. from django.template.loader import render_to_string
  14. from graphos.sources.simple import SimpleDataSource
  15. from graphos.sources.model import ModelDataSource
  16. import random
  17. import math
  18. import copy
  19. import functools
  20. from django.utils import timezone
  21. from counter.utils import parseSeumReason
  22. # Number of counters displayed on the home page's best seumeurs graph
  23. bestSeumeursNumber = 15
  24. @login_required
  25. def home(request):
  26. # Used later to keep track of the maximum JSS
  27. lastResets = []
  28. no_seum_delta = timedelta.max
  29. # First select our counter
  30. try:
  31. myCounter = Counter.objects.get(user__id=request.user.id)
  32. lastReset = Reset.objects.filter(
  33. counter=myCounter).order_by('-timestamp')
  34. if (lastReset.count() == 0):
  35. # This person never had the seum
  36. myCounter.lastReset = Reset()
  37. myCounter.lastReset.delta = no_seum_delta
  38. myCounter.lastReset.noSeum = True
  39. else:
  40. myCounter.lastReset = lastReset[0]
  41. myCounter.lastReset.noSeum = False
  42. if (myCounter.lastReset.who is None or
  43. myCounter.lastReset.who.id == myCounter.id):
  44. myCounter.lastReset.selfSeum = True
  45. else:
  46. myCounter.lastReset.selfSeum = False
  47. myCounter.lastReset.delta = datetime.now(
  48. ) - myCounter.lastReset.timestamp.replace(tzinfo=None)
  49. likesMe = Like.objects.filter(
  50. reset=myCounter.lastReset)
  51. myCounter.likeCount = likesMe.count()
  52. if myCounter.likeCount:
  53. myCounter.likersString = functools.reduce(
  54. lambda a, b: a + ", " + b,
  55. [like.liker.trigramme for like in likesMe])
  56. myCounter.lastReset.formatted_delta = format_timedelta(
  57. myCounter.lastReset.delta, locale='fr', threshold=1)
  58. except Counter.DoesNotExist:
  59. return HttpResponseRedirect(reverse('login'))
  60. # Building data for counters display
  61. counters = Counter.objects.all()
  62. for counter in counters:
  63. # Only the last reset is displayed
  64. lastReset = Reset.objects.filter(
  65. counter=counter).order_by('-timestamp')
  66. if (lastReset.count() == 0): # This person never had the seum
  67. counter.lastReset = Reset()
  68. counter.lastReset.delta = no_seum_delta
  69. counter.lastReset.noSeum = True
  70. counter.CSSclass = "warning"
  71. counter.likeCount = -1
  72. else: # This person already had the seum
  73. counter.lastReset = lastReset[0]
  74. # To display the last seum we have to know if it is self-inflicted
  75. if (counter.lastReset.who is None or
  76. counter.lastReset.who.id == counter.id):
  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. if counter.id == myCounter.id:
  86. counter.CSSclass = 'primary'
  87. else:
  88. counter.CSSclass = 'default'
  89. # Computing the total number of likes for this counter
  90. likesMe = Like.objects.filter(
  91. reset=counter.lastReset)
  92. counter.likeCount = likesMe.count()
  93. counter.alreadyLiked = (Like.objects.filter(
  94. reset=counter.lastReset, liker=myCounter).exists())
  95. if counter.likeCount > 0:
  96. counter.likersString = functools.reduce(
  97. lambda a, b: a + ", " + b,
  98. [like.liker.trigramme for like in likesMe])
  99. counter.lastReset.formatted_delta = format_timedelta(
  100. counter.lastReset.delta, locale='fr', threshold=1)
  101. counter.isHidden = 'hidden'
  102. if myCounter.sort_by_score:
  103. # Now we sort the counters according to a reddit-like ranking formula
  104. # We take into account the number of likes of a reset and recentness
  105. # The log on the score will give increased value to the first likes
  106. # The counters with no seum have a like count of -1 by convention
  107. counters = sorted(counters, key=lambda t: - (
  108. math.log(t.likeCount + 2) /
  109. (1 + (t.lastReset.delta.total_seconds()) /
  110. (24 * 3600))))
  111. else:
  112. counters = sorted(counters, key=lambda t: +
  113. t.lastReset.delta.total_seconds())
  114. # Timeline graph
  115. resets = Reset.objects.filter(
  116. timestamp__gte=timezone.now() - timedelta(days=1))
  117. if (resets.count() == 0):
  118. noTimeline = True
  119. line_chart = None
  120. else:
  121. noTimeline = False
  122. for reset in resets:
  123. reset.timestamp = {
  124. 'v': reset.timestamp.timestamp(),
  125. 'f': "Il y a " + format_timedelta(datetime.now() -
  126. reset.timestamp.replace(
  127. tzinfo=None),
  128. locale='fr', threshold=1)
  129. }
  130. if (reset.who is None or
  131. reset.who.id == reset.counter.id):
  132. reset.Seum = {'v': 0,
  133. 'f': reset.counter.trigramme +
  134. " : " + reset.reason}
  135. else:
  136. reset.Seum = {'v': 0,
  137. 'f': reset.who.trigramme + ' à ' +
  138. reset.counter.trigramme +
  139. " : " + reset.reason}
  140. line_data = ModelDataSource(resets, fields=['timestamp', 'Seum'])
  141. line_chart = gchart.LineChart(line_data, options={
  142. 'lineWidth': 0,
  143. 'pointSize': 10,
  144. 'title': '',
  145. 'vAxis': {'ticks': []},
  146. 'hAxis': {
  147. 'ticks': [
  148. {'v': (datetime.now() - timedelta(days=1)
  149. ).timestamp(), 'f': 'Il y a 24 h'},
  150. {'v': datetime.now().timestamp(), 'f': 'Présent'}
  151. ]
  152. },
  153. 'legend': 'none',
  154. 'height': 90
  155. })
  156. # Graph of greatest seumers
  157. seumCounts = []
  158. for counter in counters:
  159. seumCounts.append([counter.trigramme, Reset.objects.filter(
  160. counter=counter).count()])
  161. if (len(seumCounts) == 0):
  162. noBestSeum = True
  163. best_chart = None
  164. else:
  165. seumCounts.sort(key=lambda x: -x[1])
  166. noBestSeum = False
  167. seumCounts.insert(0, ['Trigramme', 'Nombre de seums'])
  168. best_data = SimpleDataSource(seumCounts[:bestSeumeursNumber])
  169. best_chart = gchart.ColumnChart(best_data, options={
  170. 'title': '',
  171. 'legend': 'none',
  172. 'vAxis': {'title': 'Nombre de seums'},
  173. 'hAxis': {'title': 'Trigramme'},
  174. })
  175. # Graph of seum activity
  176. resets = Reset.objects.filter(
  177. timestamp__gte=timezone.now() - timedelta(days=365))
  178. months = {}
  179. for reset in resets:
  180. monthDate = datetime(reset.timestamp.year, reset.timestamp.month, 1)
  181. months[monthDate] = months.get(monthDate, 0) + 1
  182. monthList = sorted(months.items(), key=lambda t: t[0])
  183. seumActivity = []
  184. for month in monthList:
  185. seumActivity.append(
  186. [format_datetime(month[0], locale='fr',
  187. format="MMM Y").capitalize(), month[1]])
  188. if (len(seumActivity) == 0):
  189. noSeumActivity = True
  190. activity_chart = None
  191. else:
  192. noSeumActivity = False
  193. seumActivity.insert(0, ['Mois', 'Nombre de seums'])
  194. activity_data = SimpleDataSource(seumActivity)
  195. activity_chart = gchart.ColumnChart(activity_data, options={
  196. 'title': '',
  197. 'legend': 'none',
  198. 'vAxis': {'title': 'Nombre de seums'},
  199. 'hAxis': {'title': 'Mois'},
  200. })
  201. # Graph of best likers
  202. likersCounts = []
  203. for counter in counters:
  204. likersCounts.append(
  205. [counter.trigramme, Like.objects.filter(liker=counter).count()])
  206. if (len(likersCounts) == 0):
  207. noBestLikers = True
  208. likers_chart = None
  209. else:
  210. likersCounts.sort(key=lambda x: -x[1])
  211. noBestLikers = False
  212. likersCounts.insert(0, ['Trigramme', 'Nombre de likes distribués'])
  213. likers_data = SimpleDataSource(likersCounts[:bestSeumeursNumber])
  214. likers_chart = gchart.ColumnChart(likers_data, options={
  215. 'title': '',
  216. 'legend': 'none',
  217. 'vAxis': {'title': 'Nombre de likes distribués'},
  218. 'hAxis': {'title': 'Trigramme'},
  219. })
  220. # Graph of popular hashtags
  221. hashtagsCounts = []
  222. keywords = Keyword.objects.all()
  223. for keyword in keywords:
  224. hashtagsCounts.append(
  225. ['#' + keyword.text,
  226. Hashtag.objects.filter(keyword=keyword).count()])
  227. if (len(hashtagsCounts) == 0):
  228. noBestHashtags = True
  229. hashtags_chart = None
  230. else:
  231. hashtagsCounts.sort(key=lambda x: -x[1])
  232. noBestHashtags = False
  233. hashtagsCounts.insert(0, ['Trigramme', 'Nombre de likes distribués'])
  234. hashtags_data = SimpleDataSource(hashtagsCounts[:bestSeumeursNumber])
  235. hashtags_chart = gchart.ColumnChart(hashtags_data, options={
  236. 'title': '',
  237. 'legend': 'none',
  238. 'vAxis': {'title': 'Nombre de seums contenant le hashtag'},
  239. 'hAxis': {'title': 'Hashtag'},
  240. })
  241. # Graph of best likee
  242. likeesCounts = []
  243. for counter in counters:
  244. likeesCounts.append(
  245. [counter.trigramme,
  246. Like.objects.filter(reset__counter=counter).count()])
  247. if (len(likeesCounts) == 0):
  248. noBestLikees = True
  249. likees_chart = None
  250. else:
  251. likeesCounts.sort(key=lambda x: -x[1])
  252. noBestLikees = False
  253. likeesCounts.insert(0, ['Trigramme', 'Nombre de likes reçus'])
  254. likees_data = SimpleDataSource(likeesCounts[:bestSeumeursNumber])
  255. likees_chart = gchart.ColumnChart(likees_data, options={
  256. 'title': '',
  257. 'legend': 'none',
  258. 'vAxis': {'title': 'Nombre de likes reçus'},
  259. 'hAxis': {'title': 'Trigramme'},
  260. })
  261. # At last we render the page
  262. return render(request, 'homeTemplate.html', {
  263. 'counters': counters,
  264. 'line_chart': line_chart,
  265. 'best_chart': best_chart,
  266. 'likers_chart': likers_chart,
  267. 'likees_chart': likees_chart,
  268. 'hashtags_chart': hashtags_chart,
  269. 'activity_chart': activity_chart,
  270. 'noTimeline': noTimeline,
  271. 'noBestSeum': noBestSeum,
  272. 'noBestLikers': noBestLikers,
  273. 'noBestLikees': noBestLikees,
  274. 'noBestHashtags': noBestHashtags,
  275. 'noSeumActivity': noSeumActivity,
  276. 'myCounter': myCounter,
  277. })
  278. @login_required
  279. def resetCounter(request):
  280. # Update Form counter
  281. if (request.method == 'POST'):
  282. # create a form instance and populate it with data from the request:
  283. data = dict(request.POST)
  284. who = Counter.objects.get(pk=int(data['who'][0]))
  285. reason = data['reason'][0]
  286. if 'counter' in data.keys():
  287. counter = Counter.objects.get(pk=int(data['counter'][0]))
  288. else:
  289. try:
  290. counter = Counter.objects.get(trigramme=data['trigramme'][0])
  291. except Counter.DoesNotExist:
  292. return HttpResponseRedirect(data['redirect'][0])
  293. reset = Reset()
  294. reset.counter = counter
  295. reset.who = who
  296. reset.reason = data['reason'][0]
  297. reset.timestamp = datetime.now()
  298. # we check that the seumer is the autenticated user
  299. if (reset.who.user is None or
  300. reset.who.user.id != request.user.id):
  301. return HttpResponseRedirect(data['redirect'][0])
  302. reset.save()
  303. # Now we deal with the hashtags
  304. keywords = parseSeumReason(reason)
  305. for keyword in keywords:
  306. hashtag = Hashtag(reset=reset, keyword=keyword)
  307. hashtag.save()
  308. # We send the emails only to those who want
  309. emails = [u.email for u in Counter.objects.all()
  310. if u.email_notifications]
  311. # Now send emails to everyone
  312. if (reset.who is None or
  313. reset.who.id == counter.id):
  314. selfSeum = True
  315. else:
  316. selfSeum = False
  317. text_of_email = render_to_string(
  318. 'seumEmail.txt', {'reason': data['reason'][0],
  319. 'name': counter.name,
  320. 'who': reset.who,
  321. 'selfSeum': selfSeum,
  322. })
  323. email_to_send = EmailMessage(
  324. '[SeumBook] ' + counter.trigramme + ' a le seum',
  325. text_of_email,
  326. 'SeumMan <seum@merigoux.ovh>', emails, [],
  327. reply_to=emails)
  328. email_to_send.send(fail_silently=True)
  329. return HttpResponseRedirect(data['redirect'][0])
  330. @login_required
  331. def counter(request, id_counter):
  332. try:
  333. myCounter = Counter.objects.get(user__id=request.user.id)
  334. except Counter.DoesNotExist:
  335. return HttpResponseRedirect(reverse('login'))
  336. counter = Counter.objects.get(pk=id_counter)
  337. resets = Reset.objects.filter(counter=counter).order_by('-timestamp')
  338. timezero = timedelta(0)
  339. # Display
  340. if (resets.count() == 0):
  341. counter.lastReset = Reset()
  342. counter.lastReset.delta = timezero
  343. counter.lastReset.noSeum = True
  344. seumFrequency = 'inconnu'
  345. else:
  346. firstReset = copy.copy(resets[len(resets) - 1])
  347. counter.lastReset = resets[0]
  348. counter.lastReset.noSeum = False
  349. if (counter.lastReset.who is None or
  350. counter.lastReset.who.id == counter.id):
  351. counter.lastReset.selfSeum = True
  352. else:
  353. counter.lastReset.selfSeum = False
  354. counter.lastReset.delta = datetime.now(
  355. ) - counter.lastReset.timestamp.replace(tzinfo=None)
  356. counter.lastReset.formatted_delta = format_timedelta(
  357. counter.lastReset.delta, locale='fr', threshold=1)
  358. counter.seumCount = Reset.objects.filter(
  359. counter=counter).count()
  360. seumFrequency = format_timedelta((
  361. datetime.now() - firstReset.timestamp.replace(tzinfo=None)) /
  362. counter.seumCount, locale='fr', threshold=1)
  363. counter.alreadyLiked = (Like.objects.filter(
  364. reset=counter.lastReset, liker=myCounter).exists())
  365. likesMe = Like.objects.filter(
  366. reset=counter.lastReset)
  367. counter.likeCount = likesMe.count()
  368. if counter.likeCount > 0:
  369. counter.likersString = functools.reduce(
  370. lambda a, b: a + ", " + b,
  371. [like.liker.trigramme for like in likesMe])
  372. for reset in resets:
  373. if (reset.who is None or
  374. reset.who.id == reset.counter.id):
  375. reset.selfSeum = True
  376. else:
  377. reset.selfSeum = False
  378. reset.date = format_datetime(
  379. reset.timestamp, locale='fr',
  380. format="dd/MM/Y HH:mm")
  381. reset.likeCount = Like.objects.filter(reset=reset).count()
  382. # Timeline graph
  383. # Data pre-processing
  384. if not counter.lastReset.noSeum:
  385. resets_graph = resets
  386. for reset in resets_graph:
  387. reset.timestamp = {
  388. 'v': reset.timestamp.timestamp(),
  389. 'f': "Il y a " + format_timedelta(
  390. datetime.now() - reset.timestamp.replace(tzinfo=None),
  391. locale='fr', threshold=1)
  392. }
  393. if reset.selfSeum:
  394. reset.Seum = {'v': 0, 'f': reset.reason}
  395. else:
  396. reset.Seum = {'v': 0, 'f': 'De ' +
  397. reset.who.trigramme + ' : ' + reset.reason}
  398. # Drawing the graph
  399. data = ModelDataSource(
  400. resets, fields=['timestamp', 'Seum'])
  401. chart = gchart.LineChart(data, options={
  402. 'lineWidth': 0,
  403. 'pointSize': 10,
  404. 'title': '',
  405. 'vAxis': {'ticks': []},
  406. 'hAxis': {'ticks': [{
  407. 'v': firstReset.timestamp.timestamp(),
  408. 'f': 'Il y a ' + format_timedelta(
  409. datetime.now() - firstReset.timestamp.replace(tzinfo=None),
  410. locale='fr', threshold=1)
  411. }, {
  412. 'v': datetime.now().timestamp(),
  413. 'f': 'Présent'}
  414. ]},
  415. 'legend': 'none',
  416. 'height': 90
  417. })
  418. else:
  419. chart = None
  420. return render(request, 'counterTemplate.html', {
  421. 'counter': counter,
  422. 'chart': chart,
  423. 'resets': resets,
  424. 'seumFrequency': seumFrequency,
  425. 'myCounter': myCounter,
  426. })
  427. @login_required
  428. def hashtag(request, keyword):
  429. try:
  430. keyword = Keyword.objects.get(text=keyword)
  431. except Keyword.DoesNotExist:
  432. print('erreur !')
  433. return HttpResponseRedirect(reverse('home'))
  434. hashtag = '#' + keyword.text
  435. resets = Reset.objects.filter(
  436. hashtag__keyword=keyword).order_by('-timestamp')
  437. for reset in resets:
  438. if (reset.who is None or
  439. reset.who.id == reset.counter.id):
  440. reset.selfSeum = True
  441. else:
  442. reset.selfSeum = False
  443. reset.date = format_datetime(
  444. reset.timestamp, locale='fr',
  445. format="dd/MM/Y HH:mm")
  446. reset.likeCount = Like.objects.filter(reset=reset).count()
  447. return render(request, 'hashtagTemplate.html', {
  448. 'hashtag': hashtag,
  449. 'totalNumber': resets.count(),
  450. 'resets': resets,
  451. })
  452. def createUser(request):
  453. if (request.method == 'POST'):
  454. # create a form instance and populate it with data from the request:
  455. data = dict(request.POST)
  456. email = data['email'][0]
  457. username = email.split('@')[0]
  458. trigramme = data['trigramme'][0]
  459. nick = data['nick'][0]
  460. password1 = data['password1'][0]
  461. password2 = data['password2'][0]
  462. email_notifications = ('email_notifications' in data.keys())
  463. if password1 != password2:
  464. error = "Les deux mots de passe sont différents."
  465. return render(request, 'createUser.html', {'error': error})
  466. try:
  467. test_user = User.objects.get(email=email)
  468. error = "Un utilisateur avec cette adresse email existe déjà !"
  469. return render(request, 'createUser.html', {'error': error})
  470. except User.DoesNotExist:
  471. try:
  472. user = User.objects.create_user(username, email, password1)
  473. except IntegrityError:
  474. error = "Utilise une autre adresse email, un autre utilisateur \
  475. a le même login que toi."
  476. return render(request, 'createUser.html', {'error': error})
  477. counter = Counter()
  478. counter.name = nick
  479. counter.email = email
  480. counter.trigramme = trigramme
  481. counter.user = user
  482. counter.email_notifications = False
  483. counter.save()
  484. return render(request, 'createUserDone.html', {'login': username})
  485. else:
  486. return render(request, 'createUser.html', {'error': None})
  487. @login_required
  488. def toggleEmailNotifications(request):
  489. counter = Counter.objects.get(user=request.user)
  490. counter.email_notifications = not counter.email_notifications
  491. counter.save()
  492. return HttpResponseRedirect(reverse('home'))
  493. @login_required
  494. def toggleScoreSorting(request):
  495. counter = Counter.objects.get(user=request.user)
  496. counter.sort_by_score = not counter.sort_by_score
  497. counter.save()
  498. return HttpResponseRedirect(reverse('home'))
  499. @login_required
  500. def like(request):
  501. if (request.method == 'POST'):
  502. # create a form instance and populate it with data from the request:
  503. data = dict(request.POST)
  504. liker = Counter.objects.get(pk=data['liker'][0])
  505. reset = Reset.objects.get(pk=data['reset'][0])
  506. like = Like()
  507. like.liker = liker
  508. like.reset = reset
  509. like.save()
  510. return HttpResponseRedirect(data['redirect'][0])