Denis K преди 9 години
родител
ревизия
77b76b734d

+ 10 - 0
CHANGELOG.rst

@@ -1,6 +1,16 @@
 Changelog
 Changelog
 =========
 =========
 
 
+0.1.2
+-----
+* [Fix] Issue-14: Fixed ajax fields choices being rendered in page (thanks to dnmellen for the report)
+* [Fix] Issue-15: Fixed textarea text wrapping in Firefox
+* [Feature] PR-16: Allow usage of select2_lookups filter in ModelForms outside of Admin (thansk to dnmellen for pull request)
+* [Fix] Fixed select2_lookups for posted data
+* [Feature] Issue-14: Added ajax related field filters
+* [Fix] Made booleanfield icons cross browser compatible
+
+
 0.1.1
 0.1.1
 -----
 -----
 * [Feature] Added fade animation to sidebar application popup
 * [Feature] Added fade animation to sidebar application popup

+ 36 - 0
jet/filters.py

@@ -0,0 +1,36 @@
+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
+
+
+class RelatedFieldAjaxListFilter(RelatedFieldListFilter):
+    ajax_attrs = None
+
+    def has_output(self):
+        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
+
+        self.ajax_attrs = format_html('{}', flatatt({
+            'data-app-label': app_label,
+            'data-model': model_name,
+            'data-ajax--url': reverse('jet:model_lookup'),
+            'data-queryset--lookup': self.lookup_kwarg
+        }))
+
+        if self.lookup_val is None:
+            return []
+
+        other_model = get_model_from_relation(field)
+        if hasattr(field, 'rel'):
+            rel_name = field.rel.get_related_field().name
+        else:
+            rel_name = other_model._meta.pk.name
+
+        queryset = field.related_model._default_manager.filter(**{rel_name: self.lookup_val}).all()
+        return [(x._get_pk_val(), smart_text(x)) for x in queryset]

+ 8 - 11
jet/static/jet/css/_content.scss

@@ -111,21 +111,18 @@ ul.object-tools {
 @keyframes spin { 100% { -webkit-transform: rotate(360deg); transform:rotate(360deg); } }
 @keyframes spin { 100% { -webkit-transform: rotate(360deg); transform:rotate(360deg); } }
 
 
 img[src$="admin/img/icon-yes.gif"], img[src$="admin/img/icon-no.gif"], img[src$="admin/img/icon-unknown.gif"] {
 img[src$="admin/img/icon-yes.gif"], img[src$="admin/img/icon-no.gif"], img[src$="admin/img/icon-unknown.gif"] {
-  @include font-icon;
-  content: "";
-  font-weight: bold;
+  display: none;
+
+  + span {
+    font-weight: bold;
+    color: $success-text-color;
+  }
 }
 }
 
 
-img[src$="admin/img/icon-yes.gif"]:before {
-  content: $icon-tick;
+img[src$="admin/img/icon-yes.gif"] + span {
   color: $success-text-color;
   color: $success-text-color;
 }
 }
 
 
-img[src$="admin/img/icon-no.gif"]:before {
-  content: $icon-cross;
+img[src$="admin/img/icon-no.gif"] + span {
   color: $warning-text-color;
   color: $warning-text-color;
-}
-
-img[src$="admin/img/icon-unknown.gif"]:before {
-  content: $icon-question;
 }
 }

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

@@ -93,7 +93,7 @@ textarea {
   height: auto;
   height: auto;
   line-height: normal;
   line-height: normal;
   padding: 12px;
   padding: 12px;
-  white-space: normal;
+  white-space: pre-wrap;
 }
 }
 
 
 .button, input[type="submit"], input[type="button"] {
 .button, input[type="submit"], input[type="button"] {

+ 4 - 4
jet/static/jet/css/_variables.scss

@@ -20,16 +20,16 @@ $contrast-color: #47bac1;
 $contrast-dim-color: transparentize($contrast-color, 0.25);
 $contrast-dim-color: transparentize($contrast-color, 0.25);
 $contrast2-color: #639af5;
 $contrast2-color: #639af5;
 
 
-$success-color-1: #8ecb8e;
-$success-color-2: #c9eaca;
+$success-color-1: #82b982;
+$success-color-2: #c4ecc5;
 
 
-$info-color-1: #bebe92;
+$info-color-1: #b9b97f;
 $info-color-2: #e8e8bd;
 $info-color-2: #e8e8bd;
 
 
 $selected-color-1: #e5e2a5;
 $selected-color-1: #e5e2a5;
 $selected-color-2: #fffcc0;
 $selected-color-2: #fffcc0;
 
 
-$warning-color-1: #dba4a4;
+$warning-color-1: #d49d9d;
 $warning-color-2: #f0dada;
 $warning-color-2: #f0dada;
 $warning-color-3: #c14747;
 $warning-color-3: #c14747;
 
 

+ 14 - 28
jet/static/jet/css/themes/default/base.css

@@ -2301,7 +2301,7 @@ textarea {
   height: auto;
   height: auto;
   line-height: normal;
   line-height: normal;
   padding: 12px;
   padding: 12px;
-  white-space: normal; }
+  white-space: pre-wrap; }
 
 
 input[type=checkbox] {
 input[type=checkbox] {
   display: none; }
   display: none; }
@@ -2660,19 +2660,19 @@ input[type=checkbox] {
   border-radius: 6px;
   border-radius: 6px;
   padding: 10px;
   padding: 10px;
   background: #f0dada;
   background: #f0dada;
-  color: #dba4a4; }
+  color: #d49d9d; }
 .messagelist .info, .messagelist .debug {
 .messagelist .info, .messagelist .debug {
   margin: 0 20px 10px 20px;
   margin: 0 20px 10px 20px;
   border-radius: 6px;
   border-radius: 6px;
   padding: 10px;
   padding: 10px;
   background: #e8e8bd;
   background: #e8e8bd;
-  color: #bebe92; }
+  color: #b9b97f; }
 .messagelist .success {
 .messagelist .success {
   margin: 0 20px 10px 20px;
   margin: 0 20px 10px 20px;
   border-radius: 6px;
   border-radius: 6px;
   padding: 10px;
   padding: 10px;
-  background: #c9eaca;
-  color: #8ecb8e; }
+  background: #c4ecc5;
+  color: #82b982; }
 
 
 /*
 /*
  * General
  * General
@@ -3904,7 +3904,7 @@ table#change-history {
 
 
 .delete-summary {
 .delete-summary {
   background: #f0dada;
   background: #f0dada;
-  color: #dba4a4;
+  color: #d49d9d;
   border-radius: 4px;
   border-radius: 4px;
   padding: 20px;
   padding: 20px;
   margin-bottom: 20px; }
   margin-bottom: 20px; }
@@ -4683,30 +4683,16 @@ ul.object-tools {
     -webkit-transform: rotate(360deg);
     -webkit-transform: rotate(360deg);
     transform: rotate(360deg); } }
     transform: rotate(360deg); } }
 img[src$="admin/img/icon-yes.gif"], img[src$="admin/img/icon-no.gif"], img[src$="admin/img/icon-unknown.gif"] {
 img[src$="admin/img/icon-yes.gif"], img[src$="admin/img/icon-no.gif"], img[src$="admin/img/icon-unknown.gif"] {
-  font-family: 'jet-icons';
-  speak: none;
-  font-style: normal;
-  font-weight: normal;
-  font-variant: normal;
-  text-transform: none;
-  line-height: 1;
-  /* Better Font Rendering =========== */
-  -webkit-font-smoothing: antialiased;
-  -moz-osx-font-smoothing: grayscale;
-  display: inline-block;
-  content: "";
-  font-weight: bold; }
-
-img[src$="admin/img/icon-yes.gif"]:before {
-  content: "";
-  color: #8ecb8e; }
+  display: none; }
+  img[src$="admin/img/icon-yes.gif"] + span, img[src$="admin/img/icon-no.gif"] + span, img[src$="admin/img/icon-unknown.gif"] + span {
+    font-weight: bold;
+    color: #82b982; }
 
 
-img[src$="admin/img/icon-no.gif"]:before {
-  content: "";
-  color: #dba4a4; }
+img[src$="admin/img/icon-yes.gif"] + span {
+  color: #82b982; }
 
 
-img[src$="admin/img/icon-unknown.gif"]:before {
-  content: ""; }
+img[src$="admin/img/icon-no.gif"] + span {
+  color: #d49d9d; }
 
 
 /*
 /*
  * General
  * General

Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
jet/static/jet/css/themes/default/base.css.map


+ 7 - 21
jet/static/jet/css/themes/green/base.css

@@ -2332,7 +2332,7 @@ textarea {
   height: auto;
   height: auto;
   line-height: normal;
   line-height: normal;
   padding: 12px;
   padding: 12px;
-  white-space: normal; }
+  white-space: pre-wrap; }
 
 
 input[type=checkbox] {
 input[type=checkbox] {
   display: none; }
   display: none; }
@@ -4714,31 +4714,17 @@ ul.object-tools {
     -webkit-transform: rotate(360deg);
     -webkit-transform: rotate(360deg);
     transform: rotate(360deg); } }
     transform: rotate(360deg); } }
 img[src$="admin/img/icon-yes.gif"], img[src$="admin/img/icon-no.gif"], img[src$="admin/img/icon-unknown.gif"] {
 img[src$="admin/img/icon-yes.gif"], img[src$="admin/img/icon-no.gif"], img[src$="admin/img/icon-unknown.gif"] {
-  font-family: 'jet-icons';
-  speak: none;
-  font-style: normal;
-  font-weight: normal;
-  font-variant: normal;
-  text-transform: none;
-  line-height: 1;
-  /* Better Font Rendering =========== */
-  -webkit-font-smoothing: antialiased;
-  -moz-osx-font-smoothing: grayscale;
-  display: inline-block;
-  content: "";
-  font-weight: bold; }
+  display: none; }
+  img[src$="admin/img/icon-yes.gif"] + span, img[src$="admin/img/icon-no.gif"] + span, img[src$="admin/img/icon-unknown.gif"] + span {
+    font-weight: bold;
+    color: #bcd386; }
 
 
-img[src$="admin/img/icon-yes.gif"]:before {
-  content: "";
+img[src$="admin/img/icon-yes.gif"] + span {
   color: #bcd386; }
   color: #bcd386; }
 
 
-img[src$="admin/img/icon-no.gif"]:before {
-  content: "";
+img[src$="admin/img/icon-no.gif"] + span {
   color: #dba4a4; }
   color: #dba4a4; }
 
 
-img[src$="admin/img/icon-unknown.gif"]:before {
-  content: ""; }
-
 /*
 /*
  * General
  * General
  */
  */

Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
jet/static/jet/css/themes/green/base.css.map


+ 10 - 0
jet/static/jet/js/main.js

@@ -5,9 +5,12 @@
                 var $select = $(this);
                 var $select = $(this);
                 var $selectedOption = $select.find('option:selected');
                 var $selectedOption = $select.find('option:selected');
                 var url = $selectedOption.data('url');
                 var url = $selectedOption.data('url');
+                var querysetLookup = $select.data('queryset--lookup');
 
 
                 if (url) {
                 if (url) {
                     document.location = $selectedOption.data('url');
                     document.location = $selectedOption.data('url');
+                } else if (querysetLookup) {
+                    document.location = '?' + querysetLookup + '=' + $selectedOption.val();
                 }
                 }
             });
             });
         };
         };
@@ -642,8 +645,15 @@
                 updateChangelistFooters();
                 updateChangelistFooters();
             };
             };
 
 
+            var initChangelistImages = function() {
+                $('img[src$="admin/img/icon-yes.gif"]').after($('<span class="icon-tick">'));
+                $('img[src$="admin/img/icon-no.gif"]').after($('<span class="icon-cross">'));
+                $('img[src$="admin/img/icon-unknown.gif"]').after($('<span class="icon-question">'));
+            };
+
             initChangelistHeaders();
             initChangelistHeaders();
             initChangelistFooters();
             initChangelistFooters();
+            initChangelistImages();
         };
         };
 
 
         var initTooltips = function() {
         var initTooltips = function() {

Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
jet/static/jet/js/main.min.js


+ 4 - 5
jet/templates/admin/change_list.html

@@ -65,11 +65,10 @@
 
 
     <div class="{% if cl.has_filters %} filtered{% endif %}" id="changelist">
     <div class="{% if cl.has_filters %} filtered{% endif %}" id="changelist">
         <div class="cf">
         <div class="cf">
-        <div id="changelist-filter" class="changelist-filter background-form">
-            {% block search %}{% search_form cl %}{% endblock %}
-            {% block date_hierarchy %}{% date_hierarchy cl %}{% endblock %}
-        </div>
-
+            <div id="changelist-filter" class="changelist-filter background-form">
+                {% block search %}{% search_form cl %}{% endblock %}
+                {% block date_hierarchy %}{% date_hierarchy cl %}{% endblock %}
+            </div>
         </div>
         </div>
 
 
         <form id="changelist-form" action="" method="post"{% if cl.formset.is_multipart %} enctype="multipart/form-data"{% endif %} novalidate>{% csrf_token %}
         <form id="changelist-form" action="" method="post"{% if cl.formset.is_multipart %} enctype="multipart/form-data"{% endif %} novalidate>{% csrf_token %}

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

@@ -1,6 +1,6 @@
 {% load i18n %}
 {% load i18n %}
 
 
-<select class="changelist-filter-select">
+<select class="changelist-filter-select{% if spec.ajax_attrs %} ajax{% endif %}"{% if spec.ajax_attrs %}{{ spec.ajax_attrs }}{% endif %}>
     {% for choice in choices %}
     {% for choice in choices %}
         {% if forloop.first %}
         {% if forloop.first %}
             <option data-url="{{ choice.query_string|iriencode }}">{% blocktrans with filter_title=title %}{{ filter_title }}{% endblocktrans %}</option>
             <option data-url="{{ choice.query_string|iriencode }}">{% blocktrans with filter_title=title %}{{ filter_title }}{% endblocktrans %}</option>

+ 13 - 3
jet/templatetags/jet_tags.py

@@ -3,6 +3,7 @@ from django import template
 from django.core.urlresolvers import reverse
 from django.core.urlresolvers import reverse
 from django.db.models import OneToOneField
 from django.db.models import OneToOneField
 from django.forms import CheckboxInput, ModelChoiceField, Select, ModelMultipleChoiceField, SelectMultiple
 from django.forms import CheckboxInput, ModelChoiceField, Select, ModelMultipleChoiceField, SelectMultiple
+from django.contrib.admin.widgets import RelatedFieldWidgetWrapper
 from django.utils.formats import get_format
 from django.utils.formats import get_format
 from django.template import loader, Context
 from django.template import loader, Context
 from jet import settings
 from jet import settings
@@ -184,7 +185,8 @@ def select2_lookups(field):
                 'data-ajax--url': reverse('jet:model_lookup')
                 'data-ajax--url': reverse('jet:model_lookup')
             }
             }
 
 
-            initial_value = field.form.initial.get(field.name)
+            form = field.form
+            initial_value = form.data.get(field.name) if form.data != {} else form.initial.get(field.name)
 
 
             if hasattr(field, 'field') and isinstance(field.field, ModelMultipleChoiceField):
             if hasattr(field, 'field') and isinstance(field.field, ModelMultipleChoiceField):
                 if initial_value:
                 if initial_value:
@@ -194,14 +196,22 @@ def select2_lookups(field):
                             for initial_object in initial_objects]
                             for initial_object in initial_objects]
                     )
                     )
 
 
-                field.field.widget.widget = SelectMultiple(attrs, choices=choices)
+                if isinstance(field.field.widget, RelatedFieldWidgetWrapper):
+                    field.field.widget.widget = SelectMultiple(attrs)
+                else:
+                    field.field.widget = SelectMultiple(attrs)
+                field.field.choices = choices
             elif hasattr(field, 'field') and isinstance(field.field, ModelChoiceField):
             elif hasattr(field, 'field') and isinstance(field.field, ModelChoiceField):
                 if initial_value:
                 if initial_value:
                     initial_object = model.objects.get(pk=initial_value)
                     initial_object = model.objects.get(pk=initial_value)
                     attrs['data-object-id'] = initial_value
                     attrs['data-object-id'] = initial_value
                     choices.append((initial_object.pk, get_model_instance_label(initial_object)))
                     choices.append((initial_object.pk, get_model_instance_label(initial_object)))
 
 
-                field.field.widget.widget = Select(attrs, choices=choices)
+                if isinstance(field.field.widget, RelatedFieldWidgetWrapper):
+                    field.field.widget.widget = Select(attrs)
+                else:
+                    field.field.widget = Select(attrs)
+                field.field.choices = choices
 
 
     return field
     return field
 
 

+ 13 - 0
jet/tests/models.py

@@ -9,3 +9,16 @@ class TestModel(models.Model):
 
 
     def __str__(self):
     def __str__(self):
         return '%s%d' % (self.field1, self.field2)
         return '%s%d' % (self.field1, self.field2)
+
+
+@python_2_unicode_compatible
+class SearchableTestModel(models.Model):
+    field1 = models.CharField(max_length=255)
+    field2 = models.IntegerField()
+
+    def __str__(self):
+        return '%s%d' % (self.field1, self.field2)
+
+    @staticmethod
+    def autocomplete_search_fields():
+        return 'field1'

+ 60 - 0
jet/tests/test_tags.py

@@ -0,0 +1,60 @@
+from django import forms
+from django.forms import ModelChoiceField
+from django.forms.forms import BoundField
+from django.test import TestCase
+from jet.templatetags.jet_tags import select2_lookups
+from jet.tests.models import TestModel, SearchableTestModel
+
+
+class TagsTestCase(TestCase):
+    models = []
+    searchable_models = []
+
+    def setUp(self):
+        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))
+        self.searchable_models.append(SearchableTestModel.objects.create(field1='second', field2=2))
+
+    def test_select2_lookups(self):
+        class TestForm(forms.Form):
+            form_field = forms.ModelChoiceField(SearchableTestModel.objects)
+
+        value = self.searchable_models[0]
+
+        form = TestForm(initial={'form_field': value.pk})
+        field = form['form_field']
+        field = select2_lookups(field)
+        choices = [choice for choice in field.field.choices]
+
+        self.assertEqual(len(choices), 1)
+        self.assertEqual(choices[0][0], value.pk)
+
+    def test_select2_lookups_posted(self):
+        class TestForm(forms.Form):
+            form_field = forms.ModelChoiceField(SearchableTestModel.objects)
+
+        value = self.searchable_models[0]
+
+        form = TestForm(data={'form_field': value.pk})
+        field = form['form_field']
+        field = select2_lookups(field)
+        choices = [choice for choice in field.field.choices]
+
+        self.assertEqual(len(choices), 1)
+        self.assertEqual(choices[0][0], value.pk)
+
+    def test_non_select2_lookups(self):
+        class TestForm(forms.Form):
+            form_field = forms.ModelChoiceField(TestModel.objects)
+
+        value = self.searchable_models[0]
+
+        form = TestForm(initial={'form_field': value.pk})
+        field = form['form_field']
+        field = select2_lookups(field)
+        choices = [choice for choice in field.field.choices]
+
+        self.assertEqual(len(choices), len(self.models) + 1)
+
+

Някои файлове не бяха показани, защото твърде много файлове са промени