Browse Source

WIP Admin actions move to floppyforms. #114

Rafał Pitoń 12 years ago
parent
commit
fdc4c96bc5

+ 7 - 17
misago/apps/admin/online/forms.py

@@ -3,24 +3,14 @@ from django.utils.translation import ugettext_lazy as _
 from misago.forms import Form
 
 class SearchSessionsForm(Form):
-    username = forms.CharField(max_length=255, required=False)
-    ip_address = forms.CharField(max_length=255, required=False)
-    useragent = forms.CharField(max_length=255, required=False)
-    type = forms.ChoiceField(choices=(
+    search_name = _("Search Sessions")
+    username = forms.CharField(label=_("Username"), max_length=255, required=False)
+    ip_address = forms.CharField(label=_("IP Address"), max_length=255, required=False)
+    useragent = forms.CharField(label=_("User Agent"), max_length=255, required=False)
+    type = forms.ChoiceField(label=_("Session Type"),
+                             choices=(
                                       ('all', _("All types")),
                                       ('registered', _("Registered Members Sessions")),
                                       ('guest', _("Guests Sessions")),
                                       ('crawler', _("Crawler Sessions")),
-                                      ), required=False)
-
-    layout = (
-              (
-               _("Search Sessions"),
-               (
-                ('ip_address', {'label': _("IP Address"), 'attrs': {'placeholder': _("IP begins with...")}}),
-                ('username', {'label': _("Username"), 'attrs': {'placeholder': _("Username begings with...")}}),
-                ('useragent', {'label': _("User Agent"), 'attrs': {'placeholder': _("User Agent contains...")}}),
-                ('type', {'label': _("Session Type")}),
-               ),
-              ),
-             )
+                                     ), required=False)

+ 118 - 118
misago/apps/admin/settings/views.py

@@ -1,118 +1,118 @@
-from django.core.cache import cache
-from django.core.urlresolvers import reverse
-from django.shortcuts import redirect
-from django.template import RequestContext
-from django.utils.translation import ungettext, ugettext as _
-from misago.conf import settings as misago_settings
-from misago.forms import Form, FormLayout, FormFields
-from misago.messages import Message
-from misago.search import SearchQuery, SearchException
-from misago.models import SettingsGroup, Setting
-from misago.shortcuts import render_to_response
-from misago.apps.errors import error404
-from misago.apps.admin.settings.forms import SearchForm
-
-def settings(request, group_id=None, group_slug=None):
-    # Load groups and find selected group
-    settings_groups = SettingsGroup.objects.all().order_by('key')
-    if not group_id:
-        active_group = settings_groups[0]
-        group_id = active_group.pk
-    else:
-        group_id = int(group_id)
-        for group in settings_groups:
-            if group.pk == group_id:
-                active_group = group
-                break
-        else:
-            return error404(request, _('Requested settings group could not be found.'))
-
-    # Load selected group settings and turn them into form
-    group_settings = Setting.objects.filter(group=active_group).order_by('position')
-    last_fieldset = (None, [])
-    group_form = {'layout': []}
-    for setting in group_settings:
-        # New field subgroup?
-        if setting.separator and last_fieldset[0] != setting.separator:
-            if last_fieldset[0]:
-                group_form['layout'].append(last_fieldset)
-            last_fieldset = (_(setting.separator), [])
-        last_fieldset[1].append(setting.pk)
-        group_form[setting.pk] = setting.get_field()
-    group_form['layout'].append(last_fieldset)
-    SettingsGroupForm = type('SettingsGroupForm', (Form,), group_form)
-
-    #Submit form
-    message = request.messages.get_message('admin_settings')
-    if request.method == 'POST':
-        form = SettingsGroupForm(request.POST, request=request)
-        if form.is_valid():
-            for setting in form.cleaned_data.keys():
-                misago_settings[setting] = form.cleaned_data[setting]
-            cache.delete('settings')
-            request.messages.set_flash(Message(_('Configuration has been changed.')), 'success', 'admin_settings')
-            return redirect(reverse('admin_settings', kwargs={
-                                                       'group_id': active_group.pk,
-                                                       'group_slug': active_group.key,
-                                                       }))
-        else:
-            message = Message(form.non_field_errors()[0], 'error')
-    else:
-        form = SettingsGroupForm(request=request)
-
-    # Display settings group form      
-    return render_to_response('settings/settings.html',
-                              {
-                              'message': message,
-                              'groups': settings_groups,
-                              'active_group': active_group,
-                              'search_form': FormFields(SearchForm(request=request)),
-                              'form': FormLayout(form),
-                              'raw_form': form,
-                              },
-                              context_instance=RequestContext(request));
-
-
-def settings_search(request):
-    settings_groups = SettingsGroup.objects.all().order_by('key')
-    message = None
-    found_settings = []
-    try:
-        if request.method == 'POST' and request.csrf.request_secure(request):
-            form = SearchForm(request.POST, request=request)
-            if form.is_valid():
-                # Start search
-                search_strings = SearchQuery(form.cleaned_data['search_text'])
-
-                # Loop over groups using our search query
-                for setting in Setting.objects.all().order_by('setting'):
-                    if (search_strings.search(_(setting.name))
-                        or (setting.description and search_strings.search(_(setting.description)))
-                        or (setting.value and search_strings.search(setting.value))):
-                        found_settings.append(setting)
-
-                # Scream if nothing could be found
-                if found_settings:
-                    message = Message(ungettext(
-                                                'One setting that matches search criteria has been found.',
-                                                '%(count)d settings that match search criteria have been found.',
-                                                len(found_settings)) % {
-                                                    'count': len(found_settings),
-                                                }, 'success')
-                else:
-                    raise SearchException(_('No settings that match search criteria have been found.'))
-            else:
-                raise SearchException(_('Search query is empty.'))
-        else:
-            raise SearchException(_('Search query is invalid.'))
-    except SearchException as e:
-        message = Message(unicode(e), 'error')
-    return render_to_response('settings/search_results.html',
-                              {
-                              'message': message,
-                              'groups': settings_groups,
-                              'active_group': None,
-                              'found_settings': found_settings,
-                              'search_form': FormFields(form),
-                              },
-                              context_instance=RequestContext(request));
+from django.core.cache import cache
+from django.core.urlresolvers import reverse
+from django.shortcuts import redirect
+from django.template import RequestContext
+from django.utils.translation import ungettext, ugettext as _
+from misago.conf import settings as misago_settings
+from misago.forms import Form
+from misago.messages import Message
+from misago.search import SearchQuery, SearchException
+from misago.models import SettingsGroup, Setting
+from misago.shortcuts import render_to_response
+from misago.apps.errors import error404
+from misago.apps.admin.settings.forms import SearchForm
+
+def settings(request, group_id=None, group_slug=None):
+    # Load groups and find selected group
+    settings_groups = SettingsGroup.objects.all().order_by('key')
+    if not group_id:
+        active_group = settings_groups[0]
+        group_id = active_group.pk
+    else:
+        group_id = int(group_id)
+        for group in settings_groups:
+            if group.pk == group_id:
+                active_group = group
+                break
+        else:
+            return error404(request, _('Requested settings group could not be found.'))
+
+    # Load selected group settings and turn them into form
+    group_settings = Setting.objects.filter(group=active_group).order_by('position')
+    last_fieldset = (None, [])
+    group_form = {'layout': []}
+    for setting in group_settings:
+        # New field subgroup?
+        if setting.separator and last_fieldset[0] != setting.separator:
+            if last_fieldset[0]:
+                group_form['layout'].append(last_fieldset)
+            last_fieldset = (_(setting.separator), [])
+        last_fieldset[1].append(setting.pk)
+        group_form[setting.pk] = setting.get_field()
+    group_form['layout'].append(last_fieldset)
+    SettingsGroupForm = type('SettingsGroupForm', (Form,), group_form)
+
+    #Submit form
+    message = request.messages.get_message('admin_settings')
+    if request.method == 'POST':
+        form = SettingsGroupForm(request.POST, request=request)
+        if form.is_valid():
+            for setting in form.cleaned_data.keys():
+                misago_settings[setting] = form.cleaned_data[setting]
+            cache.delete('settings')
+            request.messages.set_flash(Message(_('Configuration has been changed.')), 'success', 'admin_settings')
+            return redirect(reverse('admin_settings', kwargs={
+                                                       'group_id': active_group.pk,
+                                                       'group_slug': active_group.key,
+                                                       }))
+        else:
+            message = Message(form.non_field_errors()[0], 'error')
+    else:
+        form = SettingsGroupForm(request=request)
+
+    # Display settings group form      
+    return render_to_response('settings/settings.html',
+                              {
+                              'message': message,
+                              'groups': settings_groups,
+                              'active_group': active_group,
+                              'search_form': SearchForm(request=request),
+                              'form': form,
+                              'raw_form': form,
+                              },
+                              context_instance=RequestContext(request));
+
+
+def settings_search(request):
+    settings_groups = SettingsGroup.objects.all().order_by('key')
+    message = None
+    found_settings = []
+    try:
+        if request.method == 'POST' and request.csrf.request_secure(request):
+            form = SearchForm(request.POST, request=request)
+            if form.is_valid():
+                # Start search
+                search_strings = SearchQuery(form.cleaned_data['search_text'])
+
+                # Loop over groups using our search query
+                for setting in Setting.objects.all().order_by('setting'):
+                    if (search_strings.search(_(setting.name))
+                        or (setting.description and search_strings.search(_(setting.description)))
+                        or (setting.value and search_strings.search(setting.value))):
+                        found_settings.append(setting)
+
+                # Scream if nothing could be found
+                if found_settings:
+                    message = Message(ungettext(
+                                                'One setting that matches search criteria has been found.',
+                                                '%(count)d settings that match search criteria have been found.',
+                                                len(found_settings)) % {
+                                                    'count': len(found_settings),
+                                                }, 'success')
+                else:
+                    raise SearchException(_('No settings that match search criteria have been found.'))
+            else:
+                raise SearchException(_('Search query is empty.'))
+        else:
+            raise SearchException(_('Search query is invalid.'))
+    except SearchException as e:
+        message = Message(unicode(e), 'error')
+    return render_to_response('settings/search_results.html',
+                              {
+                              'message': message,
+                              'groups': settings_groups,
+                              'active_group': None,
+                              'found_settings': found_settings,
+                              'search_form': FormFields(form),
+                              },
+                              context_instance=RequestContext(request));

+ 7 - 14
misago/apps/admin/stats/forms.py

@@ -5,21 +5,14 @@ from django.utils.translation import ugettext_lazy as _
 from misago.forms import Form
 
 class GenerateStatisticsForm(Form):
-    provider_model = forms.ChoiceField()
-    date_start = forms.DateField(initial=tz.now() - timedelta(days=7))
+    provider_model = forms.ChoiceField(label=_('Report Type'),
+                                       help_text=_('Select statistics provider.'))
+    date_start = forms.DateField(label=_('Time Period'),
+                                 help_text=_('Enter start and end date for time period you want to take data from to use in graph.'),
+                                 initial=tz.now() - timedelta(days=7))
     date_end = forms.DateField(initial=tz.now())
-    stats_precision = forms.ChoiceField(choices=(('day', _('For each day')), ('week', _('For each week')), ('month', _('For each month')), ('year', _('For each year'))))
-
-    layout = (
-              (None, (
-                        ('provider_model', {'label': _('Report Type'), 'help_text': _('Select statistics provider.')}),
-                        ('nested', (
-                            ('date_start', {'label': _('Time'), 'help_text': _('Enter start and end date for time period you want to take data from to use in graph.'), 'attrs': {'placeholder': _('Start Date: YYYY-MM-DD')}, 'width': 50}),
-                            ('date_end', {'attrs': {'placeholder': _('End Date: YYYY-MM-DD')}, 'width': 50}),
-                        )),
-                        ('stats_precision', {'label': _('Graph Precision')}),
-                      )),
-              )
+    stats_precision = forms.ChoiceField(label=_('Graph Precision'),
+                                        choices=(('day', _('For each day')), ('week', _('For each week')), ('month', _('For each month')), ('year', _('For each year'))))
 
     def __init__(self, *args, **kwargs):
         provider_choices = kwargs.get('provider_choices')

+ 174 - 175
misago/apps/admin/stats/views.py

@@ -1,175 +1,174 @@
-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.shortcuts import render_to_response
-from misago.apps.admin.stats.forms import GenerateStatisticsForm
-from misago.apps.errors 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 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 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(RequestContext)
-
-    # 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 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(request)
-
-    form = GenerateStatisticsForm(
-                                  provider_choices=statistics_providers,
-                                  request=request,
-                                  initial={'provider_model': model, 'date_start': date_start, 'date_end': date_end, 'stats_precision': precision})
-    return 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
+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.messages import Message
+from misago.shortcuts import render_to_response
+from misago.apps.admin.stats.forms import GenerateStatisticsForm
+from misago.apps.errors 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 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 render_to_response('stats/form.html',
+                              {
+                              'form': 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(RequestContext)
+
+    # 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 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(request)
+
+    form = GenerateStatisticsForm(
+                                  provider_choices=statistics_providers,
+                                  request=request,
+                                  initial={'provider_model': model, 'date_start': date_start, 'date_end': date_end, 'stats_precision': precision})
+    return render_to_response('stats/graph.html',
+                              {
+                              'title': models_map[model].statistics_name,
+                              'graph': build_graph(models_map[model], date_start, date_end, precision),
+                              'form': 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

+ 5 - 5
misago/apps/admin/widgets.py

@@ -7,7 +7,7 @@ from django.template import RequestContext
 from django.utils.translation import ugettext_lazy as _
 from jinja2 import TemplateNotFound
 import math
-from misago.forms import Form, FormLayout, FormFields, FormFieldsets
+from misago.forms import Form
 from misago.messages import Message
 from misago.shortcuts import render_to_response
 from misago.utils.pagination import make_pagination
@@ -364,9 +364,9 @@ class ListWidget(BaseWidget):
                                    'sorting': self.sortables,
                                    'sorting_method': sorting_method,
                                    'pagination': paginating_method,
-                                   'list_form': FormLayout(list_form) if list_form else None,
-                                   'search_form': FormLayout(search_form) if search_form else None,
-                                   'table_form': FormFields(table_form).fields if table_form else None,
+                                   'list_form': list_form or None,
+                                   'search_form': search_form or None,
+                                   'table_form': table_form or None,
                                    'items': items,
                                    'items_total': items_total,
                                    'items_shown': items_shown,
@@ -474,7 +474,7 @@ class FormWidget(BaseWidget):
                                    'tabbed': self.tabbed,
                                    'target': self.get_target_name(original_model),
                                    'target_model': original_model,
-                                   'form': FormLayout(form, self.get_layout(form, target)),
+                                   'form': form,
                                   }),
                                   context_instance=RequestContext(request));
 

+ 11 - 313
misago/forms/layouts.py

@@ -1,313 +1,11 @@
-from UserDict import IterableUserDict
-from recaptcha.client.captcha import displayhtml
-from django.utils import formats
-from misago.conf import settings
-
-class FormLayout(object):
-    """
-    Conglomelate of fields and fieldsets describing form structure
-    """
-    def __init__(self, form, fieldsets=False):
-        scaffold_fields = FormFields(form)
-        scaffold_fieldsets = FormFieldsets(form, scaffold_fields.fields, fieldsets)
-
-        self.multipart_form = scaffold_fields.multipart_form
-        self.fieldsets = scaffold_fieldsets.fieldsets
-        self.hidden = scaffold_fields.hidden
-
-        # Extract fields definitions from form layout
-        if self.fieldsets:
-            self.fields = {}
-            for fieldset in self.fieldsets:
-                for field in fieldset['fields']:
-                    if field['nested']:
-                        for nested in field['nested']:
-                            self.fields[nested['id']] = nested
-                    else:
-                        self.fields[field['id']] = field
-        else:
-            self.fields = scaffold_fields.fields
-
-
-class FormFields(object):
-    """
-    Hydrator that builds fields list from form and blueprint
-    """
-    def __init__(self, form):
-        self.multipart_form = False
-        self.fields = {}
-        self.hidden = []
-
-        # Extract widgets from meta
-        self.meta_widgets = {}
-        try:
-            self.meta_widgets = form.Meta.widgets
-        except AttributeError:
-            pass
-
-        # Find out field input types
-        for field in form.fields.keys():
-            widget = self._get_widget(field, form.fields[field])
-            widget_name = widget.__class__.__name__
-            bound_field = form[field]
-            blueprint = {
-                         'attrs': {
-                                    'id': bound_field.auto_id,
-                                    'name': bound_field.html_name,
-                                   },
-                         'endrow': False,
-                         'errors': [],
-                         'has_value': bound_field.value() != None,
-                         'help_text': bound_field.help_text,
-                         'hidden': widget.is_hidden,
-                         'html_id': bound_field.auto_id,
-                         'html_name': bound_field.html_name,
-                         'id': field,
-                         'initial': bound_field.field.initial,
-                         'label': bound_field.label,
-                         'last': False,
-                         'nested': [],
-                         'required': bound_field.field.widget.is_required,
-                         'show_hidden_initial': bound_field.field.show_hidden_initial,
-                         'value': bound_field.value(),
-                         'width': 100,
-                         'widget': '',
-                         'choices': [],
-                        }
-
-            # Set multipart form
-            if widget.needs_multipart_form:
-                self.multipart_form = True
-
-            # Get errors?
-            if form.is_bound:
-                for error in bound_field._errors():
-                    blueprint['errors'].append(error)
-                try:
-                    for error in form.errors[field]:
-                        if not error in blueprint['errors']:
-                            blueprint['errors'].append(error)
-                except KeyError:
-                    pass
-
-            # Use clean value instead?
-            try:
-                if field in form.cleaned_data:
-                    blueprint['value'] = form.cleaned_data[field]
-            except AttributeError:
-                pass
-
-            # TextInput
-            if widget_name in ['TextInput', 'PasswordInput', 'Textarea']:
-                blueprint['widget'] = 'text'
-                blueprint['attrs']['type'] = 'text'
-                try:
-                    blueprint['attrs']['maxlength'] = bound_field.field.max_length
-                except AttributeError:
-                    pass
-
-            # PasswordInput
-            if widget_name == 'PasswordInput':
-                blueprint['attrs']['type'] = 'password'
-
-            # Textarea      
-            if widget_name == 'Textarea':
-                blueprint['widget'] = 'textarea'
-
-            # ReCaptcha      
-            if widget_name == 'ReCaptchaWidget':
-                blueprint['widget'] = 'recaptcha'
-                blueprint['attrs'] = {'html': displayhtml(
-                                                          settings.recaptcha_public,
-                                                          settings.recaptcha_ssl,
-                                                          bound_field.field.api_error,
-                                                          )}
-
-            # HiddenInput
-            if widget_name == 'HiddenInput':
-                blueprint['widget'] = 'hidden'
-
-            # MultipleHiddenInput
-            if widget_name == 'MultipleHiddenInput':
-                blueprint['widget'] = 'multiple_hidden'
-                blueprint['attrs'] = {
-                                      'choices': widget.choices
-                                     }
-
-            # FileInput
-            if widget_name == 'FileInput':
-                blueprint['widget'] = 'file'
-
-            # ClearableFileInput
-            if widget_name == 'ClearableFileInput':
-                blueprint['widget'] = 'file_clearable'
-
-            # DateInput
-            if widget_name == 'DateInput':
-                blueprint['widget'] = 'date'
-                try:
-                    blueprint['value'] = blueprint['value'].strftime('%Y-%m-%d')
-                except AttributeError as e:
-                    pass
-
-            # DateTimeInput
-            if widget_name == 'DateTimeInput':
-                blueprint['widget'] = 'datetime'
-                try:
-                    blueprint['value'] = blueprint['value'].strftime('%Y-%m-%d %H:%M')
-                except AttributeError as e:
-                    pass
-
-            # TimeInput
-            if widget_name == 'TimeInput':
-                blueprint['widget'] = 'time'
-                try:
-                    blueprint['value'] = blueprint['value'].strftime('%H:%M')
-                except AttributeError as e:
-                    pass
-
-            # CheckboxInput
-            if widget_name == 'CheckboxInput':
-                blueprint['widget'] = 'checkbox'
-
-            # Select, NullBooleanSelect, SelectMultiple, RadioSelect, CheckboxSelectMultiple
-            if widget_name in ['Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect', 'CheckboxSelectMultiple']:
-                blueprint['choices'] = widget.choices
-
-            # Yes-no radio select
-            if widget_name == 'YesNoSwitch':
-                blueprint['widget'] = 'yes_no_switch'
-
-            # Select
-            if widget_name == 'Select':
-                blueprint['widget'] = 'select'
-                if not blueprint['has_value']:
-                    blueprint['value'] = None
-
-            # NullBooleanSelect
-            if widget_name == 'NullBooleanSelect':
-                blueprint['widget'] = 'null_boolean_select'
-
-            # SelectMultiple
-            if widget_name == 'SelectMultiple':
-                blueprint['widget'] = 'select_multiple'
-
-            # RadioSelect
-            if widget_name == 'RadioSelect':
-                blueprint['widget'] = 'radio_select'
-
-            # CheckboxSelectMultiple
-            if widget_name == 'CheckboxSelectMultiple':
-                blueprint['widget'] = 'checkbox_select_multiple'
-
-            # MultiWidget
-            if widget_name == 'MultiWidget':
-                blueprint['widget'] = 'multi'
-
-            # SplitDateTimeWidget
-            if widget_name == 'SplitDateTimeWidget':
-                blueprint['widget'] = 'split_datetime'
-
-            # SplitHiddenDateTimeWidget
-            if widget_name == 'SplitHiddenDateTimeWidget':
-                blueprint['widget'] = 'split_hidden_datetime'
-
-            # SelectDateWidget
-            if widget_name == 'SelectDateWidget':
-                blueprint['widget'] = 'select_date'
-                blueprint['years'] = widget.years
-
-            # Store field in either of collections
-            if blueprint['hidden']:
-                blueprint['attrs']['type'] = 'hidden'
-                self.hidden.append(blueprint)
-            else:
-                self.fields[field] = blueprint
-
-    def _get_widget(self, name, field):
-        if name in self.meta_widgets:
-            return self.meta_widgets[name]
-        return field.widget
-
-
-class FormFieldsets(object):
-    """
-    Hydrator that builds fieldset from form and blueprint
-    """
-    def __init__(self, form, fields, fieldsets=None):
-        self.fieldsets = []
-
-        # Use form layout
-        if not fieldsets:
-            try:
-                fieldsets = form.layout
-            except AttributeError:
-                pass
-
-        # Build fieldsets data
-        if fieldsets:
-            for blueprint in fieldsets:
-                fieldset = {'legend': None, 'fields': [], 'help': None, 'last': False}
-                fieldset['legend'] = blueprint[0]
-                row_width = 0
-                for field in blueprint[1]:
-                    try:
-                        if isinstance(field, basestring):
-                            fieldset['fields'].append(fields[field])
-                        elif field[0] == 'nested':
-                            subfields = {'label': None, 'help_text': None, 'nested': [], 'errors':[], 'endrow': False, 'last': False, 'width': 100}
-                            subfiels_ids = []
-                            try:
-                                subfields = field[2].update(subfields)
-                            except IndexError:
-                                pass
-                            for subfield in field[1]:
-                                if isinstance(subfield, basestring):
-                                    subfiels_ids.append(subfield)
-                                    subfields['nested'].append(fields[subfield])
-                                    for error in fields[subfield]['errors']:
-                                        if not error in subfields['errors']:
-                                            subfields['errors'].append(error)
-                                else:
-                                    subfiels_ids.append(subfield[0])
-                                    try:
-                                        subfield[1]['attrs'] = dict(fields[subfield[0]]['attrs'], **subfield[1]['attrs'])
-                                    except KeyError:
-                                        pass
-                                    subfields['nested'].append(dict(fields[subfield[0]], **subfield[1]))
-                                    for error in fields[subfield[0]]['errors']:
-                                        if not error in subfields['errors']:
-                                            subfields['errors'].append(error)
-                            if not subfields['label']:
-                                subfields['label'] = subfields['nested'][0]['label']
-                            if not subfields['help_text']:
-                                subfields['help_text'] = subfields['nested'][0]['help_text']
-                            try:
-                                subfields['errors'] = form.errors["_".join(subfiels_ids)]
-                            except KeyError:
-                                pass
-                            fieldset['fields'].append(subfields)
-                        else:
-                            try:
-                                field[1]['attrs'] = dict(fields[field[0]]['attrs'], **field[1]['attrs'])
-                            except KeyError:
-                                pass
-                            fieldset['fields'].append(dict(fields[field[0]], **field[1]))
-                        row_width += fieldset['fields'][-1]['width']
-                        if row_width >= 100:
-                            fieldset['fields'][-1]['endrow'] = True
-                            row_width = 0
-                    except (AttributeError, IndexError, KeyError):
-                        pass
-                if fieldset['fields']:
-                    fieldset['fields'][-1]['endrow'] = True
-                    fieldset['fields'][-1]['last'] = True
-                try:
-                    fieldset['help'] = blueprint[2]
-                except IndexError:
-                    pass
-
-                # Append complete fieldset
-                if fieldset['fields']:
-                    self.fieldsets.append(fieldset)
-            self.fieldsets[-1]['last'] = True
+from UserDict import IterableUserDict
+from recaptcha.client.captcha import displayhtml
+from django.utils import formats
+from misago.conf import settings
+
+class FormLayout(object):
+    """
+    Simple scaffold for dynamically generated forms that allows for better rendering.
+    """
+    def __init__(self, layout, form):
+        raise NotImplementedError("Forms layouts are not yet implemented")

+ 1 - 1
templates/admin/admin/acl_form.html

@@ -1,5 +1,5 @@
 {% extends "admin/admin/layout.html" %}
-{% import "_forms.html" as form_theme with context %}
+{% import "forms.html" as form_theme with context %}
 
 {% block action_body %}
 <form action="{{ link }}" method="post">

+ 1 - 1
templates/admin/admin/form.html

@@ -1,5 +1,5 @@
 {% extends "admin/admin/layout.html" %}
-{% import "_forms.html" as form_theme with context %}
+{% import "forms.html" as form_theme with context %}
 
 {% block action_body %}
 <form action="{{ link }}" method="post">

+ 1 - 1
templates/admin/admin/layout.html

@@ -1,6 +1,6 @@
 {% extends "admin/layout.html" %}
 {% from "admin/macros.html" import page_title, draw_message %}
-{% import "_forms.html" as form_theme with context %}
+{% import "forms.html" as form_theme with context %}
 
 {% block title %}{% if admin.actions[0].id != action.id and action.name -%}
 {% if target %}{{ page_title(parent=action.name, title=target) }}{% else %}{{ page_title(title=action.name) }}{% endif %}

+ 5 - 6
templates/admin/admin/list.html

@@ -1,5 +1,5 @@
 {% extends "admin/admin/layout.html" %}
-{% import "_forms.html" as form_theme with context %}
+{% import "forms.html" as form_theme with context %}
 
 {% block action_body %}
 {%- if search_form %}
@@ -71,7 +71,7 @@ Showing {{ shown }} of {{ total }} items
   <form id="list_form" class="form-inline pull-right" action="{{ link }}" method="POST">
     <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
     <input type="hidden" name="origin" value="list">
-    {{ form_theme.input_select(list_form.fields['list_action'],width=3) }}
+    {{ form_theme.field(list_form.list_action, attrs={'class': 'span3'}) }}
     <button type="submit" class="btn btn-primary">{% trans %}Go{% endtrans %}</button>
   </form>
 {%- endif %}
@@ -85,13 +85,12 @@ Showing {{ shown }} of {{ total }} items
 {%- if search_form %}
   </div>
   <div class="span3 side-search">
-  	<h4>{% if search_form.fieldsets[0].legend %}{{ search_form.fieldsets[0].legend }}{% else %}{% trans %}Search Items{% endtrans %}{% endif %}</h4>
+  	<h4>{% if search_form.search_name %}{{ search_form.search_name }}{% else %}{% trans %}Search Items{% endtrans %}{% endif %}</h4>
     <form id="search_form" action="{{ link }}" method="post">
       <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
       <input type="hidden" name="origin" value="search">
-      {% for field in search_form.fieldsets[0].fields %}
-      {{ form_theme.row_widget(field, width=3) }}
-      {% endfor %}
+      {% block search_form %}
+      {% endblock %}
     </form>
     <div class="form-actions">
       <div class="row">

+ 16 - 0
templates/admin/online/list.html

@@ -21,4 +21,20 @@
   <td>
   	{{ item.last|date("TIME_FORMAT") }}
   </td>
+{% endblock %}
+
+{% block search_form scoped %}
+{{ form_theme.row(search_form.ip_address, attrs={
+                                                 'class': 'span3',
+                                                 'placeholder': _("IP begins with..."),
+                                                }) }}
+{{ form_theme.row(search_form.username, attrs={
+                                               'class': 'span3',
+                                               'placeholder': _("Username begings with..."),
+                                              }) }}
+{{ form_theme.row(search_form.useragent, attrs={
+                                               'class': 'span3',
+                                               'placeholder': _("User Agent contains..."),
+                                              }) }}
+{{ form_theme.row(search_form.type, attrs={'class': 'span3'}) }}
 {% endblock %}

+ 13 - 7
templates/admin/settings/settings.html

@@ -1,6 +1,6 @@
 {% extends "admin/layout.html" %}
 {% from "admin/macros.html" import page_title, draw_message %}
-{% import "_forms.html" as form_theme with context %}
+{% import "forms.html" as form_theme with context %}
 
 {% block title %}{{ page_title(title=_(active_group.name), parent=_('Settings')) }}{% endblock %}
 
@@ -14,27 +14,33 @@
       <h4>{% trans %}Search Settings{% endtrans %}</h4>
       <form action="{{ url('admin_settings_search') }}" class="form-inline" method="post">
         <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
-        {{ form_theme.input_text(search_form.fields.search_text, width=2, attrs={'placeholder': lang_search_settings()}) }}
+        {{ form_theme.field(search_form.search_text, attrs={'class': 'span2', 'placeholder': lang_search_settings()}) }}
         <button type="submit" class="btn btn-primary"><i class="icon-search icon-white"></i></button>
       </form>
       <h4>{% trans%}Settings Groups{% endtrans %}</h4>{% for group in groups %}
       <li{% if group.is_active(active_group) %} class="active"{% endif %}><a href="{{ url('admin_settings', group_id=group.id, group_slug=group.key) }}"{% if not group.is_active(active_group) and group.description %} class="tooltip-right" title="{{ _(group.description) }}"{% endif %}>{{ _(group.name) }}</a></li>{% endfor %}
     </ul>
   </div>
-  <div class="span9">{% block action %}
-  	<h2 class="sidepanel-header">{{ _(active_group.name) }}</h2>{% if message %}
+  <div class="span9">
+  {% block action %}
+  	<h2 class="sidepanel-header">{{ _(active_group.name) }}</h2>
+    {% if message %}
   	{{ draw_message(message, 'alert-form') }}
-  	{% endif %}{% if active_group.description %}
+  	{% endif %}
+    {% if active_group.description %}
   	<p>{{ _(active_group.description) }}</p>{% endif %}
     <form class="form-vertical" action="{{ url('admin_settings', group_id=active_group.id, group_slug=active_group.key) }}" method="post">
       <div class="form-container">
-       	{{ form_theme.form_widget(form, width=9) }}
+        {% for field in form %}
+        {{ form_theme.row(field, attrs={'class': 'span9'}) }}
+        {% endfor %}
       </div>
       <div class="form-actions">
         <button type="submit" class="btn btn-primary">{% trans %}Change Settings{% endtrans %}</button>
       </div>
     </form>
-  {% endblock %}</div>
+  {% endblock %}
+  </div>
 </div>
 {% endblock %}
 

+ 10 - 2
templates/admin/stats/form.html

@@ -1,12 +1,20 @@
 {% extends "admin/stats/layout.html" %}
-{% import "_forms.html" as form_theme with context %}
+{% import "forms.html" as form_theme with context %}
 
 {% block action %}<div class="row">
   <div class="span8 offset2">
   	<h2>{% trans %}New Report{% endtrans %}</h2>
     <form action="{{ url('admin_stats') }}" class="form-vertical" method="post">
+      {{ form_theme.hidden_fields(form) }}
       <div class="form-container">
-        {{ form_theme.form_widget(form, width=8) }}
+        {{ form_theme.row(form.provider_model, attrs={'class': 'span8'}) }}
+        {{ form_theme.repeat(form,
+                             (form.date_start, form.date_end),
+                             attrs=(
+                                    {'placeholder': _("Start Date: YYYY-MM-DD"), 'class': 'span4'},
+                                    {'placeholder': _("End Date: YYYY-MM-DD"), 'class': 'span4'}
+                                   )) }}
+        {{ form_theme.row(form.stats_precision, attrs={'class': 'span8'}) }}
       </div>
       <div class="form-actions">
         <button type="submit" class="btn btn-primary">{% trans %}Generate Report{% endtrans %}</button>