Browse Source

Merge branch 'dev'

Conflicts:
	setup.py
Denis K 9 years ago
parent
commit
773f3d3867

+ 32 - 0
.travis.yml

@@ -0,0 +1,32 @@
+language: python
+python:
+  - 2.6
+  - 2.7
+  - 3.2
+  - 3.3
+  - 3.4
+  - pypy
+env:
+  - DJANGO=1.6.11
+  - DJANGO=1.7.7
+  - DJANGO=1.8.3
+before_install:
+  - export DJANGO_SETTINGS_MODULE=jet.tests.settings
+install:
+  - if [[ $TRAVIS_PYTHON_VERSION == 2.6 ]]; then pip install importlib; fi
+  - pip install -q Django==$DJANGO --use-mirrors
+  - pip install . --use-mirrors
+  - pip install coveralls
+script:
+  - coverage run --source=jet --omit=*/migrations/*,*/south_migrations/*,*/tests/* manage.py test jet
+after_success:
+  - coverage report
+  - coveralls
+matrix:
+  exclude:
+    - python: 2.6
+      env: DJANGO=1.6.11
+    - python: 2.6
+      env: DJANGO=1.7.7
+    - python: 2.6
+      env: DJANGO=1.8.3

+ 7 - 0
CHANGELOG.rst

@@ -1,6 +1,13 @@
 Changelog
 =========
 
+0.0.6
+-----
+
+* [Feature] Added initial unit tests
+* [Fixes] Compatibility fixes
+
+
 0.0.5
 -----
 

+ 1 - 1
jet/__init__.py

@@ -1 +1 @@
-VERSION = '0.0.5'
+VERSION = '0.0.6'

+ 9 - 8
jet/dashboard.py

@@ -5,6 +5,7 @@ from jet import modules
 from jet.models import UserDashboardModule
 from django.core.context_processors import csrf
 from django.utils.translation import ugettext_lazy as _
+from jet.ordered_set import OrderedSet
 from jet.utils import get_admin_site_name
 
 
@@ -111,23 +112,23 @@ class Dashboard(object):
         return render_to_string('jet/dashboard/dashboard_tools.html', context)
 
     def media(self):
-        unique_css = {}
-        unique_js = {}
+        unique_css = OrderedSet()
+        unique_js = OrderedSet()
 
         for js in getattr(self.Media, 'js', ()):
-            unique_js[js] = True
+            unique_js.add(js)
         for css in getattr(self.Media, 'css', ()):
-            unique_css[css] = True
+            unique_css.add(css)
 
         for module in self.modules:
             for js in getattr(module.Media, 'js', ()):
-                unique_js[js] = True
+                unique_js.add(js)
             for css in getattr(module.Media, 'css', ()):
-                unique_css[css] = True
+                unique_css.add(css)
 
         class Media:
-            css = unique_css.keys()
-            js = unique_js.keys()
+            css = list(unique_css)
+            js = list(unique_js)
 
         return Media
 

+ 12 - 4
jet/forms.py

@@ -22,6 +22,14 @@ class AddBookmarkForm(forms.ModelForm):
         model = Bookmark
         fields = ['url', 'title']
 
+    def clean(self):
+        data = super(AddBookmarkForm, self).clean()
+        if not self.request.user.is_authenticated():
+            raise ValidationError('error')
+        if not self.request.user.has_perm('jet.change_bookmark'):
+            raise ValidationError('error')
+        return data
+
     def save(self, commit=True):
         self.instance.user = self.request.user.pk
         return super(AddBookmarkForm, self).save(commit)
@@ -37,12 +45,12 @@ class RemoveBookmarkForm(forms.ModelForm):
         fields = []
 
     def clean(self):
-        cleaned_data = super(RemoveBookmarkForm, self).clean()
-
+        data = super(RemoveBookmarkForm, self).clean()
+        if not self.request.user.is_authenticated():
+            raise ValidationError('error')
         if self.instance.user != self.request.user.pk:
             raise ValidationError('error')
-
-        return cleaned_data
+        return data
 
     def save(self, commit=True):
         if commit:

+ 4 - 4
jet/modules.py

@@ -177,11 +177,11 @@ class AppList(DashboardModule):
 
         for app in app_list:
             app['models'] = filter(
-                lambda model: self.models is None or model['object_name'] in self.models or app['app_label'] + '.*' in self.models,
+                lambda model: self.models is None or model['object_name'] in self.models or app.get('app_label', app.get('name')) + '.*' in self.models,
                 app['models']
             )
             app['models'] = filter(
-                lambda model: self.exclude is None or model['object_name'] not in self.exclude and app['app_label'] + '.*' not in self.exclude,
+                lambda model: self.exclude is None or model['object_name'] not in self.exclude and app.get('app_label', app.get('name')) + '.*' not in self.exclude,
                 app['models']
             )
             app['models'] = list(app['models'])
@@ -218,11 +218,11 @@ class ModelList(DashboardModule):
 
         for app in app_list:
             app['models'] = filter(
-                lambda model: self.models is None or model['object_name'] in self.models or app['app_label'] + '.*' in self.models,
+                lambda model: self.models is None or model['object_name'] in self.models or app.get('app_label', app.get('name')) + '.*' in self.models,
                 app['models']
             )
             app['models'] = filter(
-                lambda model: self.exclude is None or model['object_name'] not in self.exclude and app['app_label'] + '.*' not in self.exclude,
+                lambda model: self.exclude is None or model['object_name'] not in self.exclude and app.get('app_label', app.get('name')) + '.*' not in self.exclude,
                 app['models']
             )
             app['models'] = list(app['models'])

+ 67 - 0
jet/ordered_set.py

@@ -0,0 +1,67 @@
+import collections
+
+
+class OrderedSet(collections.MutableSet):
+    def __init__(self, iterable=None):
+        self.end = end = []
+        end += [None, end, end]         # sentinel node for doubly linked list
+        self.map = {}                   # key --> [key, prev, next]
+        if iterable is not None:
+            self |= iterable
+
+    def __len__(self):
+        return len(self.map)
+
+    def __contains__(self, key):
+        return key in self.map
+
+    def add(self, key):
+        if key not in self.map:
+            end = self.end
+            curr = end[1]
+            curr[2] = end[1] = self.map[key] = [key, curr, end]
+
+    def discard(self, key):
+        if key in self.map:
+            key, prev, next = self.map.pop(key)
+            prev[2] = next
+            next[1] = prev
+
+    def __iter__(self):
+        end = self.end
+        curr = end[2]
+        while curr is not end:
+            yield curr[0]
+            curr = curr[2]
+
+    def __reversed__(self):
+        end = self.end
+        curr = end[1]
+        while curr is not end:
+            yield curr[0]
+            curr = curr[1]
+
+    def pop(self, last=True):
+        if not self:
+            raise KeyError('set is empty')
+        key = self.end[1][0] if last else self.end[2][0]
+        self.discard(key)
+        return key
+
+    def __repr__(self):
+        if not self:
+            return '%s()' % (self.__class__.__name__,)
+        return '%s(%r)' % (self.__class__.__name__, list(self))
+
+    def __eq__(self, other):
+        if isinstance(other, OrderedSet):
+            return len(self) == len(other) and list(self) == list(other)
+        return set(self) == set(other)
+
+
+if __name__ == '__main__':
+    s = OrderedSet('abracadaba')
+    t = OrderedSet('simsalabim')
+    print(s | t)
+    print(s & t)
+    print(s - t)

+ 1 - 1
jet/templates/admin/change_form.html

@@ -32,7 +32,7 @@
                             {% block object-tools-items %}
                                 <li>
                                     {% url opts|admin_urlname:'history' original.pk|admin_urlquote as history_url %}
-                                    <a href="{% add_preserved_filters history_url %}" class="historylink">{% trans "History" %}</a>
+                                    <a href="{% jet_add_preserved_filters history_url %}" class="historylink">{% trans "History" %}</a>
                                 </li>
                                 {% if has_absolute_url %}
                                     <li><a href="{{ absolute_url }}" class="viewsitelink">{% trans "View on site" %}</a></li>

+ 2 - 2
jet/templates/admin/submit_line.html

@@ -1,4 +1,4 @@
-{% load i18n admin_urls %}
+{% load i18n admin_urls jet_tags %}
 
 <div class="submit-row">
     {% if show_save %}<input type="submit" value="{% trans 'Save' %}" class="default" name="_save" />{% endif %}
@@ -8,6 +8,6 @@
 
     {% if show_delete_link %}
         {% url opts|admin_urlname:'delete' original.pk|admin_urlquote as delete_url %}
-        <p class="deletelink-box"><a href="{% add_preserved_filters delete_url %}" class="deletelink button button-red">{% trans "Delete" %}</a></p>
+        <p class="deletelink-box"><a href="{% jet_add_preserved_filters delete_url %}" class="deletelink button button-red">{% trans "Delete" %}</a></p>
     {% endif %}
 </div>

+ 10 - 10
jet/templatetags/jet_tags.py

@@ -1,16 +1,12 @@
-from distutils.version import StrictVersion
+from __future__ import unicode_literals
 from django import template
-import django
-from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
 from django.core.urlresolvers import reverse
 from django.db.models import OneToOneField
 from django.forms import CheckboxInput, ModelChoiceField, Select, ModelMultipleChoiceField, SelectMultiple
-from django.utils.encoding import smart_text
 from django.utils.formats import get_format
 from django.template import loader, Context
 from jet import settings
 from jet.models import Bookmark, PinnedApplication
-from django.utils.translation import ugettext_lazy as _
 import re
 from jet.utils import get_app_list, get_model_instance_label, get_current_dashboard
 
@@ -48,7 +44,7 @@ class FormatBreadcrumbsNode(template.Node):
 
         regex = re.compile('<[^!(a>)]([^>]|\n)*[^!(/a)]>', re.IGNORECASE)
         clean = re.sub(regex, '', output)
-        clean = clean.replace(u'\u203A', '&rsaquo;')
+        clean = clean.replace('\u203A', '&rsaquo;')
         items = clean.split('&rsaquo;')
 
         items = map(lambda i: i.strip(), items)
@@ -150,7 +146,7 @@ def get_menu(context):
                 app['current'] = True
                 current_found = True
 
-        if app['app_label'] in pinned:
+        if app.get('app_label', app.get('name')) in pinned:
             pinned_apps.append(app)
         else:
             apps.append(app)
@@ -213,9 +209,13 @@ def select2_lookups(field):
 @register.simple_tag(takes_context=True)
 def jet_add_preserved_filters(context, url, popup=False, to_field=None):
     try:
-        return add_preserved_filters(context, url, popup, to_field)
-    except TypeError:
-        return add_preserved_filters(context, url, popup)  # old django
+        from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
+        try:
+            return add_preserved_filters(context, url, popup, to_field)
+        except TypeError:
+            return add_preserved_filters(context, url, popup)  # old django
+    except ImportError:
+        return url
 
 
 @register.assignment_tag(takes_context=True)

+ 0 - 3
jet/tests.py

@@ -1,3 +0,0 @@
-from django.test import TestCase
-
-# Create your tests here.

+ 3 - 0
jet/tests/__init__.py

@@ -0,0 +1,3 @@
+from jet.tests.test_dashboard import DashboardTestCase
+from jet.tests.test_utils import UtilsTestCase
+from jet.tests.test_views import ViewsTestCase

+ 33 - 0
jet/tests/dashboard.py

@@ -0,0 +1,33 @@
+from jet import modules
+from jet.dashboard import Dashboard
+
+
+class TestIndexDashboard(Dashboard):
+    columns = 3
+    init_with_context_called = False
+
+    class Media:
+        js = ('file.js', 'file2.js')
+        css = ('file.css', 'file2.css')
+
+    def init_with_context(self, context):
+        self.init_with_context_called = True
+        self.available_children.append(modules.LinkList)
+        self.available_children.append(modules.Feed)
+
+        # append a recent actions module
+        self.children.append(modules.RecentActions(
+            'Recent Actions',
+            10,
+            column=0,
+            order=1
+        ))
+
+        # append a feed module
+        self.children.append(modules.Feed(
+            'Latest Django News',
+            feed_url='http://www.djangoproject.com/rss/weblog/',
+            limit=5,
+            column=1,
+            order=1
+        ))

+ 11 - 0
jet/tests/models.py

@@ -0,0 +1,11 @@
+from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+
+
+@python_2_unicode_compatible
+class TestModel(models.Model):
+    field1 = models.CharField(max_length=255)
+    field2 = models.IntegerField()
+
+    def __str__(self):
+        return '%s%d' % (self.field1, self.field2)

+ 52 - 0
jet/tests/settings.py

@@ -0,0 +1,52 @@
+from django.conf import global_settings
+
+SECRET_KEY = '!DJANGO_JET_TESTS!'
+
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+DEBUG_PROPAGATE_EXCEPTIONS = True
+
+ROOT_URLCONF = 'jet.tests.urls'
+
+INSTALLED_APPS = (
+    'jet',
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.sites',
+    'django.contrib.messages',
+    'django.contrib.admin',
+    'jet.tests',
+)
+
+MIDDLEWARE_CLASSES = (
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.middleware.common.CommonMiddleware',
+    'django.middleware.csrf.CsrfViewMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django.contrib.messages.middleware.MessageMiddleware',
+)
+
+TEMPLATE_CONTEXT_PROCESSORS = global_settings.TEMPLATE_CONTEXT_PROCESSORS + (
+    'django.core.context_processors.request',
+)
+
+DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.sqlite3',
+    }
+}
+
+TIME_ZONE = 'UTC'
+LANGUAGE_CODE = 'en-US'
+USE_I18N = True
+USE_L10N = True
+
+MEDIA_ROOT = ''
+MEDIA_URL = ''
+
+STATIC_URL = '/static/'
+
+
+
+

+ 65 - 0
jet/tests/test_dashboard.py

@@ -0,0 +1,65 @@
+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.tests.dashboard import TestIndexDashboard
+
+
+class DashboardTestCase(TestCase):
+    class Request:
+        def __init__(self, user):
+            self.user = user
+
+    def setUp(self):
+        self._login()
+        self._init_dashboard()
+
+    def _login(self):
+        username = 'admin'
+        email = 'admin@example.com'
+        password = 'admin'
+        self.admin = Client()
+        self.admin_user = User.objects.create_superuser(username, email, password)
+        return self.admin.login(username=username, password=password)
+
+    def _init_dashboard(self):
+        UserDashboardModule.objects.create(
+            title='',
+            module='jet.modules.LinkList',
+            app_label=None,
+            user=self.admin_user.pk,
+            column=0,
+            order=0
+        )
+        UserDashboardModule.objects.create(
+            title='',
+            module='jet.modules.RecentActions',
+            app_label=None,
+            user=self.admin_user.pk,
+            column=0,
+            order=1
+        )
+        self.dashboard = TestIndexDashboard({'request': self.Request(self.admin_user)})
+
+    def test_custom_columns(self):
+        self.assertEqual(self.dashboard.columns, 3)
+
+    def test_init_with_context_called(self):
+        self.assertTrue(self.dashboard.init_with_context_called)
+
+    def test_load_modules(self):
+        self.assertEqual(len(self.dashboard.modules), 2)
+        self.assertTrue(isinstance(self.dashboard.modules[0], LinkList))
+        self.assertTrue(isinstance(self.dashboard.modules[1], RecentActions))
+
+    def test_media(self):
+        media = self.dashboard.media()
+        self.assertEqual(len(media.js), 2)
+        self.assertEqual(media.js[0], 'file.js')
+        self.assertEqual(media.js[1], 'file2.js')
+        self.assertEqual(len(media.css), 2)
+        self.assertEqual(media.css[0], 'file.css')
+        self.assertEqual(media.css[1], 'file2.css')
+
+
+

+ 22 - 0
jet/tests/test_utils.py

@@ -0,0 +1,22 @@
+import json
+from django.test import TestCase
+from jet.tests.models import TestModel
+from jet.utils import JsonResponse, get_model_instance_label
+
+
+class UtilsTestCase(TestCase):
+    def test_json_response(self):
+        response = JsonResponse({'str': 'string', 'int': 1})
+        response_dict = json.loads(response.content.decode())
+        expected_dict = {"int": 1, "str": "string"}
+        self.assertEqual(response_dict, expected_dict)
+        self.assertEqual(response.get('Content-Type'), 'application/json')
+
+    def test_get_model_instance_label(self):
+        field1 = 'value'
+        field2 = 2
+        pinned_application = TestModel.objects.create(field1=field1, field2=field2)
+        self.assertEqual(get_model_instance_label(pinned_application), '%s%d' % (field1, field2))
+
+
+

+ 213 - 0
jet/tests/test_views.py

@@ -0,0 +1,213 @@
+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
+
+
+class ViewsTestCase(TestCase):
+    def setUp(self):
+        self.assertTrue(self._login())
+
+    def _login(self):
+        username = 'admin'
+        email = 'admin@example.com'
+        password = 'admin'
+        self.admin = Client()
+        self.admin_user = User.objects.create_superuser(username, email, password)
+        return self.admin.login(username=username, password=password)
+
+    def test_module_update_view(self):
+        title = 'Quick links Test'
+        new_title = title + '2'
+        new_layout = 'stacked'
+        module = UserDashboardModule.objects.create(
+            title=title,
+            module='jet.modules.LinkList',
+            app_label=None,
+            user=self.admin_user.pk,
+            column=0,
+            order=0,
+            settings='{"layout": "inline"}',
+            children='[]'
+        )
+
+        response = self.admin.get(reverse('jet: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))
+        self.assertEqual(response.context['module'].title, title)
+
+        post = {
+            'title': new_title,
+            'layout': new_layout,
+            'children-TOTAL_FORMS': '2',
+            'children-INITIAL_FORMS': '1',
+            'children-MIN_NUM_FORMS': '0',
+            'children-MAX_NUM_FORMS': '1000',
+            'children-0-url': 'http://docs.djangoproject.com/',
+            'children-0-title': 'Django documentation',
+            'children-0-external': 'on',
+            'children-1-url': '',
+            'children-1-title': '',
+            'children-__prefix__-url': '',
+            'children-__prefix__-title': '',
+            '_save': 'Save'
+        }
+
+        self.admin.post(reverse('jet: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)
+        self.assertEqual(module.title, new_title)
+        self.assertEqual(settings['layout'], new_layout)
+
+        module.delete()
+
+    def test_add_bookmark_view(self):
+        url = 'http://test.com/'
+        title = 'Title'
+        response = self.admin.post(reverse('jet:add_bookmark'), {'url': url, 'title': title})
+        self.assertEqual(response.status_code, 200)
+        response = json.loads(response.content.decode())
+        self.assertFalse(response['error'])
+        self.assertNotEqual(response['id'], None)
+        bookmark = Bookmark.objects.get(pk=response['id'])
+        self.assertNotEqual(bookmark, None)
+        self.assertEqual(bookmark.title, title)
+        self.assertEqual(bookmark.url, url)
+
+    def test_add_bookmark_view_unauthorized(self):
+        url = 'http://test.com/'
+        title = 'Title'
+        response = self.client.post(reverse('jet:add_bookmark'), {'url': url, 'title': title})
+        self.assertEqual(response.status_code, 200)
+        response = json.loads(response.content.decode())
+        self.assertTrue(response['error'])
+
+    def test_remove_bookmark_view(self):
+        url = 'http://test.com/'
+        title = 'Title'
+        bookmark = Bookmark.objects.create(url=url, title=title, user=self.admin_user.pk)
+        response = self.admin.post(reverse('jet:remove_bookmark'), {'id': bookmark.id})
+        self.assertEqual(response.status_code, 200)
+        response = json.loads(response.content.decode())
+        self.assertFalse(response['error'])
+        self.assertFalse(Bookmark.objects.filter(pk=bookmark.pk).exists())
+
+    def test_toggle_application_pin_view(self):
+        app_label = 'test_app'
+
+        response = self.admin.post(reverse('jet:toggle_application_pin'), {'app_label': app_label})
+        self.assertEqual(response.status_code, 200)
+        response = json.loads(response.content.decode())
+        self.assertFalse(response['error'])
+        self.assertTrue(response['pinned'])
+
+        response = self.admin.post(reverse('jet:toggle_application_pin'), {'app_label': app_label})
+        self.assertEqual(response.status_code, 200)
+        response = json.loads(response.content.decode())
+        self.assertFalse(response['error'])
+        self.assertFalse(response['pinned'])
+
+    def test_update_dashboard_modules_view(self):
+        app_label = None
+        module_0 = UserDashboardModule.objects.create(
+            title='',
+            module='jet.modules.LinkList',
+            app_label=app_label,
+            user=self.admin_user.pk,
+            column=0,
+            order=0
+        )
+        module_1 = UserDashboardModule.objects.create(
+            title='',
+            module='jet.modules.LinkList',
+            app_label=app_label,
+            user=self.admin_user.pk,
+            column=0,
+            order=1
+        )
+        response = self.admin.post(reverse('jet:update_dashboard_modules'), {
+            'app_label': '',
+            'modules': json.dumps([
+                {'id': module_0.pk, 'column': 0, 'order': 1},
+                {'id': module_1.pk, 'column': 0, 'order': 0}
+            ])
+        })
+
+        self.assertEqual(response.status_code, 200)
+        response = json.loads(response.content.decode())
+        self.assertFalse(response['error'])
+
+        module_0 = UserDashboardModule.objects.get(pk=module_0.pk)
+        module_1 = UserDashboardModule.objects.get(pk=module_1.pk)
+
+        self.assertEqual(module_0.order, 1)
+        self.assertEqual(module_1.order, 0)
+
+        module_0.delete()
+        module_1.delete()
+
+    def test_add_user_dashboard_module_view(self):
+        response = self.admin.post(reverse('jet:add_user_dashboard_module'), {
+            'app_label': '',
+            'type': 'available_children',
+            'module': 0
+        })
+
+        self.assertEqual(response.status_code, 200)
+        response = json.loads(response.content.decode())
+        self.assertFalse(response['error'])
+        self.assertNotEqual(response['id'], None)
+        module = UserDashboardModule.objects.get(pk=response['id'])
+        self.assertNotEqual(module, None)
+
+    def test_update_dashboard_module_collapse_view(self):
+        module = UserDashboardModule.objects.create(
+            title='',
+            module='jet.modules.LinkList',
+            app_label=None,
+            user=self.admin_user.pk,
+            column=0,
+            order=0
+        )
+        response = self.admin.post(reverse('jet:update_dashboard_module_collapse'), {
+            'id': module.pk, 'collapsed': True
+        })
+        self.assertEqual(response.status_code, 200)
+        response = json.loads(response.content.decode())
+        self.assertFalse(response['error'])
+        self.assertTrue(response['collapsed'])
+
+        module = UserDashboardModule.objects.get(pk=module.pk)
+        response = self.admin.post(reverse('jet:update_dashboard_module_collapse'), {
+            'id': module.pk, 'collapsed': False
+        })
+        self.assertEqual(response.status_code, 200)
+        response = json.loads(response.content.decode())
+        self.assertFalse(response['error'])
+        self.assertFalse(response['collapsed'])
+
+        module.delete()
+
+    def test_remove_dashboard_module_view(self):
+        module = UserDashboardModule.objects.create(
+            title='',
+            module='jet.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})
+        self.assertEqual(response.status_code, 200)
+        response = json.loads(response.content.decode())
+        self.assertFalse(response['error'])
+        self.assertFalse(UserDashboardModule.objects.filter(pk=module.pk).exists())
+
+
+
+
+

+ 11 - 0
jet/tests/urls.py

@@ -0,0 +1,11 @@
+from django.conf.urls import patterns, include, url
+from django.contrib import admin
+
+admin.autodiscover()
+
+urlpatterns = patterns(
+    '',
+    url(r'^jet/', include('jet.urls', 'jet')),
+    url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
+    url(r'^admin/', include(admin.site.urls)),
+)

+ 18 - 0
jet/utils.py

@@ -9,6 +9,7 @@ from django.utils.encoding import force_text
 from django.utils.encoding import smart_text
 from django.utils.functional import Promise
 from jet import settings
+from django.contrib import messages
 
 
 class JsonResponse(HttpResponse):
@@ -83,3 +84,20 @@ def get_model_instance_label(instance):
     if getattr(instance, "related_label", None):
         return instance.related_label()
     return smart_text(instance)
+
+
+class SuccessMessageMixin(object):
+    """
+    Adds a success message on successful form submission.
+    """
+    success_message = ''
+
+    def form_valid(self, form):
+        response = super(SuccessMessageMixin, self).form_valid(form)
+        success_message = self.get_success_message(form.cleaned_data)
+        if success_message:
+            messages.success(self.request, success_message)
+        return response
+
+    def get_success_message(self, cleaned_data):
+        return self.success_message % cleaned_data

+ 5 - 5
jet/views.py

@@ -1,12 +1,11 @@
 from django.contrib import messages
-from django.contrib.messages.views import SuccessMessageMixin
 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
+from jet.utils import JsonResponse, get_app_list, SuccessMessageMixin
 from django.views.generic import UpdateView
 from django.utils.translation import ugettext_lazy as _
 
@@ -75,7 +74,7 @@ class UpdateDashboardModuleView(SuccessMessageMixin, UpdateView):
         app_list = get_app_list({'request': self.request})
 
         for app in app_list:
-            if app['app_label'] == self.object.app_label:
+            if app.get('app_label', app.get('name')) == self.object.app_label:
                 return app
 
     def get_context_data(self, **kwargs):
@@ -105,14 +104,14 @@ class UpdateDashboardModuleView(SuccessMessageMixin, UpdateView):
                 settings = settings_form.cleaned_data
                 data['settings'] = self.module.dump_settings(settings)
             else:
-                return self.form_invalid(self.get_form())
+                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())
+                return self.form_invalid(self.get_form(self.get_form_class()))
 
         request.POST = data
 
@@ -192,6 +191,7 @@ def add_user_dashboard_module_view(request):
 
     if form.is_valid():
         module = form.save()
+        result['id'] = module.pk
         messages.success(request, _('Widget has been successfully added'))
 
         if module.app_label:

+ 10 - 0
manage.py

@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+import os
+import sys
+
+if __name__ == "__main__":
+    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "jet.tests.settings")
+
+    from django.core.management import execute_from_command_line
+
+    execute_from_command_line(sys.argv)

+ 1 - 1
setup.py

@@ -12,7 +12,7 @@ setup(
     long_description=read('README.rst'),
     author='Denis Kildishev',
     author_email='support@jet.geex-arts.com',
-    url='https://github.com/geex-arts/jet',
+    url='https://github.com/geex-arts/django-jet',
     packages=find_packages(),
     license='Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Public License',
     classifiers=[