설명 없음

views.py 21KB

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