Jelajahi Sumber

Merge branch 'dev'

Denis K 9 tahun lalu
induk
melakukan
77b76b734d

+ 10 - 0
CHANGELOG.rst

@@ -1,6 +1,16 @@
 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
 -----
 * [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); } }
 
 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;
 }
 
-img[src$="admin/img/icon-no.gif"]:before {
-  content: $icon-cross;
+img[src$="admin/img/icon-no.gif"] + span {
   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;
   line-height: normal;
   padding: 12px;
-  white-space: normal;
+  white-space: pre-wrap;
 }
 
 .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);
 $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;
 
 $selected-color-1: #e5e2a5;
 $selected-color-2: #fffcc0;
 
-$warning-color-1: #dba4a4;
+$warning-color-1: #d49d9d;
 $warning-color-2: #f0dada;
 $warning-color-3: #c14747;
 

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

@@ -2301,7 +2301,7 @@ textarea {
   height: auto;
   line-height: normal;
   padding: 12px;
-  white-space: normal; }
+  white-space: pre-wrap; }
 
 input[type=checkbox] {
   display: none; }
@@ -2660,19 +2660,19 @@ input[type=checkbox] {
   border-radius: 6px;
   padding: 10px;
   background: #f0dada;
-  color: #dba4a4; }
+  color: #d49d9d; }
 .messagelist .info, .messagelist .debug {
   margin: 0 20px 10px 20px;
   border-radius: 6px;
   padding: 10px;
   background: #e8e8bd;
-  color: #bebe92; }
+  color: #b9b97f; }
 .messagelist .success {
   margin: 0 20px 10px 20px;
   border-radius: 6px;
   padding: 10px;
-  background: #c9eaca;
-  color: #8ecb8e; }
+  background: #c4ecc5;
+  color: #82b982; }
 
 /*
  * General
@@ -3904,7 +3904,7 @@ table#change-history {
 
 .delete-summary {
   background: #f0dada;
-  color: #dba4a4;
+  color: #d49d9d;
   border-radius: 4px;
   padding: 20px;
   margin-bottom: 20px; }
@@ -4683,30 +4683,16 @@ ul.object-tools {
     -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"] {
-  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

File diff ditekan karena terlalu besar
+ 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;
   line-height: normal;
   padding: 12px;
-  white-space: normal; }
+  white-space: pre-wrap; }
 
 input[type=checkbox] {
   display: none; }
@@ -4714,31 +4714,17 @@ ul.object-tools {
     -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"] {
-  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; }
 
-img[src$="admin/img/icon-no.gif"]:before {
-  content: "";
+img[src$="admin/img/icon-no.gif"] + span {
   color: #dba4a4; }
 
-img[src$="admin/img/icon-unknown.gif"]:before {
-  content: ""; }
-
 /*
  * General
  */

File diff ditekan karena terlalu besar
+ 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 $selectedOption = $select.find('option:selected');
                 var url = $selectedOption.data('url');
+                var querysetLookup = $select.data('queryset--lookup');
 
                 if (url) {
                     document.location = $selectedOption.data('url');
+                } else if (querysetLookup) {
+                    document.location = '?' + querysetLookup + '=' + $selectedOption.val();
                 }
             });
         };
@@ -642,8 +645,15 @@
                 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();
             initChangelistFooters();
+            initChangelistImages();
         };
 
         var initTooltips = function() {

File diff ditekan karena terlalu besar
+ 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="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>
 
         <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 %}
 
-<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 %}
         {% if forloop.first %}
             <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.db.models import OneToOneField
 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.template import loader, Context
 from jet import settings
@@ -184,7 +185,8 @@ def select2_lookups(field):
                 '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 initial_value:
@@ -194,14 +196,22 @@ def select2_lookups(field):
                             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):
                 if initial_value:
                     initial_object = model.objects.get(pk=initial_value)
                     attrs['data-object-id'] = initial_value
                     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
 

+ 13 - 0
jet/tests/models.py

@@ -9,3 +9,16 @@ class TestModel(models.Model):
 
     def __str__(self):
         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)
+
+

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini