Merged recent trunk changes.

This commit is contained in:
Russell Keith-Magee 2012-09-26 07:09:23 +08:00
commit 531e7715da
18 changed files with 144 additions and 111 deletions

View File

@ -43,13 +43,28 @@ class LazySettings(LazyObject):
% (name, ENVIRONMENT_VARIABLE)) % (name, ENVIRONMENT_VARIABLE))
self._wrapped = Settings(settings_module) self._wrapped = Settings(settings_module)
self._configure_logging()
def __getattr__(self, name): def __getattr__(self, name):
if self._wrapped is empty: if self._wrapped is empty:
self._setup(name) self._setup(name)
return getattr(self._wrapped, name) return getattr(self._wrapped, name)
def _configure_logging(self):
"""
Setup logging from LOGGING_CONFIG and LOGGING settings.
"""
if self.LOGGING_CONFIG:
# First find the logging configuration function ...
logging_config_path, logging_config_func_name = self.LOGGING_CONFIG.rsplit('.', 1)
logging_config_module = importlib.import_module(logging_config_path)
logging_config_func = getattr(logging_config_module, logging_config_func_name)
# Backwards-compatibility shim for #16288 fix
compat_patch_logging_config(self.LOGGING)
# ... then invoke it with the logging settings
logging_config_func(self.LOGGING)
def configure(self, default_settings=global_settings, **options): def configure(self, default_settings=global_settings, **options):
""" """
@ -133,19 +148,6 @@ class Settings(BaseSettings):
os.environ['TZ'] = self.TIME_ZONE os.environ['TZ'] = self.TIME_ZONE
time.tzset() time.tzset()
# Settings are configured, so we can set up the logger if required
if self.LOGGING_CONFIG:
# First find the logging configuration function ...
logging_config_path, logging_config_func_name = self.LOGGING_CONFIG.rsplit('.', 1)
logging_config_module = importlib.import_module(logging_config_path)
logging_config_func = getattr(logging_config_module, logging_config_func_name)
# Backwards-compatibility shim for #16288 fix
compat_patch_logging_config(self.LOGGING)
# ... then invoke it with the logging settings
logging_config_func(self.LOGGING)
class UserSettingsHolder(BaseSettings): class UserSettingsHolder(BaseSettings):
""" """

View File

@ -29,7 +29,7 @@
{% if change %}{% if not is_popup %} {% if change %}{% if not is_popup %}
<ul class="object-tools"> <ul class="object-tools">
{% block object-tools-items %} {% block object-tools-items %}
<li><a href="{% url opts|admin_urlname:'history' original.pk %}" class="historylink">{% trans "History" %}</a></li> <li><a href="{% url opts|admin_urlname:'history' original.pk|admin_urlquote %}" class="historylink">{% trans "History" %}</a></li>
{% 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%} {% 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%}
{% endblock %} {% endblock %}
</ul> </ul>

View File

@ -1,7 +1,7 @@
{% load i18n %} {% load i18n admin_urls %}
<div class="submit-row"> <div class="submit-row">
{% if show_save %}<input type="submit" value="{% trans 'Save' %}" class="default" name="_save" {{ onclick_attrib }}/>{% endif %} {% if show_save %}<input type="submit" value="{% trans 'Save' %}" class="default" name="_save" {{ onclick_attrib }}/>{% endif %}
{% if show_delete_link %}<p class="deletelink-box"><a href="delete/" class="deletelink">{% trans "Delete" %}</a></p>{% endif %} {% 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 %}
{% if show_save_as_new %}<input type="submit" value="{% trans 'Save as new' %}" name="_saveasnew" {{ onclick_attrib }}/>{%endif%} {% if show_save_as_new %}<input type="submit" value="{% trans 'Save as new' %}" name="_saveasnew" {{ onclick_attrib }}/>{%endif%}
{% if show_save_and_add_another %}<input type="submit" value="{% trans 'Save and add another' %}" name="_addanother" {{ onclick_attrib }}/>{% endif %} {% if show_save_and_add_another %}<input type="submit" value="{% trans 'Save and add another' %}" name="_addanother" {{ onclick_attrib }}/>{% endif %}
{% if show_save_and_continue %}<input type="submit" value="{% trans 'Save and continue editing' %}" name="_continue" {{ onclick_attrib }}/>{% endif %} {% if show_save_and_continue %}<input type="submit" value="{% trans 'Save and continue editing' %}" name="_continue" {{ onclick_attrib }}/>{% endif %}

View File

@ -28,7 +28,8 @@ def submit_row(context):
change = context['change'] change = context['change']
is_popup = context['is_popup'] is_popup = context['is_popup']
save_as = context['save_as'] save_as = context['save_as']
return { ctx = {
'opts': opts,
'onclick_attrib': (opts.get_ordered_objects() and change 'onclick_attrib': (opts.get_ordered_objects() and change
and 'onclick="submitOrderForm();"' or ''), and 'onclick="submitOrderForm();"' or ''),
'show_delete_link': (not is_popup and context['has_delete_permission'] 'show_delete_link': (not is_popup and context['has_delete_permission']
@ -40,6 +41,9 @@ def submit_row(context):
'is_popup': is_popup, 'is_popup': is_popup,
'show_save': True 'show_save': True
} }
if context.get('original') is not None:
ctx['original'] = context['original']
return ctx
@register.filter @register.filter
def cell_count(inline_admin_form): def cell_count(inline_admin_form):

View File

@ -48,9 +48,9 @@ def prepare_lookup_value(key, value):
def quote(s): def quote(s):
""" """
Ensure that primary key values do not confuse the admin URLs by escaping Ensure that primary key values do not confuse the admin URLs by escaping
any '/', '_' and ':' characters. Similar to urllib.quote, except that the any '/', '_' and ':' and similarly problematic characters.
quoting is slightly different so that it doesn't get automatically Similar to urllib.quote, except that the quoting is slightly different so
unquoted by the Web browser. that it doesn't get automatically unquoted by the Web browser.
""" """
if not isinstance(s, six.string_types): if not isinstance(s, six.string_types):
return s return s

View File

@ -3,6 +3,7 @@ from functools import reduce
from django.core.exceptions import SuspiciousOperation, ImproperlyConfigured from django.core.exceptions import SuspiciousOperation, ImproperlyConfigured
from django.core.paginator import InvalidPage from django.core.paginator import InvalidPage
from django.core.urlresolvers import reverse
from django.db import models from django.db import models
from django.db.models.fields import FieldDoesNotExist from django.db.models.fields import FieldDoesNotExist
from django.utils.datastructures import SortedDict from django.utils.datastructures import SortedDict
@ -376,4 +377,8 @@ class ChangeList(object):
return qs return qs
def url_for_result(self, result): def url_for_result(self, result):
return "%s/" % quote(getattr(result, self.pk_attname)) pk = getattr(result, self.pk_attname)
return reverse('admin:%s_%s_change' % (self.opts.app_label,
self.opts.module_name),
args=(quote(pk),),
current_app=self.model_admin.admin_site.name)

View File

@ -12,6 +12,7 @@ from django.conf import settings
from django.contrib.formtools import preview, utils from django.contrib.formtools import preview, utils
from django.contrib.formtools.wizard import FormWizard from django.contrib.formtools.wizard import FormWizard
from django.test import TestCase from django.test import TestCase
from django.test.html import parse_html
from django.test.utils import override_settings from django.test.utils import override_settings
from django.utils import unittest from django.utils import unittest
@ -218,7 +219,6 @@ class DummyRequest(http.HttpRequest):
) )
class WizardTests(TestCase): class WizardTests(TestCase):
urls = 'django.contrib.formtools.tests.urls' urls = 'django.contrib.formtools.tests.urls'
input_re = re.compile('name="([^"]+)" value="([^"]+)"')
wizard_step_data = ( wizard_step_data = (
{ {
'0-name': 'Pony', '0-name': 'Pony',
@ -409,14 +409,13 @@ class WizardTests(TestCase):
""" """
Pull the appropriate field data from the context to pass to the next wizard step Pull the appropriate field data from the context to pass to the next wizard step
""" """
previous_fields = response.context['previous_fields'] previous_fields = parse_html(response.context['previous_fields'])
fields = {'wizard_step': response.context['step0']} fields = {'wizard_step': response.context['step0']}
def grab(m): for input_field in previous_fields:
fields[m.group(1)] = m.group(2) input_attrs = dict(input_field.attributes)
return '' fields[input_attrs["name"]] = input_attrs["value"]
self.input_re.sub(grab, previous_fields)
return fields return fields
def check_wizard_step(self, response, step_no): def check_wizard_step(self, response, step_no):
@ -428,7 +427,6 @@ class WizardTests(TestCase):
""" """
step_count = len(self.wizard_step_data) step_count = len(self.wizard_step_data)
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Step %d of %d' % (step_no, step_count)) self.assertContains(response, 'Step %d of %d' % (step_no, step_count))
data = self.grab_field_data(response) data = self.grab_field_data(response)

View File

@ -1,3 +1,5 @@
import json
from django.test import TestCase from django.test import TestCase
from django.core import signing from django.core import signing
from django.core.exceptions import SuspiciousOperation from django.core.exceptions import SuspiciousOperation
@ -41,4 +43,5 @@ class TestCookieStorage(TestStorage, TestCase):
storage.init_data() storage.init_data()
storage.update_response(response) storage.update_response(response)
unsigned_cookie_data = cookie_signer.unsign(response.cookies[storage.prefix].value) unsigned_cookie_data = cookie_signer.unsign(response.cookies[storage.prefix].value)
self.assertEqual(unsigned_cookie_data, '{"step_files":{},"step":null,"extra_data":{},"step_data":{}}') self.assertEqual(json.loads(unsigned_cookie_data),
{"step_files": {}, "step": None, "extra_data": {}, "step_data": {}})

View File

@ -41,7 +41,7 @@ try:
from django.contrib.gis.gdal.srs import SpatialReference, CoordTransform from django.contrib.gis.gdal.srs import SpatialReference, CoordTransform
from django.contrib.gis.gdal.geometries import OGRGeometry from django.contrib.gis.gdal.geometries import OGRGeometry
HAS_GDAL = True HAS_GDAL = True
except ImportError: except Exception:
HAS_GDAL = False HAS_GDAL = False
try: try:

View File

@ -286,7 +286,7 @@ cache is multi-process and thread-safe. To use it, set
The cache :setting:`LOCATION <CACHES-LOCATION>` is used to identify individual The cache :setting:`LOCATION <CACHES-LOCATION>` is used to identify individual
memory stores. If you only have one locmem cache, you can omit the memory stores. If you only have one locmem cache, you can omit the
:setting:`LOCATION <CACHES-LOCATION>`; however, if you have more that one local :setting:`LOCATION <CACHES-LOCATION>`; however, if you have more than one local
memory cache, you will need to assign a name to at least one of them in memory cache, you will need to assign a name to at least one of them in
order to keep them separate. order to keep them separate.

View File

@ -345,36 +345,6 @@ This logging configuration does the following things:
printed to the console; ``ERROR`` and ``CRITICAL`` printed to the console; ``ERROR`` and ``CRITICAL``
messages will also be output via email. messages will also be output via email.
.. admonition:: Custom handlers and circular imports
If your ``settings.py`` specifies a custom handler class and the file
defining that class also imports ``settings.py`` a circular import will
occur.
For example, if ``settings.py`` contains the following config for
:setting:`LOGGING`::
LOGGING = {
'version': 1,
'handlers': {
'custom_handler': {
'level': 'INFO',
'class': 'myproject.logconfig.MyHandler',
}
}
}
and ``myproject/logconfig.py`` has the following line before the
``MyHandler`` definition::
from django.conf import settings
then the ``dictconfig`` module will raise an exception like the following::
ValueError: Unable to configure handler 'custom_handler':
Unable to configure handler 'custom_handler':
'module' object has no attribute 'logconfig'
.. _formatter documentation: http://docs.python.org/library/logging.html#formatter-objects .. _formatter documentation: http://docs.python.org/library/logging.html#formatter-objects
Custom logging configuration Custom logging configuration

View File

@ -6,6 +6,7 @@ from django.contrib import admin
from django.contrib.admin.options import IncorrectLookupParameters from django.contrib.admin.options import IncorrectLookupParameters
from django.contrib.admin.views.main import ChangeList, SEARCH_VAR, ALL_VAR from django.contrib.admin.views.main import ChangeList, SEARCH_VAR, ALL_VAR
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from django.template import Context, Template from django.template import Context, Template
from django.test import TestCase from django.test import TestCase
from django.test.client import RequestFactory from django.test.client import RequestFactory
@ -65,7 +66,8 @@ class ChangeListTests(TestCase):
template = Template('{% load admin_list %}{% spaceless %}{% result_list cl %}{% endspaceless %}') template = Template('{% load admin_list %}{% spaceless %}{% result_list cl %}{% endspaceless %}')
context = Context({'cl': cl}) context = Context({'cl': cl})
table_output = template.render(context) table_output = template.render(context)
row_html = '<tbody><tr class="row1"><th><a href="%d/">name</a></th><td class="nowrap">(None)</td></tr></tbody>' % new_child.id link = reverse('admin:admin_changelist_child_change', args=(new_child.id,))
row_html = '<tbody><tr class="row1"><th><a href="%s">name</a></th><td class="nowrap">(None)</td></tr></tbody>' % link
self.assertFalse(table_output.find(row_html) == -1, self.assertFalse(table_output.find(row_html) == -1,
'Failed to find expected row element: %s' % table_output) 'Failed to find expected row element: %s' % table_output)
@ -87,7 +89,8 @@ class ChangeListTests(TestCase):
template = Template('{% load admin_list %}{% spaceless %}{% result_list cl %}{% endspaceless %}') template = Template('{% load admin_list %}{% spaceless %}{% result_list cl %}{% endspaceless %}')
context = Context({'cl': cl}) context = Context({'cl': cl})
table_output = template.render(context) table_output = template.render(context)
row_html = '<tbody><tr class="row1"><th><a href="%d/">name</a></th><td class="nowrap">Parent object</td></tr></tbody>' % new_child.id link = reverse('admin:admin_changelist_child_change', args=(new_child.id,))
row_html = '<tbody><tr class="row1"><th><a href="%s">name</a></th><td class="nowrap">Parent object</td></tr></tbody>' % link
self.assertFalse(table_output.find(row_html) == -1, self.assertFalse(table_output.find(row_html) == -1,
'Failed to find expected row element: %s' % table_output) 'Failed to find expected row element: %s' % table_output)
@ -425,7 +428,8 @@ class ChangeListTests(TestCase):
request = self._mocked_authenticated_request('/child/', superuser) request = self._mocked_authenticated_request('/child/', superuser)
response = m.changelist_view(request) response = m.changelist_view(request)
for i in range(1, 10): for i in range(1, 10):
self.assertContains(response, '<a href="%s/">%s</a>' % (i, i)) link = reverse('admin:admin_changelist_child_change', args=(i,))
self.assertContains(response, '<a href="%s">%s</a>' % (link, i))
list_display = m.get_list_display(request) list_display = m.get_list_display(request)
list_display_links = m.get_list_display_links(request, list_display) list_display_links = m.get_list_display_links(request, list_display)

View File

@ -40,12 +40,5 @@
"fields": { "fields": {
"description": "An action with a name suspected of being a XSS attempt" "description": "An action with a name suspected of being a XSS attempt"
} }
},
{
"pk": "The name of an action",
"model": "admin_custom_urls.action",
"fields": {
"description": "A generic action"
}
} }
] ]

View File

@ -1,5 +1,6 @@
from __future__ import absolute_import, unicode_literals from __future__ import absolute_import, unicode_literals
from django.contrib.admin.util import quote
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.test import TestCase from django.test import TestCase
@ -67,7 +68,7 @@ class AdminCustomUrlsTest(TestCase):
# Ditto, but use reverse() to build the URL # Ditto, but use reverse() to build the URL
url = reverse('admin:%s_action_change' % Action._meta.app_label, url = reverse('admin:%s_action_change' % Action._meta.app_label,
args=('add',)) args=(quote('add'),))
response = self.client.get(url) response = self.client.get(url)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Change action') self.assertContains(response, 'Change action')
@ -75,19 +76,8 @@ class AdminCustomUrlsTest(TestCase):
# Should correctly get the change_view for the model instance with the # Should correctly get the change_view for the model instance with the
# funny-looking PK (the one wth a 'path/to/html/document.html' value) # funny-looking PK (the one wth a 'path/to/html/document.html' value)
url = reverse('admin:%s_action_change' % Action._meta.app_label, url = reverse('admin:%s_action_change' % Action._meta.app_label,
args=("path/to/html/document.html",)) args=(quote("path/to/html/document.html"),))
response = self.client.get(url) response = self.client.get(url)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Change action') self.assertContains(response, 'Change action')
self.assertContains(response, 'value="path/to/html/document.html"') self.assertContains(response, 'value="path/to/html/document.html"')
def testChangeViewHistoryButton(self):
url = reverse('admin:%s_action_change' % Action._meta.app_label,
args=('The name of an action',))
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
expected_link = reverse('admin:%s_action_history' %
Action._meta.app_label,
args=('The name of an action',))
self.assertContains(response, '<a href="%s" class="historylink"' %
expected_link)

View File

@ -261,19 +261,21 @@ class AdminViewBasicTest(TestCase):
p1 = Person.objects.create(name="Chris", gender=1, alive=True) p1 = Person.objects.create(name="Chris", gender=1, alive=True)
p2 = Person.objects.create(name="Chris", gender=2, alive=True) p2 = Person.objects.create(name="Chris", gender=2, alive=True)
p3 = Person.objects.create(name="Bob", gender=1, alive=True) p3 = Person.objects.create(name="Bob", gender=1, alive=True)
link = '<a href="%s/' link1 = reverse('admin:admin_views_person_change', args=(p1.pk,))
link2 = reverse('admin:admin_views_person_change', args=(p2.pk,))
link3 = reverse('admin:admin_views_person_change', args=(p3.pk,))
# Sort by name, gender # Sort by name, gender
# This hard-codes the URL because it'll fail if it runs against the # This hard-codes the URL because it'll fail if it runs against the
# 'admin2' custom admin (which doesn't have the Person model). # 'admin2' custom admin (which doesn't have the Person model).
response = self.client.get('/test_admin/admin/admin_views/person/', {'o': '1.2'}) response = self.client.get('/test_admin/admin/admin_views/person/', {'o': '1.2'})
self.assertContentBefore(response, link % p3.id, link % p1.id) self.assertContentBefore(response, link3, link1)
self.assertContentBefore(response, link % p1.id, link % p2.id) self.assertContentBefore(response, link1, link2)
# Sort by gender descending, name # Sort by gender descending, name
response = self.client.get('/test_admin/admin/admin_views/person/', {'o': '-2.1'}) response = self.client.get('/test_admin/admin/admin_views/person/', {'o': '-2.1'})
self.assertContentBefore(response, link % p2.id, link % p3.id) self.assertContentBefore(response, link2, link3)
self.assertContentBefore(response, link % p3.id, link % p1.id) self.assertContentBefore(response, link3, link1)
def testChangeListSortingPreserveQuerySetOrdering(self): def testChangeListSortingPreserveQuerySetOrdering(self):
""" """
@ -285,37 +287,41 @@ class AdminViewBasicTest(TestCase):
p1 = Person.objects.create(name="Amy", gender=1, alive=True, age=80) p1 = Person.objects.create(name="Amy", gender=1, alive=True, age=80)
p2 = Person.objects.create(name="Bob", gender=1, alive=True, age=70) p2 = Person.objects.create(name="Bob", gender=1, alive=True, age=70)
p3 = Person.objects.create(name="Chris", gender=2, alive=False, age=60) p3 = Person.objects.create(name="Chris", gender=2, alive=False, age=60)
link = '<a href="%s/' link1 = reverse('admin:admin_views_person_change', args=(p1.pk,))
link2 = reverse('admin:admin_views_person_change', args=(p2.pk,))
link3 = reverse('admin:admin_views_person_change', args=(p3.pk,))
# This hard-codes the URL because it'll fail if it runs against the # This hard-codes the URL because it'll fail if it runs against the
# 'admin2' custom admin (which doesn't have the Person model). # 'admin2' custom admin (which doesn't have the Person model).
response = self.client.get('/test_admin/admin/admin_views/person/', {}) response = self.client.get('/test_admin/admin/admin_views/person/', {})
self.assertContentBefore(response, link % p3.id, link % p2.id) self.assertContentBefore(response, link3, link2)
self.assertContentBefore(response, link % p2.id, link % p1.id) self.assertContentBefore(response, link2, link1)
def testChangeListSortingModelMeta(self): def testChangeListSortingModelMeta(self):
# Test ordering on Model Meta is respected # Test ordering on Model Meta is respected
l1 = Language.objects.create(iso='ur', name='Urdu') l1 = Language.objects.create(iso='ur', name='Urdu')
l2 = Language.objects.create(iso='ar', name='Arabic') l2 = Language.objects.create(iso='ar', name='Arabic')
link = '<a href="%s/' link1 = reverse('admin:admin_views_language_change', args=(quote(l1.pk),))
link2 = reverse('admin:admin_views_language_change', args=(quote(l2.pk),))
response = self.client.get('/test_admin/admin/admin_views/language/', {}) response = self.client.get('/test_admin/admin/admin_views/language/', {})
self.assertContentBefore(response, link % l2.pk, link % l1.pk) self.assertContentBefore(response, link2, link1)
# Test we can override with query string # Test we can override with query string
response = self.client.get('/test_admin/admin/admin_views/language/', {'o': '-1'}) response = self.client.get('/test_admin/admin/admin_views/language/', {'o': '-1'})
self.assertContentBefore(response, link % l1.pk, link % l2.pk) self.assertContentBefore(response, link1, link2)
def testChangeListSortingOverrideModelAdmin(self): def testChangeListSortingOverrideModelAdmin(self):
# Test ordering on Model Admin is respected, and overrides Model Meta # Test ordering on Model Admin is respected, and overrides Model Meta
dt = datetime.datetime.now() dt = datetime.datetime.now()
p1 = Podcast.objects.create(name="A", release_date=dt) p1 = Podcast.objects.create(name="A", release_date=dt)
p2 = Podcast.objects.create(name="B", release_date=dt - datetime.timedelta(10)) p2 = Podcast.objects.create(name="B", release_date=dt - datetime.timedelta(10))
link1 = reverse('admin:admin_views_podcast_change', args=(p1.pk,))
link2 = reverse('admin:admin_views_podcast_change', args=(p2.pk,))
link = '<a href="%s/'
response = self.client.get('/test_admin/admin/admin_views/podcast/', {}) response = self.client.get('/test_admin/admin/admin_views/podcast/', {})
self.assertContentBefore(response, link % p1.pk, link % p2.pk) self.assertContentBefore(response, link1, link2)
def testMultipleSortSameField(self): def testMultipleSortSameField(self):
# Check that we get the columns we expect if we have two columns # Check that we get the columns we expect if we have two columns
@ -323,14 +329,16 @@ class AdminViewBasicTest(TestCase):
dt = datetime.datetime.now() dt = datetime.datetime.now()
p1 = Podcast.objects.create(name="A", release_date=dt) p1 = Podcast.objects.create(name="A", release_date=dt)
p2 = Podcast.objects.create(name="B", release_date=dt - datetime.timedelta(10)) p2 = Podcast.objects.create(name="B", release_date=dt - datetime.timedelta(10))
link1 = reverse('admin:admin_views_podcast_change', args=(quote(p1.pk),))
link2 = reverse('admin:admin_views_podcast_change', args=(quote(p2.pk),))
link = '<a href="%s/'
response = self.client.get('/test_admin/admin/admin_views/podcast/', {}) response = self.client.get('/test_admin/admin/admin_views/podcast/', {})
self.assertContentBefore(response, link % p1.pk, link % p2.pk) self.assertContentBefore(response, link1, link2)
p1 = ComplexSortedPerson.objects.create(name="Bob", age=10) p1 = ComplexSortedPerson.objects.create(name="Bob", age=10)
p2 = ComplexSortedPerson.objects.create(name="Amy", age=20) p2 = ComplexSortedPerson.objects.create(name="Amy", age=20)
link = '<a href="%s/' link1 = reverse('admin:admin_views_complexsortedperson_change', args=(p1.pk,))
link2 = reverse('admin:admin_views_complexsortedperson_change', args=(p2.pk,))
response = self.client.get('/test_admin/admin/admin_views/complexsortedperson/', {}) response = self.client.get('/test_admin/admin/admin_views/complexsortedperson/', {})
# Should have 5 columns (including action checkbox col) # Should have 5 columns (including action checkbox col)
@ -343,7 +351,7 @@ class AdminViewBasicTest(TestCase):
self.assertContentBefore(response, 'Name', 'Colored name') self.assertContentBefore(response, 'Name', 'Colored name')
# Check sorting - should be by name # Check sorting - should be by name
self.assertContentBefore(response, link % p2.id, link % p1.id) self.assertContentBefore(response, link2, link1)
def testSortIndicatorsAdminOrder(self): def testSortIndicatorsAdminOrder(self):
""" """
@ -462,10 +470,12 @@ class AdminViewBasicTest(TestCase):
for rows corresponding to instances of a model in which a named group for rows corresponding to instances of a model in which a named group
has been used in the choices option of a field. has been used in the choices option of a field.
""" """
link1 = reverse('admin:admin_views_fabric_change', args=(1,), current_app=self.urlbit)
link2 = reverse('admin:admin_views_fabric_change', args=(2,), current_app=self.urlbit)
response = self.client.get('/test_admin/%s/admin_views/fabric/' % self.urlbit) response = self.client.get('/test_admin/%s/admin_views/fabric/' % self.urlbit)
fail_msg = "Changelist table isn't showing the right human-readable values set by a model field 'choices' option named group." fail_msg = "Changelist table isn't showing the right human-readable values set by a model field 'choices' option named group."
self.assertContains(response, '<a href="1/">Horizontal</a>', msg_prefix=fail_msg, html=True) self.assertContains(response, '<a href="%s">Horizontal</a>' % link1, msg_prefix=fail_msg, html=True)
self.assertContains(response, '<a href="2/">Vertical</a>', msg_prefix=fail_msg, html=True) self.assertContains(response, '<a href="%s">Vertical</a>' % link2, msg_prefix=fail_msg, html=True)
def testNamedGroupFieldChoicesFilter(self): def testNamedGroupFieldChoicesFilter(self):
""" """
@ -1375,9 +1385,12 @@ class AdminViewStringPrimaryKeyTest(TestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
def test_changelist_to_changeform_link(self): def test_changelist_to_changeform_link(self):
"The link from the changelist referring to the changeform of the object should be quoted" "Link to the changeform of the object in changelist should use reverse() and be quoted -- #18072"
response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/') prefix = '/test_admin/admin/admin_views/modelwithstringprimarykey/'
should_contain = """<th><a href="%s/">%s</a></th></tr>""" % (escape(quote(self.pk)), escape(self.pk)) response = self.client.get(prefix)
# this URL now comes through reverse(), thus iri_to_uri encoding
pk_final_url = escape(iri_to_uri(quote(self.pk)))
should_contain = """<th><a href="%s%s/">%s</a></th>""" % (prefix, pk_final_url, escape(self.pk))
self.assertContains(response, should_contain) self.assertContains(response, should_contain)
def test_recentactions_link(self): def test_recentactions_link(self):
@ -1445,6 +1458,18 @@ class AdminViewStringPrimaryKeyTest(TestCase):
should_contain = '/%s/" class="viewsitelink">' % model.pk should_contain = '/%s/" class="viewsitelink">' % model.pk
self.assertContains(response, should_contain) self.assertContains(response, should_contain)
def test_change_view_history_link(self):
"""Object history button link should work and contain the pk value quoted."""
url = reverse('admin:%s_modelwithstringprimarykey_change' %
ModelWithStringPrimaryKey._meta.app_label,
args=(quote(self.pk),))
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
expected_link = reverse('admin:%s_modelwithstringprimarykey_history' %
ModelWithStringPrimaryKey._meta.app_label,
args=(quote(self.pk),))
self.assertContains(response, '<a href="%s" class="historylink"' % expected_link)
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class SecureViewTests(TestCase): class SecureViewTests(TestCase):
@ -2028,12 +2053,14 @@ class AdminViewListEditable(TestCase):
""" """
story1 = OtherStory.objects.create(title='The adventures of Guido', content='Once upon a time in Djangoland...') story1 = OtherStory.objects.create(title='The adventures of Guido', content='Once upon a time in Djangoland...')
story2 = OtherStory.objects.create(title='Crouching Tiger, Hidden Python', content='The Python was sneaking into...') story2 = OtherStory.objects.create(title='Crouching Tiger, Hidden Python', content='The Python was sneaking into...')
link1 = reverse('admin:admin_views_otherstory_change', args=(story1.pk,))
link2 = reverse('admin:admin_views_otherstory_change', args=(story2.pk,))
response = self.client.get('/test_admin/admin/admin_views/otherstory/') response = self.client.get('/test_admin/admin/admin_views/otherstory/')
self.assertContains(response, 'id="id_form-0-id"', 1) # Only one hidden field, in a separate place than the table. self.assertContains(response, 'id="id_form-0-id"', 1) # Only one hidden field, in a separate place than the table.
self.assertContains(response, 'id="id_form-1-id"', 1) self.assertContains(response, 'id="id_form-1-id"', 1)
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) 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)
self.assertContains(response, '<th><a href="%d/">%d</a></th>' % (story1.id, story1.id), 1) self.assertContains(response, '<th><a href="%s">%d</a></th>' % (link1, story1.id), 1)
self.assertContains(response, '<th><a href="%d/">%d</a></th>' % (story2.id, story2.id), 1) self.assertContains(response, '<th><a href="%s">%d</a></th>' % (link2, story2.id), 1)
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',)) @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))

View File

@ -0,0 +1,8 @@
import logging
from django.conf import settings
class MyHandler(logging.Handler):
def __init__(self):
logging.Handler.__init__(self)
self.config = settings.LOGGING

View File

@ -10,6 +10,8 @@ from django.test import TestCase, RequestFactory
from django.test.utils import override_settings from django.test.utils import override_settings
from django.utils.log import CallbackFilter, RequireDebugFalse from django.utils.log import CallbackFilter, RequireDebugFalse
from ..admin_scripts.tests import AdminScriptTestCase
# logging config prior to using filter with mail_admins # logging config prior to using filter with mail_admins
OLD_LOGGING = { OLD_LOGGING = {
@ -253,3 +255,30 @@ class AdminEmailHandlerTest(TestCase):
self.assertEqual(len(mail.outbox), 1) self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].subject, expected_subject) self.assertEqual(mail.outbox[0].subject, expected_subject)
class SettingsConfigTest(AdminScriptTestCase):
"""
Test that accessing settings in a custom logging handler does not trigger
a circular import error.
"""
def setUp(self):
log_config = """{
'version': 1,
'handlers': {
'custom_handler': {
'level': 'INFO',
'class': 'logging_tests.logconfig.MyHandler',
}
}
}"""
self.write_settings('settings.py', sdict={'LOGGING': log_config})
def tearDown(self):
self.remove_settings('settings.py')
def test_circular_dependency(self):
# validate is just an example command to trigger settings configuration
out, err = self.run_manage(['validate'])
self.assertNoOutput(err)
self.assertOutput(out, "0 errors found")

View File

@ -154,4 +154,4 @@ class TestUtilsHtml(unittest.TestCase):
("<a>x</a> <p><b>y</b></p>", "a b", "x <p>y</p>"), ("<a>x</a> <p><b>y</b></p>", "a b", "x <p>y</p>"),
) )
for value, tags, output in items: for value, tags, output in items:
self.assertEquals(f(value, tags), output) self.assertEqual(f(value, tags), output)