mirror of
https://github.com/django/django.git
synced 2025-07-04 01:39:20 +00:00
[soc2009/http-wsgi-improvements] Merged up to r12258 from trunk.
git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2009/http-wsgi-improvements@11260 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
bf66b536a2
commit
a873df6f37
1
AUTHORS
1
AUTHORS
@ -131,6 +131,7 @@ answer newbie questions, and generally made Django that much better:
|
||||
Andrew Durdin <adurdin@gmail.com>
|
||||
dusk@woofle.net
|
||||
Andy Dustman <farcepest@gmail.com>
|
||||
J. Clifford Dyer <jcd@unc.edu>
|
||||
Clint Ecker
|
||||
Nick Efford <nick@efford.org>
|
||||
eibaan@gmail.com
|
||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -6,7 +6,16 @@ __all__ = ['handler404', 'handler500', 'include', 'patterns', 'url']
|
||||
handler404 = 'django.views.defaults.page_not_found'
|
||||
handler500 = 'django.views.defaults.server_error'
|
||||
|
||||
include = lambda urlconf_module: [urlconf_module]
|
||||
def include(arg, namespace=None, app_name=None):
|
||||
if isinstance(arg, tuple):
|
||||
# callable returning a namespace hint
|
||||
if namespace:
|
||||
raise ImproperlyConfigured('Cannot override the namespace for a dynamic module that provides a namespace')
|
||||
urlconf_module, app_name, namespace = arg
|
||||
else:
|
||||
# No namespace hint - use manually provided namespace
|
||||
urlconf_module = arg
|
||||
return (urlconf_module, app_name, namespace)
|
||||
|
||||
def patterns(prefix, *args):
|
||||
pattern_list = []
|
||||
@ -19,9 +28,10 @@ def patterns(prefix, *args):
|
||||
return pattern_list
|
||||
|
||||
def url(regex, view, kwargs=None, name=None, prefix=''):
|
||||
if type(view) == list:
|
||||
if isinstance(view, (list,tuple)):
|
||||
# For include(...) processing.
|
||||
return RegexURLResolver(regex, view[0], kwargs)
|
||||
urlconf_module, app_name, namespace = view
|
||||
return RegexURLResolver(regex, urlconf_module, kwargs, app_name=app_name, namespace=namespace)
|
||||
else:
|
||||
if isinstance(view, basestring):
|
||||
if not view:
|
||||
|
@ -226,24 +226,24 @@ class ModelAdmin(BaseModelAdmin):
|
||||
return self.admin_site.admin_view(view)(*args, **kwargs)
|
||||
return update_wrapper(wrapper, view)
|
||||
|
||||
info = self.admin_site.name, self.model._meta.app_label, self.model._meta.module_name
|
||||
info = self.model._meta.app_label, self.model._meta.module_name
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^$',
|
||||
wrap(self.changelist_view),
|
||||
name='%sadmin_%s_%s_changelist' % info),
|
||||
name='%s_%s_changelist' % info),
|
||||
url(r'^add/$',
|
||||
wrap(self.add_view),
|
||||
name='%sadmin_%s_%s_add' % info),
|
||||
name='%s_%s_add' % info),
|
||||
url(r'^(.+)/history/$',
|
||||
wrap(self.history_view),
|
||||
name='%sadmin_%s_%s_history' % info),
|
||||
name='%s_%s_history' % info),
|
||||
url(r'^(.+)/delete/$',
|
||||
wrap(self.delete_view),
|
||||
name='%sadmin_%s_%s_delete' % info),
|
||||
name='%s_%s_delete' % info),
|
||||
url(r'^(.+)/$',
|
||||
wrap(self.change_view),
|
||||
name='%sadmin_%s_%s_change' % info),
|
||||
name='%s_%s_change' % info),
|
||||
)
|
||||
return urlpatterns
|
||||
|
||||
@ -582,11 +582,12 @@ class ModelAdmin(BaseModelAdmin):
|
||||
'save_on_top': self.save_on_top,
|
||||
'root_path': self.admin_site.root_path,
|
||||
})
|
||||
context_instance = template.RequestContext(request, current_app=self.admin_site.name)
|
||||
return render_to_response(self.change_form_template or [
|
||||
"admin/%s/%s/change_form.html" % (app_label, opts.object_name.lower()),
|
||||
"admin/%s/change_form.html" % app_label,
|
||||
"admin/change_form.html"
|
||||
], context, context_instance=template.RequestContext(request))
|
||||
], context, context_instance=context_instance)
|
||||
|
||||
def response_add(self, request, obj, post_url_continue='../%s/'):
|
||||
"""
|
||||
@ -977,11 +978,12 @@ class ModelAdmin(BaseModelAdmin):
|
||||
'actions_on_bottom': self.actions_on_bottom,
|
||||
}
|
||||
context.update(extra_context or {})
|
||||
context_instance = template.RequestContext(request, current_app=self.admin_site.name)
|
||||
return render_to_response(self.change_list_template or [
|
||||
'admin/%s/%s/change_list.html' % (app_label, opts.object_name.lower()),
|
||||
'admin/%s/change_list.html' % app_label,
|
||||
'admin/change_list.html'
|
||||
], context, context_instance=template.RequestContext(request))
|
||||
], context, context_instance=context_instance)
|
||||
|
||||
def delete_view(self, request, object_id, extra_context=None):
|
||||
"The 'delete' admin view for this model."
|
||||
@ -1032,11 +1034,12 @@ class ModelAdmin(BaseModelAdmin):
|
||||
"app_label": app_label,
|
||||
}
|
||||
context.update(extra_context or {})
|
||||
context_instance = template.RequestContext(request, current_app=self.admin_site.name)
|
||||
return render_to_response(self.delete_confirmation_template or [
|
||||
"admin/%s/%s/delete_confirmation.html" % (app_label, opts.object_name.lower()),
|
||||
"admin/%s/delete_confirmation.html" % app_label,
|
||||
"admin/delete_confirmation.html"
|
||||
], context, context_instance=template.RequestContext(request))
|
||||
], context, context_instance=context_instance)
|
||||
|
||||
def history_view(self, request, object_id, extra_context=None):
|
||||
"The 'history' admin view for this model."
|
||||
@ -1059,11 +1062,12 @@ class ModelAdmin(BaseModelAdmin):
|
||||
'app_label': app_label,
|
||||
}
|
||||
context.update(extra_context or {})
|
||||
context_instance = template.RequestContext(request, current_app=self.admin_site.name)
|
||||
return render_to_response(self.object_history_template or [
|
||||
"admin/%s/%s/object_history.html" % (app_label, opts.object_name.lower()),
|
||||
"admin/%s/object_history.html" % app_label,
|
||||
"admin/object_history.html"
|
||||
], context, context_instance=template.RequestContext(request))
|
||||
], context, context_instance=context_instance)
|
||||
|
||||
#
|
||||
# DEPRECATED methods.
|
||||
|
@ -5,6 +5,7 @@ from django.contrib.admin import actions
|
||||
from django.contrib.auth import authenticate, login
|
||||
from django.db.models.base import ModelBase
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.shortcuts import render_to_response
|
||||
from django.utils.functional import update_wrapper
|
||||
from django.utils.safestring import mark_safe
|
||||
@ -38,17 +39,14 @@ class AdminSite(object):
|
||||
login_template = None
|
||||
app_index_template = None
|
||||
|
||||
def __init__(self, name=None):
|
||||
def __init__(self, name=None, app_name='admin'):
|
||||
self._registry = {} # model_class class -> admin_class instance
|
||||
# TODO Root path is used to calculate urls under the old root() method
|
||||
# in order to maintain backwards compatibility we are leaving that in
|
||||
# so root_path isn't needed, not sure what to do about this.
|
||||
self.root_path = 'admin/'
|
||||
self.root_path = None
|
||||
if name is None:
|
||||
name = ''
|
||||
self.name = 'admin'
|
||||
else:
|
||||
name += '_'
|
||||
self.name = name
|
||||
self.app_name = app_name
|
||||
self._actions = {'delete_selected': actions.delete_selected}
|
||||
self._global_actions = self._actions.copy()
|
||||
|
||||
@ -159,9 +157,9 @@ class AdminSite(object):
|
||||
if 'django.core.context_processors.auth' not in settings.TEMPLATE_CONTEXT_PROCESSORS:
|
||||
raise ImproperlyConfigured("Put 'django.core.context_processors.auth' in your TEMPLATE_CONTEXT_PROCESSORS setting in order to use the admin application.")
|
||||
|
||||
def admin_view(self, view):
|
||||
def admin_view(self, view, cacheable=False):
|
||||
"""
|
||||
Decorator to create an "admin view attached to this ``AdminSite``. This
|
||||
Decorator to create an admin view attached to this ``AdminSite``. This
|
||||
wraps the view and provides permission checking by calling
|
||||
``self.has_permission``.
|
||||
|
||||
@ -177,43 +175,49 @@ class AdminSite(object):
|
||||
url(r'^my_view/$', self.admin_view(some_view))
|
||||
)
|
||||
return urls
|
||||
|
||||
By default, admin_views are marked non-cacheable using the
|
||||
``never_cache`` decorator. If the view can be safely cached, set
|
||||
cacheable=True.
|
||||
"""
|
||||
def inner(request, *args, **kwargs):
|
||||
if not self.has_permission(request):
|
||||
return self.login(request)
|
||||
return view(request, *args, **kwargs)
|
||||
if not cacheable:
|
||||
inner = never_cache(inner)
|
||||
return update_wrapper(inner, view)
|
||||
|
||||
def get_urls(self):
|
||||
from django.conf.urls.defaults import patterns, url, include
|
||||
|
||||
def wrap(view):
|
||||
def wrap(view, cacheable=False):
|
||||
def wrapper(*args, **kwargs):
|
||||
return self.admin_view(view)(*args, **kwargs)
|
||||
return self.admin_view(view, cacheable)(*args, **kwargs)
|
||||
return update_wrapper(wrapper, view)
|
||||
|
||||
# Admin-site-wide views.
|
||||
urlpatterns = patterns('',
|
||||
url(r'^$',
|
||||
wrap(self.index),
|
||||
name='%sadmin_index' % self.name),
|
||||
name='index'),
|
||||
url(r'^logout/$',
|
||||
wrap(self.logout),
|
||||
name='%sadmin_logout'),
|
||||
name='logout'),
|
||||
url(r'^password_change/$',
|
||||
wrap(self.password_change),
|
||||
name='%sadmin_password_change' % self.name),
|
||||
wrap(self.password_change, cacheable=True),
|
||||
name='password_change'),
|
||||
url(r'^password_change/done/$',
|
||||
wrap(self.password_change_done),
|
||||
name='%sadmin_password_change_done' % self.name),
|
||||
wrap(self.password_change_done, cacheable=True),
|
||||
name='password_change_done'),
|
||||
url(r'^jsi18n/$',
|
||||
wrap(self.i18n_javascript),
|
||||
name='%sadmin_jsi18n' % self.name),
|
||||
wrap(self.i18n_javascript, cacheable=True),
|
||||
name='jsi18n'),
|
||||
url(r'^r/(?P<content_type_id>\d+)/(?P<object_id>.+)/$',
|
||||
'django.views.defaults.shortcut'),
|
||||
url(r'^(?P<app_label>\w+)/$',
|
||||
wrap(self.app_index),
|
||||
name='%sadmin_app_list' % self.name),
|
||||
name='app_list')
|
||||
)
|
||||
|
||||
# Add in each model's views.
|
||||
@ -225,7 +229,7 @@ class AdminSite(object):
|
||||
return urlpatterns
|
||||
|
||||
def urls(self):
|
||||
return self.get_urls()
|
||||
return self.get_urls(), self.app_name, self.name
|
||||
urls = property(urls)
|
||||
|
||||
def password_change(self, request):
|
||||
@ -233,8 +237,11 @@ class AdminSite(object):
|
||||
Handles the "change password" task -- both form display and validation.
|
||||
"""
|
||||
from django.contrib.auth.views import password_change
|
||||
return password_change(request,
|
||||
post_change_redirect='%spassword_change/done/' % self.root_path)
|
||||
if self.root_path is not None:
|
||||
url = '%spassword_change/done/' % self.root_path
|
||||
else:
|
||||
url = reverse('admin:password_change_done', current_app=self.name)
|
||||
return password_change(request, post_change_redirect=url)
|
||||
|
||||
def password_change_done(self, request):
|
||||
"""
|
||||
@ -362,8 +369,9 @@ class AdminSite(object):
|
||||
'root_path': self.root_path,
|
||||
}
|
||||
context.update(extra_context or {})
|
||||
context_instance = template.RequestContext(request, current_app=self.name)
|
||||
return render_to_response(self.index_template or 'admin/index.html', context,
|
||||
context_instance=template.RequestContext(request)
|
||||
context_instance=context_instance
|
||||
)
|
||||
index = never_cache(index)
|
||||
|
||||
@ -376,8 +384,9 @@ class AdminSite(object):
|
||||
'root_path': self.root_path,
|
||||
}
|
||||
context.update(extra_context or {})
|
||||
context_instance = template.RequestContext(request, current_app=self.name)
|
||||
return render_to_response(self.login_template or 'admin/login.html', context,
|
||||
context_instance=template.RequestContext(request)
|
||||
context_instance=context_instance
|
||||
)
|
||||
|
||||
def app_index(self, request, app_label, extra_context=None):
|
||||
@ -419,9 +428,10 @@ class AdminSite(object):
|
||||
'root_path': self.root_path,
|
||||
}
|
||||
context.update(extra_context or {})
|
||||
context_instance = template.RequestContext(request, current_app=self.name)
|
||||
return render_to_response(self.app_index_template or ('admin/%s/app_index.html' % app_label,
|
||||
'admin/app_index.html'), context,
|
||||
context_instance=template.RequestContext(request)
|
||||
context_instance=context_instance
|
||||
)
|
||||
|
||||
def root(self, request, url):
|
||||
|
@ -23,7 +23,30 @@
|
||||
{% block branding %}{% endblock %}
|
||||
</div>
|
||||
{% if user.is_authenticated and user.is_staff %}
|
||||
<div id="user-tools">{% trans 'Welcome,' %} <strong>{% firstof user.first_name user.username %}</strong>. {% block userlinks %}{% url django-admindocs-docroot as docsroot %}{% if docsroot %}<a href="{{ docsroot }}">{% trans 'Documentation' %}</a> / {% endif %}<a href="{{ root_path }}password_change/">{% trans 'Change password' %}</a> / <a href="{{ root_path }}logout/">{% trans 'Log out' %}</a>{% endblock %}</div>
|
||||
<div id="user-tools">
|
||||
{% trans 'Welcome,' %}
|
||||
<strong>{% firstof user.first_name user.username %}</strong>.
|
||||
{% block userlinks %}
|
||||
{% url django-admindocs-docroot as docsroot %}
|
||||
{% if docsroot %}
|
||||
<a href="{{ docsroot }}">{% trans 'Documentation' %}</a> /
|
||||
{% endif %}
|
||||
{% url admin:password_change as password_change_url %}
|
||||
{% if password_change_url %}
|
||||
<a href="{{ password_change_url }}">
|
||||
{% else %}
|
||||
<a href="{{ root_path }}password_change/">
|
||||
{% endif %}
|
||||
{% trans 'Change password' %}</a> /
|
||||
{% url admin:logout as logout_url %}
|
||||
{% if logout_url %}
|
||||
<a href="{{ logout_url }}">
|
||||
{% else %}
|
||||
<a href="{{ root_path }}logout/">
|
||||
{% endif %}
|
||||
{% trans 'Log out' %}</a>
|
||||
{% endblock %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% block nav-global %}{% endblock %}
|
||||
</div>
|
||||
|
@ -222,8 +222,7 @@ class RelatedFieldWidgetWrapper(forms.Widget):
|
||||
rel_to = self.rel.to
|
||||
info = (rel_to._meta.app_label, rel_to._meta.object_name.lower())
|
||||
try:
|
||||
related_info = (self.admin_site.name,) + info
|
||||
related_url = reverse('%sadmin_%s_%s_add' % related_info)
|
||||
related_url = reverse('admin:%s_%s_add' % info, current_app=self.admin_site.name)
|
||||
except NoReverseMatch:
|
||||
related_url = '../../../%s/%s/add/' % info
|
||||
self.widget.choices = self.choices
|
||||
|
@ -1,6 +1,6 @@
|
||||
{% extends "admin/base_site.html" %}
|
||||
{% load i18n %}
|
||||
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">Home</a> › Documentation</div>{% endblock %}
|
||||
{% block breadcrumbs %}<div class="breadcrumbs"><a href="{{ root_path }}">Home</a> › Documentation</div>{% endblock %}
|
||||
{% block title %}Documentation{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
@ -22,6 +22,9 @@ class GenericSite(object):
|
||||
name = 'my site'
|
||||
|
||||
def get_root_path():
|
||||
try:
|
||||
return urlresolvers.reverse('admin:index')
|
||||
except urlresolvers.NoReverseMatch:
|
||||
from django.contrib import admin
|
||||
try:
|
||||
return urlresolvers.reverse(admin.site.root, args=[''])
|
||||
|
@ -19,6 +19,9 @@ class GeoManager(Manager):
|
||||
def centroid(self, *args, **kwargs):
|
||||
return self.get_query_set().centroid(*args, **kwargs)
|
||||
|
||||
def collect(self, *args, **kwargs):
|
||||
return self.get_query_set().collect(*args, **kwargs)
|
||||
|
||||
def difference(self, *args, **kwargs):
|
||||
return self.get_query_set().difference(*args, **kwargs)
|
||||
|
||||
|
@ -62,6 +62,14 @@ class GeoQuerySet(QuerySet):
|
||||
"""
|
||||
return self._geom_attribute('centroid', **kwargs)
|
||||
|
||||
def collect(self, **kwargs):
|
||||
"""
|
||||
Performs an aggregate collect operation on the given geometry field.
|
||||
This is analagous to a union operation, but much faster because
|
||||
boundaries are not dissolved.
|
||||
"""
|
||||
return self._spatial_aggregate(aggregates.Collect, **kwargs)
|
||||
|
||||
def difference(self, geom, **kwargs):
|
||||
"""
|
||||
Returns the spatial difference of the geographic field in a `difference`
|
||||
|
@ -13,7 +13,9 @@ from django.contrib.gis.measure import Area, Distance
|
||||
ALL_TERMS = sql.constants.QUERY_TERMS.copy()
|
||||
ALL_TERMS.update(SpatialBackend.gis_terms)
|
||||
|
||||
# Pulling out other needed constants/routines to avoid attribute lookups.
|
||||
TABLE_NAME = sql.constants.TABLE_NAME
|
||||
get_proxied_model = sql.query.get_proxied_model
|
||||
|
||||
class GeoQuery(sql.Query):
|
||||
"""
|
||||
@ -153,7 +155,9 @@ class GeoQuery(sql.Query):
|
||||
opts = self.model._meta
|
||||
aliases = set()
|
||||
only_load = self.deferred_to_columns()
|
||||
proxied_model = opts.proxy and opts.proxy_for_model or 0
|
||||
# Skip all proxy to the root proxied model
|
||||
proxied_model = get_proxied_model(opts)
|
||||
|
||||
if start_alias:
|
||||
seen = {None: start_alias}
|
||||
for field, model in opts.get_fields_with_model():
|
||||
@ -205,6 +209,10 @@ class GeoQuery(sql.Query):
|
||||
"""
|
||||
values = []
|
||||
aliases = self.extra_select.keys()
|
||||
if self.aggregates:
|
||||
# If we have an aggregate annotation, must extend the aliases
|
||||
# so their corresponding row values are included.
|
||||
aliases.extend([None for i in xrange(len(self.aggregates))])
|
||||
|
||||
# Have to set a starting row number offset that is used for
|
||||
# determining the correct starting row index -- needed for
|
||||
|
@ -8,6 +8,9 @@ from django.contrib.gis.gdal.prototypes.errcheck import \
|
||||
check_arg_errcode, check_errcode, check_geom, check_geom_offset, \
|
||||
check_pointer, check_srs, check_str_arg, check_string, check_const_string
|
||||
|
||||
class gdal_char_p(c_char_p):
|
||||
pass
|
||||
|
||||
def double_output(func, argtypes, errcheck=False, strarg=False):
|
||||
"Generates a ctypes function that returns a double value."
|
||||
func.argtypes = argtypes
|
||||
@ -77,9 +80,9 @@ def string_output(func, argtypes, offset=-1, str_result=False):
|
||||
"""
|
||||
func.argtypes = argtypes
|
||||
if str_result:
|
||||
# String is the result, don't explicitly define
|
||||
# the argument type so we can get the pointer.
|
||||
pass
|
||||
# Use subclass of c_char_p so the error checking routine
|
||||
# can free the memory at the pointer's address.
|
||||
func.restype = gdal_char_p
|
||||
else:
|
||||
# Error code is returned
|
||||
func.restype = c_int
|
||||
|
@ -10,6 +10,7 @@ __all__ = ['geos_boundary', 'geos_buffer', 'geos_centroid', 'geos_convexhull',
|
||||
from ctypes import c_char_p, c_double, c_int
|
||||
from django.contrib.gis.geos.libgeos import lgeos, GEOM_PTR, GEOS_PREPARE
|
||||
from django.contrib.gis.geos.prototypes.errcheck import check_geom, check_string
|
||||
from django.contrib.gis.geos.prototypes.geom import geos_char_p
|
||||
|
||||
def topology(func, *args):
|
||||
"For GEOS unary topology functions."
|
||||
@ -38,6 +39,7 @@ geos_union = topology(lgeos.GEOSUnion, GEOM_PTR)
|
||||
# GEOSRelate returns a string, not a geometry.
|
||||
geos_relate = lgeos.GEOSRelate
|
||||
geos_relate.argtypes = [GEOM_PTR, GEOM_PTR]
|
||||
geos_relate.restype = geos_char_p
|
||||
geos_relate.errcheck = check_string
|
||||
|
||||
# Routines only in GEOS 3.1+
|
||||
|
@ -1,7 +1,7 @@
|
||||
import os, unittest
|
||||
from django.contrib.gis.geos import *
|
||||
from django.contrib.gis.db.backend import SpatialBackend
|
||||
from django.contrib.gis.db.models import Count, Extent, F, Union
|
||||
from django.contrib.gis.db.models import Collect, Count, Extent, F, Union
|
||||
from django.contrib.gis.tests.utils import no_mysql, no_oracle, no_spatialite
|
||||
from django.conf import settings
|
||||
from models import City, Location, DirectoryEntry, Parcel, Book, Author
|
||||
@ -231,8 +231,12 @@ class RelatedGeoModelTest(unittest.TestCase):
|
||||
q = pickle.loads(q_str)
|
||||
self.assertEqual(GeoQuery, q.__class__)
|
||||
|
||||
def test12_count(self):
|
||||
"Testing `Count` aggregate use with the `GeoManager`. See #11087."
|
||||
# TODO: fix on Oracle -- get the following error because the SQL is ordered
|
||||
# by a geometry object, which Oracle apparently doesn't like:
|
||||
# ORA-22901: cannot compare nested table or VARRAY or LOB attributes of an object type
|
||||
@no_oracle
|
||||
def test12a_count(self):
|
||||
"Testing `Count` aggregate use with the `GeoManager` on geo-fields."
|
||||
# Creating a new City, 'Fort Worth', that uses the same location
|
||||
# as Dallas.
|
||||
dallas = City.objects.get(name='Dallas')
|
||||
@ -242,6 +246,8 @@ class RelatedGeoModelTest(unittest.TestCase):
|
||||
loc = Location.objects.annotate(num_cities=Count('city')).get(id=dallas.location.id)
|
||||
self.assertEqual(2, loc.num_cities)
|
||||
|
||||
def test12b_count(self):
|
||||
"Testing `Count` aggregate use with the `GeoManager` on non geo-fields. See #11087."
|
||||
# Creating some data for the Book/Author non-geo models that
|
||||
# use GeoManager. See #11087.
|
||||
tp = Author.objects.create(name='Trevor Paglen')
|
||||
@ -252,11 +258,17 @@ class RelatedGeoModelTest(unittest.TestCase):
|
||||
Book.objects.create(title='Patry on Copyright', author=wp)
|
||||
|
||||
# Should only be one author (Trevor Paglen) returned by this query, and
|
||||
# the annotation should have 3 for the number of books.
|
||||
# the annotation should have 3 for the number of books. Also testing
|
||||
# with a `GeoValuesQuerySet` (see #11489).
|
||||
qs = Author.objects.annotate(num_books=Count('books')).filter(num_books__gt=1)
|
||||
vqs = Author.objects.values('name').annotate(num_books=Count('books')).filter(num_books__gt=1)
|
||||
self.assertEqual(1, len(qs))
|
||||
self.assertEqual(3, qs[0].num_books)
|
||||
self.assertEqual(1, len(vqs))
|
||||
self.assertEqual(3, vqs[0]['num_books'])
|
||||
|
||||
# TODO: The phantom model does appear on Oracle.
|
||||
@no_oracle
|
||||
def test13_select_related_null_fk(self):
|
||||
"Testing `select_related` on a nullable ForeignKey via `GeoManager`. See #11381."
|
||||
no_author = Book.objects.create(title='Without Author')
|
||||
@ -264,6 +276,26 @@ class RelatedGeoModelTest(unittest.TestCase):
|
||||
# Should be `None`, and not a 'dummy' model.
|
||||
self.assertEqual(None, b.author)
|
||||
|
||||
@no_mysql
|
||||
@no_oracle
|
||||
@no_spatialite
|
||||
def test14_collect(self):
|
||||
"Testing the `collect` GeoQuerySet method and `Collect` aggregate."
|
||||
# Reference query:
|
||||
# SELECT AsText(ST_Collect("relatedapp_location"."point")) FROM "relatedapp_city" LEFT OUTER JOIN
|
||||
# "relatedapp_location" ON ("relatedapp_city"."location_id" = "relatedapp_location"."id")
|
||||
# WHERE "relatedapp_city"."state" = 'TX';
|
||||
ref_geom = fromstr('MULTIPOINT(-97.516111 33.058333,-96.801611 32.782057,-95.363151 29.763374,-96.801611 32.782057)')
|
||||
|
||||
c1 = City.objects.filter(state='TX').collect(field_name='location__point')
|
||||
c2 = City.objects.filter(state='TX').aggregate(Collect('location__point'))['location__point__collect']
|
||||
|
||||
for coll in (c1, c2):
|
||||
# Even though Dallas and Ft. Worth share same point, Collect doesn't
|
||||
# consolidate -- that's why 4 points in MultiPoint.
|
||||
self.assertEqual(4, len(coll))
|
||||
self.assertEqual(ref_geom, coll)
|
||||
|
||||
# TODO: Related tests for KML, GML, and distance lookups.
|
||||
|
||||
def suite():
|
||||
|
@ -139,7 +139,7 @@ class RegexURLPattern(object):
|
||||
callback = property(_get_callback)
|
||||
|
||||
class RegexURLResolver(object):
|
||||
def __init__(self, regex, urlconf_name, default_kwargs=None):
|
||||
def __init__(self, regex, urlconf_name, default_kwargs=None, app_name=None, namespace=None):
|
||||
# regex is a string representing a regular expression.
|
||||
# urlconf_name is a string representing the module containing URLconfs.
|
||||
self.regex = re.compile(regex, re.UNICODE)
|
||||
@ -148,19 +148,29 @@ class RegexURLResolver(object):
|
||||
self._urlconf_module = self.urlconf_name
|
||||
self.callback = None
|
||||
self.default_kwargs = default_kwargs or {}
|
||||
self._reverse_dict = MultiValueDict()
|
||||
self.namespace = namespace
|
||||
self.app_name = app_name
|
||||
self._reverse_dict = None
|
||||
self._namespace_dict = None
|
||||
self._app_dict = None
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s %s %s>' % (self.__class__.__name__, self.urlconf_name, self.regex.pattern)
|
||||
return '<%s %s (%s:%s) %s>' % (self.__class__.__name__, self.urlconf_name, self.app_name, self.namespace, self.regex.pattern)
|
||||
|
||||
def _get_reverse_dict(self):
|
||||
if not self._reverse_dict:
|
||||
def _populate(self):
|
||||
lookups = MultiValueDict()
|
||||
namespaces = {}
|
||||
apps = {}
|
||||
for pattern in reversed(self.url_patterns):
|
||||
p_pattern = pattern.regex.pattern
|
||||
if p_pattern.startswith('^'):
|
||||
p_pattern = p_pattern[1:]
|
||||
if isinstance(pattern, RegexURLResolver):
|
||||
if pattern.namespace:
|
||||
namespaces[pattern.namespace] = (p_pattern, pattern)
|
||||
if pattern.app_name:
|
||||
apps.setdefault(pattern.app_name, []).append(pattern.namespace)
|
||||
else:
|
||||
parent = normalize(pattern.regex.pattern)
|
||||
for name in pattern.reverse_dict:
|
||||
for matches, pat in pattern.reverse_dict.getlist(name):
|
||||
@ -168,14 +178,36 @@ class RegexURLResolver(object):
|
||||
for piece, p_args in parent:
|
||||
new_matches.extend([(piece + suffix, p_args + args) for (suffix, args) in matches])
|
||||
lookups.appendlist(name, (new_matches, p_pattern + pat))
|
||||
for namespace, (prefix, sub_pattern) in pattern.namespace_dict.items():
|
||||
namespaces[namespace] = (p_pattern + prefix, sub_pattern)
|
||||
for app_name, namespace_list in pattern.app_dict.items():
|
||||
apps.setdefault(app_name, []).extend(namespace_list)
|
||||
else:
|
||||
bits = normalize(p_pattern)
|
||||
lookups.appendlist(pattern.callback, (bits, p_pattern))
|
||||
lookups.appendlist(pattern.name, (bits, p_pattern))
|
||||
self._reverse_dict = lookups
|
||||
self._namespace_dict = namespaces
|
||||
self._app_dict = apps
|
||||
|
||||
def _get_reverse_dict(self):
|
||||
if self._reverse_dict is None:
|
||||
self._populate()
|
||||
return self._reverse_dict
|
||||
reverse_dict = property(_get_reverse_dict)
|
||||
|
||||
def _get_namespace_dict(self):
|
||||
if self._namespace_dict is None:
|
||||
self._populate()
|
||||
return self._namespace_dict
|
||||
namespace_dict = property(_get_namespace_dict)
|
||||
|
||||
def _get_app_dict(self):
|
||||
if self._app_dict is None:
|
||||
self._populate()
|
||||
return self._app_dict
|
||||
app_dict = property(_get_app_dict)
|
||||
|
||||
def resolve(self, path):
|
||||
tried = []
|
||||
match = self.regex.search(path)
|
||||
@ -261,12 +293,51 @@ class RegexURLResolver(object):
|
||||
def resolve(path, urlconf=None):
|
||||
return get_resolver(urlconf).resolve(path)
|
||||
|
||||
def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None):
|
||||
def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None, current_app=None):
|
||||
resolver = get_resolver(urlconf)
|
||||
args = args or []
|
||||
kwargs = kwargs or {}
|
||||
|
||||
if prefix is None:
|
||||
prefix = get_script_prefix()
|
||||
return iri_to_uri(u'%s%s' % (prefix, get_resolver(urlconf).reverse(viewname,
|
||||
|
||||
if not isinstance(viewname, basestring):
|
||||
view = viewname
|
||||
else:
|
||||
parts = viewname.split(':')
|
||||
parts.reverse()
|
||||
view = parts[0]
|
||||
path = parts[1:]
|
||||
|
||||
resolved_path = []
|
||||
while path:
|
||||
ns = path.pop()
|
||||
|
||||
# Lookup the name to see if it could be an app identifier
|
||||
try:
|
||||
app_list = resolver.app_dict[ns]
|
||||
# Yes! Path part matches an app in the current Resolver
|
||||
if current_app and current_app in app_list:
|
||||
# If we are reversing for a particular app, use that namespace
|
||||
ns = current_app
|
||||
elif ns not in app_list:
|
||||
# The name isn't shared by one of the instances (i.e., the default)
|
||||
# so just pick the first instance as the default.
|
||||
ns = app_list[0]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
try:
|
||||
extra, resolver = resolver.namespace_dict[ns]
|
||||
resolved_path.append(ns)
|
||||
prefix = prefix + extra
|
||||
except KeyError, key:
|
||||
if resolved_path:
|
||||
raise NoReverseMatch("%s is not a registered namespace inside '%s'" % (key, ':'.join(resolved_path)))
|
||||
else:
|
||||
raise NoReverseMatch("%s is not a registered namespace" % key)
|
||||
|
||||
return iri_to_uri(u'%s%s' % (prefix, resolver.reverse(view,
|
||||
*args, **kwargs)))
|
||||
|
||||
def clear_url_caches():
|
||||
|
@ -217,6 +217,7 @@ WHEN (new.%(col_name)s IS NULL)
|
||||
# continue to loop
|
||||
break
|
||||
for f in model._meta.many_to_many:
|
||||
if not f.rel.through:
|
||||
table_name = self.quote_name(f.m2m_db_table())
|
||||
sequence_name = get_sequence_name(f.m2m_db_table())
|
||||
column_name = self.quote_name('id')
|
||||
|
@ -121,6 +121,7 @@ class DatabaseOperations(BaseDatabaseOperations):
|
||||
style.SQL_TABLE(qn(model._meta.db_table))))
|
||||
break # Only one AutoField is allowed per model, so don't bother continuing.
|
||||
for f in model._meta.many_to_many:
|
||||
if not f.rel.through:
|
||||
output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \
|
||||
(style.SQL_KEYWORD('SELECT'),
|
||||
style.SQL_FIELD(qn('%s_id_seq' % f.m2m_db_table())),
|
||||
|
@ -9,10 +9,11 @@ class ContextPopException(Exception):
|
||||
|
||||
class Context(object):
|
||||
"A stack container for variable context"
|
||||
def __init__(self, dict_=None, autoescape=True):
|
||||
def __init__(self, dict_=None, autoescape=True, current_app=None):
|
||||
dict_ = dict_ or {}
|
||||
self.dicts = [dict_]
|
||||
self.autoescape = autoescape
|
||||
self.current_app = current_app
|
||||
|
||||
def __repr__(self):
|
||||
return repr(self.dicts)
|
||||
@ -96,8 +97,8 @@ class RequestContext(Context):
|
||||
Additional processors can be specified as a list of callables
|
||||
using the "processors" keyword argument.
|
||||
"""
|
||||
def __init__(self, request, dict=None, processors=None):
|
||||
Context.__init__(self, dict)
|
||||
def __init__(self, request, dict=None, processors=None, current_app=None):
|
||||
Context.__init__(self, dict, current_app=current_app)
|
||||
if processors is None:
|
||||
processors = ()
|
||||
else:
|
||||
|
@ -367,13 +367,13 @@ class URLNode(Node):
|
||||
# {% url ... as var %} construct in which cause return nothing.
|
||||
url = ''
|
||||
try:
|
||||
url = reverse(self.view_name, args=args, kwargs=kwargs)
|
||||
url = reverse(self.view_name, args=args, kwargs=kwargs, current_app=context.current_app)
|
||||
except NoReverseMatch, e:
|
||||
if settings.SETTINGS_MODULE:
|
||||
project_name = settings.SETTINGS_MODULE.split('.')[0]
|
||||
try:
|
||||
url = reverse(project_name + '.' + self.view_name,
|
||||
args=args, kwargs=kwargs)
|
||||
args=args, kwargs=kwargs, current_app=context.current_app)
|
||||
except NoReverseMatch:
|
||||
if self.asvar is None:
|
||||
# Re-raise the original exception, not the one with
|
||||
|
@ -464,7 +464,7 @@ should raise either a ``ValueError`` if the ``value`` is of the wrong sort (a
|
||||
list when you were expecting an object, for example) or a ``TypeError`` if
|
||||
your field does not support that type of lookup. For many fields, you can get
|
||||
by with handling the lookup types that need special handling for your field
|
||||
and pass the rest of the :meth:`get_db_prep_lookup` method of the parent class.
|
||||
and pass the rest to the :meth:`get_db_prep_lookup` method of the parent class.
|
||||
|
||||
If you needed to implement ``get_db_prep_save()``, you will usually need to
|
||||
implement ``get_db_prep_lookup()``. If you don't, ``get_db_prep_value`` will be
|
||||
|
@ -55,15 +55,64 @@ just above the final ``import`` line to place your project on the path. Remember
|
||||
replace 'mysite.settings' with your correct settings file, and '/usr/local/django'
|
||||
with your own project's location.
|
||||
|
||||
See the :ref:`Apache/mod_python documentation<howto-deployment-modpython>` for
|
||||
directions on serving static media, and the `mod_wsgi documentation`_ for an
|
||||
explanation of other directives and configuration options you can use.
|
||||
Serving media files
|
||||
===================
|
||||
|
||||
Django doesn't serve media files itself; it leaves that job to whichever Web
|
||||
server you choose.
|
||||
|
||||
We recommend using a separate Web server -- i.e., one that's not also running
|
||||
Django -- for serving media. Here are some good choices:
|
||||
|
||||
* lighttpd_
|
||||
* Nginx_
|
||||
* TUX_
|
||||
* A stripped-down version of Apache_
|
||||
* Cherokee_
|
||||
|
||||
If, however, you have no option but to serve media files on the same Apache
|
||||
``VirtualHost`` as Django, you can set up Apache to serve some URLs as
|
||||
static media, and others using the mod_wsgi interface to Django.
|
||||
|
||||
This example sets up Django at the site root, but explicitly serves ``robots.txt``,
|
||||
``favicon.ico``, any CSS file, and anything in the ``/media/`` URL space as a static
|
||||
file. All other URLs will be served using mod_wsgi::
|
||||
|
||||
Alias /robots.txt /usr/local/wsgi/static/robots.txt
|
||||
Alias /favicon.ico /usr/local/wsgi/static/favicon.ico
|
||||
|
||||
AliasMatch /([^/]*\.css) /usr/local/wsgi/static/styles/$1
|
||||
|
||||
Alias /media/ /usr/local/wsgi/static/media/
|
||||
|
||||
<Directory /usr/local/wsgi/static>
|
||||
Order deny,allow
|
||||
Allow from all
|
||||
</Directory>
|
||||
|
||||
WSGIScriptAlias / /usr/local/wsgi/scripts/django.wsgi
|
||||
|
||||
<Directory /usr/local/wsgi/scripts>
|
||||
Order allow,deny
|
||||
Allow from all
|
||||
</Directory>
|
||||
|
||||
.. _lighttpd: http://www.lighttpd.net/
|
||||
.. _Nginx: http://wiki.codemongers.com/Main
|
||||
.. _TUX: http://en.wikipedia.org/wiki/TUX_web_server
|
||||
.. _Apache: http://httpd.apache.org/
|
||||
.. _Cherokee: http://www.cherokee-project.com/
|
||||
|
||||
More details on configuring a mod_wsgi site to serve static files can be found
|
||||
in the mod_wsgi documentation on `hosting static files`_.
|
||||
|
||||
.. _hosting static files: http://code.google.com/p/modwsgi/wiki/ConfigurationGuidelines#Hosting_Of_Static_Files
|
||||
|
||||
Details
|
||||
=======
|
||||
========
|
||||
|
||||
For more details, see the `mod_wsgi documentation`_, which explains the above in
|
||||
more detail, and walks through all the various options you've got when deploying
|
||||
under mod_wsgi.
|
||||
For more details, see the `mod_wsgi documentation on Django integration`_,
|
||||
which explains the above in more detail, and walks through all the various
|
||||
options you've got when deploying under mod_wsgi.
|
||||
|
||||
.. _mod_wsgi documentation: http://code.google.com/p/modwsgi/wiki/IntegrationWithDjango
|
||||
.. _mod_wsgi documentation on Django integration: http://code.google.com/p/modwsgi/wiki/IntegrationWithDjango
|
||||
|
@ -23,6 +23,10 @@ administrators immediate notification of any errors. The :setting:`ADMINS` will
|
||||
get a description of the error, a complete Python traceback, and details about
|
||||
the HTTP request that caused the error.
|
||||
|
||||
By default, Django will send email from root@localhost. However, some mail
|
||||
providers reject all email from this address. To use a different sender
|
||||
address, modify the :setting:`SERVER_EMAIL` setting.
|
||||
|
||||
To disable this behavior, just remove all entries from the :setting:`ADMINS`
|
||||
setting.
|
||||
|
||||
|
@ -365,7 +365,7 @@ That takes care of setting ``handler404`` in the current module. As you can see
|
||||
in ``django/conf/urls/defaults.py``, ``handler404`` is set to
|
||||
:func:`django.views.defaults.page_not_found` by default.
|
||||
|
||||
Three more things to note about 404 views:
|
||||
Four more things to note about 404 views:
|
||||
|
||||
* If :setting:`DEBUG` is set to ``True`` (in your settings module) then your
|
||||
404 view will never be used (and thus the ``404.html`` template will never
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 13 KiB |
@ -762,12 +762,19 @@ documented in :ref:`topics-http-urls`::
|
||||
anything, so you'll usually want to prepend your custom URLs to the built-in
|
||||
ones.
|
||||
|
||||
Note, however, that the ``self.my_view`` function registered above will *not*
|
||||
have any permission check done; it'll be accessible to the general public. Since
|
||||
this is usually not what you want, Django provides a convience wrapper to check
|
||||
permissions. This wrapper is :meth:`AdminSite.admin_view` (i.e.
|
||||
``self.admin_site.admin_view`` inside a ``ModelAdmin`` instance); use it like
|
||||
so::
|
||||
However, the ``self.my_view`` function registered above suffers from two
|
||||
problems:
|
||||
|
||||
* It will *not* perform and permission checks, so it will be accessible to
|
||||
the general public.
|
||||
* It will *not* provide any header details to prevent caching. This means if
|
||||
the page retrieves data from the database, and caching middleware is
|
||||
active, the page could show outdated information.
|
||||
|
||||
Since this is usually not what you want, Django provides a convenience wrapper
|
||||
to check permissions and mark the view as non-cacheable. This wrapper is
|
||||
:meth:`AdminSite.admin_view` (i.e. ``self.admin_site.admin_view`` inside a
|
||||
``ModelAdmin`` instance); use it like so::
|
||||
|
||||
class MyModelAdmin(admin.ModelAdmin):
|
||||
def get_urls(self):
|
||||
@ -781,7 +788,14 @@ Notice the wrapped view in the fifth line above::
|
||||
|
||||
(r'^my_view/$', self.admin_site.admin_view(self.my_view))
|
||||
|
||||
This wrapping will protect ``self.my_view`` from unauthorized access.
|
||||
This wrapping will protect ``self.my_view`` from unauthorized access and will
|
||||
apply the ``django.views.decorators.cache.never_cache`` decorator to make sure
|
||||
it is not cached if the cache middleware is active.
|
||||
|
||||
If the page is cacheable, but you still want the permission check to be performed,
|
||||
you can pass a ``cacheable=True`` argument to :meth:`AdminSite.admin_view`::
|
||||
|
||||
(r'^my_view/$', self.admin_site.admin_view(self.my_view, cacheable=True))
|
||||
|
||||
.. method:: ModelAdmin.formfield_for_foreignkey(self, db_field, request, **kwargs)
|
||||
|
||||
@ -849,7 +863,7 @@ provided some extra mapping data that would not otherwise be available::
|
||||
'osm_data': self.get_osm_info(),
|
||||
}
|
||||
return super(MyModelAdmin, self).change_view(request, object_id,
|
||||
extra_context=my_context))
|
||||
extra_context=my_context)
|
||||
|
||||
``ModelAdmin`` media definitions
|
||||
--------------------------------
|
||||
@ -1228,7 +1242,7 @@ or :attr:`AdminSite.login_template` properties.
|
||||
``AdminSite`` objects
|
||||
=====================
|
||||
|
||||
.. class:: AdminSite
|
||||
.. class:: AdminSite(name=None)
|
||||
|
||||
A Django administrative site is represented by an instance of
|
||||
``django.contrib.admin.sites.AdminSite``; by default, an instance of
|
||||
@ -1242,6 +1256,14 @@ or add anything you like. Then, simply create an instance of your
|
||||
Python class), and register your models and ``ModelAdmin`` subclasses
|
||||
with it instead of using the default.
|
||||
|
||||
.. versionadded:: 1.1
|
||||
|
||||
When constructing an instance of an ``AdminSite``, you are able to provide
|
||||
a unique instance name using the ``name`` argument to the constructor. This
|
||||
instance name is used to identify the instance, especially when
|
||||
:ref:`reversing admin URLs <admin-reverse-urls>`. If no instance name is
|
||||
provided, a default instance name of ``admin`` will be used.
|
||||
|
||||
``AdminSite`` attributes
|
||||
------------------------
|
||||
|
||||
@ -1339,10 +1361,10 @@ a pattern for your new view.
|
||||
|
||||
.. note::
|
||||
Any view you render that uses the admin templates, or extends the base
|
||||
admin template, should include in it's context a variable named
|
||||
``admin_site`` that contains the name of the :class:`AdminSite` instance. For
|
||||
:class:`AdminSite` instances, this means ``self.name``; for :class:`ModelAdmin`
|
||||
instances, this means ``self.admin_site.name``.
|
||||
admin template, should provide the ``current_app`` argument to
|
||||
``RequestContext`` or ``Context`` when rendering the template. It should
|
||||
be set to either ``self.name`` if your view is on an ``AdminSite`` or
|
||||
``self.admin_site.name`` if your view is on a ``ModelAdmin``.
|
||||
|
||||
.. _admin-reverse-urls:
|
||||
|
||||
@ -1356,37 +1378,31 @@ accessible using Django's :ref:`URL reversing system <naming-url-patterns>`.
|
||||
|
||||
The :class:`AdminSite` provides the following named URL patterns:
|
||||
|
||||
====================== =============================== =============
|
||||
====================== ======================== =============
|
||||
Page URL name Parameters
|
||||
====================== =============================== =============
|
||||
Index ``admin_index``
|
||||
Logout ``admin_logout``
|
||||
Password change ``admin_password_change``
|
||||
Password change done ``admin_password_change_done``
|
||||
i18n javascript ``admin_jsi18n``
|
||||
Application index page ``admin_app_list`` ``app_label``
|
||||
====================== =============================== =============
|
||||
|
||||
These names will be prefixed with the name of the :class:`AdminSite` instance,
|
||||
plus an underscore. For example, if your :class:`AdminSite` was named
|
||||
``custom``, then the Logout view would be served using a URL with the name
|
||||
``custom_admin_logout``. The default :class:`AdminSite` doesn't use a prefix
|
||||
in it's URL names.
|
||||
====================== ======================== =============
|
||||
Index ``index``
|
||||
Logout ``logout``
|
||||
Password change ``password_change``
|
||||
Password change done ``password_change_done``
|
||||
i18n javascript ``jsi18n``
|
||||
Application index page ``app_list`` ``app_label``
|
||||
====================== ======================== =============
|
||||
|
||||
Each :class:`ModelAdmin` instance provides an additional set of named URLs:
|
||||
|
||||
====================== ===================================================== =============
|
||||
====================== =============================================== =============
|
||||
Page URL name Parameters
|
||||
====================== ===================================================== =============
|
||||
Changelist ``admin_{{ app_label }}_{{ model_name }}_changelist``
|
||||
Add ``admin_{{ app_label }}_{{ model_name }}_add``
|
||||
History ``admin_{{ app_label }}_{{ model_name }}_history`` ``object_id``
|
||||
Delete ``admin_{{ app_label }}_{{ model_name }}_delete`` ``object_id``
|
||||
Change ``admin_{{ app_label }}_{{ model_name }}_change`` ``object_id``
|
||||
====================== ===================================================== =============
|
||||
====================== =============================================== =============
|
||||
Changelist ``{{ app_label }}_{{ model_name }}_changelist``
|
||||
Add ``{{ app_label }}_{{ model_name }}_add``
|
||||
History ``{{ app_label }}_{{ model_name }}_history`` ``object_id``
|
||||
Delete ``{{ app_label }}_{{ model_name }}_delete`` ``object_id``
|
||||
Change ``{{ app_label }}_{{ model_name }}_change`` ``object_id``
|
||||
====================== =============================================== =============
|
||||
|
||||
Again, these names will be prefixed by the name of the :class:`AdminSite` in
|
||||
which they are deployed.
|
||||
These named URLs are registered with the application namespace ``admin``, and
|
||||
with an instance namespace corresponding to the name of the Site instance.
|
||||
|
||||
So - if you wanted to get a reference to the Change view for a particular
|
||||
``Choice`` object (from the polls application) in the default admin, you would
|
||||
@ -1394,8 +1410,16 @@ call::
|
||||
|
||||
>>> from django.core import urlresolvers
|
||||
>>> c = Choice.objects.get(...)
|
||||
>>> change_url = urlresolvers.reverse('admin_polls_choice_change', args=(c.id,))
|
||||
>>> change_url = urlresolvers.reverse('admin:polls_choice_change', args=(c.id,))
|
||||
|
||||
However, if the admin instance was named ``custom``, you would need to call::
|
||||
This will find the first registered instance of the admin application (whatever the instance
|
||||
name), and resolve to the view for changing ``poll.Choice`` instances in that instance.
|
||||
|
||||
>>> change_url = urlresolvers.reverse('custom_admin_polls_choice_change', args=(c.id,))
|
||||
If you want to find a URL in a specific admin instance, provide the name of that instance
|
||||
as a ``current_app`` hint to the reverse call. For example, if you specifically wanted
|
||||
the admin view from the admin instance named ``custom``, you would need to call::
|
||||
|
||||
>>> change_url = urlresolvers.reverse('custom:polls_choice_change', args=(c.id,))
|
||||
|
||||
For more details, see the documentation on :ref:`reversing namespaced URLs
|
||||
<topics-http-reversing-url-namespaces>`.
|
||||
|
@ -177,9 +177,9 @@ The ``ContentTypeManager``
|
||||
.. method:: models.ContentTypeManager.clear_cache()
|
||||
|
||||
Clears an internal cache used by
|
||||
:class:`~django.contrib.contenttypes.models.ContentType>` to keep track
|
||||
:class:`~django.contrib.contenttypes.models.ContentType` to keep track
|
||||
of which models for which it has created
|
||||
:class:`django.contrib.contenttypes.models.ContentType>` instances. You
|
||||
:class:`django.contrib.contenttypes.models.ContentType` instances. You
|
||||
probably won't ever need to call this method yourself; Django will call
|
||||
it automatically when it's needed.
|
||||
|
||||
|
@ -275,7 +275,7 @@ For each field, we describe the default widget used if you don't specify
|
||||
* Default widget: ``CheckboxInput``
|
||||
* Empty value: ``False``
|
||||
* Normalizes to: A Python ``True`` or ``False`` value.
|
||||
* Validates that the check box is checked (i.e. the value is ``True``) if
|
||||
* Validates that the value is ``True`` (e.g. the check box is checked) if
|
||||
the field has ``required=True``.
|
||||
* Error message keys: ``required``
|
||||
|
||||
@ -287,9 +287,10 @@ For each field, we describe the default widget used if you don't specify
|
||||
.. note::
|
||||
|
||||
Since all ``Field`` subclasses have ``required=True`` by default, the
|
||||
validation condition here is important. If you want to include a checkbox
|
||||
in your form that can be either checked or unchecked, you must remember to
|
||||
pass in ``required=False`` when creating the ``BooleanField``.
|
||||
validation condition here is important. If you want to include a boolean
|
||||
in your form that can be either ``True`` or ``False`` (e.g. a checked or
|
||||
unchecked checkbox), you must remember to pass in ``required=False`` when
|
||||
creating the ``BooleanField``.
|
||||
|
||||
``CharField``
|
||||
~~~~~~~~~~~~~
|
||||
|
@ -668,7 +668,7 @@ of the arguments is required, but you should use at least one of them.
|
||||
|
||||
The resulting SQL of the above example would be::
|
||||
|
||||
SELECT blog_blog.*, (SELECT COUNT(*) FROM blog_entry WHERE blog_entry.blog_id = blog_blog.id)
|
||||
SELECT blog_blog.*, (SELECT COUNT(*) FROM blog_entry WHERE blog_entry.blog_id = blog_blog.id) AS entry_count
|
||||
FROM blog_blog;
|
||||
|
||||
Note that the parenthesis required by most database engines around
|
||||
|
@ -86,9 +86,16 @@ Rendering a context
|
||||
|
||||
Once you have a compiled ``Template`` object, you can render a context -- or
|
||||
multiple contexts -- with it. The ``Context`` class lives at
|
||||
``django.template.Context``, and the constructor takes one (optional)
|
||||
argument: a dictionary mapping variable names to variable values. Call the
|
||||
``Template`` object's ``render()`` method with the context to "fill" the
|
||||
``django.template.Context``, and the constructor takes two (optional)
|
||||
arguments:
|
||||
|
||||
* A dictionary mapping variable names to variable values.
|
||||
|
||||
* The name of the current application. This application name is used
|
||||
to help :ref:`resolve namespaced URLs<topics-http-reversing-url-namespaces>`.
|
||||
If you're not using namespaced URLs, you can ignore this argument.
|
||||
|
||||
Call the ``Template`` object's ``render()`` method with the context to "fill" the
|
||||
template::
|
||||
|
||||
>>> from django.template import Context, Template
|
||||
|
@ -795,6 +795,16 @@ missing. In practice you'll use this to link to views that are optional::
|
||||
<a href="{{ the_url }}">Link to optional stuff</a>
|
||||
{% endif %}
|
||||
|
||||
.. versionadded:: 1.1
|
||||
|
||||
If you'd like to retrieve a namespaced URL, specify the fully qualified name::
|
||||
|
||||
{% url myapp:view-name %}
|
||||
|
||||
This will follow the normal :ref:`namespaced URL resolution strategy
|
||||
<topics-http-reversing-url-namespaces>`, including using any hints provided
|
||||
by the context as to the current application.
|
||||
|
||||
.. templatetag:: widthratio
|
||||
|
||||
widthratio
|
||||
|
@ -86,9 +86,9 @@ displayed.
|
||||
Formset validation
|
||||
------------------
|
||||
|
||||
Validation with a formset is about identical to a regular ``Form``. There is
|
||||
Validation with a formset is almost identical to a regular ``Form``. There is
|
||||
an ``is_valid`` method on the formset to provide a convenient way to validate
|
||||
each form in the formset::
|
||||
all forms in the formset::
|
||||
|
||||
>>> ArticleFormSet = formset_factory(ArticleForm)
|
||||
>>> formset = ArticleFormSet({})
|
||||
@ -97,22 +97,25 @@ each form in the formset::
|
||||
|
||||
We passed in no data to the formset which is resulting in a valid form. The
|
||||
formset is smart enough to ignore extra forms that were not changed. If we
|
||||
attempt to provide an article, but fail to do so::
|
||||
provide an invalid article::
|
||||
|
||||
>>> data = {
|
||||
... 'form-TOTAL_FORMS': u'1',
|
||||
... 'form-INITIAL_FORMS': u'1',
|
||||
... 'form-TOTAL_FORMS': u'2',
|
||||
... 'form-INITIAL_FORMS': u'0',
|
||||
... 'form-0-title': u'Test',
|
||||
... 'form-0-pub_date': u'',
|
||||
... 'form-0-pub_date': u'16 June 1904',
|
||||
... 'form-1-title': u'Test',
|
||||
... 'form-1-pub_date': u'', # <-- this date is missing but required
|
||||
... }
|
||||
>>> formset = ArticleFormSet(data)
|
||||
>>> formset.is_valid()
|
||||
False
|
||||
>>> formset.errors
|
||||
[{'pub_date': [u'This field is required.']}]
|
||||
[{}, {'pub_date': [u'This field is required.']}]
|
||||
|
||||
As we can see the formset properly performed validation and gave us the
|
||||
expected errors.
|
||||
As we can see, ``formset.errors`` is a list whose entries correspond to the
|
||||
forms in the formset. Validation was performed for each of the two forms, and
|
||||
the expected error message appears for the second item.
|
||||
|
||||
.. _understanding-the-managementform:
|
||||
|
||||
@ -155,20 +158,40 @@ Custom formset validation
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A formset has a ``clean`` method similar to the one on a ``Form`` class. This
|
||||
is where you define your own validation that deals at the formset level::
|
||||
is where you define your own validation that works at the formset level::
|
||||
|
||||
>>> from django.forms.formsets import BaseFormSet
|
||||
|
||||
>>> class BaseArticleFormSet(BaseFormSet):
|
||||
... def clean(self):
|
||||
... raise forms.ValidationError, u'An error occured.'
|
||||
... """Checks that no two articles have the same title."""
|
||||
... if any(self.errors):
|
||||
... # Don't bother validating the formset unless each form is valid on its own
|
||||
... return
|
||||
... titles = []
|
||||
... for i in range(0, self.total_form_count()):
|
||||
... form = self.forms[i]
|
||||
... title = form.cleaned_data['title']
|
||||
... if title in titles:
|
||||
... raise forms.ValidationError, "Articles in a set must have distinct titles."
|
||||
... titles.append(title)
|
||||
|
||||
>>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet)
|
||||
>>> formset = ArticleFormSet({})
|
||||
>>> data = {
|
||||
... 'form-TOTAL_FORMS': u'2',
|
||||
... 'form-INITIAL_FORMS': u'0',
|
||||
... 'form-0-title': u'Test',
|
||||
... 'form-0-pub_date': u'16 June 1904',
|
||||
... 'form-1-title': u'Test',
|
||||
... 'form-1-pub_date': u'23 June 1912',
|
||||
... }
|
||||
>>> formset = ArticleFormSet(data)
|
||||
>>> formset.is_valid()
|
||||
False
|
||||
>>> formset.errors
|
||||
[{}, {}]
|
||||
>>> formset.non_form_errors()
|
||||
[u'An error occured.']
|
||||
[u'Articles in a set must have distinct titles.']
|
||||
|
||||
The formset ``clean`` method is called after all the ``Form.clean`` methods
|
||||
have been called. The errors will be found using the ``non_form_errors()``
|
||||
|
@ -4,6 +4,8 @@
|
||||
URL dispatcher
|
||||
==============
|
||||
|
||||
.. module:: django.core.urlresolvers
|
||||
|
||||
A clean, elegant URL scheme is an important detail in a high-quality Web
|
||||
application. Django lets you design URLs however you want, with no framework
|
||||
limitations.
|
||||
@ -182,11 +184,13 @@ your URLconf. This gives your module access to these objects:
|
||||
patterns
|
||||
--------
|
||||
|
||||
.. function:: patterns(prefix, pattern_description, ...)
|
||||
|
||||
A function that takes a prefix, and an arbitrary number of URL patterns, and
|
||||
returns a list of URL patterns in the format Django needs.
|
||||
|
||||
The first argument to ``patterns()`` is a string ``prefix``. See
|
||||
"The view prefix" below.
|
||||
`The view prefix`_ below.
|
||||
|
||||
The remaining arguments should be tuples in this format::
|
||||
|
||||
@ -222,6 +226,8 @@ url
|
||||
|
||||
.. versionadded:: 1.0
|
||||
|
||||
.. function:: url(regex, view, kwargs=None, name=None, prefix='')
|
||||
|
||||
You can use the ``url()`` function, instead of a tuple, as an argument to
|
||||
``patterns()``. This is convenient if you want to specify a name without the
|
||||
optional extra arguments dictionary. For example::
|
||||
@ -244,6 +250,8 @@ The ``prefix`` parameter has the same meaning as the first argument to
|
||||
handler404
|
||||
----------
|
||||
|
||||
.. data:: handler404
|
||||
|
||||
A string representing the full Python import path to the view that should be
|
||||
called if none of the URL patterns match.
|
||||
|
||||
@ -253,6 +261,8 @@ value should suffice.
|
||||
handler500
|
||||
----------
|
||||
|
||||
.. data:: handler500
|
||||
|
||||
A string representing the full Python import path to the view that should be
|
||||
called in case of server errors. Server errors happen when you have runtime
|
||||
errors in view code.
|
||||
@ -263,8 +273,17 @@ value should suffice.
|
||||
include
|
||||
-------
|
||||
|
||||
A function that takes a full Python import path to another URLconf that should
|
||||
be "included" in this place. See `Including other URLconfs`_ below.
|
||||
.. function:: include(<module or pattern_list>)
|
||||
|
||||
A function that takes a full Python import path to another URLconf module that
|
||||
should be "included" in this place.
|
||||
|
||||
.. versionadded:: 1.1
|
||||
|
||||
:func:`include` also accepts as an argument an iterable that returns URL
|
||||
patterns.
|
||||
|
||||
See `Including other URLconfs`_ below.
|
||||
|
||||
Notes on capturing text in URLs
|
||||
===============================
|
||||
@ -391,6 +410,32 @@ Django encounters ``include()``, it chops off whatever part of the URL matched
|
||||
up to that point and sends the remaining string to the included URLconf for
|
||||
further processing.
|
||||
|
||||
.. versionadded:: 1.1
|
||||
|
||||
Another possibility is to include additional URL patterns not by specifying the
|
||||
URLconf Python module defining them as the `include`_ argument but by using
|
||||
directly the pattern list as returned by `patterns`_ instead. For example::
|
||||
|
||||
from django.conf.urls.defaults import *
|
||||
|
||||
extra_patterns = patterns('',
|
||||
url(r'reports/(?P<id>\d+)/$', 'credit.views.report', name='credit-reports'),
|
||||
url(r'charge/$', 'credit.views.charge', name='credit-charge'),
|
||||
)
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^$', 'apps.main.views.homepage', name='site-homepage'),
|
||||
(r'^help/', include('apps.help.urls')),
|
||||
(r'^credit/', include(extra_patterns)),
|
||||
)
|
||||
|
||||
This approach can be seen in use when you deploy an instance of the Django
|
||||
Admin application. The Django Admin is deployed as instances of a
|
||||
:class:`AdminSite`; each :class:`AdminSite` instance has an attribute
|
||||
``urls`` that returns the url patterns available to that instance. It is this
|
||||
attribute that you ``include()`` into your projects ``urlpatterns`` when you
|
||||
deploy the admin instance.
|
||||
|
||||
.. _`Django Web site`: http://www.djangoproject.com/
|
||||
|
||||
Captured parameters
|
||||
@ -413,6 +458,58 @@ the following example is valid::
|
||||
In the above example, the captured ``"username"`` variable is passed to the
|
||||
included URLconf, as expected.
|
||||
|
||||
.. _topics-http-defining-url-namespaces:
|
||||
|
||||
Defining URL Namespaces
|
||||
-----------------------
|
||||
|
||||
When you need to deploy multiple instances of a single application, it can be
|
||||
helpful to be able to differentiate between instances. This is especially
|
||||
important when using _`named URL patterns <naming-url-patterns>`, since
|
||||
multiple instances of a single application will share named URLs. Namespaces
|
||||
provide a way to tell these named URLs apart.
|
||||
|
||||
A URL namespace comes in two parts, both of which are strings:
|
||||
|
||||
* An **application namespace**. This describes the name of the application
|
||||
that is being deployed. Every instance of a single application will have
|
||||
the same application namespace. For example, Django's admin application
|
||||
has the somewhat predictable application namespace of ``admin``.
|
||||
|
||||
* An **instance namespace**. This identifies a specific instance of an
|
||||
application. Instance namespaces should be unique across your entire
|
||||
project. However, an instance namespace can be the same as the
|
||||
application namespace. This is used to specify a default instance of an
|
||||
application. For example, the default Django Admin instance has an
|
||||
instance namespace of ``admin``.
|
||||
|
||||
URL Namespaces can be specified in two ways.
|
||||
|
||||
Firstly, you can provide the application and instance namespace as arguments
|
||||
to ``include()`` when you construct your URL patterns. For example,::
|
||||
|
||||
(r'^help/', include('apps.help.urls', namespace='foo', app_name='bar')),
|
||||
|
||||
This will include the URLs defined in ``apps.help.urls`` into the application
|
||||
namespace ``bar``, with the instance namespace ``foo``.
|
||||
|
||||
Secondly, you can include an object that contains embedded namespace data. If
|
||||
you ``include()`` a ``patterns`` object, that object will be added to the
|
||||
global namespace. However, you can also ``include()`` an object that contains
|
||||
a 3-tuple containing::
|
||||
|
||||
(<patterns object>, <application namespace>, <instance namespace>)
|
||||
|
||||
This will include the nominated URL patterns into the given application and
|
||||
instance namespace. For example, the ``urls`` attribute of Django's
|
||||
:class:`AdminSite` object returns a 3-tuple that contains all the patterns in
|
||||
an admin site, plus the name of the admin instance, and the application
|
||||
namespace ``admin``.
|
||||
|
||||
Once you have defined namespaced URLs, you can reverse them. For details on
|
||||
reversing namespaced urls, see the documentation on :ref:`reversing namespaced
|
||||
URLs <topics-http-reversing-url-namespaces>`.
|
||||
|
||||
Passing extra options to view functions
|
||||
=======================================
|
||||
|
||||
@ -587,6 +684,86 @@ not restricted to valid Python names.
|
||||
name, will decrease the chances of collision. We recommend something like
|
||||
``myapp-comment`` instead of ``comment``.
|
||||
|
||||
.. _topics-http-reversing-url-namespaces:
|
||||
|
||||
URL namespaces
|
||||
--------------
|
||||
|
||||
.. versionadded:: 1.1
|
||||
|
||||
Namespaced URLs are specified using the ``:`` operator. For example, the main
|
||||
index page of the admin application is referenced using ``admin:index``. This
|
||||
indicates a namespace of ``admin``, and a named URL of ``index``.
|
||||
|
||||
Namespaces can also be nested. The named URL ``foo:bar:whiz`` would look for
|
||||
a pattern named ``whiz`` in the namespace ``bar`` that is itself defined within
|
||||
the top-level namespace ``foo``.
|
||||
|
||||
When given a namespaced URL (e.g. ``myapp:index``) to resolve, Django splits
|
||||
the fully qualified name into parts, and then tries the following lookup:
|
||||
|
||||
1. First, Django looks for a matching application namespace (in this
|
||||
example, ``myapp``). This will yield a list of instances of that
|
||||
application.
|
||||
|
||||
2. If there is a ``current`` application defined, Django finds and returns
|
||||
the URL resolver for that instance. The ``current`` can be specified
|
||||
as an attribute on the template context - applications that expect to
|
||||
have multiple deployments should set the ``current_app`` attribute on
|
||||
any ``Context`` or ``RequestContext`` that is used to render a
|
||||
template.
|
||||
|
||||
The current application can also be specified manually as an argument
|
||||
to the :func:`reverse()` function.
|
||||
|
||||
3. If there is no current application. Django looks for a default
|
||||
application instance. The default application instance is the instance
|
||||
that has an instance namespace matching the application namespace (in
|
||||
this example, an instance of the ``myapp`` called ``myapp``).
|
||||
|
||||
4. If there is no default application instance, Django will pick the first
|
||||
deployed instance of the application, whatever its instance name may be.
|
||||
|
||||
5. If the provided namespace doesn't match an application namespace in
|
||||
step 2, Django will attempt a direct lookup of the namespace as an
|
||||
instance namespace.
|
||||
|
||||
If there are nested namespaces, these steps are repeated for each part of the
|
||||
namespace until only the view name is unresolved. The view name will then be
|
||||
resolved into a URL in the namespace that has been found.
|
||||
|
||||
To show this resolution strategy in action, consider an example of two instances
|
||||
of ``myapp``: one called ``foo``, and one called ``bar``. ``myapp`` has a main
|
||||
index page with a URL named `index`. Using this setup, the following lookups are
|
||||
possible:
|
||||
|
||||
* If one of the instances is current - say, if we were rendering a utility page
|
||||
in the instance ``bar`` - ``myapp:index`` will resolve to the index page of
|
||||
the instance ``bar``.
|
||||
|
||||
* If there is no current instance - say, if we were rendering a page
|
||||
somewhere else on the site - ``myapp:index`` will resolve to the first
|
||||
registered instance of ``myapp``. Since there is no default instance,
|
||||
the first instance of ``myapp`` that is registered will be used. This could
|
||||
be ``foo`` or ``bar``, depending on the order they are introduced into the
|
||||
urlpatterns of the project.
|
||||
|
||||
* ``foo:index`` will always resolve to the index page of the instance ``foo``.
|
||||
|
||||
If there was also a default instance - i.e., an instance named `myapp` - the
|
||||
following would happen:
|
||||
|
||||
* If one of the instances is current - say, if we were rendering a utility page
|
||||
in the instance ``bar`` - ``myapp:index`` will resolve to the index page of
|
||||
the instance ``bar``.
|
||||
|
||||
* If there is no current instance - say, if we were rendering a page somewhere
|
||||
else on the site - ``myapp:index`` will resolve to the index page of the
|
||||
default instance.
|
||||
|
||||
* ``foo:index`` will again resolve to the index page of the instance ``foo``.
|
||||
|
||||
|
||||
Utility methods
|
||||
===============
|
||||
|
||||
@ -597,8 +774,7 @@ If you need to use something similar to the :ttag:`url` template tag in
|
||||
your code, Django provides the following method (in the
|
||||
``django.core.urlresolvers`` module):
|
||||
|
||||
.. currentmodule:: django.core.urlresolvers
|
||||
.. function:: reverse(viewname, urlconf=None, args=None, kwargs=None)
|
||||
.. function:: reverse(viewname, urlconf=None, args=None, kwargs=None, current_app=None)
|
||||
|
||||
``viewname`` is either the function name (either a function reference, or the
|
||||
string version of the name, if you used that form in ``urlpatterns``) or the
|
||||
@ -620,6 +796,14 @@ vertical bar (``"|"``) character. You can quite happily use such patterns for
|
||||
matching against incoming URLs and sending them off to views, but you cannot
|
||||
reverse such patterns.
|
||||
|
||||
.. versionadded:: 1.1
|
||||
|
||||
The ``current_app`` argument allows you to provide a hint to the resolver
|
||||
indicating the application to which the currently executing view belongs.
|
||||
This ``current_app`` argument is used as a hint to resolve application
|
||||
namespaces into URLs on specific application instances, according to the
|
||||
:ref:`namespaced URL resolution strategy <topics-http-reversing-url-namespaces>`.
|
||||
|
||||
.. admonition:: Make sure your views are all correct
|
||||
|
||||
As part of working out which URL names map to which patterns, the
|
||||
@ -639,7 +823,6 @@ resolve()
|
||||
The :func:`django.core.urlresolvers.resolve` function can be used for resolving
|
||||
URL paths to the corresponding view functions. It has the following signature:
|
||||
|
||||
.. currentmodule:: django.core.urlresolvers
|
||||
.. function:: resolve(path, urlconf=None)
|
||||
|
||||
``path`` is the URL path you want to resolve. As with ``reverse()`` above, you
|
||||
|
@ -959,11 +959,11 @@ Using the JavaScript translation catalog
|
||||
|
||||
To use the catalog, just pull in the dynamically generated script like this::
|
||||
|
||||
<script type="text/javascript" src="/path/to/jsi18n/"></script>
|
||||
<script type="text/javascript" src="{% url django.views.i18n.javascript_catalog %}"></script>
|
||||
|
||||
This is how the admin fetches the translation catalog from the server. When the
|
||||
catalog is loaded, your JavaScript code can use the standard ``gettext``
|
||||
interface to access it::
|
||||
This uses reverse URL lookup to find the URL of the JavaScript catalog view.
|
||||
When the catalog is loaded, your JavaScript code can use the standard
|
||||
``gettext`` interface to access it::
|
||||
|
||||
document.write(gettext('this is to be translated'));
|
||||
|
||||
|
@ -686,7 +686,13 @@ arguments at time of construction:
|
||||
user accounts that are valid on your production site will not work
|
||||
under test conditions. You'll need to create users as part of the test
|
||||
suite -- either manually (using the Django model API) or with a test
|
||||
fixture.
|
||||
fixture. Remember that if you want your test user to have a password,
|
||||
you can't set the user's password by setting the password attribute
|
||||
directly -- you must use the
|
||||
:meth:`~django.contrib.auth.models.User.set_password()` function to
|
||||
store a correctly hashed password. Alternatively, you can use the
|
||||
:meth:`~django.contrib.auth.models.UserManager.create_user` helper
|
||||
method to create a new user with a correctly hashed password.
|
||||
|
||||
.. method:: Client.logout()
|
||||
|
||||
|
@ -10,6 +10,7 @@ from django.contrib.admin.models import LogEntry, DELETION
|
||||
from django.contrib.admin.sites import LOGIN_FORM_KEY
|
||||
from django.contrib.admin.util import quote
|
||||
from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
|
||||
from django.utils.cache import get_max_age
|
||||
from django.utils.html import escape
|
||||
|
||||
# local test models
|
||||
@ -204,6 +205,11 @@ class AdminViewBasicTest(TestCase):
|
||||
response = self.client.get('/test_admin/%s/admin_views/thing/' % self.urlbit, {'color__id__exact': 'StringNotInteger!'})
|
||||
self.assertRedirects(response, '/test_admin/%s/admin_views/thing/?e=1' % self.urlbit)
|
||||
|
||||
def testLogoutAndPasswordChangeURLs(self):
|
||||
response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit)
|
||||
self.failIf('<a href="/test_admin/%s/logout/">' % self.urlbit not in response.content)
|
||||
self.failIf('<a href="/test_admin/%s/password_change/">' % self.urlbit not in response.content)
|
||||
|
||||
def testNamedGroupFieldChoicesChangeList(self):
|
||||
"""
|
||||
Ensures the admin changelist shows correct values in the relevant column
|
||||
@ -1527,3 +1533,76 @@ class AdminInlineTests(TestCase):
|
||||
self.failUnlessEqual(Category.objects.get(id=2).order, 13)
|
||||
self.failUnlessEqual(Category.objects.get(id=3).order, 1)
|
||||
self.failUnlessEqual(Category.objects.get(id=4).order, 0)
|
||||
|
||||
|
||||
class NeverCacheTests(TestCase):
|
||||
fixtures = ['admin-views-users.xml', 'admin-views-colors.xml', 'admin-views-fabrics.xml']
|
||||
|
||||
def setUp(self):
|
||||
self.client.login(username='super', password='secret')
|
||||
|
||||
def tearDown(self):
|
||||
self.client.logout()
|
||||
|
||||
def testAdminIndex(self):
|
||||
"Check the never-cache status of the main index"
|
||||
response = self.client.get('/test_admin/admin/')
|
||||
self.failUnlessEqual(get_max_age(response), 0)
|
||||
|
||||
def testAppIndex(self):
|
||||
"Check the never-cache status of an application index"
|
||||
response = self.client.get('/test_admin/admin/admin_views/')
|
||||
self.failUnlessEqual(get_max_age(response), 0)
|
||||
|
||||
def testModelIndex(self):
|
||||
"Check the never-cache status of a model index"
|
||||
response = self.client.get('/test_admin/admin/admin_views/fabric/')
|
||||
self.failUnlessEqual(get_max_age(response), 0)
|
||||
|
||||
def testModelAdd(self):
|
||||
"Check the never-cache status of a model add page"
|
||||
response = self.client.get('/test_admin/admin/admin_views/fabric/add/')
|
||||
self.failUnlessEqual(get_max_age(response), 0)
|
||||
|
||||
def testModelView(self):
|
||||
"Check the never-cache status of a model edit page"
|
||||
response = self.client.get('/test_admin/admin/admin_views/section/1/')
|
||||
self.failUnlessEqual(get_max_age(response), 0)
|
||||
|
||||
def testModelHistory(self):
|
||||
"Check the never-cache status of a model history page"
|
||||
response = self.client.get('/test_admin/admin/admin_views/section/1/history/')
|
||||
self.failUnlessEqual(get_max_age(response), 0)
|
||||
|
||||
def testModelDelete(self):
|
||||
"Check the never-cache status of a model delete page"
|
||||
response = self.client.get('/test_admin/admin/admin_views/section/1/delete/')
|
||||
self.failUnlessEqual(get_max_age(response), 0)
|
||||
|
||||
def testLogin(self):
|
||||
"Check the never-cache status of login views"
|
||||
self.client.logout()
|
||||
response = self.client.get('/test_admin/admin/')
|
||||
self.failUnlessEqual(get_max_age(response), 0)
|
||||
|
||||
def testLogout(self):
|
||||
"Check the never-cache status of logout view"
|
||||
response = self.client.get('/test_admin/admin/logout/')
|
||||
self.failUnlessEqual(get_max_age(response), 0)
|
||||
|
||||
def testPasswordChange(self):
|
||||
"Check the never-cache status of the password change view"
|
||||
self.client.logout()
|
||||
response = self.client.get('/test_admin/password_change/')
|
||||
self.failUnlessEqual(get_max_age(response), None)
|
||||
|
||||
def testPasswordChangeDone(self):
|
||||
"Check the never-cache status of the password change done view"
|
||||
response = self.client.get('/test_admin/admin/password_change/done/')
|
||||
self.failUnlessEqual(get_max_age(response), None)
|
||||
|
||||
def testJsi18n(self):
|
||||
"Check the never-cache status of the Javascript i18n view"
|
||||
response = self.client.get('/test_admin/jsi18n/')
|
||||
self.failUnlessEqual(get_max_age(response), None)
|
||||
|
||||
|
@ -19,7 +19,7 @@ class CarTireAdmin(admin.ModelAdmin):
|
||||
return db_field.formfield(**kwargs)
|
||||
return super(CarTireAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
|
||||
|
||||
site = WidgetAdmin()
|
||||
site = WidgetAdmin(name='widget-admin')
|
||||
|
||||
site.register(models.User)
|
||||
site.register(models.Car, CarAdmin)
|
||||
|
@ -0,0 +1,34 @@
|
||||
[
|
||||
{
|
||||
"pk": "1",
|
||||
"model": "m2m_through_regress.person",
|
||||
"fields": {
|
||||
"name": "Guido"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": "1",
|
||||
"model": "auth.user",
|
||||
"fields": {
|
||||
"username": "Guido",
|
||||
"email": "bdfl@python.org",
|
||||
"password": "abcde"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": "1",
|
||||
"model": "m2m_through_regress.group",
|
||||
"fields": {
|
||||
"name": "Python Core Group"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": "1",
|
||||
"model": "m2m_through_regress.usermembership",
|
||||
"fields": {
|
||||
"user": "1",
|
||||
"group": "1",
|
||||
"price": "100"
|
||||
}
|
||||
}
|
||||
]
|
@ -12,7 +12,9 @@ class Membership(models.Model):
|
||||
def __unicode__(self):
|
||||
return "%s is a member of %s" % (self.person.name, self.group.name)
|
||||
|
||||
# using custom id column to test ticket #11107
|
||||
class UserMembership(models.Model):
|
||||
id = models.AutoField(db_column='usermembership_id', primary_key=True)
|
||||
user = models.ForeignKey(User)
|
||||
group = models.ForeignKey('Group')
|
||||
price = models.IntegerField(default=100)
|
||||
@ -196,4 +198,12 @@ doing a join.
|
||||
# Flush the database, just to make sure we can.
|
||||
>>> management.call_command('flush', verbosity=0, interactive=False)
|
||||
|
||||
## Regression test for #11107
|
||||
Ensure that sequences on m2m_through tables are being created for the through
|
||||
model, not for a phantom auto-generated m2m table.
|
||||
|
||||
>>> management.call_command('loaddata', 'm2m_through', verbosity=0)
|
||||
>>> management.call_command('dumpdata', 'm2m_through_regress', format='json')
|
||||
[{"pk": 1, "model": "m2m_through_regress.usermembership", "fields": {"price": 100, "group": 1, "user": 1}}, {"pk": 1, "model": "m2m_through_regress.person", "fields": {"name": "Guido"}}, {"pk": 1, "model": "m2m_through_regress.group", "fields": {"name": "Python Core Group"}}]
|
||||
|
||||
"""}
|
||||
|
@ -0,0 +1,13 @@
|
||||
from django.conf.urls.defaults import *
|
||||
from namespace_urls import URLObject
|
||||
|
||||
testobj3 = URLObject('testapp', 'test-ns3')
|
||||
|
||||
urlpatterns = patterns('regressiontests.urlpatterns_reverse.views',
|
||||
url(r'^normal/$', 'empty_view', name='inc-normal-view'),
|
||||
url(r'^normal/(?P<arg1>\d+)/(?P<arg2>\d+)/$', 'empty_view', name='inc-normal-view'),
|
||||
|
||||
(r'^test3/', include(testobj3.urls)),
|
||||
(r'^ns-included3/', include('regressiontests.urlpatterns_reverse.included_urls', namespace='inc-ns3')),
|
||||
)
|
||||
|
38
tests/regressiontests/urlpatterns_reverse/namespace_urls.py
Normal file
38
tests/regressiontests/urlpatterns_reverse/namespace_urls.py
Normal file
@ -0,0 +1,38 @@
|
||||
from django.conf.urls.defaults import *
|
||||
|
||||
class URLObject(object):
|
||||
def __init__(self, app_name, namespace):
|
||||
self.app_name = app_name
|
||||
self.namespace = namespace
|
||||
|
||||
def urls(self):
|
||||
return patterns('',
|
||||
url(r'^inner/$', 'empty_view', name='urlobject-view'),
|
||||
url(r'^inner/(?P<arg1>\d+)/(?P<arg2>\d+)/$', 'empty_view', name='urlobject-view'),
|
||||
), self.app_name, self.namespace
|
||||
urls = property(urls)
|
||||
|
||||
testobj1 = URLObject('testapp', 'test-ns1')
|
||||
testobj2 = URLObject('testapp', 'test-ns2')
|
||||
default_testobj = URLObject('testapp', 'testapp')
|
||||
|
||||
otherobj1 = URLObject('nodefault', 'other-ns1')
|
||||
otherobj2 = URLObject('nodefault', 'other-ns2')
|
||||
|
||||
urlpatterns = patterns('regressiontests.urlpatterns_reverse.views',
|
||||
url(r'^normal/$', 'empty_view', name='normal-view'),
|
||||
url(r'^normal/(?P<arg1>\d+)/(?P<arg2>\d+)/$', 'empty_view', name='normal-view'),
|
||||
|
||||
(r'^test1/', include(testobj1.urls)),
|
||||
(r'^test2/', include(testobj2.urls)),
|
||||
(r'^default/', include(default_testobj.urls)),
|
||||
|
||||
(r'^other1/', include(otherobj1.urls)),
|
||||
(r'^other2/', include(otherobj2.urls)),
|
||||
|
||||
(r'^ns-included1/', include('regressiontests.urlpatterns_reverse.included_namespace_urls', namespace='inc-ns1')),
|
||||
(r'^ns-included2/', include('regressiontests.urlpatterns_reverse.included_namespace_urls', namespace='inc-ns2')),
|
||||
|
||||
(r'^included/', include('regressiontests.urlpatterns_reverse.included_namespace_urls')),
|
||||
|
||||
)
|
@ -159,3 +159,83 @@ class ReverseShortcutTests(TestCase):
|
||||
self.assertEqual(res['Location'], '/foo/')
|
||||
res = redirect('http://example.com/')
|
||||
self.assertEqual(res['Location'], 'http://example.com/')
|
||||
|
||||
|
||||
class NamespaceTests(TestCase):
|
||||
urls = 'regressiontests.urlpatterns_reverse.namespace_urls'
|
||||
|
||||
def test_ambiguous_object(self):
|
||||
"Names deployed via dynamic URL objects that require namespaces can't be resolved"
|
||||
self.assertRaises(NoReverseMatch, reverse, 'urlobject-view')
|
||||
self.assertRaises(NoReverseMatch, reverse, 'urlobject-view', args=[37,42])
|
||||
self.assertRaises(NoReverseMatch, reverse, 'urlobject-view', kwargs={'arg1':42, 'arg2':37})
|
||||
|
||||
def test_ambiguous_urlpattern(self):
|
||||
"Names deployed via dynamic URL objects that require namespaces can't be resolved"
|
||||
self.assertRaises(NoReverseMatch, reverse, 'inner-nothing')
|
||||
self.assertRaises(NoReverseMatch, reverse, 'inner-nothing', args=[37,42])
|
||||
self.assertRaises(NoReverseMatch, reverse, 'inner-nothing', kwargs={'arg1':42, 'arg2':37})
|
||||
|
||||
def test_non_existent_namespace(self):
|
||||
"Non-existent namespaces raise errors"
|
||||
self.assertRaises(NoReverseMatch, reverse, 'blahblah:urlobject-view')
|
||||
self.assertRaises(NoReverseMatch, reverse, 'test-ns1:blahblah:urlobject-view')
|
||||
|
||||
def test_normal_name(self):
|
||||
"Normal lookups work as expected"
|
||||
self.assertEquals('/normal/', reverse('normal-view'))
|
||||
self.assertEquals('/normal/37/42/', reverse('normal-view', args=[37,42]))
|
||||
self.assertEquals('/normal/42/37/', reverse('normal-view', kwargs={'arg1':42, 'arg2':37}))
|
||||
|
||||
def test_simple_included_name(self):
|
||||
"Normal lookups work on names included from other patterns"
|
||||
self.assertEquals('/included/normal/', reverse('inc-normal-view'))
|
||||
self.assertEquals('/included/normal/37/42/', reverse('inc-normal-view', args=[37,42]))
|
||||
self.assertEquals('/included/normal/42/37/', reverse('inc-normal-view', kwargs={'arg1':42, 'arg2':37}))
|
||||
|
||||
def test_namespace_object(self):
|
||||
"Dynamic URL objects can be found using a namespace"
|
||||
self.assertEquals('/test1/inner/', reverse('test-ns1:urlobject-view'))
|
||||
self.assertEquals('/test1/inner/37/42/', reverse('test-ns1:urlobject-view', args=[37,42]))
|
||||
self.assertEquals('/test1/inner/42/37/', reverse('test-ns1:urlobject-view', kwargs={'arg1':42, 'arg2':37}))
|
||||
|
||||
def test_embedded_namespace_object(self):
|
||||
"Namespaces can be installed anywhere in the URL pattern tree"
|
||||
self.assertEquals('/included/test3/inner/', reverse('test-ns3:urlobject-view'))
|
||||
self.assertEquals('/included/test3/inner/37/42/', reverse('test-ns3:urlobject-view', args=[37,42]))
|
||||
self.assertEquals('/included/test3/inner/42/37/', reverse('test-ns3:urlobject-view', kwargs={'arg1':42, 'arg2':37}))
|
||||
|
||||
def test_namespace_pattern(self):
|
||||
"Namespaces can be applied to include()'d urlpatterns"
|
||||
self.assertEquals('/ns-included1/normal/', reverse('inc-ns1:inc-normal-view'))
|
||||
self.assertEquals('/ns-included1/normal/37/42/', reverse('inc-ns1:inc-normal-view', args=[37,42]))
|
||||
self.assertEquals('/ns-included1/normal/42/37/', reverse('inc-ns1:inc-normal-view', kwargs={'arg1':42, 'arg2':37}))
|
||||
|
||||
def test_multiple_namespace_pattern(self):
|
||||
"Namespaces can be embedded"
|
||||
self.assertEquals('/ns-included1/test3/inner/', reverse('inc-ns1:test-ns3:urlobject-view'))
|
||||
self.assertEquals('/ns-included1/test3/inner/37/42/', reverse('inc-ns1:test-ns3:urlobject-view', args=[37,42]))
|
||||
self.assertEquals('/ns-included1/test3/inner/42/37/', reverse('inc-ns1:test-ns3:urlobject-view', kwargs={'arg1':42, 'arg2':37}))
|
||||
|
||||
def test_app_lookup_object(self):
|
||||
"A default application namespace can be used for lookup"
|
||||
self.assertEquals('/default/inner/', reverse('testapp:urlobject-view'))
|
||||
self.assertEquals('/default/inner/37/42/', reverse('testapp:urlobject-view', args=[37,42]))
|
||||
self.assertEquals('/default/inner/42/37/', reverse('testapp:urlobject-view', kwargs={'arg1':42, 'arg2':37}))
|
||||
|
||||
def test_app_lookup_object_with_default(self):
|
||||
"A default application namespace is sensitive to the 'current' app can be used for lookup"
|
||||
self.assertEquals('/included/test3/inner/', reverse('testapp:urlobject-view', current_app='test-ns3'))
|
||||
self.assertEquals('/included/test3/inner/37/42/', reverse('testapp:urlobject-view', args=[37,42], current_app='test-ns3'))
|
||||
self.assertEquals('/included/test3/inner/42/37/', reverse('testapp:urlobject-view', kwargs={'arg1':42, 'arg2':37}, current_app='test-ns3'))
|
||||
|
||||
def test_app_lookup_object_without_default(self):
|
||||
"An application namespace without a default is sensitive to the 'current' app can be used for lookup"
|
||||
self.assertEquals('/other2/inner/', reverse('nodefault:urlobject-view'))
|
||||
self.assertEquals('/other2/inner/37/42/', reverse('nodefault:urlobject-view', args=[37,42]))
|
||||
self.assertEquals('/other2/inner/42/37/', reverse('nodefault:urlobject-view', kwargs={'arg1':42, 'arg2':37}))
|
||||
|
||||
self.assertEquals('/other1/inner/', reverse('nodefault:urlobject-view', current_app='other-ns1'))
|
||||
self.assertEquals('/other1/inner/37/42/', reverse('nodefault:urlobject-view', args=[37,42], current_app='other-ns1'))
|
||||
self.assertEquals('/other1/inner/42/37/', reverse('nodefault:urlobject-view', kwargs={'arg1':42, 'arg2':37}, current_app='other-ns1'))
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user