Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Merged recent trunk changes.

  • Loading branch information...
commit 531e7715da545f930c49919a19e954d41c59b446 2 parents 29d1abb + 1f84b04
Russell Keith-Magee authored September 26, 2012
30  django/conf/__init__.py
@@ -43,13 +43,28 @@ def _setup(self, name):
43 43
                 % (name, ENVIRONMENT_VARIABLE))
44 44
 
45 45
         self._wrapped = Settings(settings_module)
46  
-
  46
+        self._configure_logging()
47 47
 
48 48
     def __getattr__(self, name):
49 49
         if self._wrapped is empty:
50 50
             self._setup(name)
51 51
         return getattr(self._wrapped, name)
52 52
 
  53
+    def _configure_logging(self):
  54
+        """
  55
+        Setup logging from LOGGING_CONFIG and LOGGING settings.
  56
+        """
  57
+        if self.LOGGING_CONFIG:
  58
+            # First find the logging configuration function ...
  59
+            logging_config_path, logging_config_func_name = self.LOGGING_CONFIG.rsplit('.', 1)
  60
+            logging_config_module = importlib.import_module(logging_config_path)
  61
+            logging_config_func = getattr(logging_config_module, logging_config_func_name)
  62
+
  63
+            # Backwards-compatibility shim for #16288 fix
  64
+            compat_patch_logging_config(self.LOGGING)
  65
+
  66
+            # ... then invoke it with the logging settings
  67
+            logging_config_func(self.LOGGING)
53 68
 
54 69
     def configure(self, default_settings=global_settings, **options):
55 70
         """
@@ -133,19 +148,6 @@ def __init__(self, settings_module):
133 148
             os.environ['TZ'] = self.TIME_ZONE
134 149
             time.tzset()
135 150
 
136  
-        # Settings are configured, so we can set up the logger if required
137  
-        if self.LOGGING_CONFIG:
138  
-            # First find the logging configuration function ...
139  
-            logging_config_path, logging_config_func_name = self.LOGGING_CONFIG.rsplit('.', 1)
140  
-            logging_config_module = importlib.import_module(logging_config_path)
141  
-            logging_config_func = getattr(logging_config_module, logging_config_func_name)
142  
-
143  
-            # Backwards-compatibility shim for #16288 fix
144  
-            compat_patch_logging_config(self.LOGGING)
145  
-
146  
-            # ... then invoke it with the logging settings
147  
-            logging_config_func(self.LOGGING)
148  
-
149 151
 
150 152
 class UserSettingsHolder(BaseSettings):
151 153
     """
2  django/contrib/admin/templates/admin/change_form.html
@@ -29,7 +29,7 @@
29 29
 {% if change %}{% if not is_popup %}
30 30
   <ul class="object-tools">
31 31
     {% block object-tools-items %}
32  
-    <li><a href="{% url opts|admin_urlname:'history' original.pk %}" class="historylink">{% trans "History" %}</a></li>
  32
+    <li><a href="{% url opts|admin_urlname:'history' original.pk|admin_urlquote %}" class="historylink">{% trans "History" %}</a></li>
33 33
     {% if has_absolute_url %}<li><a href="{% url 'admin:view_on_site' content_type_id original.pk %}" class="viewsitelink">{% trans "View on site" %}</a></li>{% endif%}
34 34
     {% endblock %}
35 35
   </ul>
6  django/contrib/admin/templates/admin/submit_line.html
... ...
@@ -1,8 +1,8 @@
1  
-{% load i18n %}
  1
+{% load i18n admin_urls %}
2 2
 <div class="submit-row">
3 3
 {% if show_save %}<input type="submit" value="{% trans 'Save' %}" class="default" name="_save" {{ onclick_attrib }}/>{% endif %}
4  
-{% if show_delete_link %}<p class="deletelink-box"><a href="delete/" class="deletelink">{% trans "Delete" %}</a></p>{% endif %}
  4
+{% if show_delete_link %}<p class="deletelink-box"><a href="{% url opts|admin_urlname:'delete' original.pk|admin_urlquote %}" class="deletelink">{% trans "Delete" %}</a></p>{% endif %}
5 5
 {% if show_save_as_new %}<input type="submit" value="{% trans 'Save as new' %}" name="_saveasnew" {{ onclick_attrib }}/>{%endif%}
6  
-{% if show_save_and_add_another %}<input type="submit" value="{% trans 'Save and add another' %}" name="_addanother" {{ onclick_attrib }} />{% endif %}
  6
+{% if show_save_and_add_another %}<input type="submit" value="{% trans 'Save and add another' %}" name="_addanother" {{ onclick_attrib }}/>{% endif %}
7 7
 {% if show_save_and_continue %}<input type="submit" value="{% trans 'Save and continue editing' %}" name="_continue" {{ onclick_attrib }}/>{% endif %}
8 8
 </div>
6  django/contrib/admin/templatetags/admin_modify.py
@@ -28,7 +28,8 @@ def submit_row(context):
28 28
     change = context['change']
29 29
     is_popup = context['is_popup']
30 30
     save_as = context['save_as']
31  
-    return {
  31
+    ctx = {
  32
+        'opts': opts,
32 33
         'onclick_attrib': (opts.get_ordered_objects() and change
33 34
                             and 'onclick="submitOrderForm();"' or ''),
34 35
         'show_delete_link': (not is_popup and context['has_delete_permission']
@@ -40,6 +41,9 @@ def submit_row(context):
40 41
         'is_popup': is_popup,
41 42
         'show_save': True
42 43
     }
  44
+    if context.get('original') is not None:
  45
+        ctx['original'] = context['original']
  46
+    return ctx
43 47
 
44 48
 @register.filter
45 49
 def cell_count(inline_admin_form):
6  django/contrib/admin/util.py
@@ -48,9 +48,9 @@ def prepare_lookup_value(key, value):
48 48
 def quote(s):
49 49
     """
50 50
     Ensure that primary key values do not confuse the admin URLs by escaping
51  
-    any '/', '_' and ':' characters. Similar to urllib.quote, except that the
52  
-    quoting is slightly different so that it doesn't get automatically
53  
-    unquoted by the Web browser.
  51
+    any '/', '_' and ':' and similarly problematic characters.
  52
+    Similar to urllib.quote, except that the quoting is slightly different so
  53
+    that it doesn't get automatically unquoted by the Web browser.
54 54
     """
55 55
     if not isinstance(s, six.string_types):
56 56
         return s
7  django/contrib/admin/views/main.py
@@ -3,6 +3,7 @@
3 3
 
4 4
 from django.core.exceptions import SuspiciousOperation, ImproperlyConfigured
5 5
 from django.core.paginator import InvalidPage
  6
+from django.core.urlresolvers import reverse
6 7
 from django.db import models
7 8
 from django.db.models.fields import FieldDoesNotExist
8 9
 from django.utils.datastructures import SortedDict
@@ -376,4 +377,8 @@ def construct_search(field_name):
376 377
             return qs
377 378
 
378 379
     def url_for_result(self, result):
379  
-        return "%s/" % quote(getattr(result, self.pk_attname))
  380
+        pk = getattr(result, self.pk_attname)
  381
+        return reverse('admin:%s_%s_change' % (self.opts.app_label,
  382
+                                               self.opts.module_name),
  383
+                       args=(quote(pk),),
  384
+                       current_app=self.model_admin.admin_site.name)
12  django/contrib/formtools/tests/__init__.py
@@ -12,6 +12,7 @@
12 12
 from django.contrib.formtools import preview, utils
13 13
 from django.contrib.formtools.wizard import FormWizard
14 14
 from django.test import TestCase
  15
+from django.test.html import parse_html
15 16
 from django.test.utils import override_settings
16 17
 from django.utils import unittest
17 18
 
@@ -218,7 +219,6 @@ def __init__(self, POST=None):
218 219
 )
219 220
 class WizardTests(TestCase):
220 221
     urls = 'django.contrib.formtools.tests.urls'
221  
-    input_re = re.compile('name="([^"]+)" value="([^"]+)"')
222 222
     wizard_step_data = (
223 223
         {
224 224
             '0-name': 'Pony',
@@ -409,14 +409,13 @@ def grab_field_data(self, response):
409 409
         """
410 410
         Pull the appropriate field data from the context to pass to the next wizard step
411 411
         """
412  
-        previous_fields = response.context['previous_fields']
  412
+        previous_fields = parse_html(response.context['previous_fields'])
413 413
         fields = {'wizard_step': response.context['step0']}
414 414
 
415  
-        def grab(m):
416  
-            fields[m.group(1)] = m.group(2)
417  
-            return ''
  415
+        for input_field in previous_fields:
  416
+            input_attrs = dict(input_field.attributes)
  417
+            fields[input_attrs["name"]] = input_attrs["value"]
418 418
 
419  
-        self.input_re.sub(grab, previous_fields)
420 419
         return fields
421 420
 
422 421
     def check_wizard_step(self, response, step_no):
@@ -428,7 +427,6 @@ def check_wizard_step(self, response, step_no):
428 427
         """
429 428
         step_count = len(self.wizard_step_data)
430 429
 
431  
-        self.assertEqual(response.status_code, 200)
432 430
         self.assertContains(response, 'Step %d of %d' % (step_no, step_count))
433 431
 
434 432
         data = self.grab_field_data(response)
5  django/contrib/formtools/tests/wizard/cookiestorage.py
... ...
@@ -1,3 +1,5 @@
  1
+import json
  2
+
1 3
 from django.test import TestCase
2 4
 from django.core import signing
3 5
 from django.core.exceptions import SuspiciousOperation
@@ -41,4 +43,5 @@ def test_reset_cookie(self):
41 43
         storage.init_data()
42 44
         storage.update_response(response)
43 45
         unsigned_cookie_data = cookie_signer.unsign(response.cookies[storage.prefix].value)
44  
-        self.assertEqual(unsigned_cookie_data, '{"step_files":{},"step":null,"extra_data":{},"step_data":{}}')
  46
+        self.assertEqual(json.loads(unsigned_cookie_data),
  47
+            {"step_files": {}, "step": None, "extra_data": {}, "step_data": {}})
2  django/contrib/gis/gdal/__init__.py
@@ -41,7 +41,7 @@
41 41
     from django.contrib.gis.gdal.srs import SpatialReference, CoordTransform
42 42
     from django.contrib.gis.gdal.geometries import OGRGeometry
43 43
     HAS_GDAL = True
44  
-except ImportError:
  44
+except Exception:
45 45
     HAS_GDAL = False
46 46
 
47 47
 try:
2  docs/topics/cache.txt
@@ -286,7 +286,7 @@ cache is multi-process and thread-safe. To use it, set
286 286
 
287 287
 The cache :setting:`LOCATION <CACHES-LOCATION>` is used to identify individual
288 288
 memory stores. If you only have one locmem cache, you can omit the
289  
-:setting:`LOCATION <CACHES-LOCATION>`; however, if you have more that one local
  289
+:setting:`LOCATION <CACHES-LOCATION>`; however, if you have more than one local
290 290
 memory cache, you will need to assign a name to at least one of them in
291 291
 order to keep them separate.
292 292
 
30  docs/topics/logging.txt
@@ -345,36 +345,6 @@ This logging configuration does the following things:
345 345
     printed to the console; ``ERROR`` and ``CRITICAL``
346 346
     messages will also be output via email.
347 347
 
348  
-.. admonition:: Custom handlers and circular imports
349  
-
350  
-    If your ``settings.py`` specifies a custom handler class and the file
351  
-    defining that class also imports ``settings.py`` a circular import will
352  
-    occur.
353  
-
354  
-    For example, if ``settings.py`` contains the following config for
355  
-    :setting:`LOGGING`::
356  
-
357  
-        LOGGING = {
358  
-          'version': 1,
359  
-          'handlers': {
360  
-            'custom_handler': {
361  
-              'level': 'INFO',
362  
-              'class': 'myproject.logconfig.MyHandler',
363  
-            }
364  
-          }
365  
-        }
366  
-
367  
-    and ``myproject/logconfig.py`` has the following line before the
368  
-    ``MyHandler`` definition::
369  
-
370  
-        from django.conf import settings
371  
-
372  
-    then the ``dictconfig`` module will raise an exception like the following::
373  
-
374  
-        ValueError: Unable to configure handler 'custom_handler':
375  
-        Unable to configure handler 'custom_handler':
376  
-        'module' object has no attribute 'logconfig'
377  
-
378 348
 .. _formatter documentation: http://docs.python.org/library/logging.html#formatter-objects
379 349
 
380 350
 Custom logging configuration
10  tests/regressiontests/admin_changelist/tests.py
@@ -6,6 +6,7 @@
6 6
 from django.contrib.admin.options import IncorrectLookupParameters
7 7
 from django.contrib.admin.views.main import ChangeList, SEARCH_VAR, ALL_VAR
8 8
 from django.contrib.auth.models import User
  9
+from django.core.urlresolvers import reverse
9 10
 from django.template import Context, Template
10 11
 from django.test import TestCase
11 12
 from django.test.client import RequestFactory
@@ -65,7 +66,8 @@ def test_result_list_empty_changelist_value(self):
65 66
         template = Template('{% load admin_list %}{% spaceless %}{% result_list cl %}{% endspaceless %}')
66 67
         context = Context({'cl': cl})
67 68
         table_output = template.render(context)
68  
-        row_html = '<tbody><tr class="row1"><th><a href="%d/">name</a></th><td class="nowrap">(None)</td></tr></tbody>' % new_child.id
  69
+        link = reverse('admin:admin_changelist_child_change', args=(new_child.id,))
  70
+        row_html = '<tbody><tr class="row1"><th><a href="%s">name</a></th><td class="nowrap">(None)</td></tr></tbody>' % link
69 71
         self.assertFalse(table_output.find(row_html) == -1,
70 72
             'Failed to find expected row element: %s' % table_output)
71 73
 
@@ -87,7 +89,8 @@ def test_result_list_html(self):
87 89
         template = Template('{% load admin_list %}{% spaceless %}{% result_list cl %}{% endspaceless %}')
88 90
         context = Context({'cl': cl})
89 91
         table_output = template.render(context)
90  
-        row_html = '<tbody><tr class="row1"><th><a href="%d/">name</a></th><td class="nowrap">Parent object</td></tr></tbody>' % new_child.id
  92
+        link = reverse('admin:admin_changelist_child_change', args=(new_child.id,))
  93
+        row_html = '<tbody><tr class="row1"><th><a href="%s">name</a></th><td class="nowrap">Parent object</td></tr></tbody>' % link
91 94
         self.assertFalse(table_output.find(row_html) == -1,
92 95
             'Failed to find expected row element: %s' % table_output)
93 96
 
@@ -425,7 +428,8 @@ def test_dynamic_list_display_links(self):
425 428
         request = self._mocked_authenticated_request('/child/', superuser)
426 429
         response = m.changelist_view(request)
427 430
         for i in range(1, 10):
428  
-            self.assertContains(response, '<a href="%s/">%s</a>' % (i, i))
  431
+            link = reverse('admin:admin_changelist_child_change', args=(i,))
  432
+            self.assertContains(response, '<a href="%s">%s</a>' % (link, i))
429 433
 
430 434
         list_display = m.get_list_display(request)
431 435
         list_display_links = m.get_list_display_links(request, list_display)
7  tests/regressiontests/admin_custom_urls/fixtures/actions.json
@@ -40,12 +40,5 @@
40 40
     "fields": {
41 41
       "description": "An action with a name suspected of being a XSS attempt"
42 42
     }
43  
-  },
44  
-  {
45  
-    "pk": "The name of an action", 
46  
-    "model": "admin_custom_urls.action", 
47  
-    "fields": {
48  
-      "description": "A generic action"
49  
-    }
50 43
   }
51 44
 ]
16  tests/regressiontests/admin_custom_urls/tests.py
... ...
@@ -1,5 +1,6 @@
1 1
 from __future__ import absolute_import, unicode_literals
2 2
 
  3
+from django.contrib.admin.util import quote
3 4
 from django.core.urlresolvers import reverse
4 5
 from django.template.response import TemplateResponse
5 6
 from django.test import TestCase
@@ -67,7 +68,7 @@ def testAdminUrlsNoClash(self):
67 68
 
68 69
         # Ditto, but use reverse() to build the URL
69 70
         url = reverse('admin:%s_action_change' % Action._meta.app_label,
70  
-                args=('add',))
  71
+                args=(quote('add'),))
71 72
         response = self.client.get(url)
72 73
         self.assertEqual(response.status_code, 200)
73 74
         self.assertContains(response, 'Change action')
@@ -75,19 +76,8 @@ def testAdminUrlsNoClash(self):
75 76
         # Should correctly get the change_view for the model instance with the
76 77
         # funny-looking PK (the one wth a 'path/to/html/document.html' value)
77 78
         url = reverse('admin:%s_action_change' % Action._meta.app_label,
78  
-                args=("path/to/html/document.html",))
  79
+                args=(quote("path/to/html/document.html"),))
79 80
         response = self.client.get(url)
80 81
         self.assertEqual(response.status_code, 200)
81 82
         self.assertContains(response, 'Change action')
82 83
         self.assertContains(response, 'value="path/to/html/document.html"')
83  
-
84  
-    def testChangeViewHistoryButton(self):
85  
-        url = reverse('admin:%s_action_change' % Action._meta.app_label,
86  
-                args=('The name of an action',))
87  
-        response = self.client.get(url)
88  
-        self.assertEqual(response.status_code, 200)
89  
-        expected_link = reverse('admin:%s_action_history' %
90  
-                                Action._meta.app_label,
91  
-                                args=('The name of an action',))
92  
-        self.assertContains(response, '<a href="%s" class="historylink"' %
93  
-                            expected_link)
75  tests/regressiontests/admin_views/tests.py
@@ -261,19 +261,21 @@ def testChangeListSortingMultiple(self):
261 261
         p1 = Person.objects.create(name="Chris", gender=1, alive=True)
262 262
         p2 = Person.objects.create(name="Chris", gender=2, alive=True)
263 263
         p3 = Person.objects.create(name="Bob", gender=1, alive=True)
264  
-        link = '<a href="%s/'
  264
+        link1 = reverse('admin:admin_views_person_change', args=(p1.pk,))
  265
+        link2 = reverse('admin:admin_views_person_change', args=(p2.pk,))
  266
+        link3 = reverse('admin:admin_views_person_change', args=(p3.pk,))
265 267
 
266 268
         # Sort by name, gender
267 269
         # This hard-codes the URL because it'll fail if it runs against the
268 270
         # 'admin2' custom admin (which doesn't have the Person model).
269 271
         response = self.client.get('/test_admin/admin/admin_views/person/', {'o': '1.2'})
270  
-        self.assertContentBefore(response, link % p3.id, link % p1.id)
271  
-        self.assertContentBefore(response, link % p1.id, link % p2.id)
  272
+        self.assertContentBefore(response, link3, link1)
  273
+        self.assertContentBefore(response, link1, link2)
272 274
 
273 275
         # Sort by gender descending, name
274 276
         response = self.client.get('/test_admin/admin/admin_views/person/', {'o': '-2.1'})
275  
-        self.assertContentBefore(response, link % p2.id, link % p3.id)
276  
-        self.assertContentBefore(response, link % p3.id, link % p1.id)
  277
+        self.assertContentBefore(response, link2, link3)
  278
+        self.assertContentBefore(response, link3, link1)
277 279
 
278 280
     def testChangeListSortingPreserveQuerySetOrdering(self):
279 281
         """
@@ -285,37 +287,41 @@ def testChangeListSortingPreserveQuerySetOrdering(self):
285 287
         p1 = Person.objects.create(name="Amy", gender=1, alive=True, age=80)
286 288
         p2 = Person.objects.create(name="Bob", gender=1, alive=True, age=70)
287 289
         p3 = Person.objects.create(name="Chris", gender=2, alive=False, age=60)
288  
-        link = '<a href="%s/'
  290
+        link1 = reverse('admin:admin_views_person_change', args=(p1.pk,))
  291
+        link2 = reverse('admin:admin_views_person_change', args=(p2.pk,))
  292
+        link3 = reverse('admin:admin_views_person_change', args=(p3.pk,))
289 293
 
290 294
         # This hard-codes the URL because it'll fail if it runs against the
291 295
         # 'admin2' custom admin (which doesn't have the Person model).
292 296
         response = self.client.get('/test_admin/admin/admin_views/person/', {})
293  
-        self.assertContentBefore(response, link % p3.id, link % p2.id)
294  
-        self.assertContentBefore(response, link % p2.id, link % p1.id)
  297
+        self.assertContentBefore(response, link3, link2)
  298
+        self.assertContentBefore(response, link2, link1)
295 299
 
296 300
     def testChangeListSortingModelMeta(self):
297 301
         # Test ordering on Model Meta is respected
298 302
 
299 303
         l1 = Language.objects.create(iso='ur', name='Urdu')
300 304
         l2 = Language.objects.create(iso='ar', name='Arabic')
301  
-        link = '<a href="%s/'
  305
+        link1 = reverse('admin:admin_views_language_change', args=(quote(l1.pk),))
  306
+        link2 = reverse('admin:admin_views_language_change', args=(quote(l2.pk),))
302 307
 
303 308
         response = self.client.get('/test_admin/admin/admin_views/language/', {})
304  
-        self.assertContentBefore(response, link % l2.pk, link % l1.pk)
  309
+        self.assertContentBefore(response, link2, link1)
305 310
 
306 311
         # Test we can override with query string
307 312
         response = self.client.get('/test_admin/admin/admin_views/language/', {'o': '-1'})
308  
-        self.assertContentBefore(response, link % l1.pk, link % l2.pk)
  313
+        self.assertContentBefore(response, link1, link2)
309 314
 
310 315
     def testChangeListSortingOverrideModelAdmin(self):
311 316
         # Test ordering on Model Admin is respected, and overrides Model Meta
312 317
         dt = datetime.datetime.now()
313 318
         p1 = Podcast.objects.create(name="A", release_date=dt)
314 319
         p2 = Podcast.objects.create(name="B", release_date=dt - datetime.timedelta(10))
  320
+        link1 = reverse('admin:admin_views_podcast_change', args=(p1.pk,))
  321
+        link2 = reverse('admin:admin_views_podcast_change', args=(p2.pk,))
315 322
 
316  
-        link = '<a href="%s/'
317 323
         response = self.client.get('/test_admin/admin/admin_views/podcast/', {})
318  
-        self.assertContentBefore(response, link % p1.pk, link % p2.pk)
  324
+        self.assertContentBefore(response, link1, link2)
319 325
 
320 326
     def testMultipleSortSameField(self):
321 327
         # Check that we get the columns we expect if we have two columns
@@ -323,14 +329,16 @@ def testMultipleSortSameField(self):
323 329
         dt = datetime.datetime.now()
324 330
         p1 = Podcast.objects.create(name="A", release_date=dt)
325 331
         p2 = Podcast.objects.create(name="B", release_date=dt - datetime.timedelta(10))
  332
+        link1 = reverse('admin:admin_views_podcast_change', args=(quote(p1.pk),))
  333
+        link2 = reverse('admin:admin_views_podcast_change', args=(quote(p2.pk),))
326 334
 
327  
-        link = '<a href="%s/'
328 335
         response = self.client.get('/test_admin/admin/admin_views/podcast/', {})
329  
-        self.assertContentBefore(response, link % p1.pk, link % p2.pk)
  336
+        self.assertContentBefore(response, link1, link2)
330 337
 
331 338
         p1 = ComplexSortedPerson.objects.create(name="Bob", age=10)
332 339
         p2 = ComplexSortedPerson.objects.create(name="Amy", age=20)
333  
-        link = '<a href="%s/'
  340
+        link1 = reverse('admin:admin_views_complexsortedperson_change', args=(p1.pk,))
  341
+        link2 = reverse('admin:admin_views_complexsortedperson_change', args=(p2.pk,))
334 342
 
335 343
         response = self.client.get('/test_admin/admin/admin_views/complexsortedperson/', {})
336 344
         # Should have 5 columns (including action checkbox col)
@@ -343,7 +351,7 @@ def testMultipleSortSameField(self):
343 351
         self.assertContentBefore(response, 'Name', 'Colored name')
344 352
 
345 353
         # Check sorting - should be by name
346  
-        self.assertContentBefore(response, link % p2.id, link % p1.id)
  354
+        self.assertContentBefore(response, link2, link1)
347 355
 
348 356
     def testSortIndicatorsAdminOrder(self):
349 357
         """
@@ -462,10 +470,12 @@ def testNamedGroupFieldChoicesChangeList(self):
462 470
         for rows corresponding to instances of a model in which a named group
463 471
         has been used in the choices option of a field.
464 472
         """
  473
+        link1 = reverse('admin:admin_views_fabric_change', args=(1,), current_app=self.urlbit)
  474
+        link2 = reverse('admin:admin_views_fabric_change', args=(2,), current_app=self.urlbit)
465 475
         response = self.client.get('/test_admin/%s/admin_views/fabric/' % self.urlbit)
466 476
         fail_msg = "Changelist table isn't showing the right human-readable values set by a model field 'choices' option named group."
467  
-        self.assertContains(response, '<a href="1/">Horizontal</a>', msg_prefix=fail_msg, html=True)
468  
-        self.assertContains(response, '<a href="2/">Vertical</a>', msg_prefix=fail_msg, html=True)
  477
+        self.assertContains(response, '<a href="%s">Horizontal</a>' % link1, msg_prefix=fail_msg, html=True)
  478
+        self.assertContains(response, '<a href="%s">Vertical</a>' % link2, msg_prefix=fail_msg, html=True)
469 479
 
470 480
     def testNamedGroupFieldChoicesFilter(self):
471 481
         """
@@ -1375,9 +1385,12 @@ def test_get_change_view(self):
1375 1385
         self.assertEqual(response.status_code, 200)
1376 1386
 
1377 1387
     def test_changelist_to_changeform_link(self):
1378  
-        "The link from the changelist referring to the changeform of the object should be quoted"
1379  
-        response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/')
1380  
-        should_contain = """<th><a href="%s/">%s</a></th></tr>""" % (escape(quote(self.pk)), escape(self.pk))
  1388
+        "Link to the changeform of the object in changelist should use reverse() and be quoted -- #18072"
  1389
+        prefix = '/test_admin/admin/admin_views/modelwithstringprimarykey/'
  1390
+        response = self.client.get(prefix)
  1391
+        # this URL now comes through reverse(), thus iri_to_uri encoding
  1392
+        pk_final_url = escape(iri_to_uri(quote(self.pk)))
  1393
+        should_contain = """<th><a href="%s%s/">%s</a></th>""" % (prefix, pk_final_url, escape(self.pk))
1381 1394
         self.assertContains(response, should_contain)
1382 1395
 
1383 1396
     def test_recentactions_link(self):
@@ -1445,6 +1458,18 @@ def test_shortcut_view_with_escaping(self):
1445 1458
         should_contain = '/%s/" class="viewsitelink">' % model.pk
1446 1459
         self.assertContains(response, should_contain)
1447 1460
 
  1461
+    def test_change_view_history_link(self):
  1462
+        """Object history button link should work and contain the pk value quoted."""
  1463
+        url = reverse('admin:%s_modelwithstringprimarykey_change' %
  1464
+                          ModelWithStringPrimaryKey._meta.app_label,
  1465
+                      args=(quote(self.pk),))
  1466
+        response = self.client.get(url)
  1467
+        self.assertEqual(response.status_code, 200)
  1468
+        expected_link = reverse('admin:%s_modelwithstringprimarykey_history' %
  1469
+                                    ModelWithStringPrimaryKey._meta.app_label,
  1470
+                                args=(quote(self.pk),))
  1471
+        self.assertContains(response, '<a href="%s" class="historylink"' % expected_link)
  1472
+
1448 1473
 
1449 1474
 @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
1450 1475
 class SecureViewTests(TestCase):
@@ -2028,12 +2053,14 @@ def test_pk_hidden_fields_with_list_display_links(self):
2028 2053
         """
2029 2054
         story1 = OtherStory.objects.create(title='The adventures of Guido', content='Once upon a time in Djangoland...')
2030 2055
         story2 = OtherStory.objects.create(title='Crouching Tiger, Hidden Python', content='The Python was sneaking into...')
  2056
+        link1 = reverse('admin:admin_views_otherstory_change', args=(story1.pk,))
  2057
+        link2 = reverse('admin:admin_views_otherstory_change', args=(story2.pk,))
2031 2058
         response = self.client.get('/test_admin/admin/admin_views/otherstory/')
2032 2059
         self.assertContains(response, 'id="id_form-0-id"', 1)  # Only one hidden field, in a separate place than the table.
2033 2060
         self.assertContains(response, 'id="id_form-1-id"', 1)
2034 2061
         self.assertContains(response, '<div class="hiddenfields">\n<input type="hidden" name="form-0-id" value="%d" id="id_form-0-id" /><input type="hidden" name="form-1-id" value="%d" id="id_form-1-id" />\n</div>' % (story2.id, story1.id), html=True)
2035  
-        self.assertContains(response, '<th><a href="%d/">%d</a></th>' % (story1.id, story1.id), 1)
2036  
-        self.assertContains(response, '<th><a href="%d/">%d</a></th>' % (story2.id, story2.id), 1)
  2062
+        self.assertContains(response, '<th><a href="%s">%d</a></th>' % (link1, story1.id), 1)
  2063
+        self.assertContains(response, '<th><a href="%s">%d</a></th>' % (link2, story2.id), 1)
2037 2064
 
2038 2065
 
2039 2066
 @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
8  tests/regressiontests/logging_tests/logconfig.py
... ...
@@ -0,0 +1,8 @@
  1
+import logging
  2
+
  3
+from django.conf import settings
  4
+
  5
+class MyHandler(logging.Handler):
  6
+    def __init__(self):
  7
+        logging.Handler.__init__(self)
  8
+        self.config = settings.LOGGING
29  tests/regressiontests/logging_tests/tests.py
@@ -10,6 +10,8 @@
10 10
 from django.test.utils import override_settings
11 11
 from django.utils.log import CallbackFilter, RequireDebugFalse
12 12
 
  13
+from ..admin_scripts.tests import AdminScriptTestCase
  14
+
13 15
 
14 16
 # logging config prior to using filter with mail_admins
15 17
 OLD_LOGGING = {
@@ -253,3 +255,30 @@ def test_truncate_subject(self):
253 255
 
254 256
         self.assertEqual(len(mail.outbox), 1)
255 257
         self.assertEqual(mail.outbox[0].subject, expected_subject)
  258
+
  259
+
  260
+class SettingsConfigTest(AdminScriptTestCase):
  261
+    """
  262
+    Test that accessing settings in a custom logging handler does not trigger
  263
+    a circular import error.
  264
+    """
  265
+    def setUp(self):
  266
+        log_config = """{
  267
+    'version': 1,
  268
+    'handlers': {
  269
+        'custom_handler': {
  270
+            'level': 'INFO',
  271
+            'class': 'logging_tests.logconfig.MyHandler',
  272
+        }
  273
+    }
  274
+}"""
  275
+        self.write_settings('settings.py', sdict={'LOGGING': log_config})
  276
+
  277
+    def tearDown(self):
  278
+        self.remove_settings('settings.py')
  279
+
  280
+    def test_circular_dependency(self):
  281
+        # validate is just an example command to trigger settings configuration
  282
+        out, err = self.run_manage(['validate'])
  283
+        self.assertNoOutput(err)
  284
+        self.assertOutput(out, "0 errors found")
2  tests/regressiontests/utils/html.py
@@ -154,4 +154,4 @@ def test_remove_tags(self):
154 154
             ("<a>x</a> <p><b>y</b></p>", "a b", "x <p>y</p>"),
155 155
         )
156 156
         for value, tags, output in items:
157  
-            self.assertEquals(f(value, tags), output)
  157
+            self.assertEqual(f(value, tags), output)

0 notes on commit 531e771

Please sign in to comment.
Something went wrong with that request. Please try again.