Merged recent trunk changes.
This commit is contained in:
commit
531e7715da
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{% 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 %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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": {}})
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -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)
|
|
||||||
|
|
|
@ -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',))
|
||||||
|
|
|
@ -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
|
|
@ -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")
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue