views.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. import math
  2. from datetime import datetime, timedelta
  3. from django.conf import settings
  4. from django.core.urlresolvers import reverse
  5. from django.db import models
  6. from django.shortcuts import redirect
  7. from django.template import RequestContext
  8. from django.utils import formats, timezone
  9. from django.utils.translation import ugettext as _
  10. from misago.admin import site
  11. from misago.admin.widgets import *
  12. from misago.forms import FormLayout
  13. from misago.forums.models import Thread, Post
  14. from misago.messages import Message
  15. from misago.overview.admin.forms import GenerateStatisticsForm
  16. from misago.sessions.models import Session
  17. from misago.users.models import User
  18. from misago.views import error404
  19. def overview_stats(request):
  20. """
  21. Allow admins to generate fancy statistic graphs for different models
  22. """
  23. statistics_providers = []
  24. models_map = {}
  25. for model in models.get_models():
  26. try:
  27. getattr(model.objects, 'filter_overview')
  28. statistics_providers.append((str(model.__name__).lower(), model.statistics_name))
  29. models_map[str(model.__name__).lower()] = model
  30. except AttributeError:
  31. pass
  32. if not statistics_providers:
  33. """
  34. Something went FUBAR - Misago ships with some stats providers out of box
  35. If those providers cant be found, this means Misago filesystem is corrupted
  36. """
  37. return request.theme.render_to_response('overview/stats/not_available.html',
  38. context_instance=RequestContext(request));
  39. message = None
  40. if request.method == 'POST':
  41. form = GenerateStatisticsForm(request.POST, provider_choices=statistics_providers, request=request)
  42. if form.is_valid():
  43. date_start = form.cleaned_data['date_start']
  44. date_end = form.cleaned_data['date_end']
  45. if date_start > date_end:
  46. # Reverse dates if start is after end
  47. date_temp = date_end
  48. date_end = date_start
  49. date_start = date_temp
  50. # Assert that dates are correct
  51. if date_end == date_start:
  52. message = Message(_('Start and end date are same'), type='error')
  53. elif check_dates(date_start, date_end, form.cleaned_data['stats_precision']):
  54. message = check_dates(date_start, date_end, form.cleaned_data['stats_precision'])
  55. else:
  56. request.messages.set_flash(Message(_('Statistical report has been created.')), 'success', 'admin_stats')
  57. return redirect(reverse('admin_overview_graph', kwargs={
  58. 'model': form.cleaned_data['provider_model'],
  59. 'date_start': date_start.strftime('%Y-%m-%d'),
  60. 'date_end': date_end.strftime('%Y-%m-%d'),
  61. 'precision': form.cleaned_data['stats_precision']
  62. }))
  63. else:
  64. message = Message(form.non_field_errors()[0], 'error')
  65. else:
  66. form = GenerateStatisticsForm(provider_choices=statistics_providers, request=request)
  67. return request.theme.render_to_response('overview/stats/form.html', {
  68. 'form': FormLayout(form),
  69. 'message': message,
  70. }, context_instance=RequestContext(request));
  71. def overview_graph(request, model, date_start, date_end, precision):
  72. """
  73. Generate fancy graph for model and stuff
  74. """
  75. if date_start == date_end:
  76. # Bad dates
  77. raise error404()
  78. # Turn stuff into datetime's
  79. date_start = datetime.strptime(date_start, '%Y-%m-%d')
  80. date_end = datetime.strptime(date_end, '%Y-%m-%d')
  81. statistics_providers = []
  82. models_map = {}
  83. for model_obj in models.get_models():
  84. try:
  85. getattr(model_obj.objects, 'filter_overview')
  86. statistics_providers.append((str(model_obj.__name__).lower(), model_obj.statistics_name))
  87. models_map[str(model_obj.__name__).lower()] = model_obj
  88. except AttributeError:
  89. pass
  90. if not statistics_providers:
  91. # Like before, q.q on lack of models
  92. return request.theme.render_to_response('overview/stats/not_available.html',
  93. context_instance=RequestContext(request));
  94. if not model in models_map or check_dates(date_start, date_end, precision):
  95. # Bad model name or graph data!
  96. raise error404()
  97. form = GenerateStatisticsForm(
  98. provider_choices=statistics_providers,
  99. request=request,
  100. initial={'provider_model': model, 'date_start': date_start, 'date_end': date_end, 'stats_precision': precision})
  101. return request.theme.render_to_response('overview/stats/graph.html', {
  102. 'title': models_map[model].statistics_name,
  103. 'graph': build_graph(models_map[model], date_start, date_end, precision),
  104. 'form': FormLayout(form),
  105. 'message': request.messages.get_message('admin_stats'),
  106. }, context_instance=RequestContext(request));
  107. def check_dates(date_start, date_end, precision):
  108. date_diff = date_end - date_start
  109. date_diff = date_diff.seconds + date_diff.days * 86400
  110. if ((precision == 'day' and date_diff / 86400 > 60)
  111. or (precision == 'week' and date_diff / 604800 > 60)
  112. or (precision == 'month' and date_diff / 2592000 > 60)
  113. or (precision == 'year' and date_diff / 31536000 > 60)):
  114. return Message(_('Too many many items to display on graph.'), 'error')
  115. elif ((precision == 'day' and date_diff / 86400 < 1)
  116. or (precision == 'week' and date_diff / 604800 < 1)
  117. or (precision == 'month' and date_diff / 2592000 < 1)
  118. or (precision == 'year' and date_diff / 31536000 < 1)):
  119. return Message(_('Too few items to display on graph'), 'error')
  120. return None
  121. def build_graph(model, date_start, date_end, precision):
  122. if precision == 'day':
  123. format = 'F j, Y'
  124. step = 86400
  125. if precision == 'week':
  126. format = 'W, Y'
  127. step = 604800
  128. if precision == 'month':
  129. format = 'F, Y'
  130. step = 2592000
  131. if precision == 'year':
  132. format = 'Y'
  133. step = 31536000
  134. date_end = timezone.make_aware(date_end, timezone.get_current_timezone())
  135. date_start = timezone.make_aware(date_start, timezone.get_current_timezone())
  136. date_diff = date_end - date_start
  137. date_diff = date_diff.seconds + date_diff.days * 86400
  138. steps = int(math.ceil(float(date_diff / step))) + 1
  139. timeline = [0 for i in range(0, steps)]
  140. for i in range(0, steps):
  141. step_date = date_end - timedelta(seconds=(i * step));
  142. timeline[steps - i - 1] = step_date
  143. stat = {'total': 0, 'max': 0, 'stat': [0 for i in range(0, steps)], 'timeline': timeline, 'start': date_start, 'end': date_end, 'format': format}
  144. # Loop model items
  145. for item in model.objects.filter_overview(date_start, date_end).iterator():
  146. date_diff = date_end - item.get_date()
  147. date_diff = date_diff.seconds + date_diff.days * 86400
  148. date_diff = steps - int(math.floor(float(date_diff / step))) - 2
  149. stat['stat'][date_diff] += 1
  150. stat['total'] += 1
  151. # Find max
  152. for i in stat['stat']:
  153. if i > stat['max']:
  154. stat['max'] = i
  155. return stat