views.py 7.3 KB

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