Browse Source

Merge branch 'dev'

Denis K 8 years ago
parent
commit
fbaac217de

+ 7 - 0
CHANGELOG.rst

@@ -1,6 +1,13 @@
 Changelog
 =========
 
+1.0.1
+-----
+* StackedInline from earlier JET versions is back as a CompactInline custom class
+* Changed license to AGPLv3
+* Fixed filters with multiple selectable items behavior
+
+
 1.0.0
 -----
 * Fixed dashboard module buttons mobile layout misplacement

BIN
docs/_static/compact_inline.png


+ 42 - 0
docs/compact_inline.rst

@@ -0,0 +1,42 @@
+==============
+Compact Inline
+==============
+
+
+By default Django admin interface provides two types of inlines to edit models on the same page as a
+related model – ``StackedInline`` and ``TabularInline``. ``StackedInline`` is mostly used when there are
+not so many objects. If number of models is rather big, ``TabularInline`` can help you. Unfortunately when
+related model has a lot of fields it may be not convenient to interact with them.
+To solve this problem JET has a ``CompactInline`` class built-in.
+
+.. image:: _static/compact_inline.png
+    :width: 100%
+
+
+Usage
+-----
+
+``CompactInline`` works exactly like Django built-in inlines, you need just
+to inherit ``jet.admin.CompactInline`` inline class. That's all.
+
+.. code:: python
+
+    from django.contrib import admin
+    from people.models import County, State, City
+    from jet.admin import CompactInline
+
+
+    class StateCountiesInline(admin.TabularInline):
+        model = County
+        extra = 1
+        show_change_link = True
+
+
+    class StateCitiesInline(CompactInline):
+        model = City
+        extra = 1
+        show_change_link = True
+
+
+    class StateAdmin(admin.ModelAdmin):
+        inlines = (StateCountiesInline, StateCitiesInline)

+ 1 - 0
docs/configuration.rst

@@ -9,3 +9,4 @@ Contents:
 
    config_file
    autocomplete
+   compact_inline

+ 1 - 1
jet/__init__.py

@@ -1 +1 @@
-VERSION = '1.0.0'
+VERSION = '1.0.1'

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

@@ -505,6 +505,16 @@ body.popup .submit-row {
   border-radius: 4px;
   border: 0;
 
+  &.compact {
+    position: relative;
+    min-height: 400px;
+
+    @include for-mobile {
+      position: static;
+      min-height: 0;
+    }
+  }
+
   thead th {
     padding: 8px 10px;
   }
@@ -622,6 +632,10 @@ body.popup .submit-row {
   }
 }
 
+.inline-group.compact .inline-related h3 {
+  background: transparent;
+}
+
 .inline-related.tabular fieldset.module {
   padding: 0;
 
@@ -630,6 +644,112 @@ body.popup .submit-row {
   }
 }
 
+.inline-navigation {
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  width: 200px;
+  background: $content-contrast-background-color;
+
+  @include for-mobile {
+    position: relative;
+    width: auto;
+    top: auto;
+    bottom: auto;
+    left: auto;
+  }
+
+  &-top {
+    position: absolute;
+    top: 0;
+    right: 0;
+    left: 0;
+    height: 40px;
+    background: linear-gradient(to bottom, $content-background-color 25%, transparentize($content-contrast-background-color, 1) 100%);
+    z-index: 1;
+  }
+
+  &-bottom {
+    position: absolute;
+    right: 0;
+    bottom: 0;
+    left: 0;
+    height: 40px;
+    background: linear-gradient(to top, $content-background-color 25%, transparentize($content-contrast-background-color, 1) 100%);
+    z-index: 1;
+
+    @include for-mobile {
+      display: none;
+    }
+  }
+
+  .add-row {
+    position: absolute;
+    top: 10px;
+    right: 0;
+    left: 0;
+    padding: 0 16px !important;
+    z-index: 1;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    overflow: hidden;
+  }
+
+  &-content {
+    position: absolute;
+    top: 0;
+    right: 0;
+    bottom: 0;
+    left: 0;
+    padding: 40px 0 30px 0;
+    overflow-y: auto;
+    -webkit-overflow-scrolling: touch;
+
+    @include for-mobile {
+      position: static;
+      top: auto;
+      right: auto;
+      bottom: auto;
+      left: auto;
+      padding-bottom: 10px;
+      max-height: 200px;
+    }
+  }
+
+  &-item {
+    &, &:visited, &:hover {
+      display: block;
+      white-space: nowrap;
+      text-overflow: ellipsis;
+      overflow: hidden;
+      padding: 8px 10px 8px 20px;
+      color: $dim-text-color;
+      transition: background-color $transitions-duration, color $transitions-duration;
+    }
+
+    html.no-touchevents &:hover, &:active {
+      background: $button-hover-background-color;
+      color: $button-hover-text-color;
+    }
+
+    &.empty {
+      display: none;
+    }
+
+    &.selected {
+      background: transparent;
+      color: $text-color;
+      font-weight: bold;
+      cursor: default;
+    }
+
+    &.delete {
+      text-decoration: line-through;
+    }
+  }
+}
+
 .inline-group {
   .tabular {
     overflow-x: auto;
@@ -676,6 +796,19 @@ body.popup .submit-row {
     }
   }
 
+  .compact {
+    display: none;
+    margin-left: 200px;
+
+    @include for-mobile {
+      margin-left: 0;
+    }
+
+    &.selected {
+      display: block;
+    }
+  }
+
   ul.tools {
     padding: 0;
     margin: 0;

File diff suppressed because it is too large
+ 0 - 0
jet/static/jet/css/themes/default/base.css


File diff suppressed because it is too large
+ 0 - 0
jet/static/jet/css/themes/default/base.css.map


File diff suppressed because it is too large
+ 0 - 0
jet/static/jet/css/themes/green/base.css


File diff suppressed because it is too large
+ 0 - 0
jet/static/jet/css/themes/green/base.css.map


File diff suppressed because it is too large
+ 0 - 0
jet/static/jet/css/themes/light-blue/base.css


File diff suppressed because it is too large
+ 0 - 0
jet/static/jet/css/themes/light-blue/base.css.map


File diff suppressed because it is too large
+ 0 - 0
jet/static/jet/css/themes/light-gray/base.css


File diff suppressed because it is too large
+ 0 - 0
jet/static/jet/css/themes/light-gray/base.css.map


File diff suppressed because it is too large
+ 0 - 0
jet/static/jet/css/themes/light-green/base.css


File diff suppressed because it is too large
+ 0 - 0
jet/static/jet/css/themes/light-green/base.css.map


File diff suppressed because it is too large
+ 0 - 0
jet/static/jet/css/themes/light-violet/base.css


File diff suppressed because it is too large
+ 0 - 0
jet/static/jet/css/themes/light-violet/base.css.map


File diff suppressed because it is too large
+ 0 - 0
jet/static/jet/js/build/bundle.min.js


+ 183 - 0
jet/static/jet/js/src/features/compact-inline.js

@@ -0,0 +1,183 @@
+var $ = require('jquery');
+
+var CompactInline = function($inline) {
+    this.$inline = $inline;
+    this.prefix = $inline.data('inline-prefix');
+    this.verboseName = $inline.data('inline-verbose-name');
+    this.deleteText = $inline.data('inline-delete-text');
+};
+
+CompactInline.prototype = {
+    updateLabels: function($inline) {
+        var self = this;
+        var $navigationItems = $inline.find('.inline-navigation-item');
+
+        $inline.find('.inline-related').each(function(i) {
+            var $inlineItem = $(this);
+            var $label = $inlineItem.find('.inline_label');
+            var label = $label.html().replace(/(#\d+)/g, "#" + (i + 1));
+            var $navigationItem = $navigationItems.eq(i);
+            var navigationLabel = $inlineItem.hasClass('has_original') ? label : self.verboseName + ' ' + label;
+
+            $label.html(label);
+            $navigationItem.html(navigationLabel);
+        });
+    },
+    updateFormIndex: function($form, index) {
+        var id_regex = new RegExp('(' + this.prefix + '-(\\d+|__prefix__))');
+        var replacement = this.prefix + "-" + index;
+
+        $form.find('*').each(function() {
+            var $el = $(this);
+
+            $.each(['for', 'id', 'name'], function() {
+                var attr = this;
+
+                if ($el.attr(attr)) {
+                    $el.attr(attr, $el.attr(attr).replace(id_regex, replacement));
+                }
+            });
+        });
+
+        if (!$form.hasClass('empty-form')) {
+            $form.attr('id', this.prefix + '-' + index);
+        }
+    },
+    updateFormsIndexes: function($inline) {
+        var self = this;
+        var $navigationItems = $inline.find('.inline-navigation-item');
+
+        $inline.find('.inline-related').each(function(i) {
+            var $inlineItem = $(this);
+
+            self.updateFormIndex($inlineItem, i);
+            $navigationItems.eq(i).attr('data-inline-related-id', $inlineItem.attr('id'));
+        });
+    },
+    updateTotalForms: function($inline) {
+        var $totalFormsInput = $inline.find('[name="' + this.prefix + '-TOTAL_FORMS"]');
+        var totalForms = parseInt($inline.find('.inline-related').length);
+
+        $totalFormsInput.val(totalForms);
+    },
+    addNavigationItem: function($inline, $inlineItem) {
+        var $empty = $inline.find('.inline-navigation-item.empty');
+
+        return $empty
+            .clone()
+            .removeClass('empty')
+            .attr('data-inline-related-id', $inlineItem.attr('id'))
+            .insertBefore($empty);
+    },
+    openNavigationItem: function($inline, $item) {
+        $inline
+            .find('.inline-related')
+            .removeClass('selected')
+            .filter('#' + $item.attr('data-inline-related-id'))
+            .addClass('selected');
+
+        $inline.find('.inline-navigation-item').removeClass('selected');
+        $item.addClass('selected');
+    },
+    removeItem: function($inline, $item) {
+        $item.remove();
+        $inline.find('.inline-navigation-item[data-inline-related-id="' + $item.attr('id') + '"]').remove();
+    },
+    openFirstNavigationItem: function($inline) {
+        this.openNavigationItem($inline, $inline.find('.inline-navigation-item').first());
+        this.scrollNavigationToTop($inline);
+    },
+    addItemDeleteButton: function($item) {
+        $item
+            .children(':first')
+            .append('<span><a class="inline-deletelink" href="#">' + this.deleteText + "</a></span>");
+    },
+    scrollNavigationToTop: function($inline) {
+        var $navigationItemsContainer = $inline.find('.inline-navigation-content');
+
+        $navigationItemsContainer.stop().animate({
+            scrollTop: 0
+        });
+    },
+    scrollNavigationToBottom: function($inline) {
+        var $navigationItemsContainer = $inline.find('.inline-navigation-content');
+
+        $navigationItemsContainer.stop().animate({
+            scrollTop: $navigationItemsContainer.prop('scrollHeight')
+        });
+    },
+    initAdding: function($inline) {
+        var self = this;
+
+        $inline.find('.add-row a').on('click', function (e) {
+            e.preventDefault();
+
+            var $empty = $inline.find('.inline-related.empty-form');
+            var cloneIndex = parseInt($inline.find('.inline-related').length) - 1;
+            var $clone = $empty
+                .clone(true)
+                .removeClass('empty-form')
+                .insertBefore($empty);
+
+            self.updateTotalForms($inline);
+            self.updateFormIndex($clone, cloneIndex);
+            self.updateFormIndex($empty, cloneIndex + 1);
+
+            var navigationItem = self.addNavigationItem($inline, $clone);
+
+            self.updateLabels($inline);
+            self.openNavigationItem($inline, navigationItem);
+            self.addItemDeleteButton($clone);
+            self.scrollNavigationToBottom($inline);
+        });
+    },
+    initDeletion: function($inline) {
+        var self = this;
+
+        $inline.on('click', '.inline-deletelink', function(e) {
+            e.preventDefault();
+
+            var $inlineItem = $(this).closest('.inline-related');
+
+            self.removeItem($inline, $inlineItem);
+            self.updateFormsIndexes($inline);
+            self.updateLabels($inline);
+            self.updateTotalForms($inline);
+            self.openFirstNavigationItem($inline);
+        });
+
+        $inline.find('.inline-related').each(function() {
+            var $inlineItem = $(this);
+
+            $inlineItem.find('.delete input').on('change', function() {
+                $inline
+                    .find('.inline-navigation-item[data-inline-related-id="' + $inlineItem.attr('id') + '"]')
+                    .toggleClass('delete', $(this).is(':checked'));
+            });
+        });
+    },
+    initNavigation: function($inline) {
+        var self = this;
+
+        $inline.on('click', '.inline-navigation-item', function(e) {
+            e.preventDefault();
+
+            self.openNavigationItem($inline, $(this));
+        });
+
+        self.openFirstNavigationItem($inline);
+    },
+    run: function() {
+        var $inline = this.$inline;
+
+        try {
+            this.initAdding($inline);
+            this.initDeletion($inline);
+            this.initNavigation($inline);
+        } catch (e) {
+            console.error(e, e.stack);
+        }
+    }
+};
+
+module.exports = CompactInline;

+ 5 - 0
jet/static/jet/js/src/features/inlines.js

@@ -1,4 +1,5 @@
 var $ = require('jquery');
+var CompactInline = require('./compact-inline');
 
 var Inline = function($inline) {
     this.$inline = $inline;
@@ -14,6 +15,10 @@ Inline.prototype = {
         var $inline = this.$inline;
 
         try {
+            if ($inline.hasClass('compact')) {
+                new CompactInline($inline).run();
+            }
+
             this.initSelectsOnAddRow($inline);
         } catch (e) {
             console.error(e, e.stack);

+ 55 - 0
jet/templates/admin/edit_inline/compact.html

@@ -0,0 +1,55 @@
+{% load i18n admin_urls admin_static %}
+
+<div class="inline-group compact" id="{{ inline_admin_formset.formset.prefix }}-group" data-inline-prefix="{{ inline_admin_formset.formset.prefix }}" data-inline-verbose-name="{{ inline_admin_formset.opts.verbose_name|capfirst }}" data-inline-delete-text="{% trans "Remove" %}">
+    <h2>{{ inline_admin_formset.opts.verbose_name_plural|capfirst }}</h2>
+    {{ inline_admin_formset.formset.management_form }}
+    {{ inline_admin_formset.formset.non_form_errors }}
+
+    <div class="inline-navigation">
+        <div class="inline-navigation-top"></div>
+        <div class="inline-navigation-bottom"></div>
+        <div class="add-row">
+            <a href="#">{% blocktrans with verbose_name=inline_admin_formset.opts.verbose_name|capfirst %}Add another {{ verbose_name }}{% endblocktrans %}</a>
+        </div>
+        <div class="inline-navigation-content">
+            <div class="inline-navigation-items">
+                {% for inline_admin_form in inline_admin_formset %}
+                    <a href="#" class="inline-navigation-item{% if forloop.last %} empty{% endif %}" data-inline-related-id="{{ inline_admin_formset.formset.prefix }}-{% if not forloop.last %}{{ forloop.counter0 }}{% else %}empty{% endif %}">
+                        {% if inline_admin_form.original %}
+                            {{ inline_admin_form.original }}
+                        {% else %}
+                            {{ inline_admin_formset.opts.verbose_name|capfirst }} #{{ forloop.counter }}
+                        {% endif %}
+                    </a>
+                {% endfor %}
+            </div>
+        </div>
+    </div>
+
+    {% for inline_admin_form in inline_admin_formset %}
+        <div class="inline-related compact{% if inline_admin_form.original or inline_admin_form.show_url %} has_original{% endif %}{% if forloop.last %} empty-form last-related{% endif %}" id="{{ inline_admin_formset.formset.prefix }}-{% if not forloop.last %}{{ forloop.counter0 }}{% else %}empty{% endif %}">
+            <h3>
+                <b>{{ inline_admin_formset.opts.verbose_name|capfirst }}:</b>&nbsp;
+                <span class="inline_label">
+                    {% if inline_admin_form.original %}
+                        {{ inline_admin_form.original }}
+                        {% if inline_admin_form.model_admin.show_change_link and inline_admin_form.model_admin.has_registered_model %}
+                            <a href="{% url inline_admin_form.model_admin.opts|admin_urlname:'change' inline_admin_form.original.pk|admin_urlquote %}" class="inlinechangelink">{% trans "Change" %}</a>
+                        {% endif %}
+                    {% else %}
+                        #{{ forloop.counter }}
+                    {% endif %}
+                </span>
+                {% if inline_admin_form.show_url %}<a href="{{ inline_admin_form.absolute_url }}">{% trans "View on site" %}</a>{% endif %}
+                {% if inline_admin_formset.formset.can_delete and inline_admin_form.original %}<span class="delete">{{ inline_admin_form.deletion_field.field }} {{ inline_admin_form.deletion_field.label_tag }}</span>{% endif %}
+            </h3>
+            {% if inline_admin_form.form.non_field_errors %}{{ inline_admin_form.form.non_field_errors }}{% endif %}
+            {% for fieldset in inline_admin_form %}
+                {% include "admin/includes/fieldset.html" %}
+            {% endfor %}
+            {% if inline_admin_form.needs_explicit_pk_field %}{{ inline_admin_form.pk_field.field }}{% endif %}
+            {{ inline_admin_form.fk_field.field }}
+        </div>
+    {% endfor %}
+</div>
+

Some files were not shown because too many files changed in this diff