views.py 7.3 KB

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