views.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  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.forms import FormLayout
  10. from misago.messages import Message
  11. from misago.core.admin.stats.forms import GenerateStatisticsForm
  12. from misago.core.views 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 request.theme.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 request.theme.render_to_response('stats/form.html', {
  62. 'form': FormLayout(form),
  63. 'message': message,
  64. }, context_instance=RequestContext(request));
  65. def graph(request, model, date_start, date_end, precision):
  66. """
  67. Generate fancy graph for model and stuff
  68. """
  69. if date_start == date_end:
  70. # Bad dates
  71. raise error404()
  72. # Turn stuff into datetime's
  73. date_start = datetime.strptime(date_start, '%Y-%m-%d')
  74. date_end = datetime.strptime(date_end, '%Y-%m-%d')
  75. statistics_providers = []
  76. models_map = {}
  77. for model_obj in models.get_models():
  78. try:
  79. getattr(model_obj.objects, 'filter_stats')
  80. statistics_providers.append((str(model_obj.__name__).lower(), model_obj.statistics_name))
  81. models_map[str(model_obj.__name__).lower()] = model_obj
  82. except AttributeError:
  83. pass
  84. if not statistics_providers:
  85. # Like before, q.q on lack of models
  86. return request.theme.render_to_response('stats/not_available.html',
  87. context_instance=RequestContext(request));
  88. if not model in models_map or check_dates(date_start, date_end, precision):
  89. # Bad model name or graph data!
  90. raise error404()
  91. form = GenerateStatisticsForm(
  92. provider_choices=statistics_providers,
  93. request=request,
  94. initial={'provider_model': model, 'date_start': date_start, 'date_end': date_end, 'stats_precision': precision})
  95. return request.theme.render_to_response('stats/graph.html', {
  96. 'title': models_map[model].statistics_name,
  97. 'graph': build_graph(models_map[model], date_start, date_end, precision),
  98. 'form': FormLayout(form),
  99. 'message': request.messages.get_message('admin_stats'),
  100. }, context_instance=RequestContext(request));
  101. def check_dates(date_start, date_end, precision):
  102. date_diff = date_end - date_start
  103. date_diff = date_diff.seconds + date_diff.days * 86400
  104. if ((precision == 'day' and date_diff / 86400 > 60)
  105. or (precision == 'week' and date_diff / 604800 > 60)
  106. or (precision == 'month' and date_diff / 2592000 > 60)
  107. or (precision == 'year' and date_diff / 31536000 > 60)):
  108. return Message(_('Too many many items to display on graph.'), 'error')
  109. elif ((precision == 'day' and date_diff / 86400 < 1)
  110. or (precision == 'week' and date_diff / 604800 < 1)
  111. or (precision == 'month' and date_diff / 2592000 < 1)
  112. or (precision == 'year' and date_diff / 31536000 < 1)):
  113. return Message(_('Too few items to display on graph'), 'error')
  114. return None
  115. def build_graph(model, date_start, date_end, precision):
  116. if precision == 'day':
  117. format = 'F j, Y'
  118. step = 86400
  119. if precision == 'week':
  120. format = 'W, Y'
  121. step = 604800
  122. if precision == 'month':
  123. format = 'F, Y'
  124. step = 2592000
  125. if precision == 'year':
  126. format = 'Y'
  127. step = 31536000
  128. date_end = timezone.make_aware(date_end, timezone.get_current_timezone())
  129. date_start = timezone.make_aware(date_start, timezone.get_current_timezone())
  130. date_diff = date_end - date_start
  131. date_diff = date_diff.seconds + date_diff.days * 86400
  132. steps = int(math.ceil(float(date_diff / step))) + 1
  133. timeline = [0 for i in range(0, steps)]
  134. for i in range(0, steps):
  135. step_date = date_end - timedelta(seconds=(i * step));
  136. timeline[steps - i - 1] = step_date
  137. stat = {'total': 0, 'max': 0, 'stat': [0 for i in range(0, steps)], 'timeline': timeline, 'start': date_start, 'end': date_end, 'format': format}
  138. # Loop model items
  139. for item in model.objects.filter_stats(date_start, date_end).iterator():
  140. date_diff = date_end - item.get_date()
  141. date_diff = date_diff.seconds + date_diff.days * 86400
  142. date_diff = steps - int(math.floor(float(date_diff / step))) - 2
  143. stat['stat'][date_diff] += 1
  144. stat['total'] += 1
  145. # Find max
  146. for i in stat['stat']:
  147. if i > stat['max']:
  148. stat['max'] = i
  149. return stat