Parcourir la source

Merge branch 'dev'

Denis K il y a 8 ans
Parent
commit
c7ef82dfaf
45 fichiers modifiés avec 677 ajouts et 113 suppressions
  1. 7 0
      .travis.yml
  2. 12 0
      CHANGELOG.rst
  3. 2 6
      README.rst
  4. 0 4
      docs/index.rst
  5. 1 1
      jet/__init__.py
  6. 4 1
      jet/dashboard/dashboard.py
  7. 2 0
      jet/dashboard/templates/jet.dashboard/module.html
  8. 6 3
      jet/dashboard/urls.py
  9. 16 7
      jet/filters.py
  10. 3 0
      jet/settings.py
  11. 27 1
      jet/static/admin/js/admin/RelatedObjectLookups.js
  12. 0 0
      jet/static/admin/js/admin/RelatedObjectLookups.min.js
  13. 0 0
      jet/static/admin/js/inlines.min.js
  14. 0 25
      jet/static/admin/js/related-widget-wrapper.js
  15. 1 0
      jet/static/admin/js/related-widget-wrapper.min.js
  16. 5 0
      jet/static/jet/css/_changeform.scss
  17. 39 1
      jet/static/jet/css/_forms.scss
  18. 7 0
      jet/static/jet/css/_top.scss
  19. 35 2
      jet/static/jet/css/themes/default/base.css
  20. 0 0
      jet/static/jet/css/themes/default/base.css.map
  21. 35 2
      jet/static/jet/css/themes/green/base.css
  22. 0 0
      jet/static/jet/css/themes/green/base.css.map
  23. 35 2
      jet/static/jet/css/themes/light-blue/base.css
  24. 0 0
      jet/static/jet/css/themes/light-blue/base.css.map
  25. 35 2
      jet/static/jet/css/themes/light-gray/base.css
  26. 0 0
      jet/static/jet/css/themes/light-gray/base.css.map
  27. 35 2
      jet/static/jet/css/themes/light-green/base.css
  28. 0 0
      jet/static/jet/css/themes/light-green/base.css.map
  29. 35 2
      jet/static/jet/css/themes/light-violet/base.css
  30. 0 0
      jet/static/jet/css/themes/light-violet/base.css.map
  31. 29 11
      jet/static/jet/js/main.js
  32. 0 0
      jet/static/jet/js/main.min.js
  33. 1 0
      jet/templates/admin/base.html
  34. 44 20
      jet/templates/admin/change_form.html
  35. 2 2
      jet/templates/registration/password_change_form.html
  36. 62 2
      jet/templatetags/jet_tags.py
  37. 0 3
      jet/tests/__init__.py
  38. 13 0
      jet/tests/admin.py
  39. 8 0
      jet/tests/models.py
  40. 21 3
      jet/tests/settings.py
  41. 54 0
      jet/tests/test_filters.py
  42. 36 4
      jet/tests/test_tags.py
  43. 6 3
      jet/tests/urls.py
  44. 6 3
      jet/urls.py
  45. 53 1
      jet/utils.py

+ 7 - 0
.travis.yml

@@ -10,6 +10,7 @@ env:
   - DJANGO=1.6.11
   - DJANGO=1.7.7
   - DJANGO=1.8.3
+  - DJANGO=1.9.8
 before_install:
   - export DJANGO_SETTINGS_MODULE=jet.tests.settings
 install:
@@ -28,3 +29,9 @@ matrix:
       env: DJANGO=1.7.7
     - python: 2.6
       env: DJANGO=1.8.3
+    - python: 2.6
+      env: DJANGO=1.9.8
+    - python: 3.2
+      env: DJANGO=1.9.8
+    - python: 3.3
+      env: DJANGO=1.9.8

+ 12 - 0
CHANGELOG.rst

@@ -1,6 +1,18 @@
 Changelog
 =========
 
+0.1.5
+-----
+* Add inlines.min.js
+* Specify IE compatibility version
+* Add previous/next buttons to change form
+* Add preserving filters when returning to changelist
+* Add opened tab remembering
+* Fix breadcrumbs text overflow
+* PR-65: Fixed Django 1.8+ compatibility issues (thanks to hanuprateek, SalahAdDin, cdrx for pull requests)
+* PR-73: Added missing safe template tag on the change password page (thanks to JensAstrup for pull request)
+
+
 0.1.4
 -----
 * [Feature] Side bar compact mode (lists all models without opening second menu)

+ 2 - 6
README.rst

@@ -9,8 +9,8 @@ Django JET
 
 Django JET has two kinds of licenses: open-source (GPLv2) and commercial. Please note that using GPLv2
 code in your programs make them GPL too. So if you don't want to comply with that we can provide you a commercial
-license (in this case please email at support@jet.geex-arts.com). The commercial license
-is designed for using Django JET in commercial products and applications without the provisions of the GPLv2.
+license (visit Home page). The commercial license is designed for using Django JET in commercial products
+and applications without the provisions of the GPLv2.
 
 .. image:: https://raw.githubusercontent.com/geex-arts/jet/static/logo.png
     :width: 500px
@@ -43,10 +43,6 @@ Screenshots
     :align: center
     :target: https://raw.githubusercontent.com/geex-arts/jet/static/screen3.png
 
-Beta
-====
-Current version is still in beta phase. Use it at your own risk (though may be already enough workable).
-
 License
 =======
 Django JET is licensed under a

+ 0 - 4
docs/index.rst

@@ -12,10 +12,6 @@ About
     :height: 500px
     :scale: 50%
 
-Beta
-====
-Current version is still in beta phase. Use it at your own risk (though may be already enough workable).
-
 Getting started
 ===============
 

+ 1 - 1
jet/__init__.py

@@ -1 +1 @@
-VERSION = '0.1.4'
+VERSION = '0.1.5'

+ 4 - 1
jet/dashboard/dashboard.py

@@ -3,10 +3,13 @@ from django.core.urlresolvers import reverse
 from django.template.loader import render_to_string
 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
 from jet.utils import get_admin_site_name
+try:
+    from django.template.context_processors import csrf
+except ImportError:
+    from django.core.context_processors import csrf
 
 
 class Dashboard(object):

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

@@ -26,6 +26,7 @@
     </div>
 
     <div class="dashboard-item-content{% if module.contrast %} contrast{% endif %}"{% if module.style %} style="{{ module.style }}"{% endif %}>
+        {{ module.pre_contenta|default_if_none:"" }}
         {% if module.ajax_load %}
             <div class="loading-indicator-wrapper">
                 <span class="icon-refresh loading-indicator"></span>
@@ -33,5 +34,6 @@
         {% else %}
             {{ module.render }}
         {% endif %}
+        {{ module.post_content|default_if_none:"" }}
     </div>
 </div>

+ 6 - 3
jet/dashboard/urls.py

@@ -1,3 +1,4 @@
+import django
 from django.conf.urls import patterns, url
 from django.views.i18n import javascript_catalog
 from jet.dashboard import dashboard
@@ -5,8 +6,7 @@ from jet.dashboard.views import update_dashboard_modules_view, add_user_dashboar
     update_dashboard_module_collapse_view, remove_dashboard_module_view, UpdateDashboardModuleView, \
     load_dashboard_module_view, reset_dashboard_view
 
-urlpatterns = patterns(
-    '',
+urlpatterns = [
     url(
         r'^module/(?P<pk>\d+)/$',
         UpdateDashboardModuleView.as_view(),
@@ -48,6 +48,9 @@ urlpatterns = patterns(
         {'packages': ('jet',)},
         name='jsi18n'
     ),
-)
+]
 
 urlpatterns += dashboard.urls.get_urls()
+
+if django.VERSION[:2] < (1, 8):
+    urlpatterns = patterns('', *urlpatterns)

+ 16 - 7
jet/filters.py

@@ -1,9 +1,17 @@
 from django.contrib.admin import RelatedFieldListFilter
-from django.contrib.admin.utils import get_model_from_relation
-from django.core.urlresolvers import reverse
-from django.forms.utils import flatatt
 from django.utils.encoding import smart_text
 from django.utils.html import format_html
+from django.core.urlresolvers import reverse
+
+try:
+    from django.contrib.admin.utils import get_model_from_relation
+except ImportError: # Django 1.6
+    from django.contrib.admin.util import get_model_from_relation
+
+try:
+    from django.forms.utils import flatatt
+except ImportError: # Django 1.6
+    from django.forms.util import flatatt
 
 
 class RelatedFieldAjaxListFilter(RelatedFieldListFilter):
@@ -13,10 +21,11 @@ class RelatedFieldAjaxListFilter(RelatedFieldListFilter):
         return True
 
     def field_choices(self, field, request, model_admin):
-        app_label = field.related_model._meta.app_label
-        model_name = field.related_model._meta.object_name
+        model = field.remote_field.model if hasattr(field, 'remote_field') else field.related_field.model
+        app_label = model._meta.app_label
+        model_name = model._meta.object_name
 
-        self.ajax_attrs = format_html('{}', flatatt({
+        self.ajax_attrs = format_html('{0}', flatatt({
             'data-app-label': app_label,
             'data-model': model_name,
             'data-ajax--url': reverse('jet:model_lookup'),
@@ -32,5 +41,5 @@ class RelatedFieldAjaxListFilter(RelatedFieldListFilter):
         else:
             rel_name = other_model._meta.pk.name
 
-        queryset = field.related_model._default_manager.filter(**{rel_name: self.lookup_val}).all()
+        queryset = model._default_manager.filter(**{rel_name: self.lookup_val}).all()
         return [(x._get_pk_val(), smart_text(x)) for x in queryset]

+ 3 - 0
jet/settings.py

@@ -7,3 +7,6 @@ JET_THEMES = getattr(settings, 'JET_THEMES', [])
 # Side menu
 JET_SIDE_MENU_COMPACT = getattr(settings, 'JET_SIDE_MENU_COMPACT', False)
 JET_SIDE_MENU_CUSTOM_APPS = getattr(settings, 'JET_SIDE_MENU_CUSTOM_APPS', None)
+
+# Improved usability
+JET_CHANGE_FORM_SIBLING_LINKS = getattr(settings, 'JET_CHANGE_FORM_SIBLING_LINKS', True)

+ 27 - 1
jet/static/admin/js/admin/RelatedObjectLookups.js

@@ -175,4 +175,30 @@ function showRelatedPopup(name, href) {
 function closeRelatedPopup(win) {
     jet.jQuery('select').trigger('select:init');
     jet.jQuery(win.parent).trigger('related-popup:close');
-}
+}
+
+django.jQuery(document).ready(function() {
+    jet.jQuery(function($){
+        function updateLinks() {
+            var $this = $(this);
+            var siblings = $this.nextAll('.change-related, .delete-related');
+            if (!siblings.length) return;
+            var value = $this.val();
+            if (value) {
+                siblings.each(function(){
+                    var elm = $(this);
+                    elm.attr('href', elm.attr('data-href-template').replace('__fk__', value));
+                });
+            } else siblings.removeAttr('href');
+        }
+        var container = $(document);
+        container.on('change', '.related-widget-wrapper select', updateLinks);
+        container.find('.related-widget-wrapper select').each(updateLinks);
+        container.on('click', '.related-widget-wrapper-link', function(event){
+            if (this.href) {
+                showRelatedObjectPopup(this);
+            }
+            event.preventDefault();
+        });
+    });
+});

Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
jet/static/admin/js/admin/RelatedObjectLookups.min.js


Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
jet/static/admin/js/inlines.min.js


+ 0 - 25
jet/static/admin/js/related-widget-wrapper.js

@@ -1,25 +0,0 @@
-django.jQuery(document).ready(function() {
-    jet.jQuery(function($){
-        function updateLinks() {
-            var $this = $(this);
-            var siblings = $this.nextAll('.change-related, .delete-related');
-            if (!siblings.length) return;
-            var value = $this.val();
-            if (value) {
-                siblings.each(function(){
-                    var elm = $(this);
-                    elm.attr('href', elm.attr('data-href-template').replace('__fk__', value));
-                });
-            } else siblings.removeAttr('href');
-        }
-        var container = $(document);
-        container.on('change', '.related-widget-wrapper select', updateLinks);
-        container.find('.related-widget-wrapper select').each(updateLinks);
-        container.on('click', '.related-widget-wrapper-link', function(event){
-            if (this.href) {
-                showRelatedObjectPopup(this);
-            }
-            event.preventDefault();
-        });
-    });
-});

+ 1 - 0
jet/static/admin/js/related-widget-wrapper.min.js

@@ -0,0 +1 @@
+

+ 5 - 0
jet/static/jet/css/_changeform.scss

@@ -138,6 +138,11 @@
   border-radius: 4px;
   min-width: 800px;
 
+  &-navigation {
+    float: left;
+    margin-bottom: 20px;
+  }
+
   &-tabs {
     @extend .clear-list;
     border-bottom: 2px solid $background-color;

+ 39 - 1
jet/static/jet/css/_forms.scss

@@ -83,7 +83,45 @@
   }
 }
 
-a.button {
+.segmented-button {
+  &, &:visited, &:hover {
+    @extend .base_input;
+    font-size: 12px;
+    text-align: center;
+    background-color: $button-background-color;
+    color: $button-text-color;
+    padding: 0 10px;
+    display: inline-block;
+    text-transform: none;
+    border-radius: 0;
+  }
+
+  &:hover {
+    background-color: $button-hover-background-color;
+    color: $button-hover-text-color;
+  }
+
+  &:active {
+    background-color: $button-active-background-color;
+    color: $button-active-text-color;
+  }
+
+  &.disabled {
+    background-color: $button-background-color !important;
+    color: $button-text-color;
+    opacity: 0.5;
+  }
+
+  &.left {
+    border-radius: 4px 0 0 4px;
+  }
+
+  &.right {
+    border-radius: 0 4px 4px 0;
+  }
+}
+
+a.button, a.segmented-button {
   line-height: 32px;
 }
 

+ 7 - 0
jet/static/jet/css/_top.scss

@@ -1,5 +1,12 @@
 @import "globals";
 
+.breadcrumbs {
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  padding-right: 200px;
+  overflow: hidden;
+}
+
 .top {
   padding: 20px;
 

+ 35 - 2
jet/static/jet/css/themes/default/base.css

@@ -1955,6 +1955,12 @@ a:hover {
 .fill_width {
   width: 100% !important; }
 
+.breadcrumbs {
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  padding-right: 200px;
+  overflow: hidden; }
+
 .top {
   padding: 20px; }
   .top-breadcrumbs {
@@ -2362,7 +2368,7 @@ a:hover {
 .fill_width {
   width: 100% !important; }
 
-.base_input, .sidebar-popup-search, .input, .background-form .input, input[type="text"], input[type="email"], input[type="password"], input[type="url"], input[type="number"], textarea, .button, input[type="submit"], input[type="button"], .button:visited, input[type="submit"]:visited, input[type="button"]:visited, .button:hover, input[type="submit"]:hover, input[type="button"]:hover {
+.base_input, .sidebar-popup-search, .input, .background-form .input, input[type="text"], input[type="email"], input[type="password"], input[type="url"], input[type="number"], textarea, .button, input[type="submit"], input[type="button"], .button:visited, input[type="submit"]:visited, input[type="button"]:visited, .button:hover, input[type="submit"]:hover, input[type="button"]:hover, .segmented-button, .segmented-button:visited, .segmented-button:hover {
   border: 0;
   border-radius: 4px;
   font-size: 13px;
@@ -2430,7 +2436,31 @@ a:hover {
   background-color: #6f7e95;
   color: #fff; }
 
-a.button {
+.segmented-button, .segmented-button:visited, .segmented-button:hover {
+  font-size: 12px;
+  text-align: center;
+  background-color: #d0dbe6;
+  color: #6f7e95;
+  padding: 0 10px;
+  display: inline-block;
+  text-transform: none;
+  border-radius: 0; }
+.segmented-button:hover {
+  background-color: #639af5;
+  color: #fff; }
+.segmented-button:active {
+  background-color: #6f7e95;
+  color: #fff; }
+.segmented-button.disabled {
+  background-color: #d0dbe6 !important;
+  color: #6f7e95;
+  opacity: 0.5; }
+.segmented-button.left {
+  border-radius: 4px 0 0 4px; }
+.segmented-button.right {
+  border-radius: 0 4px 4px 0; }
+
+a.button, a.segmented-button {
   line-height: 32px; }
 
 textarea {
@@ -3559,6 +3589,9 @@ table#change-history {
   background-color: #fff;
   border-radius: 4px;
   min-width: 800px; }
+  .changeform-navigation {
+    float: left;
+    margin-bottom: 20px; }
   .changeform-tabs {
     border-bottom: 2px solid #ecf2f6;
     padding-left: 16px !important; }

Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
jet/static/jet/css/themes/default/base.css.map


+ 35 - 2
jet/static/jet/css/themes/green/base.css

@@ -1986,6 +1986,12 @@ a:hover {
 .fill_width {
   width: 100% !important; }
 
+.breadcrumbs {
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  padding-right: 200px;
+  overflow: hidden; }
+
 .top {
   padding: 20px; }
   .top-breadcrumbs {
@@ -2393,7 +2399,7 @@ a:hover {
 .fill_width {
   width: 100% !important; }
 
-.base_input, .sidebar-popup-search, .input, .background-form .input, input[type="text"], input[type="email"], input[type="password"], input[type="url"], input[type="number"], textarea, .button, input[type="submit"], input[type="button"], .button:visited, input[type="submit"]:visited, input[type="button"]:visited, .button:hover, input[type="submit"]:hover, input[type="button"]:hover {
+.base_input, .sidebar-popup-search, .input, .background-form .input, input[type="text"], input[type="email"], input[type="password"], input[type="url"], input[type="number"], textarea, .button, input[type="submit"], input[type="button"], .button:visited, input[type="submit"]:visited, input[type="button"]:visited, .button:hover, input[type="submit"]:hover, input[type="button"]:hover, .segmented-button, .segmented-button:visited, .segmented-button:hover {
   border: 0;
   border-radius: 4px;
   font-size: 13px;
@@ -2461,7 +2467,31 @@ a:hover {
   background-color: #62a29c;
   color: #fff; }
 
-a.button {
+.segmented-button, .segmented-button:visited, .segmented-button:hover {
+  font-size: 12px;
+  text-align: center;
+  background-color: #cceae4;
+  color: #62a29c;
+  padding: 0 10px;
+  display: inline-block;
+  text-transform: none;
+  border-radius: 0; }
+.segmented-button:hover {
+  background-color: #7FB1DC;
+  color: #fff; }
+.segmented-button:active {
+  background-color: #62a29c;
+  color: #fff; }
+.segmented-button.disabled {
+  background-color: #cceae4 !important;
+  color: #62a29c;
+  opacity: 0.5; }
+.segmented-button.left {
+  border-radius: 4px 0 0 4px; }
+.segmented-button.right {
+  border-radius: 0 4px 4px 0; }
+
+a.button, a.segmented-button {
   line-height: 32px; }
 
 textarea {
@@ -3590,6 +3620,9 @@ table#change-history {
   background-color: #fff;
   border-radius: 4px;
   min-width: 800px; }
+  .changeform-navigation {
+    float: left;
+    margin-bottom: 20px; }
   .changeform-tabs {
     border-bottom: 2px solid #eff6f5;
     padding-left: 16px !important; }

Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
jet/static/jet/css/themes/green/base.css.map


+ 35 - 2
jet/static/jet/css/themes/light-blue/base.css

@@ -1986,6 +1986,12 @@ a:hover {
 .fill_width {
   width: 100% !important; }
 
+.breadcrumbs {
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  padding-right: 200px;
+  overflow: hidden; }
+
 .top {
   padding: 20px; }
   .top-breadcrumbs {
@@ -2393,7 +2399,7 @@ a:hover {
 .fill_width {
   width: 100% !important; }
 
-.base_input, .sidebar-popup-search, .input, .background-form .input, input[type="text"], input[type="email"], input[type="password"], input[type="url"], input[type="number"], textarea, .button, input[type="submit"], input[type="button"], .button:visited, input[type="submit"]:visited, input[type="button"]:visited, .button:hover, input[type="submit"]:hover, input[type="button"]:hover {
+.base_input, .sidebar-popup-search, .input, .background-form .input, input[type="text"], input[type="email"], input[type="password"], input[type="url"], input[type="number"], textarea, .button, input[type="submit"], input[type="button"], .button:visited, input[type="submit"]:visited, input[type="button"]:visited, .button:hover, input[type="submit"]:hover, input[type="button"]:hover, .segmented-button, .segmented-button:visited, .segmented-button:hover {
   border: 0;
   border-radius: 4px;
   font-size: 13px;
@@ -2461,7 +2467,31 @@ a:hover {
   background-color: #5EADDE;
   color: #fff; }
 
-a.button {
+.segmented-button, .segmented-button:visited, .segmented-button:hover {
+  font-size: 12px;
+  text-align: center;
+  background-color: #E3ECF2;
+  color: #7f8fa4;
+  padding: 0 10px;
+  display: inline-block;
+  text-transform: none;
+  border-radius: 0; }
+.segmented-button:hover {
+  background-color: #1cacfc;
+  color: #fff; }
+.segmented-button:active {
+  background-color: #5EADDE;
+  color: #fff; }
+.segmented-button.disabled {
+  background-color: #E3ECF2 !important;
+  color: #7f8fa4;
+  opacity: 0.5; }
+.segmented-button.left {
+  border-radius: 4px 0 0 4px; }
+.segmented-button.right {
+  border-radius: 0 4px 4px 0; }
+
+a.button, a.segmented-button {
   line-height: 32px; }
 
 textarea {
@@ -3590,6 +3620,9 @@ table#change-history {
   background-color: #fff;
   border-radius: 4px;
   min-width: 800px; }
+  .changeform-navigation {
+    float: left;
+    margin-bottom: 20px; }
   .changeform-tabs {
     border-bottom: 2px solid #f8fafc;
     padding-left: 16px !important; }

Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
jet/static/jet/css/themes/light-blue/base.css.map


+ 35 - 2
jet/static/jet/css/themes/light-gray/base.css

@@ -1986,6 +1986,12 @@ a:hover {
 .fill_width {
   width: 100% !important; }
 
+.breadcrumbs {
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  padding-right: 200px;
+  overflow: hidden; }
+
 .top {
   padding: 20px; }
   .top-breadcrumbs {
@@ -2393,7 +2399,7 @@ a:hover {
 .fill_width {
   width: 100% !important; }
 
-.base_input, .sidebar-popup-search, .input, .background-form .input, input[type="text"], input[type="email"], input[type="password"], input[type="url"], input[type="number"], textarea, .button, input[type="submit"], input[type="button"], .button:visited, input[type="submit"]:visited, input[type="button"]:visited, .button:hover, input[type="submit"]:hover, input[type="button"]:hover {
+.base_input, .sidebar-popup-search, .input, .background-form .input, input[type="text"], input[type="email"], input[type="password"], input[type="url"], input[type="number"], textarea, .button, input[type="submit"], input[type="button"], .button:visited, input[type="submit"]:visited, input[type="button"]:visited, .button:hover, input[type="submit"]:hover, input[type="button"]:hover, .segmented-button, .segmented-button:visited, .segmented-button:hover {
   border: 0;
   border-radius: 4px;
   font-size: 13px;
@@ -2461,7 +2467,31 @@ a:hover {
   background-color: #2faa60;
   color: #fff; }
 
-a.button {
+.segmented-button, .segmented-button:visited, .segmented-button:hover {
+  font-size: 12px;
+  text-align: center;
+  background-color: #E3ECF2;
+  color: #7f8fa4;
+  padding: 0 10px;
+  display: inline-block;
+  text-transform: none;
+  border-radius: 0; }
+.segmented-button:hover {
+  background-color: #1cacfc;
+  color: #fff; }
+.segmented-button:active {
+  background-color: #2faa60;
+  color: #fff; }
+.segmented-button.disabled {
+  background-color: #E3ECF2 !important;
+  color: #7f8fa4;
+  opacity: 0.5; }
+.segmented-button.left {
+  border-radius: 4px 0 0 4px; }
+.segmented-button.right {
+  border-radius: 0 4px 4px 0; }
+
+a.button, a.segmented-button {
   line-height: 32px; }
 
 textarea {
@@ -3590,6 +3620,9 @@ table#change-history {
   background-color: #fff;
   border-radius: 4px;
   min-width: 800px; }
+  .changeform-navigation {
+    float: left;
+    margin-bottom: 20px; }
   .changeform-tabs {
     border-bottom: 2px solid #f8fafc;
     padding-left: 16px !important; }

Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
jet/static/jet/css/themes/light-gray/base.css.map


+ 35 - 2
jet/static/jet/css/themes/light-green/base.css

@@ -1986,6 +1986,12 @@ a:hover {
 .fill_width {
   width: 100% !important; }
 
+.breadcrumbs {
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  padding-right: 200px;
+  overflow: hidden; }
+
 .top {
   padding: 20px; }
   .top-breadcrumbs {
@@ -2393,7 +2399,7 @@ a:hover {
 .fill_width {
   width: 100% !important; }
 
-.base_input, .sidebar-popup-search, .input, .background-form .input, input[type="text"], input[type="email"], input[type="password"], input[type="url"], input[type="number"], textarea, .button, input[type="submit"], input[type="button"], .button:visited, input[type="submit"]:visited, input[type="button"]:visited, .button:hover, input[type="submit"]:hover, input[type="button"]:hover {
+.base_input, .sidebar-popup-search, .input, .background-form .input, input[type="text"], input[type="email"], input[type="password"], input[type="url"], input[type="number"], textarea, .button, input[type="submit"], input[type="button"], .button:visited, input[type="submit"]:visited, input[type="button"]:visited, .button:hover, input[type="submit"]:hover, input[type="button"]:hover, .segmented-button, .segmented-button:visited, .segmented-button:hover {
   border: 0;
   border-radius: 4px;
   font-size: 13px;
@@ -2461,7 +2467,31 @@ a:hover {
   background-color: #2faa60;
   color: #fff; }
 
-a.button {
+.segmented-button, .segmented-button:visited, .segmented-button:hover {
+  font-size: 12px;
+  text-align: center;
+  background-color: #E3ECF2;
+  color: #7f8fa4;
+  padding: 0 10px;
+  display: inline-block;
+  text-transform: none;
+  border-radius: 0; }
+.segmented-button:hover {
+  background-color: #1cacfc;
+  color: #fff; }
+.segmented-button:active {
+  background-color: #2faa60;
+  color: #fff; }
+.segmented-button.disabled {
+  background-color: #E3ECF2 !important;
+  color: #7f8fa4;
+  opacity: 0.5; }
+.segmented-button.left {
+  border-radius: 4px 0 0 4px; }
+.segmented-button.right {
+  border-radius: 0 4px 4px 0; }
+
+a.button, a.segmented-button {
   line-height: 32px; }
 
 textarea {
@@ -3590,6 +3620,9 @@ table#change-history {
   background-color: #fff;
   border-radius: 4px;
   min-width: 800px; }
+  .changeform-navigation {
+    float: left;
+    margin-bottom: 20px; }
   .changeform-tabs {
     border-bottom: 2px solid #f8fafc;
     padding-left: 16px !important; }

Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
jet/static/jet/css/themes/light-green/base.css.map


+ 35 - 2
jet/static/jet/css/themes/light-violet/base.css

@@ -1986,6 +1986,12 @@ a:hover {
 .fill_width {
   width: 100% !important; }
 
+.breadcrumbs {
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  padding-right: 200px;
+  overflow: hidden; }
+
 .top {
   padding: 20px; }
   .top-breadcrumbs {
@@ -2393,7 +2399,7 @@ a:hover {
 .fill_width {
   width: 100% !important; }
 
-.base_input, .sidebar-popup-search, .input, .background-form .input, input[type="text"], input[type="email"], input[type="password"], input[type="url"], input[type="number"], textarea, .button, input[type="submit"], input[type="button"], .button:visited, input[type="submit"]:visited, input[type="button"]:visited, .button:hover, input[type="submit"]:hover, input[type="button"]:hover {
+.base_input, .sidebar-popup-search, .input, .background-form .input, input[type="text"], input[type="email"], input[type="password"], input[type="url"], input[type="number"], textarea, .button, input[type="submit"], input[type="button"], .button:visited, input[type="submit"]:visited, input[type="button"]:visited, .button:hover, input[type="submit"]:hover, input[type="button"]:hover, .segmented-button, .segmented-button:visited, .segmented-button:hover {
   border: 0;
   border-radius: 4px;
   font-size: 13px;
@@ -2461,7 +2467,31 @@ a:hover {
   background-color: #A464C4;
   color: #fff; }
 
-a.button {
+.segmented-button, .segmented-button:visited, .segmented-button:hover {
+  font-size: 12px;
+  text-align: center;
+  background-color: #E3ECF2;
+  color: #7f8fa4;
+  padding: 0 10px;
+  display: inline-block;
+  text-transform: none;
+  border-radius: 0; }
+.segmented-button:hover {
+  background-color: #1cacfc;
+  color: #fff; }
+.segmented-button:active {
+  background-color: #A464C4;
+  color: #fff; }
+.segmented-button.disabled {
+  background-color: #E3ECF2 !important;
+  color: #7f8fa4;
+  opacity: 0.5; }
+.segmented-button.left {
+  border-radius: 4px 0 0 4px; }
+.segmented-button.right {
+  border-radius: 0 4px 4px 0; }
+
+a.button, a.segmented-button {
   line-height: 32px; }
 
 textarea {
@@ -3590,6 +3620,9 @@ table#change-history {
   background-color: #fff;
   border-radius: 4px;
   min-width: 800px; }
+  .changeform-navigation {
+    float: left;
+    margin-bottom: 20px; }
   .changeform-tabs {
     border-bottom: 2px solid #f8fafc;
     padding-left: 16px !important; }

Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
jet/static/jet/css/themes/light-violet/base.css.map


+ 29 - 11
jet/static/jet/js/main.js

@@ -16,22 +16,40 @@
         };
 
         var initChangeformTabs = function() {
-            var $tabItems = $('.changeform-tabs-item');
-            var $modules = $('.module');
+            $('.changeform').each(function() {
+                var $changeform = $(this);
+                var $tabItems = $changeform.find('.changeform-tabs-item');
+                var $modules = $changeform.find('.module');
 
-            $('.changeform-tabs-item-link').click(function (e) {
-                var $tabItemLink = $(this);
-                var $tabItem = $tabItemLink.closest('.changeform-tabs-item');
-                var moduleId = $tabItemLink.data('module-id');
+                if ($tabItems.length == 0) {
+                    return;
+                }
+
+                var showTab = function(selector) {
+                    selector = selector.replace(/^#\/?/, '');
 
-                $tabItems.removeClass('selected');
-                $tabItem.addClass('selected');
+                    var $module = selector.length > 0 ? $modules.filter('#' + selector) : $();
 
-                var $module = $modules.removeClass('selected').filter('#' + moduleId).addClass('selected');
+                    if ($module && $module.length == 0) {
+                        selector = $tabItems.first().find('a').attr('href').replace(/^#\/?/, '');
+                    }
 
-                $module.find('select').trigger('select:init');
+                    var $tabItem = $tabItems.find('a[href="#/' + selector + '"]').closest('.changeform-tabs-item');
 
-                e.preventDefault();
+
+                    $tabItems.removeClass('selected');
+                    $tabItem.addClass('selected');
+                    $module = $modules.removeClass('selected').filter('#' + selector).addClass('selected');
+                    $module.find('select').trigger('select:init');
+                };
+
+                $('.changeform-tabs-item-link').click(function (e) {
+                    var moduleSelector = $(this).attr('href');
+
+                    showTab(moduleSelector);
+                });
+
+                showTab(location.hash);
             });
         };
 

Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
jet/static/jet/js/main.min.js


+ 1 - 0
jet/templates/admin/base.html

@@ -5,6 +5,7 @@
 <head>
     <title>{% block title %}{% endblock %}</title>
     <meta name="copyright" content="geex-arts.com">
+    <meta http-equiv="X-UA-Compatible" content="IE=Edge" />
     {% block blockbots %}<meta name="robots" content="NONE,NOARCHIVE" />{% endblock %}
 
     <link href="{% static "jet/vendor/select2/css/select2.min.css" %}" rel="stylesheet" />

+ 44 - 20
jet/templates/admin/change_form.html

@@ -15,7 +15,13 @@
     <div class="breadcrumbs">
     <a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
     &rsaquo; <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{% if opts.app_config.verbose_name %}{{ opts.app_config.verbose_name }}{% else %}{% trans app_label as app_label %}{{ app_label|capfirst|escape }}{% endif %}</a>
-    &rsaquo; {% if has_change_permission %}<a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>{% else %}{{ opts.verbose_name_plural|capfirst }}{% endif %}
+    &rsaquo;
+    {% if has_change_permission %}
+        {% url opts|admin_urlname:'changelist' as url %}
+        <a href="{% jet_add_preserved_filters url %}">{{ opts.verbose_name_plural|capfirst }}</a>
+    {% else %}
+        {{ opts.verbose_name_plural|capfirst }}
+    {% endif %}
     &rsaquo; {% if add %}{% trans 'Add' %} {{ opts.verbose_name }}{% else %}{{ original|truncatewords:"18" }}{% endif %}
     </div>
     {% endblock %}
@@ -23,24 +29,42 @@
 
 {% block content %}
     <div id="content-main">
-        <div class="changeform-object-tools">
-            {% block object-tools %}
-                {% if change %}
-                    {% if not is_popup %}
-                        <ul class="object-tools horizontal">
-                            {% block object-tools-items %}
-                                <li>
-                                    {% url opts|admin_urlname:'history' original.pk|admin_urlquote as history_url %}
-                                    <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>
-                                {% endif %}
-                            {% endblock %}
-                        </ul>
+        <div class="cf">
+            {% jet_change_form_sibling_links_enabled as show_siblings %}
+            {% if change and show_siblings %}
+                <div class="changeform-navigation">
+                    {% spaceless %}
+                        {% jet_previous_object_url as url %}
+                        <a{% if url %} href="{{ url }}"{% endif %} class="segmented-button left{% if not url %} disabled{% endif %}">
+                            {% trans "←" %}
+                        </a>
+
+                        {% jet_next_object_url as url %}
+                        <a{% if url %} href="{{ url }}"{% endif %} class="segmented-button right{% if not url %} disabled{% endif %}">
+                            {% trans "→" %}
+                        </a>
+                    {% endspaceless %}
+                </div>
+            {% endif %}
+            <div class="changeform-object-tools">
+                {% block object-tools %}
+                    {% if change %}
+                        {% if not is_popup %}
+                            <ul class="object-tools horizontal">
+                                {% block object-tools-items %}
+                                    <li>
+                                        {% url opts|admin_urlname:'history' original.pk|admin_urlquote as history_url %}
+                                        <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>
+                                    {% endif %}
+                                {% endblock %}
+                            </ul>
+                        {% endif %}
                     {% endif %}
-                {% endif %}
-            {% endblock %}
+                {% endblock %}
+            </div>
         </div>
         <form {% if has_file_field %}enctype="multipart/form-data" {% endif %}action="{{ form_url }}" method="post" id="{{ opts.model_name }}_form" novalidate>
             {% csrf_token %}
@@ -55,7 +79,7 @@
                             {% is_fieldset_with_errors forloop.counter0 fieldsets_with_errors as fieldset_with_errors %}
 
                             <li class="changeform-tabs-item{% if selected %} selected{% endif %}{% if fieldset_with_errors %} errors{% endif %}">
-                                <a href="#" class="changeform-tabs-item-link" data-module-id="module_{{ forloop.counter0 }}">
+                                <a href="#/module_{{ forloop.counter0 }}" class="changeform-tabs-item-link" data-module-id="module_{{ forloop.counter0 }}">
                                     {% if fieldset.name %}
                                         {{ fieldset.name }}
                                     {% else %}
@@ -68,7 +92,7 @@
                             {% formset_has_errors inline_admin_formset.formset as errors %}
 
                             <li class="changeform-tabs-item{% if errors %} errors{% endif %}">
-                                <a href="#" class="changeform-tabs-item-link" data-module-id="inline_module_{{ forloop.counter0 }}">
+                                <a href="#/inline_module_{{ forloop.counter0 }}" class="changeform-tabs-item-link" data-module-id="inline_module_{{ forloop.counter0 }}">
                                     {{ inline_admin_formset.opts.verbose_name_plural|capfirst }}
                                 </a>
                             </li>

+ 2 - 2
jet/templates/registration/password_change_form.html

@@ -33,7 +33,7 @@
                         {{ form.new_password1.errors }}
                         {{ form.new_password1.label_tag }} {{ form.new_password1 }}
                         {% if form.new_password1.help_text %}
-                            <p class="help">{{ form.new_password1.help_text }}</p>
+                            <p class="help">{{ form.new_password1.help_text|safe }}</p>
                         {% endif %}
                     </div>
 
@@ -41,7 +41,7 @@
                         {{ form.new_password2.errors }}
                         {{ form.new_password2.label_tag }} {{ form.new_password2 }}
                         {% if form.new_password2.help_text %}
-                            <p class="help">{{ form.new_password2.help_text }}</p>
+                            <p class="help">{{ form.new_password2.help_text|safe }}</p>
                         {% endif %}
                     </div>
                 </fieldset>

+ 62 - 2
jet/templatetags/jet_tags.py

@@ -1,4 +1,5 @@
 from __future__ import unicode_literals
+import django
 from django import template
 from django.core.urlresolvers import reverse
 from django.db.models import OneToOneField
@@ -9,7 +10,12 @@ from django.template import loader, Context
 from jet import settings, VERSION
 from jet.models import Bookmark, PinnedApplication
 import re
-from jet.utils import get_app_list, get_model_instance_label
+from jet.utils import get_app_list, get_model_instance_label, get_model_queryset
+try:
+    from urllib.parse import parse_qsl
+except ImportError:
+    from urlparse import parse_qsl
+
 
 register = template.Library()
 
@@ -52,7 +58,10 @@ class FormatBreadcrumbsNode(template.Node):
         items = filter(None, items)
 
         t = loader.get_template('admin/breadcrumbs.html')
-        c = Context({'items': items})
+        c = {'items': items}
+
+        if django.VERSION[:2] < (1, 9):
+            c = Context(c)
 
         return t.render(c)
 
@@ -304,3 +313,54 @@ def get_current_jet_version():
 @register.assignment_tag
 def get_side_menu_compact():
     return settings.JET_SIDE_MENU_COMPACT
+
+
+@register.assignment_tag
+def jet_change_form_sibling_links_enabled():
+    return settings.JET_CHANGE_FORM_SIBLING_LINKS
+
+
+def jet_sibling_object_url(context, next):
+    original = context.get('original')
+
+    if not original:
+        return
+
+    model = type(original)
+    preserved_filters_plain = context.get('preserved_filters', '')
+    preserved_filters = dict(parse_qsl(preserved_filters_plain))
+    queryset = get_model_queryset(model, preserved_filters=preserved_filters)
+
+    sibling_object = None
+    object_pks = list(queryset.values_list('pk', flat=True))
+
+    try:
+        index = object_pks.index(original.pk)
+        sibling_index = index + 1 if next else index - 1
+        exists = sibling_index < len(object_pks) if next else sibling_index >= 0
+        sibling_object = queryset.get(pk=object_pks[sibling_index]) if exists else None
+    except ValueError:
+        pass
+
+    if sibling_object is None:
+        return
+
+    url = reverse('admin:%s_%s_change' % (
+        model._meta.app_label,
+        model._meta.model_name
+    ), args=(sibling_object.pk,))
+
+    if preserved_filters_plain != '':
+        url += '?' + preserved_filters_plain
+
+    return url
+
+
+@register.assignment_tag(takes_context=True)
+def jet_previous_object_url(context):
+    return jet_sibling_object_url(context, False)
+
+
+@register.assignment_tag(takes_context=True)
+def jet_next_object_url(context):
+    return jet_sibling_object_url(context, True)

+ 0 - 3
jet/tests/__init__.py

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

+ 13 - 0
jet/tests/admin.py

@@ -0,0 +1,13 @@
+from django.contrib import admin
+from jet.tests.models import TestModel, RelatedToTestModel
+
+
+class TestModelAdmin(admin.ModelAdmin):
+    list_display = ('field1', 'field2')
+
+
+class RelatedToTestModelAdmin(admin.ModelAdmin):
+    pass
+
+admin.site.register(TestModel, TestModelAdmin)
+admin.site.register(RelatedToTestModel, RelatedToTestModelAdmin)

+ 8 - 0
jet/tests/models.py

@@ -11,6 +11,14 @@ class TestModel(models.Model):
         return '%s%d' % (self.field1, self.field2)
 
 
+@python_2_unicode_compatible
+class RelatedToTestModel(models.Model):
+    field = models.ForeignKey(TestModel)
+
+    def __str__(self):
+        return self.field
+
+
 @python_2_unicode_compatible
 class SearchableTestModel(models.Model):
     field1 = models.CharField(max_length=255)

+ 21 - 3
jet/tests/settings.py

@@ -1,5 +1,9 @@
+import os
+import django
 from django.conf import global_settings
 
+BASE_DIR = os.path.dirname(os.path.dirname(__file__))
+
 SECRET_KEY = '!DJANGO_JET_TESTS!'
 
 DEBUG = True
@@ -28,9 +32,23 @@ MIDDLEWARE_CLASSES = (
     'django.contrib.messages.middleware.MessageMiddleware',
 )
 
-TEMPLATE_CONTEXT_PROCESSORS = global_settings.TEMPLATE_CONTEXT_PROCESSORS + (
-    'django.core.context_processors.request',
-)
+if django.VERSION[:2] < (1, 9):
+    TEMPLATE_CONTEXT_PROCESSORS = tuple(global_settings.TEMPLATE_CONTEXT_PROCESSORS) + (
+        'django.core.context_processors.request',
+    )
+else:
+    TEMPLATES = [
+        {
+            'BACKEND': 'django.template.backends.django.DjangoTemplates',
+            'DIRS': [os.path.join(BASE_DIR, 'templates')],
+            'APP_DIRS': True,
+            'OPTIONS': {
+                'context_processors': tuple(global_settings.TEMPLATE_CONTEXT_PROCESSORS) + (
+                    'django.template.context_processors.request',
+                )
+            },
+        },
+    ]
 
 DATABASES = {
     'default': {

+ 54 - 0
jet/tests/test_filters.py

@@ -0,0 +1,54 @@
+from django.contrib import admin
+from django.test import TestCase, RequestFactory
+from django.utils.encoding import smart_text
+from jet.filters import RelatedFieldAjaxListFilter
+from jet.tests.models import RelatedToTestModel, TestModel
+
+try:
+    from django.contrib.admin.utils import get_fields_from_path
+except ImportError: # Django 1.6
+    from django.contrib.admin.util import get_fields_from_path
+
+
+class FiltersTestCase(TestCase):
+    def setUp(self):
+        self.models = []
+        self.factory = RequestFactory()
+        self.models.append(TestModel.objects.create(field1='first', field2=1))
+        self.models.append(TestModel.objects.create(field1='second', field2=2))
+
+    def get_related_field_ajax_list_filter_params(self):
+        model = RelatedToTestModel
+        field_path = 'field'
+        field = get_fields_from_path(model, field_path)[-1]
+        lookup_params = {}
+        model_admin = admin.site._registry.get(model)
+
+        return field, lookup_params, model, model_admin, field_path
+
+    def test_related_field_ajax_list_filter(self):
+        request = self.factory.get('url')
+        field, lookup_params, model, model_admin, field_path = self.get_related_field_ajax_list_filter_params()
+        list_filter = RelatedFieldAjaxListFilter(field, request, lookup_params, model, model_admin, field_path)
+
+        self.assertTrue(list_filter.has_output())
+
+        choices = list_filter.field_choices(field, request, model_admin)
+
+        self.assertIsInstance(choices, list)
+        self.assertEqual(len(choices), 0)
+
+    def test_related_field_ajax_list_filter_with_initial(self):
+        initial = self.models[1]
+        request = self.factory.get('url', {'field__id__exact': initial.pk})
+        field, lookup_params, model, model_admin, field_path = self.get_related_field_ajax_list_filter_params()
+        list_filter = RelatedFieldAjaxListFilter(field, request, lookup_params, model, model_admin, field_path)
+
+        self.assertTrue(list_filter.has_output())
+
+        choices = list_filter.field_choices(field, request, model_admin)
+
+        self.assertIsInstance(choices, list)
+        self.assertEqual(len(choices), 1)
+        self.assertEqual(choices[0], (initial.pk, smart_text(initial)))
+

+ 36 - 4
jet/tests/test_tags.py

@@ -1,14 +1,15 @@
 from django import forms
+from django.core.urlresolvers import reverse
 from django.test import TestCase
-from jet.templatetags.jet_tags import select2_lookups
+from jet.templatetags.jet_tags import select2_lookups, jet_next_object_url, jet_previous_object_url
 from jet.tests.models import TestModel, SearchableTestModel
 
 
 class TagsTestCase(TestCase):
-    models = []
-    searchable_models = []
-
     def setUp(self):
+        self.models = []
+        self.searchable_models = []
+
         self.models.append(TestModel.objects.create(field1='first', field2=1))
         self.models.append(TestModel.objects.create(field1='second', field2=2))
         self.searchable_models.append(SearchableTestModel.objects.create(field1='first', field2=1))
@@ -55,4 +56,35 @@ class TagsTestCase(TestCase):
 
         self.assertEqual(len(choices), len(self.models) + 1)
 
+    def test_jet_sibling_object_next_url(self):
+        instance = self.models[0]
+        ordering_field = 1  # field1 in list_display
+        preserved_filters = '_changelist_filters=o%%3D%d' % ordering_field
+
+        context = {
+            'original': instance,
+            'preserved_filters': preserved_filters
+        }
+
+        actual_url = jet_next_object_url(context)
+        expected_url = reverse('admin:%s_%s_change' % (
+            TestModel._meta.app_label,
+            TestModel._meta.model_name
+        ), args=(self.models[1].pk,)) + '?' + preserved_filters
+
+        self.assertEqual(actual_url, expected_url)
+
+    def test_jet_sibling_object_previous_url(self):
+        instance = self.models[0]
+        ordering_field = 1  # field1 in list_display
+        preserved_filters = '_changelist_filters=o%%3D%d' % ordering_field
+
+        context = {
+            'original': instance,
+            'preserved_filters': preserved_filters
+        }
+
+        actual_url = jet_previous_object_url(context)
+        expected_url = None
 
+        self.assertEqual(actual_url, expected_url)

+ 6 - 3
jet/tests/urls.py

@@ -1,12 +1,15 @@
+import django
 from django.conf.urls import patterns, include, url
 from django.contrib import admin
 
 admin.autodiscover()
 
-urlpatterns = patterns(
-    '',
+urlpatterns = [
     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)),
-)
+]
+
+if django.VERSION[:2] < (1, 8):
+    urlpatterns = patterns('', *urlpatterns)

+ 6 - 3
jet/urls.py

@@ -1,9 +1,9 @@
+import django
 from django.conf.urls import patterns, url
 from django.views.i18n import javascript_catalog
 from jet.views import add_bookmark_view, remove_bookmark_view, toggle_application_pin_view, model_lookup_view
 
-urlpatterns = patterns(
-    '',
+urlpatterns = [
     url(
         r'^add_bookmark/$',
         add_bookmark_view,
@@ -30,4 +30,7 @@ urlpatterns = patterns(
         {'packages': ('django.conf', 'django.contrib.admin', 'jet',)},
         name='jsi18n'
     ),
-)
+]
+
+if django.VERSION[:2] < (1, 8):
+    urlpatterns = patterns('', *urlpatterns)

+ 53 - 1
jet/utils.py

@@ -10,13 +10,16 @@ except ImportError:
 from django.core.serializers.json import DjangoJSONEncoder
 from django.http import HttpResponse
 from django.core.urlresolvers import reverse, resolve, NoReverseMatch
-from django.contrib import admin
 from django.contrib.admin import AdminSite
 from django.utils.encoding import smart_text
 from django.utils.text import capfirst
 from django.contrib import messages
 from django.utils.encoding import force_text
 from django.utils.functional import Promise
+from django.contrib.admin.options import IncorrectLookupParameters
+from django.core import urlresolvers
+from django.contrib import admin
+from django.test.client import RequestFactory
 
 
 class JsonResponse(HttpResponse):
@@ -152,3 +155,52 @@ class SuccessMessageMixin(object):
 
     def get_success_message(self, cleaned_data):
         return self.success_message % cleaned_data
+
+
+def get_model_queryset(model, preserved_filters=None):
+    model_admin = admin.site._registry.get(model)
+
+    changelist_url = urlresolvers.reverse('admin:%s_%s_changelist' % (
+        model._meta.app_label,
+        model._meta.model_name
+    ))
+    changelist_filters = None
+
+    if preserved_filters:
+        changelist_filters = preserved_filters.get('_changelist_filters')
+
+    if changelist_filters:
+        changelist_url += '?' + changelist_filters
+
+    request = RequestFactory().get(changelist_url)
+
+    if model_admin:
+        queryset = model_admin.get_queryset(request)
+    else:
+        queryset = model.objects
+
+    list_display = model_admin.get_list_display(request)
+    list_display_links = model_admin.get_list_display_links(request, list_display)
+    list_filter = model_admin.get_list_filter(request)
+    search_fields = model_admin.get_search_fields(request) \
+        if hasattr(model_admin, 'get_search_fields') else model_admin.search_fields
+    list_select_related = model_admin.get_list_select_related(request) \
+        if hasattr(model_admin, 'get_list_select_related') else model_admin.list_select_related
+
+    actions = model_admin.get_actions(request)
+    if actions:
+        list_display = ['action_checkbox'] + list(list_display)
+
+    ChangeList = model_admin.get_changelist(request)
+
+    try:
+        cl = ChangeList(
+            request, model, list_display, list_display_links, list_filter, model_admin.date_hierarchy, search_fields,
+            list_select_related, model_admin.list_per_page, model_admin.list_max_show_all, model_admin.list_editable,
+            model_admin)
+
+        queryset = cl.get_queryset(request)
+    except IncorrectLookupParameters:
+        pass
+
+    return queryset

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff