Browse Source

Overview app renamed to stats app and refractored.

Ralfp 12 years ago
parent
commit
c7543ca030

+ 4 - 4
misago/admin/layout/overview.py

@@ -21,10 +21,10 @@ ADMIN_ACTIONS=(
                name=_("Stats"),
                name=_("Stats"),
                help=_("Create Statistics Reports"),
                help=_("Create Statistics Reports"),
                icon='signal',
                icon='signal',
-               route='admin_overview_stats',
-               urlpatterns=patterns('misago.overview.admin.views',
-                        url(r'^$', 'overview_stats', name='admin_overview_stats'),
-                        url(r'^(?P<model>[a-z0-9]+)/(?P<date_start>[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9])/(?P<date_end>[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9])/(?P<precision>\w+)$', 'overview_graph', name='admin_overview_graph'),
+               route='admin_stats',
+               urlpatterns=patterns('misago.stats.views',
+                        url(r'^$', 'form', name='admin_stats'),
+                        url(r'^(?P<model>[a-z0-9]+)/(?P<date_start>[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9])/(?P<date_end>[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9])/(?P<precision>\w+)$', 'graph', name='admin_stats_graph'),
                     ),
                     ),
                ),
                ),
    AdminAction(
    AdminAction(

+ 1 - 1
misago/admin/views.py

@@ -2,7 +2,7 @@ from django.template import RequestContext
 from misago.sessions.models import Session
 from misago.sessions.models import Session
 
 
 def home(request):
 def home(request):
-    return request.theme.render_to_response('overview/home.html', {
+    return request.theme.render_to_response('home.html', {
         'users': request.monitor['users'],
         'users': request.monitor['users'],
         'users_inactive': request.monitor['users_inactive'],
         'users_inactive': request.monitor['users_inactive'],
         'threads': request.monitor['threads'],
         'threads': request.monitor['threads'],

+ 2 - 2
misago/forums/models.py

@@ -34,7 +34,7 @@ class Forum(MPTTModel):
 
 
 
 
 class ThreadManager(models.Manager):
 class ThreadManager(models.Manager):
-    def filter_overview(self, start, end):
+    def filter_stats(self, start, end):
         return self.filter(start__gte=start).filter(start__lte=end)
         return self.filter(start__gte=start).filter(start__lte=end)
 
 
 
 
@@ -69,7 +69,7 @@ class Thread(models.Model):
 
 
 
 
 class PostManager(models.Manager):
 class PostManager(models.Manager):
-    def filter_overview(self, start, end):
+    def filter_stats(self, start, end):
         return self.filter(date__gte=start).filter(date__lte=end)
         return self.filter(date__gte=start).filter(date__lte=end)
     
     
 
 

+ 0 - 0
misago/overview/admin/__init__.py


+ 1 - 1
misago/settings_base.py

@@ -105,7 +105,7 @@ INSTALLED_APPS = (
     'misago.cookie_jar', # Cookies helper
     'misago.cookie_jar', # Cookies helper
     'misago.forums', # Forums, threads and posts
     'misago.forums', # Forums, threads and posts
     'misago.messages', # Messages and Flashes
     'misago.messages', # Messages and Flashes
-    'misago.overview', # Admin system overview
+    'misago.stats', # Admin statistics generator
     'misago.security', # Security: CSRF, Firewall, etc ect
     'misago.security', # Security: CSRF, Firewall, etc ect
     'misago.sessions', # Sessions
     'misago.sessions', # Sessions
     'misago.setup', # Installation/update tool
     'misago.setup', # Installation/update tool

+ 0 - 0
misago/overview/__init__.py → misago/stats/__init__.py


+ 0 - 0
misago/overview/admin/forms.py → misago/stats/forms.py


+ 171 - 0
misago/stats/views.py

@@ -0,0 +1,171 @@
+import math
+from datetime import datetime, timedelta
+from django.core.urlresolvers import reverse
+from django.db import models
+from django.shortcuts import redirect
+from django.template import RequestContext
+from django.utils import timezone
+from django.utils.translation import ugettext as _
+from misago.forms import FormLayout
+from misago.messages import Message
+from misago.stats.forms import GenerateStatisticsForm
+from misago.views import error404
+
+
+def form(request):
+    """
+    Allow admins to generate fancy statistic graphs for different models
+    """
+    statistics_providers = []
+    models_map = {}
+    for model in models.get_models():
+        try:
+            getattr(model.objects, 'filter_stats')
+            statistics_providers.append((str(model.__name__).lower(), model.statistics_name))
+            models_map[str(model.__name__).lower()] = model
+        except AttributeError:
+            pass
+
+    if not statistics_providers:
+        """
+        Something went FUBAR - Misago ships with some stats providers out of box
+        If those providers cant be found, this means Misago filesystem is corrupted
+        """
+        return request.theme.render_to_response('stats/not_available.html',
+                                                context_instance=RequestContext(request));
+    
+    message = None
+    if request.method == 'POST':
+        form = GenerateStatisticsForm(request.POST, provider_choices=statistics_providers, request=request)
+        if form.is_valid():
+            date_start = form.cleaned_data['date_start']
+            date_end = form.cleaned_data['date_end']
+            if date_start > date_end:
+                # Reverse dates if start is after end
+                date_temp = date_end
+                date_end = date_start
+                date_start = date_temp
+            # Assert that dates are correct
+            if date_end == date_start:
+                message = Message(_('Start and end date are same'), type='error')
+            elif check_dates(date_start, date_end, form.cleaned_data['stats_precision']):
+                message = check_dates(date_start, date_end, form.cleaned_data['stats_precision'])
+            else:
+                request.messages.set_flash(Message(_('Statistical report has been created.')), 'success', 'admin_stats')
+                return redirect(reverse('admin_stats_graph', kwargs={
+                                                       'model': form.cleaned_data['provider_model'],
+                                                       'date_start': date_start.strftime('%Y-%m-%d'),
+                                                       'date_end': date_end.strftime('%Y-%m-%d'),
+                                                       'precision': form.cleaned_data['stats_precision']
+                                                        }))
+        else:
+            message = Message(form.non_field_errors()[0], 'error')
+    else:
+        form = GenerateStatisticsForm(provider_choices=statistics_providers, request=request)
+    
+    return request.theme.render_to_response('stats/form.html', {
+                                            'form': FormLayout(form),
+                                            'message': message,
+                                            }, context_instance=RequestContext(request));
+
+
+def graph(request, model, date_start, date_end, precision):
+    """
+    Generate fancy graph for model and stuff
+    """
+    if date_start == date_end:
+        # Bad dates
+        raise error404()
+    
+    # Turn stuff into datetime's
+    date_start = datetime.strptime(date_start, '%Y-%m-%d')
+    date_end = datetime.strptime(date_end, '%Y-%m-%d')
+    
+    
+    statistics_providers = []
+    models_map = {}
+    for model_obj in models.get_models():
+        try:
+            getattr(model_obj.objects, 'filter_stats')
+            statistics_providers.append((str(model_obj.__name__).lower(), model_obj.statistics_name))
+            models_map[str(model_obj.__name__).lower()] = model_obj
+        except AttributeError:
+            pass
+
+    if not statistics_providers:
+        # Like before, q.q on lack of models
+        return request.theme.render_to_response('stats/not_available.html',
+                                                context_instance=RequestContext(request));
+    
+    if not model in models_map or check_dates(date_start, date_end, precision):
+        # Bad model name or graph data!
+        raise error404()
+    
+    form = GenerateStatisticsForm(
+                                  provider_choices=statistics_providers,
+                                  request=request,
+                                  initial={'provider_model': model, 'date_start': date_start, 'date_end': date_end, 'stats_precision': precision})
+    return request.theme.render_to_response('stats/graph.html', {
+                                            'title': models_map[model].statistics_name,
+                                            'graph': build_graph(models_map[model], date_start, date_end, precision),
+                                            'form': FormLayout(form),
+                                            'message': request.messages.get_message('admin_stats'),
+                                            }, context_instance=RequestContext(request));
+
+
+def check_dates(date_start, date_end, precision):
+    date_diff = date_end - date_start
+    date_diff = date_diff.seconds + date_diff.days * 86400
+    
+    if ((precision == 'day' and date_diff / 86400 > 60)
+        or (precision == 'week' and date_diff / 604800 > 60)
+        or (precision == 'month' and date_diff / 2592000 > 60)
+        or (precision == 'year' and date_diff / 31536000 > 60)):
+        return Message(_('Too many many items to display on graph.'), 'error')
+    elif ((precision == 'day' and date_diff / 86400 < 1)
+          or (precision == 'week' and date_diff / 604800 < 1)
+          or (precision == 'month' and date_diff / 2592000 < 1)
+          or (precision == 'year' and date_diff / 31536000 < 1)):
+        return Message(_('Too few items to display on graph'), 'error')
+    return None
+        
+
+def build_graph(model, date_start, date_end, precision):
+    if precision == 'day':
+        format = 'F j, Y'
+        step = 86400
+    if precision == 'week':
+        format = 'W, Y'
+        step = 604800
+    if precision == 'month':
+        format = 'F, Y'
+        step = 2592000
+    if precision == 'year':
+        format = 'Y'
+        step = 31536000
+    
+    date_end = timezone.make_aware(date_end, timezone.get_current_timezone())
+    date_start = timezone.make_aware(date_start, timezone.get_current_timezone())
+    
+    date_diff = date_end - date_start
+    date_diff = date_diff.seconds + date_diff.days * 86400
+    steps = int(math.ceil(float(date_diff / step))) + 1
+    timeline = [0 for i in range(0, steps)]
+    for i in range(0, steps):
+        step_date = date_end - timedelta(seconds=(i * step));
+        timeline[steps - i - 1] = step_date    
+    stat = {'total': 0, 'max': 0, 'stat': [0 for i in range(0, steps)], 'timeline': timeline, 'start': date_start, 'end': date_end, 'format': format}
+        
+    # Loop model items
+    for item in model.objects.filter_stats(date_start, date_end).iterator():
+        date_diff = date_end - item.get_date()
+        date_diff = date_diff.seconds + date_diff.days * 86400
+        date_diff = steps - int(math.floor(float(date_diff / step))) - 2
+        stat['stat'][date_diff] += 1
+        stat['total'] += 1
+        
+    # Find max
+    for i in stat['stat']:
+        if i > stat['max']:
+            stat['max'] = i
+    return stat

+ 1 - 1
misago/users/models.py

@@ -100,7 +100,7 @@ class UserManager(models.Manager):
     def get_by_email(self, email):
     def get_by_email(self, email):
         return self.get(email_hash=hashlib.md5(email).hexdigest())
         return self.get(email_hash=hashlib.md5(email).hexdigest())
     
     
-    def filter_overview(self, start, end):
+    def filter_stats(self, start, end):
         return self.filter(join_date__gte=start).filter(join_date__lte=end)
         return self.filter(join_date__gte=start).filter(join_date__lte=end)
     
     
         
         

+ 2 - 2
templates/admin/overview/stats/form.html → templates/admin/stats/form.html

@@ -1,11 +1,11 @@
-{% extends "admin/overview/stats/layout.html" %}
+{% extends "admin/stats/layout.html" %}
 {% load i18n %}
 {% load i18n %}
 {% import "_forms.html" as form_theme with context %}
 {% import "_forms.html" as form_theme with context %}
 
 
 {% block action %}<div class="row">
 {% block action %}<div class="row">
   <div class="span8 offset2">
   <div class="span8 offset2">
   	<h2>{% trans %}New Report{% endtrans %}</h2>
   	<h2>{% trans %}New Report{% endtrans %}</h2>
-    <form action="{% url 'admin_overview_stats' %}" class="form-vertical" method="post">
+    <form action="{% url 'admin_stats' %}" class="form-vertical" method="post">
       <div class="form-container">
       <div class="form-container">
         {{ form_theme.form_widget(form, width=8) }}
         {{ form_theme.form_widget(form, width=8) }}
       </div>
       </div>

+ 1 - 1
templates/admin/overview/stats/graph.html → templates/admin/stats/graph.html

@@ -1,4 +1,4 @@
-{% extends "admin/overview/stats/form.html" %}
+{% extends "admin/stats/form.html" %}
 {% load i18n %}
 {% load i18n %}
 {% import "_forms.html" as form_theme with context %}
 {% import "_forms.html" as form_theme with context %}
 {% from "admin/macros.html" import page_title %}
 {% from "admin/macros.html" import page_title %}

+ 0 - 0
templates/admin/overview/stats/layout.html → templates/admin/stats/layout.html


+ 1 - 1
templates/admin/overview/stats/not_available.html → templates/admin/stats/not_available.html

@@ -1,4 +1,4 @@
-{% extends "admin/overview/stats/layout.html" %}
+{% extends "admin/stats/layout.html" %}
 {% load i18n %}
 {% load i18n %}
 
 
 {% block action %}<div class="alert">
 {% block action %}<div class="alert">

+ 0 - 0
templates/admin/overview/stats/plot.html → templates/admin/stats/plot.html