Ver código fonte

Detach dashboard to a separate application

Denis K 9 anos atrás
pai
commit
151f02f58d
60 arquivos alterados com 746 adições e 550 exclusões
  1. 0 0
      jet/dashboard/__init__.py
  2. 4 4
      jet/dashboard/dashboard.py
  3. 0 0
      jet/dashboard/dashboard_modules/__init__.py
  4. 9 9
      jet/dashboard/dashboard_modules/google_analytics.py
  5. 4 4
      jet/dashboard/dashboard_modules/google_analytics_views.py
  6. 9 9
      jet/dashboard/dashboard_modules/yandex_metrika.py
  7. 2 2
      jet/dashboard/dashboard_modules/yandex_metrika_views.py
  8. 130 0
      jet/dashboard/forms.py
  9. 33 0
      jet/dashboard/migrations/0001_initial.py
  10. 0 0
      jet/dashboard/migrations/__init__.py
  11. 57 0
      jet/dashboard/models.py
  12. 6 6
      jet/dashboard/modules.py
  13. 5 0
      jet/dashboard/settings.py
  14. 0 0
      jet/dashboard/static/jet.dashboard/dashboard_modules/google_analytics.js
  15. 0 0
      jet/dashboard/static/jet.dashboard/dashboard_modules/yandex_metrika.js
  16. 0 0
      jet/dashboard/static/jet.dashboard/vendor/chart.js/CONTRIBUTING.md
  17. 0 0
      jet/dashboard/static/jet.dashboard/vendor/chart.js/Chart.js
  18. 0 0
      jet/dashboard/static/jet.dashboard/vendor/chart.js/Chart.min.js
  19. 0 0
      jet/dashboard/static/jet.dashboard/vendor/chart.js/LICENSE.md
  20. 0 0
      jet/dashboard/static/jet.dashboard/vendor/chart.js/README.md
  21. 2 2
      jet/dashboard/templates/admin/app_index.html
  22. 35 0
      jet/dashboard/templates/admin/index.html
  23. 4 4
      jet/dashboard/templates/jet.dashboard/dashboard.html
  24. 1 1
      jet/dashboard/templates/jet.dashboard/dashboard_tools.html
  25. 2 2
      jet/dashboard/templates/jet.dashboard/module.html
  26. 0 0
      jet/dashboard/templates/jet.dashboard/modules/app_list.html
  27. 0 0
      jet/dashboard/templates/jet.dashboard/modules/feed.html
  28. 0 0
      jet/dashboard/templates/jet.dashboard/modules/google_analytics_period_visitors.html
  29. 0 0
      jet/dashboard/templates/jet.dashboard/modules/google_analytics_visitors_chart.html
  30. 0 0
      jet/dashboard/templates/jet.dashboard/modules/google_analytics_visitors_totals.html
  31. 0 0
      jet/dashboard/templates/jet.dashboard/modules/link_list.html
  32. 0 0
      jet/dashboard/templates/jet.dashboard/modules/model_list.html
  33. 0 0
      jet/dashboard/templates/jet.dashboard/modules/recent_actions.html
  34. 0 0
      jet/dashboard/templates/jet.dashboard/modules/yandex_metrika_period_visitors.html
  35. 0 0
      jet/dashboard/templates/jet.dashboard/modules/yandex_metrika_visitors_chart.html
  36. 0 0
      jet/dashboard/templates/jet.dashboard/modules/yandex_metrika_visitors_totals.html
  37. 4 4
      jet/dashboard/templates/jet.dashboard/update_module.html
  38. 0 0
      jet/dashboard/templates/jet.dashboard/update_module_fieldset.html
  39. 0 0
      jet/dashboard/templatetags/__init__.py
  40. 12 0
      jet/dashboard/templatetags/jet_dashboard_tags.py
  41. 48 0
      jet/dashboard/urls.py
  42. 17 0
      jet/dashboard/utils.py
  43. 213 0
      jet/dashboard/views.py
  44. 5 129
      jet/forms.py
  45. 17 0
      jet/migrations/0002_delete_userdashboardmodule.py
  46. 0 50
      jet/models.py
  47. 0 4
      jet/settings.py
  48. 1 1
      jet/static/jet/css/_content.scss
  49. 1 1
      jet/static/jet/css/themes/default/base.css
  50. 1 1
      jet/static/jet/css/themes/green/base.css
  51. 96 26
      jet/templates/admin/index.html
  52. 1 8
      jet/templatetags/jet_tags.py
  53. 2 2
      jet/tests/dashboard.py
  54. 1 0
      jet/tests/settings.py
  55. 4 4
      jet/tests/test_dashboard.py
  56. 15 14
      jet/tests/test_views.py
  57. 1 0
      jet/tests/urls.py
  58. 1 36
      jet/urls.py
  59. 0 15
      jet/utils.py
  60. 3 212
      jet/views.py

+ 0 - 0
jet/dashboard_modules/__init__.py → jet/dashboard/__init__.py


+ 4 - 4
jet/dashboard.py → jet/dashboard/dashboard.py

@@ -1,8 +1,8 @@
 from importlib import import_module
 from django.core.urlresolvers import resolve, reverse
 from django.template.loader import render_to_string
-from jet import modules
-from jet.models import UserDashboardModule
+from jet.dashboard import modules
+from jet.dashboard.models import UserDashboardModule
 from django.core.context_processors import csrf
 from django.utils.translation import ugettext_lazy as _
 from jet.ordered_set import OrderedSet
@@ -99,7 +99,7 @@ class Dashboard(object):
         })
         context.update(csrf(context['request']))
 
-        return render_to_string('jet/dashboard/dashboard.html', context)
+        return render_to_string('jet.dashboard/dashboard.html', context)
 
     def render_tools(self):
         context = self.context
@@ -110,7 +110,7 @@ class Dashboard(object):
         })
         context.update(csrf(context['request']))
 
-        return render_to_string('jet/dashboard/dashboard_tools.html', context)
+        return render_to_string('jet.dashboard/dashboard_tools.html', context)
 
     def media(self):
         unique_css = OrderedSet()

+ 0 - 0
jet/dashboard/dashboard_modules/__init__.py


+ 9 - 9
jet/dashboard_modules/google_analytics.py → jet/dashboard/dashboard_modules/google_analytics.py

@@ -138,9 +138,9 @@ class CredentialWidget(Widget):
 
     def render(self, name, value, attrs=None):
         if value and len(value) > 0:
-            link = '<a href="%s">Revoke access</a>' % reverse('jet:google-analytics-revoke', kwargs={'pk': self.module.model.pk})
+            link = '<a href="%s">Revoke access</a>' % reverse('jet-dashboard:google-analytics-revoke', kwargs={'pk': self.module.model.pk})
         else:
-            link = '<a href="%s">Grant access</a>' % reverse('jet:google-analytics-grant', kwargs={'pk': self.module.model.pk})
+            link = '<a href="%s">Grant access</a>' % reverse('jet-dashboard:google-analytics-grant', kwargs={'pk': self.module.model.pk})
 
         attrs = self.build_attrs({
             'type': 'hidden',
@@ -266,10 +266,10 @@ class GoogleAnalyticsBase(DashboardModule):
 
     def counter_attached(self):
         if self.credential is None:
-            self.error = mark_safe(_('Please <a href="%s">attach Google account and choose Google Analytics counter</a> to start using widget') % reverse('jet:update_module', kwargs={'pk': self.model.pk}))
+            self.error = mark_safe(_('Please <a href="%s">attach Google account and choose Google Analytics counter</a> to start using widget') % reverse('jet-dashboard:update_module', kwargs={'pk': self.model.pk}))
             return False
         elif self.counter is None:
-            self.error = mark_safe(_('Please <a href="%s">select Google Analytics counter</a> to start using widget') % reverse('jet:update_module', kwargs={'pk': self.model.pk}))
+            self.error = mark_safe(_('Please <a href="%s">select Google Analytics counter</a> to start using widget') % reverse('jet-dashboard:update_module', kwargs={'pk': self.model.pk}))
             return False
         else:
             return True
@@ -290,13 +290,13 @@ class GoogleAnalyticsBase(DashboardModule):
             except Exception as e:
                 error = _('API request failed.')
                 if isinstance(e, AccessTokenRefreshError):
-                    error += _(' Try to <a href="%s">revoke and grant access</a> again') % reverse('jet:update_module', kwargs={'pk': self.model.pk})
+                    error += _(' Try to <a href="%s">revoke and grant access</a> again') % reverse('jet-dashboard:update_module', kwargs={'pk': self.model.pk})
                 self.error = mark_safe(error)
 
 
 class GoogleAnalyticsVisitorsTotals(GoogleAnalyticsBase):
     title = _('Google Analytics visitors totals')
-    template = 'jet/dashboard/modules/google_analytics_visitors_totals.html'
+    template = 'jet.dashboard/modules/google_analytics_visitors_totals.html'
 
     def __init__(self, title=None, period=None, **kwargs):
         kwargs.update({'period': period})
@@ -316,14 +316,14 @@ class GoogleAnalyticsVisitorsTotals(GoogleAnalyticsBase):
 
 class GoogleAnalyticsVisitorsChart(GoogleAnalyticsBase):
     title = _('Google Analytics visitors chart')
-    template = 'jet/dashboard/modules/google_analytics_visitors_chart.html'
+    template = 'jet.dashboard/modules/google_analytics_visitors_chart.html'
     style = 'overflow-x: auto;'
     show = None
     group = None
     settings_form = GoogleAnalyticsChartSettingsForm
 
     class Media:
-        js = ('jet/vendor/chart.js/Chart.min.js', 'jet/dashboard_modules/google_analytics.js')
+        js = ('jet.dashboard/vendor/chart.js/Chart.min.js', 'jet.dashboard/dashboard_modules/google_analytics.js')
 
     def __init__(self, title=None, period=None, show=None, group=None, **kwargs):
         kwargs.update({'period': period, 'show': show, 'group': group})
@@ -361,7 +361,7 @@ class GoogleAnalyticsVisitorsChart(GoogleAnalyticsBase):
 
 class GoogleAnalyticsPeriodVisitors(GoogleAnalyticsBase):
     title = _('Google Analytics period visitors')
-    template = 'jet/dashboard/modules/google_analytics_period_visitors.html'
+    template = 'jet.dashboard/modules/google_analytics_period_visitors.html'
     group = None
     contrast = False
     settings_form = GoogleAnalyticsPeriodVisitorsSettingsForm

+ 4 - 4
jet/dashboard_modules/google_analytics_views.py → jet/dashboard/dashboard_modules/google_analytics_views.py

@@ -12,7 +12,7 @@ from django.utils.translation import ugettext_lazy as _
 
 
 def google_analytics_grant_view(request, pk):
-    redirect_uri = request.build_absolute_uri(reverse('jet:google-analytics-callback'))
+    redirect_uri = request.build_absolute_uri(reverse('jet-dashboard:google-analytics-callback'))
     client = GoogleAnalyticsClient(redirect_uri=redirect_uri)
     return redirect(client.get_oauth_authorize_url(pk))
 
@@ -21,7 +21,7 @@ def google_analytics_revoke_view(request, pk):
     try:
         module = UserDashboardModule.objects.get(pk=pk)
         ModuleCredentialStorage(module).delete()
-        return redirect(reverse('jet:update_module', kwargs={'pk': module.pk}))
+        return redirect(reverse('jet-dashboard:update_module', kwargs={'pk': module.pk}))
     except UserDashboardModule.DoesNotExist:
         return HttpResponse(_('Module not found'))
 
@@ -33,7 +33,7 @@ def google_analytics_callback_view(request):
         state = request.GET['state']
         module = UserDashboardModule.objects.get(pk=state)
 
-        redirect_uri = request.build_absolute_uri(reverse('jet:google-analytics-callback'))
+        redirect_uri = request.build_absolute_uri(reverse('jet-dashboard:google-analytics-callback'))
         client = GoogleAnalyticsClient(redirect_uri=redirect_uri)
         client.set_credential_from_request(request)
 
@@ -45,7 +45,7 @@ def google_analytics_callback_view(request):
     except UserDashboardModule.DoesNotExist:
         return HttpResponse(_('Module not found'))
 
-    return redirect(reverse('jet:update_module', kwargs={'pk': module.pk}))
+    return redirect(reverse('jet-dashboard:update_module', kwargs={'pk': module.pk}))
 
 dashboard.urls.register_urls([
     url(r'^google-analytics/grant/(?P<pk>\d+)/$', google_analytics_grant_view, name='google-analytics-grant'),

+ 9 - 9
jet/dashboard_modules/yandex_metrika.py → jet/dashboard/dashboard_modules/yandex_metrika.py

@@ -96,9 +96,9 @@ class AccessTokenWidget(Widget):
 
     def render(self, name, value, attrs=None):
         if value and len(value) > 0:
-            link = '<a href="%s">Revoke access</a>' % reverse('jet:yandex-metrika-revoke', kwargs={'pk': self.module.model.pk})
+            link = '<a href="%s">Revoke access</a>' % reverse('jet-dashboard:yandex-metrika-revoke', kwargs={'pk': self.module.model.pk})
         else:
-            link = '<a href="%s">Grant access</a>' % reverse('jet:yandex-metrika-grant', kwargs={'pk': self.module.model.pk})
+            link = '<a href="%s">Grant access</a>' % reverse('jet-dashboard:yandex-metrika-grant', kwargs={'pk': self.module.model.pk})
         return format_html('%s<input type="hidden" name="access_token" value="%s">' % (link, value))
 
 
@@ -204,10 +204,10 @@ class YandexMetrikaBase(DashboardModule):
 
     def counter_attached(self):
         if self.access_token is None:
-            self.error = mark_safe(_('Please <a href="%s">attach Yandex account and choose Yandex Metrika counter</a> to start using widget') % reverse('jet:update_module', kwargs={'pk': self.model.pk}))
+            self.error = mark_safe(_('Please <a href="%s">attach Yandex account and choose Yandex Metrika counter</a> to start using widget') % reverse('jet-dashboard:update_module', kwargs={'pk': self.model.pk}))
             return False
         elif self.counter is None:
-            self.error = mark_safe(_('Please <a href="%s">select Yandex Metrika counter</a> to start using widget') % reverse('jet:update_module', kwargs={'pk': self.model.pk}))
+            self.error = mark_safe(_('Please <a href="%s">select Yandex Metrika counter</a> to start using widget') % reverse('jet-dashboard:update_module', kwargs={'pk': self.model.pk}))
             return False
         else:
             return True
@@ -223,7 +223,7 @@ class YandexMetrikaBase(DashboardModule):
             if exception is not None:
                 error = _('API request failed.')
                 if isinstance(exception, HTTPError) and exception.code == 403:
-                    error += _(' Try to <a href="%s">revoke and grant access</a> again') % reverse('jet:update_module', kwargs={'pk': self.model.pk})
+                    error += _(' Try to <a href="%s">revoke and grant access</a> again') % reverse('jet-dashboard:update_module', kwargs={'pk': self.model.pk})
                 self.error = mark_safe(error)
             else:
                 return result
@@ -231,7 +231,7 @@ class YandexMetrikaBase(DashboardModule):
 
 class YandexMetrikaVisitorsTotals(YandexMetrikaBase):
     title = _('Yandex Metrika visitors totals')
-    template = 'jet/dashboard/modules/yandex_metrika_visitors_totals.html'
+    template = 'jet.dashboard/modules/yandex_metrika_visitors_totals.html'
 
     def __init__(self, title=None, period=None, **kwargs):
         kwargs.update({'period': period})
@@ -251,14 +251,14 @@ class YandexMetrikaVisitorsTotals(YandexMetrikaBase):
 
 class YandexMetrikaVisitorsChart(YandexMetrikaBase):
     title = _('Yandex Metrika visitors chart')
-    template = 'jet/dashboard/modules/yandex_metrika_visitors_chart.html'
+    template = 'jet.dashboard/modules/yandex_metrika_visitors_chart.html'
     style = 'overflow-x: auto;'
     show = None
     group = None
     settings_form = YandexMetrikaChartSettingsForm
 
     class Media:
-        js = ('jet/vendor/chart.js/Chart.min.js', 'jet/dashboard_modules/yandex_metrika.js')
+        js = ('jet.dashboard/vendor/chart.js/Chart.min.js', 'jet.dashboard/dashboard_modules/yandex_metrika.js')
 
     def __init__(self, title=None, period=None, show=None, group=None, **kwargs):
         kwargs.update({'period': period, 'show': show, 'group': group})
@@ -290,7 +290,7 @@ class YandexMetrikaVisitorsChart(YandexMetrikaBase):
 
 class YandexMetrikaPeriodVisitors(YandexMetrikaBase):
     title = _('Yandex Metrika period visitors')
-    template = 'jet/dashboard/modules/yandex_metrika_period_visitors.html'
+    template = 'jet.dashboard/modules/yandex_metrika_period_visitors.html'
     group = None
     contrast = False
     settings_form = YandexMetrikaPeriodVisitorsSettingsForm

+ 2 - 2
jet/dashboard_modules/yandex_metrika_views.py → jet/dashboard/dashboard_modules/yandex_metrika_views.py

@@ -18,7 +18,7 @@ def yandex_metrika_revoke_view(request, pk):
     try:
         module = UserDashboardModule.objects.get(pk=pk)
         module.pop_settings(('access_token', 'expires_in', 'token_type', 'counter'))
-        return redirect(reverse('jet:update_module', kwargs={'pk': module.pk}))
+        return redirect(reverse('jet-dashboard:update_module', kwargs={'pk': module.pk}))
     except UserDashboardModule.DoesNotExist:
         return HttpResponse(_('Module not found'))
 
@@ -38,7 +38,7 @@ def yandex_metrika_callback_view(request):
         else:
             module.update_settings(result)
 
-        return redirect(reverse('jet:update_module', kwargs={'pk': module.pk}))
+        return redirect(reverse('jet-dashboard:update_module', kwargs={'pk': module.pk}))
     except KeyError:
         return HttpResponse(_('Bad arguments'))
     except UserDashboardModule.DoesNotExist:

+ 130 - 0
jet/dashboard/forms.py

@@ -0,0 +1,130 @@
+import json
+from django import forms
+from django.core.exceptions import ValidationError
+from jet.dashboard.models import UserDashboardModule
+from jet.dashboard.utils import get_current_dashboard
+
+
+class UpdateDashboardModulesForm(forms.Form):
+    app_label = forms.CharField(required=False)
+    modules = forms.CharField()
+    modules_objects = []
+
+    def __init__(self, request, *args, **kwargs):
+        self.request = request
+        super(UpdateDashboardModulesForm, self).__init__(*args, **kwargs)
+
+    def clean(self):
+        data = super(UpdateDashboardModulesForm, self).clean()
+
+        try:
+            modules = json.loads(data['modules'])
+
+            for module in modules:
+                db_module = UserDashboardModule.objects.get(
+                    user=self.request.user.pk,
+                    app_label=data['app_label'] if data['app_label'] else None,
+                    pk=module['id']
+                )
+
+                column = module['column']
+                order = module['order']
+
+                if db_module.column != column or db_module.order != order:
+                    db_module.column = column
+                    db_module.order = order
+
+                    self.modules_objects.append(db_module)
+        except Exception:
+            raise ValidationError('error')
+
+        return data
+
+    def save(self):
+        for module in self.modules_objects:
+            module.save()
+
+
+class AddUserDashboardModuleForm(forms.ModelForm):
+    type = forms.CharField()
+    module = forms.IntegerField()
+    module_cls = None
+
+    def __init__(self, request, *args, **kwargs):
+        self.request = request
+        super(AddUserDashboardModuleForm, self).__init__(*args, **kwargs)
+
+    class Meta:
+        model = UserDashboardModule
+        fields = ['app_label']
+
+    def clean_app_label(self):
+        data = self.cleaned_data['app_label']
+        return data if data != '' else None
+
+    def clean(self):
+        data = super(AddUserDashboardModuleForm, self).clean()
+
+        index_dashboard_cls = get_current_dashboard('app_index' if data['app_label'] else 'index')
+        index_dashboard = index_dashboard_cls({'request': self.request}, app_label=data['app_label'])
+
+        if data['type'] == 'children':
+            module = index_dashboard.children[data['module']]
+        elif data['type'] == 'available_children':
+            module = index_dashboard.available_children[data['module']]()
+        else:
+            raise ValidationError('error')
+
+        self.module_cls = module
+        return data
+
+    def save(self, commit=True):
+        self.instance.title = self.module_cls.title
+        self.instance.module = self.module_cls.fullname()
+        self.instance.user = self.request.user.pk
+        self.instance.column = 0
+        self.instance.order = -1
+        self.instance.settings = self.module_cls.dump_settings()
+        self.instance.children = self.module_cls.dump_children()
+
+        return super(AddUserDashboardModuleForm, self).save(commit)
+
+
+class UpdateDashboardModuleCollapseForm(forms.ModelForm):
+    def __init__(self, request, *args, **kwargs):
+        self.request = request
+        super(UpdateDashboardModuleCollapseForm, self).__init__(*args, **kwargs)
+
+    class Meta:
+        model = UserDashboardModule
+        fields = ['collapsed']
+
+    def clean(self):
+        data = super(UpdateDashboardModuleCollapseForm, self).clean()
+
+        if self.instance.user != self.request.user.pk:
+            raise ValidationError('error')
+
+        return data
+
+
+class RemoveDashboardModuleForm(forms.ModelForm):
+    def __init__(self, request, *args, **kwargs):
+        self.request = request
+        super(RemoveDashboardModuleForm, self).__init__(*args, **kwargs)
+
+    class Meta:
+        model = UserDashboardModule
+        fields = []
+
+    def clean(self):
+        cleaned_data = super(RemoveDashboardModuleForm, self).clean()
+
+        if self.instance.user != self.request.user.pk:
+            raise ValidationError('error')
+
+        return cleaned_data
+
+    def save(self, commit=True):
+        if commit:
+            self.instance.delete()

+ 33 - 0
jet/dashboard/migrations/0001_initial.py

@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='UserDashboardModule',
+            fields=[
+                ('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)),
+                ('title', models.CharField(verbose_name='Title', max_length=255)),
+                ('module', models.CharField(verbose_name='module', max_length=255)),
+                ('app_label', models.CharField(verbose_name='application name', max_length=255, blank=True, null=True)),
+                ('user', models.PositiveIntegerField(verbose_name='user')),
+                ('column', models.PositiveIntegerField(verbose_name='column')),
+                ('order', models.IntegerField(verbose_name='order')),
+                ('settings', models.TextField(verbose_name='settings', blank=True, default='')),
+                ('children', models.TextField(verbose_name='children', blank=True, default='')),
+                ('collapsed', models.BooleanField(verbose_name='collapsed', default=False)),
+            ],
+            options={
+                'verbose_name': 'user dashboard module',
+                'verbose_name_plural': 'user dashboard modules',
+                'ordering': ('column', 'order'),
+            },
+        ),
+    ]

+ 0 - 0
jet/dashboard/migrations/__init__.py


+ 57 - 0
jet/dashboard/models.py

@@ -0,0 +1,57 @@
+from importlib import import_module
+import json
+from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+from django.utils.translation import ugettext_lazy as _
+from jet.utils import LazyDateTimeEncoder
+
+
+@python_2_unicode_compatible
+class UserDashboardModule(models.Model):
+    title = models.CharField(verbose_name=_('Title'), max_length=255)
+    module = models.CharField(verbose_name=_('module'), max_length=255)
+    app_label = models.CharField(verbose_name=_('application name'), max_length=255, null=True, blank=True)
+    user = models.PositiveIntegerField(verbose_name=_('user'))
+    column = models.PositiveIntegerField(verbose_name=_('column'))
+    order = models.IntegerField(verbose_name=_('order'))
+    settings = models.TextField(verbose_name=_('settings'), default='', blank=True)
+    children = models.TextField(verbose_name=_('children'), default='', blank=True)
+    collapsed = models.BooleanField(verbose_name=_('collapsed'), default=False)
+
+    class Meta:
+        verbose_name = _('user dashboard module')
+        verbose_name_plural = _('user dashboard modules')
+        ordering = ('column', 'order')
+
+    def __str__(self):
+        return self.module
+
+    def load_module(self):
+        try:
+            package, module_name = self.module.rsplit('.', 1)
+            package = import_module(package)
+            module = getattr(package, module_name)
+
+            return module
+        except AttributeError:
+            return None
+
+    def pop_settings(self, pop_settings):
+        settings = json.loads(self.settings)
+
+        for setting in pop_settings:
+            if setting in settings:
+                settings.pop(setting)
+
+        self.settings = json.dumps(settings, cls=LazyDateTimeEncoder)
+        self.save()
+
+    def update_settings(self, update_settings):
+        settings = json.loads(self.settings)
+
+        settings.update(update_settings)
+
+        self.settings = json.dumps(settings, cls=LazyDateTimeEncoder)
+        self.save()
+
+

+ 6 - 6
jet/modules.py → jet/dashboard/modules.py

@@ -9,7 +9,7 @@ import datetime
 
 
 class DashboardModule(object):
-    template = 'jet/dashboard/module.html'
+    template = 'jet.dashboard/module.html'
     enabled = True
     draggable = True
     collapsible = True
@@ -123,7 +123,7 @@ class LinkListSettingsForm(forms.Form):
 
 class LinkList(DashboardModule):
     title = _('Links')
-    template = 'jet/dashboard/modules/link_list.html'
+    template = 'jet.dashboard/modules/link_list.html'
     layout = 'stacked'
     settings_form = LinkListSettingsForm
     child_form = LinkListItemForm
@@ -158,7 +158,7 @@ class LinkList(DashboardModule):
 
 class AppList(DashboardModule):
     title = _('Applications')
-    template = 'jet/dashboard/modules/app_list.html'
+    template = 'jet.dashboard/modules/app_list.html'
     models = None
     exclude = None
     hide_empty = True
@@ -199,7 +199,7 @@ class AppList(DashboardModule):
 
 class ModelList(DashboardModule):
     title = _('Models')
-    template = 'jet/dashboard/modules/model_list.html'
+    template = 'jet.dashboard/modules/model_list.html'
     models = None
     exclude = None
     hide_empty = True
@@ -240,7 +240,7 @@ class RecentActionsSettingsForm(forms.Form):
 
 class RecentActions(DashboardModule):
     title = _('Recent Actions')
-    template = 'jet/dashboard/modules/recent_actions.html'
+    template = 'jet.dashboard/modules/recent_actions.html'
     limit = 10
     include_list = None
     exclude_list = None
@@ -312,7 +312,7 @@ class FeedSettingsForm(forms.Form):
 
 class Feed(DashboardModule):
     title = _('RSS Feed')
-    template = 'jet/dashboard/modules/feed.html'
+    template = 'jet.dashboard/modules/feed.html'
     feed_url = None
     limit = None
     settings_form = FeedSettingsForm

+ 5 - 0
jet/dashboard/settings.py

@@ -0,0 +1,5 @@
+from django.conf import settings
+
+# Dashboard
+JET_INDEX_DASHBOARD = getattr(settings, 'JET_INDEX_DASHBOARD', 'jet.dashboard.dashboard.DefaultIndexDashboard')
+JET_APP_INDEX_DASHBOARD = getattr(settings, 'JET_APP_INDEX_DASHBOARD', 'jet.dashboard.dashboard.DefaultAppIndexDashboard')

+ 0 - 0
jet/static/jet/dashboard_modules/google_analytics.js → jet/dashboard/static/jet.dashboard/dashboard_modules/google_analytics.js


+ 0 - 0
jet/static/jet/dashboard_modules/yandex_metrika.js → jet/dashboard/static/jet.dashboard/dashboard_modules/yandex_metrika.js


+ 0 - 0
jet/static/jet/vendor/chart.js/CONTRIBUTING.md → jet/dashboard/static/jet.dashboard/vendor/chart.js/CONTRIBUTING.md


+ 0 - 0
jet/static/jet/vendor/chart.js/Chart.js → jet/dashboard/static/jet.dashboard/vendor/chart.js/Chart.js


+ 0 - 0
jet/static/jet/vendor/chart.js/Chart.min.js → jet/dashboard/static/jet.dashboard/vendor/chart.js/Chart.min.js


+ 0 - 0
jet/static/jet/vendor/chart.js/LICENSE.md → jet/dashboard/static/jet.dashboard/vendor/chart.js/LICENSE.md


+ 0 - 0
jet/static/jet/vendor/chart.js/README.md → jet/dashboard/static/jet.dashboard/vendor/chart.js/README.md


+ 2 - 2
jet/templates/admin/app_index.html → jet/dashboard/templates/admin/app_index.html

@@ -1,5 +1,5 @@
 {% extends "admin/base_site.html" %}
-{% load i18n static jet_tags %}
+{% load i18n static jet_dashboard_tags %}
 
 {% block html %}
     {% get_dashboard 'app_index' as dashboard %}
@@ -24,7 +24,7 @@
 
 {% block coltype %}colMS{% endblock %}
 
-{% block bodyclass %}{{ block.super }} app-{{ app_label }} dashboard{% endblock %}
+{% block bodyclass %}{{ block.super }} app-{{ app_label }}{% endblock %}
 
 {% if not is_popup %}
     {% block breadcrumbs %}

+ 35 - 0
jet/dashboard/templates/admin/index.html

@@ -0,0 +1,35 @@
+{% extends "admin/base_site.html" %}
+{% load i18n admin_static jet_dashboard_tags static %}
+
+{% block html %}
+    {% get_dashboard 'index' as dashboard %}
+    {{ block.super }}
+{% endblock %}
+
+{% block extrastyle %}
+    {{ block.super }}
+    <link rel="stylesheet" type="text/css" href="{% static "admin/css/dashboard.css" %}" />
+
+    {% for css in dashboard.media.css %}
+        <link href="{% static css %}" rel="stylesheet" />
+    {% endfor %}
+{% endblock %}
+
+{% block extrahead %}
+    {{ block.super }}
+    {% for js in dashboard.media.js %}
+        <script src="{% static js %}"></script>
+    {% endfor %}
+{% endblock %}
+
+{% block coltype %}colMS{% endblock %}
+
+{% block sidebar %}{% endblock %}
+
+{% block top-right %}
+    {{ dashboard.render_tools }}
+{% endblock %}
+
+{% block content %}
+    {{ dashboard.render }}
+{% endblock %}

+ 4 - 4
jet/templates/jet/dashboard/dashboard.html → jet/dashboard/templates/jet.dashboard/dashboard.html

@@ -6,7 +6,7 @@
             <div class="dashboard-column{% if forloop.first %} first{% endif %}">
                 {% for module in modules %}
                     {% if module.model.column == i %}
-                        {% include "jet/dashboard/module.html" with module=module %}
+                        {% include "jet.dashboard/module.html" with module=module %}
                     {% endif %}
                 {% endfor %}
             </div>
@@ -18,19 +18,19 @@
     <p>{% trans "Are you sure want to delete this widget?" %}</p>
 </div>
 
-<form action="{% url "jet:update_dashboard_modules" %}" method="POST" id="update-dashboard-modules-form">
+<form action="{% url "jet-dashboard:update_dashboard_modules" %}" method="POST" id="update-dashboard-modules-form">
     {% csrf_token %}
     <input type="hidden" name="app_label" value="{% if app_label %}{{ app_label }}{% endif %}">
     <input type="hidden" name="modules">
 </form>
 
-<form action="{% url "jet:update_dashboard_module_collapse" %}" method="POST" id="update-dashboard-module-collapse-form">
+<form action="{% url "jet-dashboard:update_dashboard_module_collapse" %}" method="POST" id="update-dashboard-module-collapse-form">
     {% csrf_token %}
     <input type="hidden" name="id">
     <input type="hidden" name="collapsed">
 </form>
 
-<form action="{% url "jet:remove_dashboard_module" %}" method="POST" id="remove-dashboard-module-form">
+<form action="{% url "jet-dashboard:remove_dashboard_module" %}" method="POST" id="remove-dashboard-module-form">
     {% csrf_token %}
     <input type="hidden" name="id">
 </form>

+ 1 - 1
jet/templates/jet/dashboard/dashboard_tools.html → jet/dashboard/templates/jet.dashboard/dashboard_tools.html

@@ -1,7 +1,7 @@
 {% load i18n %}
 
 <div class="background-form">
-    <form action="{% url "jet:add_user_dashboard_module" %}" method="POST" id="add-dashboard-module-form">
+    <form action="{% url "jet-dashboard:add_user_dashboard_module" %}" method="POST" id="add-dashboard-module-form">
         {% csrf_token %}
         <select class="add-dashboard" name="module">
             <option>{% trans "widgets" %}</option>

+ 2 - 2
jet/templates/jet/dashboard/module.html → jet/dashboard/templates/jet.dashboard/module.html

@@ -1,6 +1,6 @@
 {% load i18n %}
 
-<div class="dashboard-item{% if module.collapsible %} collapsible{% endif %}{% if module.model.collapsed %} collapsed{% endif %}{% if module.deletable %} deletable{% endif %}{% if module.ajax_load %} ajax{% endif %}"{% if module.ajax_load %} data-ajax-url="{% url "jet:load_dashboard_module" pk=module.model.id %}"{% endif %} data-module-id="{{ module.model.id }}">
+<div class="dashboard-item{% if module.collapsible %} collapsible{% endif %}{% if module.model.collapsed %} collapsed{% endif %}{% if module.deletable %} deletable{% endif %}{% if module.ajax_load %} ajax{% endif %}"{% if module.ajax_load %} data-ajax-url="{% url "jet-dashboard:load_dashboard_module" pk=module.model.id %}"{% endif %} data-module-id="{{ module.model.id }}">
     <div class="dashboard-item-header">
         <span class="dashboard-item-header-drag icon-grid"></span>
         <span class="dashboard-item-header-title">
@@ -16,7 +16,7 @@
             {% endif %}
 
             <span class="dashboard-item-header-buttons">
-                <a href="{% url "jet:update_module" pk=module.model.id %}" title="{% trans "Change" %}"><span class="icon-edit"></span></a>
+                <a href="{% url "jet-dashboard:update_module" pk=module.model.id %}" title="{% trans "Change" %}"><span class="icon-edit"></span></a>
 
                 {% if module.deletable %}
                     <a href="#" title="{% trans "Delete" %}" class="dashboard-item-remove"><span class="icon-cross"></span></a>

+ 0 - 0
jet/templates/jet/dashboard/modules/app_list.html → jet/dashboard/templates/jet.dashboard/modules/app_list.html


+ 0 - 0
jet/templates/jet/dashboard/modules/feed.html → jet/dashboard/templates/jet.dashboard/modules/feed.html


+ 0 - 0
jet/templates/jet/dashboard/modules/google_analytics_period_visitors.html → jet/dashboard/templates/jet.dashboard/modules/google_analytics_period_visitors.html


+ 0 - 0
jet/templates/jet/dashboard/modules/google_analytics_visitors_chart.html → jet/dashboard/templates/jet.dashboard/modules/google_analytics_visitors_chart.html


+ 0 - 0
jet/templates/jet/dashboard/modules/google_analytics_visitors_totals.html → jet/dashboard/templates/jet.dashboard/modules/google_analytics_visitors_totals.html


+ 0 - 0
jet/templates/jet/dashboard/modules/link_list.html → jet/dashboard/templates/jet.dashboard/modules/link_list.html


+ 0 - 0
jet/templates/jet/dashboard/modules/model_list.html → jet/dashboard/templates/jet.dashboard/modules/model_list.html


+ 0 - 0
jet/templates/jet/dashboard/modules/recent_actions.html → jet/dashboard/templates/jet.dashboard/modules/recent_actions.html


+ 0 - 0
jet/templates/jet/dashboard/modules/yandex_metrika_period_visitors.html → jet/dashboard/templates/jet.dashboard/modules/yandex_metrika_period_visitors.html


+ 0 - 0
jet/templates/jet/dashboard/modules/yandex_metrika_visitors_chart.html → jet/dashboard/templates/jet.dashboard/modules/yandex_metrika_visitors_chart.html


+ 0 - 0
jet/templates/jet/dashboard/modules/yandex_metrika_visitors_totals.html → jet/dashboard/templates/jet.dashboard/modules/yandex_metrika_visitors_totals.html


+ 4 - 4
jet/templates/jet/dashboard/update_module.html → jet/dashboard/templates/jet.dashboard/update_module.html

@@ -58,10 +58,10 @@
                     </fieldset>
                 {% endif %}
 
-                {% include "jet/dashboard/update_module_fieldset.html" with form=form %}
+                {% include "jet.dashboard/update_module_fieldset.html" with form=form %}
 
                 {% if settings_form %}
-                    {% include "jet/dashboard/update_module_fieldset.html" with form=settings_form %}
+                    {% include "jet.dashboard/update_module_fieldset.html" with form=settings_form %}
                 {% endif %}
             </div>
 
@@ -104,13 +104,13 @@
                                 {% for form in children_formset %}
                                     <div class="stacked {% if forloop.first %}selected {% endif %}inline-related {% if forloop.last %} last-related{% endif %}" id="{{ children_formset.prefix }}-{{ forloop.counter0 }}">
                                         {% if form.non_field_errors %}{{ form.non_field_errors }}{% endif %}
-                                        {% include "jet/dashboard/update_module_fieldset.html" with form=form %}
+                                        {% include "jet.dashboard/update_module_fieldset.html" with form=form %}
                                     </div>
                                 {% endfor %}
                                 {% with form=children_formset.empty_form %}
                                     <div class="stacked inline-related empty-form last-related" id="{{ children_formset.prefix }}-empty">
                                         {% if form.non_field_errors %}{{ form.non_field_errors }}{% endif %}
-                                        {% include "jet/dashboard/update_module_fieldset.html" with form=form %}
+                                        {% include "jet.dashboard/update_module_fieldset.html" with form=form %}
                                     </div>
                                 {% endwith %}
                             </div>

+ 0 - 0
jet/templates/jet/dashboard/update_module_fieldset.html → jet/dashboard/templates/jet.dashboard/update_module_fieldset.html


+ 0 - 0
jet/dashboard/templatetags/__init__.py


+ 12 - 0
jet/dashboard/templatetags/jet_dashboard_tags.py

@@ -0,0 +1,12 @@
+from __future__ import unicode_literals
+from django import template
+from jet.dashboard.utils import get_current_dashboard
+
+register = template.Library()
+
+
+@register.assignment_tag(takes_context=True)
+def get_dashboard(context, location):
+    dashboard_cls = get_current_dashboard(location)
+    dashboard = dashboard_cls(context)
+    return dashboard

+ 48 - 0
jet/dashboard/urls.py

@@ -0,0 +1,48 @@
+from django.conf.urls import patterns, url
+from django.views.i18n import javascript_catalog
+from jet.dashboard import dashboard
+from jet.dashboard.views import update_dashboard_modules_view, add_user_dashboard_module_view, \
+    update_dashboard_module_collapse_view, remove_dashboard_module_view, UpdateDashboardModuleView, \
+    load_dashboard_module_view
+
+urlpatterns = patterns(
+    '',
+    url(
+        r'^module/(?P<pk>\d+)/$',
+        UpdateDashboardModuleView.as_view(),
+        name='update_module'
+    ),
+    url(
+        r'^update_dashboard_modules/$',
+        update_dashboard_modules_view,
+        name='update_dashboard_modules'
+    ),
+    url(
+        r'^add_user_dashboard_module/$',
+        add_user_dashboard_module_view,
+        name='add_user_dashboard_module'
+    ),
+    url(
+        r'^update_dashboard_module_collapse/$',
+        update_dashboard_module_collapse_view,
+        name='update_dashboard_module_collapse'
+    ),
+    url(
+        r'^remove_dashboard_module/$',
+        remove_dashboard_module_view,
+        name='remove_dashboard_module'
+    ),
+    url(
+        r'^load_dashboard_module/(?P<pk>\d+)/$',
+        load_dashboard_module_view,
+        name='load_dashboard_module'
+    ),
+    url(
+        r'^jsi18n/$',
+        javascript_catalog,
+        {'packages': ('jet',)},
+        name='jsi18n'
+    ),
+)
+
+urlpatterns += dashboard.urls.get_urls()

+ 17 - 0
jet/dashboard/utils.py

@@ -0,0 +1,17 @@
+from importlib import import_module
+from jet.dashboard import settings
+
+
+def get_current_dashboard(location):
+    if location == 'index':
+        path = settings.JET_INDEX_DASHBOARD
+    elif location == 'app_index':
+        path = settings.JET_APP_INDEX_DASHBOARD
+    else:
+        raise ValueError('Unknown dashboard location: %s' % location)
+
+    module, cls = path.rsplit('.', 1)
+    module = import_module(module)
+    index_dashboard_cls = getattr(module, cls)
+
+    return index_dashboard_cls

+ 213 - 0
jet/dashboard/views.py

@@ -0,0 +1,213 @@
+from django.contrib import messages
+from django.core.urlresolvers import reverse
+from django.forms.formsets import formset_factory
+from django.views.decorators.http import require_POST, require_GET
+from jet.dashboard.forms import UpdateDashboardModulesForm, AddUserDashboardModuleForm, \
+    UpdateDashboardModuleCollapseForm, RemoveDashboardModuleForm
+from jet.dashboard.models import UserDashboardModule
+from jet.utils import JsonResponse, get_app_list, SuccessMessageMixin
+from django.views.generic import UpdateView
+from django.utils.translation import ugettext_lazy as _
+
+
+class UpdateDashboardModuleView(SuccessMessageMixin, UpdateView):
+    model = UserDashboardModule
+    fields = ('title',)
+    template_name = 'jet.dashboard/update_module.html'
+    success_message = _('Widget was successfully updated')
+    object = None
+    module = None
+
+    def get_success_url(self):
+        if self.object.app_label:
+            return reverse('admin:app_list', kwargs={'app_label': self.object.app_label})
+        else:
+            return reverse('admin:index')
+
+    def get_module(self):
+        object = self.object if getattr(self, 'object', None) is not None else self.get_object()
+        return object.load_module()
+
+    def get_settings_form_kwargs(self):
+        kwargs = {
+            'initial': self.module.settings
+        }
+
+        if self.request.method in ('POST', 'PUT'):
+            kwargs.update({
+                'data': self.request.POST,
+                'files': self.request.FILES,
+            })
+        return kwargs
+
+    def get_settings_form(self):
+        if self.module.settings_form:
+            form = self.module.settings_form(**self.get_settings_form_kwargs())
+            if hasattr(form, 'set_module'):
+                form.set_module(self.module)
+            return form
+
+    def get_children_formset_kwargs(self):
+        kwargs = {
+            'initial': self.module.children,
+            'prefix': 'children',
+        }
+
+        if self.request.method in ('POST', 'PUT'):
+            kwargs.update({
+                'data': self.request.POST,
+                'files': self.request.FILES,
+            })
+        return kwargs
+
+    def get_children_formset(self):
+        if self.module.child_form:
+            return formset_factory(self.module.child_form, can_delete=True, extra=1)(**self.get_children_formset_kwargs())
+
+    def clean_children_data(self, children):
+        children = list(filter(
+            lambda item: isinstance(item, dict) and item and item.get('DELETE') is not True,
+            children
+        ))
+        for item in children:
+            item.pop('DELETE')
+        return children
+
+    def get_current_app(self):
+        app_list = get_app_list({'request': self.request})
+
+        for app in app_list:
+            if app.get('app_label', app.get('name')) == self.object.app_label:
+                return app
+
+    def get_context_data(self, **kwargs):
+        data = super(UpdateDashboardModuleView, self).get_context_data(**kwargs)
+        data['title'] = _('Change')
+        data['module'] = self.module
+        data['settings_form'] = self.get_settings_form()
+        data['children_formset'] = self.get_children_formset()
+        data['child_name'] = self.module.child_name if self.module.child_name else _('Items')
+        data['child_name_plural'] = self.module.child_name_plural if self.module.child_name_plural else _('Items')
+        data['app'] = self.get_current_app()
+        return data
+
+    def dispatch(self, request, *args, **kwargs):
+        self.object = self.get_object()
+        self.module = self.get_module()(model=self.object)
+        return super(UpdateDashboardModuleView, self).dispatch(request, *args, **kwargs)
+
+    def post(self, request, *args, **kwargs):
+        settings_form = self.get_settings_form()
+        children_formset = self.get_children_formset()
+
+        data = request.POST.copy()
+
+        if settings_form:
+            if settings_form.is_valid():
+                settings = settings_form.cleaned_data
+                data['settings'] = self.module.dump_settings(settings)
+            else:
+                return self.form_invalid(self.get_form(self.get_form_class()))
+
+        if children_formset:
+            if children_formset.is_valid():
+                self.module.children = self.clean_children_data(children_formset.cleaned_data)
+                data['children'] = self.module.dump_children()
+            else:
+                return self.form_invalid(self.get_form(self.get_form_class()))
+
+        request.POST = data
+
+        return super(UpdateDashboardModuleView, self).post(request, *args, **kwargs)
+
+    def form_valid(self, form):
+        if 'settings' in form.data:
+            form.instance.settings = form.data['settings']
+        if 'children' in form.data:
+            form.instance.children = form.data['children']
+        return super(UpdateDashboardModuleView, self).form_valid(form)
+
+
+@require_POST
+def update_dashboard_modules_view(request):
+    result = {'error': False}
+    form = UpdateDashboardModulesForm(request, request.POST)
+
+    if form.is_valid():
+        form.save()
+    else:
+        result['error'] = True
+
+    return JsonResponse(result)
+
+
+@require_POST
+def add_user_dashboard_module_view(request):
+    result = {'error': False}
+    form = AddUserDashboardModuleForm(request, request.POST)
+
+    if form.is_valid():
+        module = form.save()
+        result['id'] = module.pk
+        messages.success(request, _('Widget has been successfully added'))
+
+        if module.app_label:
+            result['success_url'] = reverse('admin:app_list', kwargs={'app_label': module.app_label})
+        else:
+            result['success_url'] = reverse('admin:index')
+    else:
+        result['error'] = True
+
+    return JsonResponse(result)
+
+
+@require_POST
+def update_dashboard_module_collapse_view(request):
+    result = {'error': False}
+
+    try:
+        instance = UserDashboardModule.objects.get(pk=request.POST.get('id'))
+        form = UpdateDashboardModuleCollapseForm(request, request.POST, instance=instance)
+
+        if form.is_valid():
+            module = form.save()
+            result['collapsed'] = module.collapsed
+        else:
+            result['error'] = True
+    except UserDashboardModule.DoesNotExist:
+        result['error'] = True
+
+    return JsonResponse(result)
+
+
+@require_POST
+def remove_dashboard_module_view(request):
+    result = {'error': False}
+
+    try:
+        instance = UserDashboardModule.objects.get(pk=request.POST.get('id'))
+        form = RemoveDashboardModuleForm(request, request.POST, instance=instance)
+
+        if form.is_valid():
+            form.save()
+        else:
+            result['error'] = True
+    except UserDashboardModule.DoesNotExist:
+        result['error'] = True
+
+    return JsonResponse(result)
+
+
+@require_GET
+def load_dashboard_module_view(request, pk):
+    result = {'error': False}
+
+    try:
+        instance = UserDashboardModule.objects.get(pk=pk)
+        module_cls = instance.load_module()
+        module = module_cls(model=instance, context={'request': request})
+        result['html'] = module.render()
+    except UserDashboardModule.DoesNotExist:
+        result['error'] = True
+
+    return JsonResponse(result)

+ 5 - 129
jet/forms.py

@@ -2,15 +2,16 @@ import json
 from django import forms
 from django.core.exceptions import ValidationError
 from django.db.models import Q
+import operator
+from jet.models import Bookmark, PinnedApplication
+from jet.utils import get_model_instance_label
+from functools import reduce
+
 try:
     from django.apps import apps
     get_model = apps.get_model
 except ImportError:
     from django.db.models.loading import get_model
-import operator
-from jet.models import Bookmark, PinnedApplication, UserDashboardModule
-from jet.utils import get_current_dashboard, get_model_instance_label
-from functools import reduce
 
 
 class AddBookmarkForm(forms.ModelForm):
@@ -83,131 +84,6 @@ class ToggleApplicationPinForm(forms.ModelForm):
                 return True
 
 
-class UpdateDashboardModulesForm(forms.Form):
-    app_label = forms.CharField(required=False)
-    modules = forms.CharField()
-    modules_objects = []
-
-    def __init__(self, request, *args, **kwargs):
-        self.request = request
-        super(UpdateDashboardModulesForm, self).__init__(*args, **kwargs)
-
-    def clean(self):
-        data = super(UpdateDashboardModulesForm, self).clean()
-
-        try:
-            modules = json.loads(data['modules'])
-
-            for module in modules:
-                db_module = UserDashboardModule.objects.get(
-                    user=self.request.user.pk,
-                    app_label=data['app_label'] if data['app_label'] else None,
-                    pk=module['id']
-                )
-
-                column = module['column']
-                order = module['order']
-
-                if db_module.column != column or db_module.order != order:
-                    db_module.column = column
-                    db_module.order = order
-
-                    self.modules_objects.append(db_module)
-        except Exception:
-            raise ValidationError('error')
-
-        return data
-
-    def save(self):
-        for module in self.modules_objects:
-            module.save()
-
-
-class AddUserDashboardModuleForm(forms.ModelForm):
-    type = forms.CharField()
-    module = forms.IntegerField()
-    module_cls = None
-
-    def __init__(self, request, *args, **kwargs):
-        self.request = request
-        super(AddUserDashboardModuleForm, self).__init__(*args, **kwargs)
-
-    class Meta:
-        model = UserDashboardModule
-        fields = ['app_label']
-
-    def clean_app_label(self):
-        data = self.cleaned_data['app_label']
-        return data if data != '' else None
-
-    def clean(self):
-        data = super(AddUserDashboardModuleForm, self).clean()
-
-        index_dashboard_cls = get_current_dashboard('app_index' if data['app_label'] else 'index')
-        index_dashboard = index_dashboard_cls({'request': self.request}, app_label=data['app_label'])
-
-        if data['type'] == 'children':
-            module = index_dashboard.children[data['module']]
-        elif data['type'] == 'available_children':
-            module = index_dashboard.available_children[data['module']]()
-        else:
-            raise ValidationError('error')
-
-        self.module_cls = module
-        return data
-
-    def save(self, commit=True):
-        self.instance.title = self.module_cls.title
-        self.instance.module = self.module_cls.fullname()
-        self.instance.user = self.request.user.pk
-        self.instance.column = 0
-        self.instance.order = -1
-        self.instance.settings = self.module_cls.dump_settings()
-        self.instance.children = self.module_cls.dump_children()
-
-        return super(AddUserDashboardModuleForm, self).save(commit)
-
-
-class UpdateDashboardModuleCollapseForm(forms.ModelForm):
-    def __init__(self, request, *args, **kwargs):
-        self.request = request
-        super(UpdateDashboardModuleCollapseForm, self).__init__(*args, **kwargs)
-
-    class Meta:
-        model = UserDashboardModule
-        fields = ['collapsed']
-
-    def clean(self):
-        data = super(UpdateDashboardModuleCollapseForm, self).clean()
-
-        if self.instance.user != self.request.user.pk:
-            raise ValidationError('error')
-
-        return data
-
-
-class RemoveDashboardModuleForm(forms.ModelForm):
-    def __init__(self, request, *args, **kwargs):
-        self.request = request
-        super(RemoveDashboardModuleForm, self).__init__(*args, **kwargs)
-
-    class Meta:
-        model = UserDashboardModule
-        fields = []
-
-    def clean(self):
-        cleaned_data = super(RemoveDashboardModuleForm, self).clean()
-
-        if self.instance.user != self.request.user.pk:
-            raise ValidationError('error')
-
-        return cleaned_data
-
-    def save(self, commit=True):
-        if commit:
-            self.instance.delete()
-
-
 class ModelLookupForm(forms.Form):
     app_label = forms.CharField()
     model = forms.CharField()

+ 17 - 0
jet/migrations/0002_delete_userdashboardmodule.py

@@ -0,0 +1,17 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('jet', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.DeleteModel(
+            name='UserDashboardModule',
+        ),
+    ]

+ 0 - 50
jet/models.py

@@ -37,53 +37,3 @@ class PinnedApplication(models.Model):
     def __str__(self):
         return self.app_label
 
-
-@python_2_unicode_compatible
-class UserDashboardModule(models.Model):
-    title = models.CharField(verbose_name=_('Title'), max_length=255)
-    module = models.CharField(verbose_name=_('module'), max_length=255)
-    app_label = models.CharField(verbose_name=_('application name'), max_length=255, null=True, blank=True)
-    user = models.PositiveIntegerField(verbose_name=_('user'))
-    column = models.PositiveIntegerField(verbose_name=_('column'))
-    order = models.IntegerField(verbose_name=_('order'))
-    settings = models.TextField(verbose_name=_('settings'), default='', blank=True)
-    children = models.TextField(verbose_name=_('children'), default='', blank=True)
-    collapsed = models.BooleanField(verbose_name=_('collapsed'), default=False)
-
-    class Meta:
-        verbose_name = _('user dashboard module')
-        verbose_name_plural = _('user dashboard modules')
-        ordering = ('column', 'order')
-
-    def __str__(self):
-        return self.module
-
-    def load_module(self):
-        try:
-            package, module_name = self.module.rsplit('.', 1)
-            package = import_module(package)
-            module = getattr(package, module_name)
-
-            return module
-        except AttributeError:
-            return None
-
-    def pop_settings(self, pop_settings):
-        settings = json.loads(self.settings)
-
-        for setting in pop_settings:
-            if setting in settings:
-                settings.pop(setting)
-
-        self.settings = json.dumps(settings, cls=LazyDateTimeEncoder)
-        self.save()
-
-    def update_settings(self, update_settings):
-        settings = json.loads(self.settings)
-
-        settings.update(update_settings)
-
-        self.settings = json.dumps(settings, cls=LazyDateTimeEncoder)
-        self.save()
-
-

+ 0 - 4
jet/settings.py

@@ -2,7 +2,3 @@ from django.conf import settings
 
 # Theme
 JET_THEME = getattr(settings, 'JET_THEME', 'default')
-
-# Dashboard
-JET_INDEX_DASHBOARD = getattr(settings, 'JET_INDEX_DASHBOARD', 'jet.dashboard.DefaultIndexDashboard')
-JET_APP_INDEX_DASHBOARD = getattr(settings, 'JET_APP_INDEX_DASHBOARD', 'jet.dashboard.DefaultAppIndexDashboard')

+ 1 - 1
jet/static/jet/css/_content.scss

@@ -5,7 +5,7 @@
 }
 
 .dashboard #content {
-  width: 700px;
+  width: 500px;
 }
 
 .small {

+ 1 - 1
jet/static/jet/css/themes/default/base.css

@@ -4596,7 +4596,7 @@ table#change-history {
   background-color: #ecf2f6; }
 
 .dashboard #content {
-  width: 700px; }
+  width: 500px; }
 
 .small, .module .form-row .help, .module .form-row .errorlist, .changeform .tabular.inline-related .module .errorlist, .login-form .form-row .errorlist {
   font-size: 12px; }

+ 1 - 1
jet/static/jet/css/themes/green/base.css

@@ -4627,7 +4627,7 @@ table#change-history {
   background-color: #eff6f5; }
 
 .dashboard #content {
-  width: 700px; }
+  width: 500px; }
 
 .small, .module .form-row .help, .module .form-row .errorlist, .changeform .tabular.inline-related .module .errorlist, .login-form .form-row .errorlist {
   font-size: 12px; }

+ 96 - 26
jet/templates/admin/index.html

@@ -1,37 +1,107 @@
 {% extends "admin/base_site.html" %}
-{% load i18n admin_static jet_tags static %}
+{% load i18n admin_static log %}
 
-{% block html %}
-    {% get_dashboard 'index' as dashboard %}
-    {{ block.super }}
-{% endblock %}
+{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% static "admin/css/dashboard.css" %}" />{% endblock %}
 
-{% block extrastyle %}
-    {{ block.super }}
-    <link rel="stylesheet" type="text/css" href="{% static "admin/css/dashboard.css" %}" />
+{% block coltype %}colMS{% endblock %}
 
-    {% for css in dashboard.media.css %}
-        <link href="{% static css %}" rel="stylesheet" />
-    {% endfor %}
-{% endblock %}
+{% block bodyclass %}{{ block.super }} dashboard{% endblock %}
+
+{% block content %}
+    <div class="dashboard-item">
+        <div class="dashboard-item-content">
+            {% if app_list %}
+                <ul>
+                    {% for app in app_list %}
+                        <li class="contrast">
+                            {% if app.name != app.app_label|capfirst|escape %}
+                                <a href="{{ app.app_url }}" title="{% blocktrans with name=app.name %}Models in the {{ name }} application{% endblocktrans %}">{{ app.name }}</a>
+                            {% else %}
+                                {% trans app.app_label as app_label %}
+                                <a href="{{ app.app_url }}" title="{% blocktrans with name=app_label %}Models in the {{ name }} application{% endblocktrans %}">{{ app_label }}</a>
+                            {% endif %}
+                        </li>
+
+                        {% for model in app.models %}
+                            <li>
+                                <span class="float-right">
+                                    {% if model.add_url %}
+                                        <a href="{{ model.add_url }}" class="addlink" title="{% trans 'Add' %}"><span class="icon-add3"></span></a>
+                                    {% else %}
+                                        &nbsp;
+                                    {% endif %}
 
-{% block extrahead %}
-    {{ block.super }}
-    {% for js in dashboard.media.js %}
-        <script src="{% static js %}"></script>
-    {% endfor %}
+                                    {% if model.admin_url %}
+                                        <a href="{{ model.admin_url }}" class="changelink" title="{% trans 'Change' %}"><span class="icon-edit"></span></a>
+                                    {% else %}
+                                        &nbsp;
+                                    {% endif %}
+                                </span>
+
+                                {% if model.admin_url %}
+                                    <a href="{{ model.admin_url }}">{{ model.name }}</a>
+                                {% else %}
+                                    {{ model.name }}
+                                {% endif %}
+                            </li>
+                        {% endfor %}
+                    {% endfor %}
+                </ul>
+            {% else %}
+                <p>{% trans "You don't have permission to edit anything." %}</p>
+            {% endif %}
+        </div>
+    </div>
 {% endblock %}
 
-{% block coltype %}colMS{% endblock %}
+{% block sidebar %}
+    {% get_admin_log 10 as admin_log for_user user %}
 
-{% block bodyclass %}{{ block.super }} dashboard{% endblock %}
+    <div class="dashboard-item">
+        <div class="dashboard-item-header">
+            <span class="dashboard-item-header-title">
+                {% trans 'Recent Actions' %}
+            </span>
+        </div>
+        <div class="dashboard-item-content">
+            <ul>
+                {% if not admin_log %}
+                    <li>
+                        {% trans 'None available' %}
+                    </li>
+                {% else %}
+                    {% for entry in admin_log %}
+                        <li class="nowrap">
+                            <span class="float-right">
+                                <span class="icon-user tooltip" title="{{ entry.user }}"></span>
+                                <span class="icon-clock tooltip" title="{{ entry.action_time }}"></span>
+                            </span>
 
-{% block sidebar %}{% endblock %}
+                            {% if entry.is_addition %}
+                                <span class="icon-add3"></span>
+                            {% endif %}
+                            {% if entry.is_change %}
+                                <span class="icon-edit"></span>
+                            {% endif %}
+                            {% if entry.is_deletion %}
+                                <span class="icon-cross"></span>
+                            {% endif %}
 
-{% block top-right %}
-    {{ dashboard.render_tools }}
-{% endblock %}
+                            {% if entry.content_type %}
+                                <span class="mini quiet">{% filter capfirst %}{{ entry.content_type }}{% endfilter %}</span>
+                            {% else %}
+                                <span class="mini quiet">{% trans 'Unknown content' %}</span>
+                            {% endif %}
 
-{% block content %}
-    {{ dashboard.render }}
-{% endblock %}
+                            {% if entry.is_deletion or not entry.get_admin_url %}
+                                {{ entry.object_repr }}
+                            {% else %}
+                                <a href="{{ entry.get_admin_url }}">{{ entry.object_repr }}</a>
+                            {% endif %}
+                        </li>
+                    {% endfor %}
+                {% endif %}
+            </ul>
+        </div>
+    </div>
+{% endblock %}

+ 1 - 8
jet/templatetags/jet_tags.py

@@ -8,7 +8,7 @@ from django.template import loader, Context
 from jet import settings
 from jet.models import Bookmark, PinnedApplication
 import re
-from jet.utils import get_app_list, get_model_instance_label, get_current_dashboard
+from jet.utils import get_app_list, get_model_instance_label
 
 register = template.Library()
 
@@ -218,13 +218,6 @@ def jet_add_preserved_filters(context, url, popup=False, to_field=None):
         return url
 
 
-@register.assignment_tag(takes_context=True)
-def get_dashboard(context, location):
-    dashboard_cls = get_current_dashboard(location)
-    dashboard = dashboard_cls(context)
-    return dashboard
-
-
 @register.filter()
 def if_onetoone(formset):
     return getattr(formset, 'fk') and isinstance(formset.fk, OneToOneField)

+ 2 - 2
jet/tests/dashboard.py

@@ -1,5 +1,5 @@
-from jet import modules
-from jet.dashboard import Dashboard
+from jet.dashboard import modules
+from jet.dashboard.dashboard import Dashboard
 
 
 class TestIndexDashboard(Dashboard):

+ 1 - 0
jet/tests/settings.py

@@ -9,6 +9,7 @@ DEBUG_PROPAGATE_EXCEPTIONS = True
 ROOT_URLCONF = 'jet.tests.urls'
 
 INSTALLED_APPS = (
+    'jet.dashboard',
     'jet',
     'django.contrib.auth',
     'django.contrib.contenttypes',

+ 4 - 4
jet/tests/test_dashboard.py

@@ -1,7 +1,7 @@
 from django.contrib.auth.models import User
 from django.test import TestCase, Client
-from jet.modules import LinkList, RecentActions
-from jet.models import UserDashboardModule
+from jet.dashboard.modules import LinkList, RecentActions
+from jet.dashboard.models import UserDashboardModule
 from jet.tests.dashboard import TestIndexDashboard
 
 
@@ -25,7 +25,7 @@ class DashboardTestCase(TestCase):
     def _init_dashboard(self):
         UserDashboardModule.objects.create(
             title='',
-            module='jet.modules.LinkList',
+            module='jet.dashboard.modules.LinkList',
             app_label=None,
             user=self.admin_user.pk,
             column=0,
@@ -33,7 +33,7 @@ class DashboardTestCase(TestCase):
         )
         UserDashboardModule.objects.create(
             title='',
-            module='jet.modules.RecentActions',
+            module='jet.dashboard.modules.RecentActions',
             app_label=None,
             user=self.admin_user.pk,
             column=0,

+ 15 - 14
jet/tests/test_views.py

@@ -2,8 +2,9 @@ import json
 from django.contrib.auth.models import User
 from django.core.urlresolvers import reverse
 from django.test import TestCase, Client
-from jet.modules import LinkList
-from jet.models import UserDashboardModule, Bookmark
+from jet.dashboard.modules import LinkList
+from jet.models import Bookmark
+from jet.dashboard.models import UserDashboardModule
 
 
 class ViewsTestCase(TestCase):
@@ -24,7 +25,7 @@ class ViewsTestCase(TestCase):
         new_layout = 'stacked'
         module = UserDashboardModule.objects.create(
             title=title,
-            module='jet.modules.LinkList',
+            module='jet.dashboard.modules.LinkList',
             app_label=None,
             user=self.admin_user.pk,
             column=0,
@@ -33,7 +34,7 @@ class ViewsTestCase(TestCase):
             children='[]'
         )
 
-        response = self.admin.get(reverse('jet:update_module', kwargs={'pk': module.pk}))
+        response = self.admin.get(reverse('jet-dashboard:update_module', kwargs={'pk': module.pk}))
         self.assertEqual(response.status_code, 200)
         self.assertTrue(response.context['module'] is not None)
         self.assertTrue(isinstance(response.context['module'], LinkList))
@@ -56,7 +57,7 @@ class ViewsTestCase(TestCase):
             '_save': 'Save'
         }
 
-        self.admin.post(reverse('jet:update_module', kwargs={'pk': module.pk}), post)
+        self.admin.post(reverse('jet-dashboard:update_module', kwargs={'pk': module.pk}), post)
         self.assertEqual(response.status_code, 200)
         module = UserDashboardModule.objects.get(pk=module.pk)
         settings = json.loads(module.settings)
@@ -115,7 +116,7 @@ class ViewsTestCase(TestCase):
         app_label = None
         module_0 = UserDashboardModule.objects.create(
             title='',
-            module='jet.modules.LinkList',
+            module='jet.dashboard.modules.LinkList',
             app_label=app_label,
             user=self.admin_user.pk,
             column=0,
@@ -123,13 +124,13 @@ class ViewsTestCase(TestCase):
         )
         module_1 = UserDashboardModule.objects.create(
             title='',
-            module='jet.modules.LinkList',
+            module='jet.dashboard.modules.LinkList',
             app_label=app_label,
             user=self.admin_user.pk,
             column=0,
             order=1
         )
-        response = self.admin.post(reverse('jet:update_dashboard_modules'), {
+        response = self.admin.post(reverse('jet-dashboard:update_dashboard_modules'), {
             'app_label': '',
             'modules': json.dumps([
                 {'id': module_0.pk, 'column': 0, 'order': 1},
@@ -151,7 +152,7 @@ class ViewsTestCase(TestCase):
         module_1.delete()
 
     def test_add_user_dashboard_module_view(self):
-        response = self.admin.post(reverse('jet:add_user_dashboard_module'), {
+        response = self.admin.post(reverse('jet-dashboard:add_user_dashboard_module'), {
             'app_label': '',
             'type': 'available_children',
             'module': 0
@@ -167,13 +168,13 @@ class ViewsTestCase(TestCase):
     def test_update_dashboard_module_collapse_view(self):
         module = UserDashboardModule.objects.create(
             title='',
-            module='jet.modules.LinkList',
+            module='jet.dashboard.modules.LinkList',
             app_label=None,
             user=self.admin_user.pk,
             column=0,
             order=0
         )
-        response = self.admin.post(reverse('jet:update_dashboard_module_collapse'), {
+        response = self.admin.post(reverse('jet-dashboard:update_dashboard_module_collapse'), {
             'id': module.pk, 'collapsed': True
         })
         self.assertEqual(response.status_code, 200)
@@ -182,7 +183,7 @@ class ViewsTestCase(TestCase):
         self.assertTrue(response['collapsed'])
 
         module = UserDashboardModule.objects.get(pk=module.pk)
-        response = self.admin.post(reverse('jet:update_dashboard_module_collapse'), {
+        response = self.admin.post(reverse('jet-dashboard:update_dashboard_module_collapse'), {
             'id': module.pk, 'collapsed': False
         })
         self.assertEqual(response.status_code, 200)
@@ -195,13 +196,13 @@ class ViewsTestCase(TestCase):
     def test_remove_dashboard_module_view(self):
         module = UserDashboardModule.objects.create(
             title='',
-            module='jet.modules.LinkList',
+            module='jet.dashboard.modules.LinkList',
             app_label=None,
             user=self.admin_user.pk,
             column=0,
             order=0
         )
-        response = self.admin.post(reverse('jet:remove_dashboard_module'), {'id': module.pk})
+        response = self.admin.post(reverse('jet-dashboard:remove_dashboard_module'), {'id': module.pk})
         self.assertEqual(response.status_code, 200)
         response = json.loads(response.content.decode())
         self.assertFalse(response['error'])

+ 1 - 0
jet/tests/urls.py

@@ -6,6 +6,7 @@ admin.autodiscover()
 urlpatterns = patterns(
     '',
     url(r'^jet/', include('jet.urls', 'jet')),
+    url(r'^jet/dashboard/', include('jet.dashboard.urls', 'jet-dashboard')),
     url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
     url(r'^admin/', include(admin.site.urls)),
 )

+ 1 - 36
jet/urls.py

@@ -1,17 +1,9 @@
 from django.conf.urls import patterns, url
 from django.views.i18n import javascript_catalog
-from jet import dashboard
-from jet.views import add_bookmark_view, remove_bookmark_view, toggle_application_pin_view, \
-    update_dashboard_modules_view, add_user_dashboard_module_view, update_dashboard_module_collapse_view, \
-    remove_dashboard_module_view, UpdateDashboardModuleView, load_dashboard_module_view, model_lookup_view
+from jet.views import add_bookmark_view, remove_bookmark_view, toggle_application_pin_view, model_lookup_view
 
 urlpatterns = patterns(
     '',
-    url(
-        r'^module/(?P<pk>\d+)/$',
-        UpdateDashboardModuleView.as_view(),
-        name='update_module'
-    ),
     url(
         r'^add_bookmark/$',
         add_bookmark_view,
@@ -27,31 +19,6 @@ urlpatterns = patterns(
         toggle_application_pin_view,
         name='toggle_application_pin'
     ),
-    url(
-        r'^update_dashboard_modules/$',
-        update_dashboard_modules_view,
-        name='update_dashboard_modules'
-    ),
-    url(
-        r'^add_user_dashboard_module/$',
-        add_user_dashboard_module_view,
-        name='add_user_dashboard_module'
-    ),
-    url(
-        r'^update_dashboard_module_collapse/$',
-        update_dashboard_module_collapse_view,
-        name='update_dashboard_module_collapse'
-    ),
-    url(
-        r'^remove_dashboard_module/$',
-        remove_dashboard_module_view,
-        name='remove_dashboard_module'
-    ),
-    url(
-        r'^load_dashboard_module/(?P<pk>\d+)/$',
-        load_dashboard_module_view,
-        name='load_dashboard_module'
-    ),
     url(
         r'^model_lookup/$',
         model_lookup_view,
@@ -64,5 +31,3 @@ urlpatterns = patterns(
         name='jsi18n'
     ),
 )
-
-urlpatterns += dashboard.urls.get_urls()

+ 0 - 15
jet/utils.py

@@ -59,21 +59,6 @@ def get_admin_site_name(context):
     return get_admin_site(context).name
 
 
-def get_current_dashboard(location):
-    if location == 'index':
-        path = settings.JET_INDEX_DASHBOARD
-    elif location == 'app_index':
-        path = settings.JET_APP_INDEX_DASHBOARD
-    else:
-        raise ValueError('Unknown dashboard location: %s' % location)
-
-    module, cls = path.rsplit('.', 1)
-    module = import_module(module)
-    index_dashboard_cls = getattr(module, cls)
-
-    return index_dashboard_cls
-
-
 class LazyDateTimeEncoder(json.JSONEncoder):
     def default(self, obj):
         if isinstance(obj, datetime.datetime) or isinstance(obj, datetime.date):

+ 3 - 212
jet/views.py

@@ -1,131 +1,7 @@
-from django.contrib import messages
-from django.core.urlresolvers import reverse
-from django.forms.formsets import formset_factory
 from django.views.decorators.http import require_POST, require_GET
-from jet.forms import AddBookmarkForm, RemoveBookmarkForm, ToggleApplicationPinForm, UpdateDashboardModulesForm, \
-    AddUserDashboardModuleForm, UpdateDashboardModuleCollapseForm, RemoveDashboardModuleForm, ModelLookupForm
-from jet.models import Bookmark, UserDashboardModule
-from jet.utils import JsonResponse, get_app_list, SuccessMessageMixin
-from django.views.generic import UpdateView
-from django.utils.translation import ugettext_lazy as _
-
-
-class UpdateDashboardModuleView(SuccessMessageMixin, UpdateView):
-    model = UserDashboardModule
-    fields = ('title',)
-    template_name = 'jet/dashboard/update_module.html'
-    success_message = _('Widget was successfully updated')
-    object = None
-    module = None
-
-    def get_success_url(self):
-        if self.object.app_label:
-            return reverse('admin:app_list', kwargs={'app_label': self.object.app_label})
-        else:
-            return reverse('admin:index')
-
-    def get_module(self):
-        object = self.object if getattr(self, 'object', None) is not None else self.get_object()
-        return object.load_module()
-
-    def get_settings_form_kwargs(self):
-        kwargs = {
-            'initial': self.module.settings
-        }
-
-        if self.request.method in ('POST', 'PUT'):
-            kwargs.update({
-                'data': self.request.POST,
-                'files': self.request.FILES,
-            })
-        return kwargs
-
-    def get_settings_form(self):
-        if self.module.settings_form:
-            form = self.module.settings_form(**self.get_settings_form_kwargs())
-            if hasattr(form, 'set_module'):
-                form.set_module(self.module)
-            return form
-
-    def get_children_formset_kwargs(self):
-        kwargs = {
-            'initial': self.module.children,
-            'prefix': 'children',
-        }
-
-        if self.request.method in ('POST', 'PUT'):
-            kwargs.update({
-                'data': self.request.POST,
-                'files': self.request.FILES,
-            })
-        return kwargs
-
-    def get_children_formset(self):
-        if self.module.child_form:
-            return formset_factory(self.module.child_form, can_delete=True, extra=1)(**self.get_children_formset_kwargs())
-
-    def clean_children_data(self, children):
-        children = list(filter(
-            lambda item: isinstance(item, dict) and item and item.get('DELETE') is not True,
-            children
-        ))
-        for item in children:
-            item.pop('DELETE')
-        return children
-
-    def get_current_app(self):
-        app_list = get_app_list({'request': self.request})
-
-        for app in app_list:
-            if app.get('app_label', app.get('name')) == self.object.app_label:
-                return app
-
-    def get_context_data(self, **kwargs):
-        data = super(UpdateDashboardModuleView, self).get_context_data(**kwargs)
-        data['title'] = _('Change')
-        data['module'] = self.module
-        data['settings_form'] = self.get_settings_form()
-        data['children_formset'] = self.get_children_formset()
-        data['child_name'] = self.module.child_name if self.module.child_name else _('Items')
-        data['child_name_plural'] = self.module.child_name_plural if self.module.child_name_plural else _('Items')
-        data['app'] = self.get_current_app()
-        return data
-
-    def dispatch(self, request, *args, **kwargs):
-        self.object = self.get_object()
-        self.module = self.get_module()(model=self.object)
-        return super(UpdateDashboardModuleView, self).dispatch(request, *args, **kwargs)
-
-    def post(self, request, *args, **kwargs):
-        settings_form = self.get_settings_form()
-        children_formset = self.get_children_formset()
-
-        data = request.POST.copy()
-
-        if settings_form:
-            if settings_form.is_valid():
-                settings = settings_form.cleaned_data
-                data['settings'] = self.module.dump_settings(settings)
-            else:
-                return self.form_invalid(self.get_form(self.get_form_class()))
-
-        if children_formset:
-            if children_formset.is_valid():
-                self.module.children = self.clean_children_data(children_formset.cleaned_data)
-                data['children'] = self.module.dump_children()
-            else:
-                return self.form_invalid(self.get_form(self.get_form_class()))
-
-        request.POST = data
-
-        return super(UpdateDashboardModuleView, self).post(request, *args, **kwargs)
-
-    def form_valid(self, form):
-        if 'settings' in form.data:
-            form.instance.settings = form.data['settings']
-        if 'children' in form.data:
-            form.instance.children = form.data['children']
-        return super(UpdateDashboardModuleView, self).form_valid(form)
+from jet.forms import AddBookmarkForm, RemoveBookmarkForm, ToggleApplicationPinForm, ModelLookupForm
+from jet.models import Bookmark
+from jet.utils import JsonResponse
 
 
 @require_POST
@@ -174,91 +50,6 @@ def toggle_application_pin_view(request):
     return JsonResponse(result)
 
 
-@require_POST
-def update_dashboard_modules_view(request):
-    result = {'error': False}
-    form = UpdateDashboardModulesForm(request, request.POST)
-
-    if form.is_valid():
-        form.save()
-    else:
-        result['error'] = True
-
-    return JsonResponse(result)
-
-
-@require_POST
-def add_user_dashboard_module_view(request):
-    result = {'error': False}
-    form = AddUserDashboardModuleForm(request, request.POST)
-
-    if form.is_valid():
-        module = form.save()
-        result['id'] = module.pk
-        messages.success(request, _('Widget has been successfully added'))
-
-        if module.app_label:
-            result['success_url'] = reverse('admin:app_list', kwargs={'app_label': module.app_label})
-        else:
-            result['success_url'] = reverse('admin:index')
-    else:
-        result['error'] = True
-
-    return JsonResponse(result)
-
-
-@require_POST
-def update_dashboard_module_collapse_view(request):
-    result = {'error': False}
-
-    try:
-        instance = UserDashboardModule.objects.get(pk=request.POST.get('id'))
-        form = UpdateDashboardModuleCollapseForm(request, request.POST, instance=instance)
-
-        if form.is_valid():
-            module = form.save()
-            result['collapsed'] = module.collapsed
-        else:
-            result['error'] = True
-    except UserDashboardModule.DoesNotExist:
-        result['error'] = True
-
-    return JsonResponse(result)
-
-
-@require_POST
-def remove_dashboard_module_view(request):
-    result = {'error': False}
-
-    try:
-        instance = UserDashboardModule.objects.get(pk=request.POST.get('id'))
-        form = RemoveDashboardModuleForm(request, request.POST, instance=instance)
-
-        if form.is_valid():
-            form.save()
-        else:
-            result['error'] = True
-    except UserDashboardModule.DoesNotExist:
-        result['error'] = True
-
-    return JsonResponse(result)
-
-
-@require_GET
-def load_dashboard_module_view(request, pk):
-    result = {'error': False}
-
-    try:
-        instance = UserDashboardModule.objects.get(pk=pk)
-        module_cls = instance.load_module()
-        module = module_cls(model=instance, context={'request': request})
-        result['html'] = module.render()
-    except UserDashboardModule.DoesNotExist:
-        result['error'] = True
-
-    return JsonResponse(result)
-
-
 @require_GET
 def model_lookup_view(request):
     result = {'error': False}