1
0
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:
Chris Cahoon 2009-07-17 11:33:35 +00:00
parent bf66b536a2
commit a873df6f37
46 changed files with 1665 additions and 745 deletions

View File

@ -131,6 +131,7 @@ answer newbie questions, and generally made Django that much better:
Andrew Durdin <adurdin@gmail.com> Andrew Durdin <adurdin@gmail.com>
dusk@woofle.net dusk@woofle.net
Andy Dustman <farcepest@gmail.com> Andy Dustman <farcepest@gmail.com>
J. Clifford Dyer <jcd@unc.edu>
Clint Ecker Clint Ecker
Nick Efford <nick@efford.org> Nick Efford <nick@efford.org>
eibaan@gmail.com eibaan@gmail.com

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,16 @@ __all__ = ['handler404', 'handler500', 'include', 'patterns', 'url']
handler404 = 'django.views.defaults.page_not_found' handler404 = 'django.views.defaults.page_not_found'
handler500 = 'django.views.defaults.server_error' 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): def patterns(prefix, *args):
pattern_list = [] pattern_list = []
@ -19,9 +28,10 @@ def patterns(prefix, *args):
return pattern_list return pattern_list
def url(regex, view, kwargs=None, name=None, prefix=''): def url(regex, view, kwargs=None, name=None, prefix=''):
if type(view) == list: if isinstance(view, (list,tuple)):
# For include(...) processing. # 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: else:
if isinstance(view, basestring): if isinstance(view, basestring):
if not view: if not view:

View File

@ -226,24 +226,24 @@ class ModelAdmin(BaseModelAdmin):
return self.admin_site.admin_view(view)(*args, **kwargs) return self.admin_site.admin_view(view)(*args, **kwargs)
return update_wrapper(wrapper, view) 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('', urlpatterns = patterns('',
url(r'^$', url(r'^$',
wrap(self.changelist_view), wrap(self.changelist_view),
name='%sadmin_%s_%s_changelist' % info), name='%s_%s_changelist' % info),
url(r'^add/$', url(r'^add/$',
wrap(self.add_view), wrap(self.add_view),
name='%sadmin_%s_%s_add' % info), name='%s_%s_add' % info),
url(r'^(.+)/history/$', url(r'^(.+)/history/$',
wrap(self.history_view), wrap(self.history_view),
name='%sadmin_%s_%s_history' % info), name='%s_%s_history' % info),
url(r'^(.+)/delete/$', url(r'^(.+)/delete/$',
wrap(self.delete_view), wrap(self.delete_view),
name='%sadmin_%s_%s_delete' % info), name='%s_%s_delete' % info),
url(r'^(.+)/$', url(r'^(.+)/$',
wrap(self.change_view), wrap(self.change_view),
name='%sadmin_%s_%s_change' % info), name='%s_%s_change' % info),
) )
return urlpatterns return urlpatterns
@ -582,11 +582,12 @@ class ModelAdmin(BaseModelAdmin):
'save_on_top': self.save_on_top, 'save_on_top': self.save_on_top,
'root_path': self.admin_site.root_path, '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 [ return render_to_response(self.change_form_template or [
"admin/%s/%s/change_form.html" % (app_label, opts.object_name.lower()), "admin/%s/%s/change_form.html" % (app_label, opts.object_name.lower()),
"admin/%s/change_form.html" % app_label, "admin/%s/change_form.html" % app_label,
"admin/change_form.html" "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/'): def response_add(self, request, obj, post_url_continue='../%s/'):
""" """
@ -977,11 +978,12 @@ class ModelAdmin(BaseModelAdmin):
'actions_on_bottom': self.actions_on_bottom, 'actions_on_bottom': self.actions_on_bottom,
} }
context.update(extra_context or {}) 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 [ return render_to_response(self.change_list_template or [
'admin/%s/%s/change_list.html' % (app_label, opts.object_name.lower()), 'admin/%s/%s/change_list.html' % (app_label, opts.object_name.lower()),
'admin/%s/change_list.html' % app_label, 'admin/%s/change_list.html' % app_label,
'admin/change_list.html' '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): def delete_view(self, request, object_id, extra_context=None):
"The 'delete' admin view for this model." "The 'delete' admin view for this model."
@ -1032,11 +1034,12 @@ class ModelAdmin(BaseModelAdmin):
"app_label": app_label, "app_label": app_label,
} }
context.update(extra_context or {}) 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 [ return render_to_response(self.delete_confirmation_template or [
"admin/%s/%s/delete_confirmation.html" % (app_label, opts.object_name.lower()), "admin/%s/%s/delete_confirmation.html" % (app_label, opts.object_name.lower()),
"admin/%s/delete_confirmation.html" % app_label, "admin/%s/delete_confirmation.html" % app_label,
"admin/delete_confirmation.html" "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): def history_view(self, request, object_id, extra_context=None):
"The 'history' admin view for this model." "The 'history' admin view for this model."
@ -1059,11 +1062,12 @@ class ModelAdmin(BaseModelAdmin):
'app_label': app_label, 'app_label': app_label,
} }
context.update(extra_context or {}) 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 [ return render_to_response(self.object_history_template or [
"admin/%s/%s/object_history.html" % (app_label, opts.object_name.lower()), "admin/%s/%s/object_history.html" % (app_label, opts.object_name.lower()),
"admin/%s/object_history.html" % app_label, "admin/%s/object_history.html" % app_label,
"admin/object_history.html" "admin/object_history.html"
], context, context_instance=template.RequestContext(request)) ], context, context_instance=context_instance)
# #
# DEPRECATED methods. # DEPRECATED methods.

View File

@ -5,6 +5,7 @@ from django.contrib.admin import actions
from django.contrib.auth import authenticate, login from django.contrib.auth import authenticate, login
from django.db.models.base import ModelBase from django.db.models.base import ModelBase
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.core.urlresolvers import reverse
from django.shortcuts import render_to_response from django.shortcuts import render_to_response
from django.utils.functional import update_wrapper from django.utils.functional import update_wrapper
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
@ -38,17 +39,14 @@ class AdminSite(object):
login_template = None login_template = None
app_index_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 self._registry = {} # model_class class -> admin_class instance
# TODO Root path is used to calculate urls under the old root() method self.root_path = None
# 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/'
if name is None: if name is None:
name = '' self.name = 'admin'
else: else:
name += '_' self.name = name
self.name = name self.app_name = app_name
self._actions = {'delete_selected': actions.delete_selected} self._actions = {'delete_selected': actions.delete_selected}
self._global_actions = self._actions.copy() self._global_actions = self._actions.copy()
@ -114,20 +112,20 @@ class AdminSite(object):
name = name or action.__name__ name = name or action.__name__
self._actions[name] = action self._actions[name] = action
self._global_actions[name] = action self._global_actions[name] = action
def disable_action(self, name): def disable_action(self, name):
""" """
Disable a globally-registered action. Raises KeyError for invalid names. Disable a globally-registered action. Raises KeyError for invalid names.
""" """
del self._actions[name] del self._actions[name]
def get_action(self, name): def get_action(self, name):
""" """
Explicitally get a registered global action wheather it's enabled or Explicitally get a registered global action wheather it's enabled or
not. Raises KeyError for invalid names. not. Raises KeyError for invalid names.
""" """
return self._global_actions[name] return self._global_actions[name]
def actions(self): def actions(self):
""" """
Get all the enabled actions as an iterable of (name, func). Get all the enabled actions as an iterable of (name, func).
@ -159,9 +157,9 @@ class AdminSite(object):
if 'django.core.context_processors.auth' not in settings.TEMPLATE_CONTEXT_PROCESSORS: 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.") 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 wraps the view and provides permission checking by calling
``self.has_permission``. ``self.has_permission``.
@ -177,43 +175,49 @@ class AdminSite(object):
url(r'^my_view/$', self.admin_view(some_view)) url(r'^my_view/$', self.admin_view(some_view))
) )
return urls 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): def inner(request, *args, **kwargs):
if not self.has_permission(request): if not self.has_permission(request):
return self.login(request) return self.login(request)
return view(request, *args, **kwargs) return view(request, *args, **kwargs)
if not cacheable:
inner = never_cache(inner)
return update_wrapper(inner, view) return update_wrapper(inner, view)
def get_urls(self): def get_urls(self):
from django.conf.urls.defaults import patterns, url, include from django.conf.urls.defaults import patterns, url, include
def wrap(view): def wrap(view, cacheable=False):
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
return self.admin_view(view)(*args, **kwargs) return self.admin_view(view, cacheable)(*args, **kwargs)
return update_wrapper(wrapper, view) return update_wrapper(wrapper, view)
# Admin-site-wide views. # Admin-site-wide views.
urlpatterns = patterns('', urlpatterns = patterns('',
url(r'^$', url(r'^$',
wrap(self.index), wrap(self.index),
name='%sadmin_index' % self.name), name='index'),
url(r'^logout/$', url(r'^logout/$',
wrap(self.logout), wrap(self.logout),
name='%sadmin_logout'), name='logout'),
url(r'^password_change/$', url(r'^password_change/$',
wrap(self.password_change), wrap(self.password_change, cacheable=True),
name='%sadmin_password_change' % self.name), name='password_change'),
url(r'^password_change/done/$', url(r'^password_change/done/$',
wrap(self.password_change_done), wrap(self.password_change_done, cacheable=True),
name='%sadmin_password_change_done' % self.name), name='password_change_done'),
url(r'^jsi18n/$', url(r'^jsi18n/$',
wrap(self.i18n_javascript), wrap(self.i18n_javascript, cacheable=True),
name='%sadmin_jsi18n' % self.name), name='jsi18n'),
url(r'^r/(?P<content_type_id>\d+)/(?P<object_id>.+)/$', url(r'^r/(?P<content_type_id>\d+)/(?P<object_id>.+)/$',
'django.views.defaults.shortcut'), 'django.views.defaults.shortcut'),
url(r'^(?P<app_label>\w+)/$', url(r'^(?P<app_label>\w+)/$',
wrap(self.app_index), wrap(self.app_index),
name='%sadmin_app_list' % self.name), name='app_list')
) )
# Add in each model's views. # Add in each model's views.
@ -225,7 +229,7 @@ class AdminSite(object):
return urlpatterns return urlpatterns
def urls(self): def urls(self):
return self.get_urls() return self.get_urls(), self.app_name, self.name
urls = property(urls) urls = property(urls)
def password_change(self, request): def password_change(self, request):
@ -233,8 +237,11 @@ class AdminSite(object):
Handles the "change password" task -- both form display and validation. Handles the "change password" task -- both form display and validation.
""" """
from django.contrib.auth.views import password_change from django.contrib.auth.views import password_change
return password_change(request, if self.root_path is not None:
post_change_redirect='%spassword_change/done/' % self.root_path) 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): def password_change_done(self, request):
""" """
@ -362,8 +369,9 @@ class AdminSite(object):
'root_path': self.root_path, 'root_path': self.root_path,
} }
context.update(extra_context or {}) 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, 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) index = never_cache(index)
@ -376,8 +384,9 @@ class AdminSite(object):
'root_path': self.root_path, 'root_path': self.root_path,
} }
context.update(extra_context or {}) 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, 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): def app_index(self, request, app_label, extra_context=None):
@ -419,9 +428,10 @@ class AdminSite(object):
'root_path': self.root_path, 'root_path': self.root_path,
} }
context.update(extra_context or {}) 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, return render_to_response(self.app_index_template or ('admin/%s/app_index.html' % app_label,
'admin/app_index.html'), context, 'admin/app_index.html'), context,
context_instance=template.RequestContext(request) context_instance=context_instance
) )
def root(self, request, url): def root(self, request, url):

View File

@ -23,7 +23,30 @@
{% block branding %}{% endblock %} {% block branding %}{% endblock %}
</div> </div>
{% if user.is_authenticated and user.is_staff %} {% 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 %} {% endif %}
{% block nav-global %}{% endblock %} {% block nav-global %}{% endblock %}
</div> </div>

View File

@ -125,7 +125,7 @@ class ForeignKeyRawIdWidget(forms.TextInput):
if value: if value:
output.append(self.label_for_value(value)) output.append(self.label_for_value(value))
return mark_safe(u''.join(output)) return mark_safe(u''.join(output))
def base_url_parameters(self): def base_url_parameters(self):
params = {} params = {}
if self.rel.limit_choices_to: if self.rel.limit_choices_to:
@ -137,14 +137,14 @@ class ForeignKeyRawIdWidget(forms.TextInput):
v = str(v) v = str(v)
items.append((k, v)) items.append((k, v))
params.update(dict(items)) params.update(dict(items))
return params return params
def url_parameters(self): def url_parameters(self):
from django.contrib.admin.views.main import TO_FIELD_VAR from django.contrib.admin.views.main import TO_FIELD_VAR
params = self.base_url_parameters() params = self.base_url_parameters()
params.update({TO_FIELD_VAR: self.rel.get_related_field().name}) params.update({TO_FIELD_VAR: self.rel.get_related_field().name})
return params return params
def label_for_value(self, value): def label_for_value(self, value):
key = self.rel.get_related_field().name key = self.rel.get_related_field().name
obj = self.rel.to._default_manager.get(**{key: value}) obj = self.rel.to._default_manager.get(**{key: value})
@ -165,10 +165,10 @@ class ManyToManyRawIdWidget(ForeignKeyRawIdWidget):
else: else:
value = '' value = ''
return super(ManyToManyRawIdWidget, self).render(name, value, attrs) return super(ManyToManyRawIdWidget, self).render(name, value, attrs)
def url_parameters(self): def url_parameters(self):
return self.base_url_parameters() return self.base_url_parameters()
def label_for_value(self, value): def label_for_value(self, value):
return '' return ''
@ -222,8 +222,7 @@ class RelatedFieldWidgetWrapper(forms.Widget):
rel_to = self.rel.to rel_to = self.rel.to
info = (rel_to._meta.app_label, rel_to._meta.object_name.lower()) info = (rel_to._meta.app_label, rel_to._meta.object_name.lower())
try: try:
related_info = (self.admin_site.name,) + info related_url = reverse('admin:%s_%s_add' % info, current_app=self.admin_site.name)
related_url = reverse('%sadmin_%s_%s_add' % related_info)
except NoReverseMatch: except NoReverseMatch:
related_url = '../../../%s/%s/add/' % info related_url = '../../../%s/%s/add/' % info
self.widget.choices = self.choices self.widget.choices = self.choices

View File

@ -1,6 +1,6 @@
{% extends "admin/base_site.html" %} {% extends "admin/base_site.html" %}
{% load i18n %} {% load i18n %}
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../">Home</a> &rsaquo; Documentation</div>{% endblock %} {% block breadcrumbs %}<div class="breadcrumbs"><a href="{{ root_path }}">Home</a> &rsaquo; Documentation</div>{% endblock %}
{% block title %}Documentation{% endblock %} {% block title %}Documentation{% endblock %}
{% block content %} {% block content %}

View File

@ -22,11 +22,14 @@ class GenericSite(object):
name = 'my site' name = 'my site'
def get_root_path(): def get_root_path():
from django.contrib import admin
try: try:
return urlresolvers.reverse(admin.site.root, args=['']) return urlresolvers.reverse('admin:index')
except urlresolvers.NoReverseMatch: except urlresolvers.NoReverseMatch:
return getattr(settings, "ADMIN_SITE_ROOT_URL", "/admin/") from django.contrib import admin
try:
return urlresolvers.reverse(admin.site.root, args=[''])
except urlresolvers.NoReverseMatch:
return getattr(settings, "ADMIN_SITE_ROOT_URL", "/admin/")
def doc_index(request): def doc_index(request):
if not utils.docutils_is_available: if not utils.docutils_is_available:
@ -179,7 +182,7 @@ model_index = staff_member_required(model_index)
def model_detail(request, app_label, model_name): def model_detail(request, app_label, model_name):
if not utils.docutils_is_available: if not utils.docutils_is_available:
return missing_docutils_page(request) return missing_docutils_page(request)
# Get the model class. # Get the model class.
try: try:
app_mod = models.get_app(app_label) app_mod = models.get_app(app_label)

View File

@ -19,6 +19,9 @@ class GeoManager(Manager):
def centroid(self, *args, **kwargs): def centroid(self, *args, **kwargs):
return self.get_query_set().centroid(*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): def difference(self, *args, **kwargs):
return self.get_query_set().difference(*args, **kwargs) return self.get_query_set().difference(*args, **kwargs)

View File

@ -62,6 +62,14 @@ class GeoQuerySet(QuerySet):
""" """
return self._geom_attribute('centroid', **kwargs) 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): def difference(self, geom, **kwargs):
""" """
Returns the spatial difference of the geographic field in a `difference` Returns the spatial difference of the geographic field in a `difference`

View File

@ -13,7 +13,9 @@ from django.contrib.gis.measure import Area, Distance
ALL_TERMS = sql.constants.QUERY_TERMS.copy() ALL_TERMS = sql.constants.QUERY_TERMS.copy()
ALL_TERMS.update(SpatialBackend.gis_terms) ALL_TERMS.update(SpatialBackend.gis_terms)
# Pulling out other needed constants/routines to avoid attribute lookups.
TABLE_NAME = sql.constants.TABLE_NAME TABLE_NAME = sql.constants.TABLE_NAME
get_proxied_model = sql.query.get_proxied_model
class GeoQuery(sql.Query): class GeoQuery(sql.Query):
""" """
@ -153,7 +155,9 @@ class GeoQuery(sql.Query):
opts = self.model._meta opts = self.model._meta
aliases = set() aliases = set()
only_load = self.deferred_to_columns() 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: if start_alias:
seen = {None: start_alias} seen = {None: start_alias}
for field, model in opts.get_fields_with_model(): for field, model in opts.get_fields_with_model():
@ -205,6 +209,10 @@ class GeoQuery(sql.Query):
""" """
values = [] values = []
aliases = self.extra_select.keys() 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 # Have to set a starting row number offset that is used for
# determining the correct starting row index -- needed for # determining the correct starting row index -- needed for

View File

@ -8,6 +8,9 @@ from django.contrib.gis.gdal.prototypes.errcheck import \
check_arg_errcode, check_errcode, check_geom, check_geom_offset, \ check_arg_errcode, check_errcode, check_geom, check_geom_offset, \
check_pointer, check_srs, check_str_arg, check_string, check_const_string 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): def double_output(func, argtypes, errcheck=False, strarg=False):
"Generates a ctypes function that returns a double value." "Generates a ctypes function that returns a double value."
func.argtypes = argtypes func.argtypes = argtypes
@ -77,9 +80,9 @@ def string_output(func, argtypes, offset=-1, str_result=False):
""" """
func.argtypes = argtypes func.argtypes = argtypes
if str_result: if str_result:
# String is the result, don't explicitly define # Use subclass of c_char_p so the error checking routine
# the argument type so we can get the pointer. # can free the memory at the pointer's address.
pass func.restype = gdal_char_p
else: else:
# Error code is returned # Error code is returned
func.restype = c_int func.restype = c_int

View File

@ -10,6 +10,7 @@ __all__ = ['geos_boundary', 'geos_buffer', 'geos_centroid', 'geos_convexhull',
from ctypes import c_char_p, c_double, c_int 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.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.errcheck import check_geom, check_string
from django.contrib.gis.geos.prototypes.geom import geos_char_p
def topology(func, *args): def topology(func, *args):
"For GEOS unary topology functions." "For GEOS unary topology functions."
@ -38,6 +39,7 @@ geos_union = topology(lgeos.GEOSUnion, GEOM_PTR)
# GEOSRelate returns a string, not a geometry. # GEOSRelate returns a string, not a geometry.
geos_relate = lgeos.GEOSRelate geos_relate = lgeos.GEOSRelate
geos_relate.argtypes = [GEOM_PTR, GEOM_PTR] geos_relate.argtypes = [GEOM_PTR, GEOM_PTR]
geos_relate.restype = geos_char_p
geos_relate.errcheck = check_string geos_relate.errcheck = check_string
# Routines only in GEOS 3.1+ # Routines only in GEOS 3.1+

View File

@ -1,7 +1,7 @@
import os, unittest import os, unittest
from django.contrib.gis.geos import * from django.contrib.gis.geos import *
from django.contrib.gis.db.backend import SpatialBackend 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.contrib.gis.tests.utils import no_mysql, no_oracle, no_spatialite
from django.conf import settings from django.conf import settings
from models import City, Location, DirectoryEntry, Parcel, Book, Author from models import City, Location, DirectoryEntry, Parcel, Book, Author
@ -231,8 +231,12 @@ class RelatedGeoModelTest(unittest.TestCase):
q = pickle.loads(q_str) q = pickle.loads(q_str)
self.assertEqual(GeoQuery, q.__class__) self.assertEqual(GeoQuery, q.__class__)
def test12_count(self): # TODO: fix on Oracle -- get the following error because the SQL is ordered
"Testing `Count` aggregate use with the `GeoManager`. See #11087." # 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 # Creating a new City, 'Fort Worth', that uses the same location
# as Dallas. # as Dallas.
dallas = City.objects.get(name='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) loc = Location.objects.annotate(num_cities=Count('city')).get(id=dallas.location.id)
self.assertEqual(2, loc.num_cities) 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 # Creating some data for the Book/Author non-geo models that
# use GeoManager. See #11087. # use GeoManager. See #11087.
tp = Author.objects.create(name='Trevor Paglen') tp = Author.objects.create(name='Trevor Paglen')
@ -250,13 +256,19 @@ class RelatedGeoModelTest(unittest.TestCase):
Book.objects.create(title='Blank Spots on the Map', author=tp) Book.objects.create(title='Blank Spots on the Map', author=tp)
wp = Author.objects.create(name='William Patry') wp = Author.objects.create(name='William Patry')
Book.objects.create(title='Patry on Copyright', author=wp) Book.objects.create(title='Patry on Copyright', author=wp)
# Should only be one author (Trevor Paglen) returned by this query, and # 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) 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(1, len(qs))
self.assertEqual(3, qs[0].num_books) 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): def test13_select_related_null_fk(self):
"Testing `select_related` on a nullable ForeignKey via `GeoManager`. See #11381." "Testing `select_related` on a nullable ForeignKey via `GeoManager`. See #11381."
no_author = Book.objects.create(title='Without Author') no_author = Book.objects.create(title='Without Author')
@ -264,6 +276,26 @@ class RelatedGeoModelTest(unittest.TestCase):
# Should be `None`, and not a 'dummy' model. # Should be `None`, and not a 'dummy' model.
self.assertEqual(None, b.author) 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. # TODO: Related tests for KML, GML, and distance lookups.
def suite(): def suite():

View File

@ -139,7 +139,7 @@ class RegexURLPattern(object):
callback = property(_get_callback) callback = property(_get_callback)
class RegexURLResolver(object): 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. # regex is a string representing a regular expression.
# urlconf_name is a string representing the module containing URLconfs. # urlconf_name is a string representing the module containing URLconfs.
self.regex = re.compile(regex, re.UNICODE) self.regex = re.compile(regex, re.UNICODE)
@ -148,19 +148,29 @@ class RegexURLResolver(object):
self._urlconf_module = self.urlconf_name self._urlconf_module = self.urlconf_name
self.callback = None self.callback = None
self.default_kwargs = default_kwargs or {} 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): 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): def _populate(self):
if not self._reverse_dict: lookups = MultiValueDict()
lookups = MultiValueDict() namespaces = {}
for pattern in reversed(self.url_patterns): apps = {}
p_pattern = pattern.regex.pattern for pattern in reversed(self.url_patterns):
if p_pattern.startswith('^'): p_pattern = pattern.regex.pattern
p_pattern = p_pattern[1:] if p_pattern.startswith('^'):
if isinstance(pattern, RegexURLResolver): 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) parent = normalize(pattern.regex.pattern)
for name in pattern.reverse_dict: for name in pattern.reverse_dict:
for matches, pat in pattern.reverse_dict.getlist(name): for matches, pat in pattern.reverse_dict.getlist(name):
@ -168,14 +178,36 @@ class RegexURLResolver(object):
for piece, p_args in parent: for piece, p_args in parent:
new_matches.extend([(piece + suffix, p_args + args) for (suffix, args) in matches]) new_matches.extend([(piece + suffix, p_args + args) for (suffix, args) in matches])
lookups.appendlist(name, (new_matches, p_pattern + pat)) lookups.appendlist(name, (new_matches, p_pattern + pat))
else: for namespace, (prefix, sub_pattern) in pattern.namespace_dict.items():
bits = normalize(p_pattern) namespaces[namespace] = (p_pattern + prefix, sub_pattern)
lookups.appendlist(pattern.callback, (bits, p_pattern)) for app_name, namespace_list in pattern.app_dict.items():
lookups.appendlist(pattern.name, (bits, p_pattern)) apps.setdefault(app_name, []).extend(namespace_list)
self._reverse_dict = lookups 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 return self._reverse_dict
reverse_dict = property(_get_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): def resolve(self, path):
tried = [] tried = []
match = self.regex.search(path) match = self.regex.search(path)
@ -261,12 +293,51 @@ class RegexURLResolver(object):
def resolve(path, urlconf=None): def resolve(path, urlconf=None):
return get_resolver(urlconf).resolve(path) 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 [] args = args or []
kwargs = kwargs or {} kwargs = kwargs or {}
if prefix is None: if prefix is None:
prefix = get_script_prefix() 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))) *args, **kwargs)))
def clear_url_caches(): def clear_url_caches():

View File

@ -217,12 +217,13 @@ WHEN (new.%(col_name)s IS NULL)
# continue to loop # continue to loop
break break
for f in model._meta.many_to_many: for f in model._meta.many_to_many:
table_name = self.quote_name(f.m2m_db_table()) if not f.rel.through:
sequence_name = get_sequence_name(f.m2m_db_table()) table_name = self.quote_name(f.m2m_db_table())
column_name = self.quote_name('id') sequence_name = get_sequence_name(f.m2m_db_table())
output.append(query % {'sequence': sequence_name, column_name = self.quote_name('id')
'table': table_name, output.append(query % {'sequence': sequence_name,
'column': column_name}) 'table': table_name,
'column': column_name})
return output return output
def start_transaction_sql(self): def start_transaction_sql(self):

View File

@ -121,14 +121,15 @@ class DatabaseOperations(BaseDatabaseOperations):
style.SQL_TABLE(qn(model._meta.db_table)))) style.SQL_TABLE(qn(model._meta.db_table))))
break # Only one AutoField is allowed per model, so don't bother continuing. break # Only one AutoField is allowed per model, so don't bother continuing.
for f in model._meta.many_to_many: for f in model._meta.many_to_many:
output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \ if not f.rel.through:
(style.SQL_KEYWORD('SELECT'), output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \
style.SQL_FIELD(qn('%s_id_seq' % f.m2m_db_table())), (style.SQL_KEYWORD('SELECT'),
style.SQL_FIELD(qn('id')), style.SQL_FIELD(qn('%s_id_seq' % f.m2m_db_table())),
style.SQL_FIELD(qn('id')), style.SQL_FIELD(qn('id')),
style.SQL_KEYWORD('IS NOT'), style.SQL_FIELD(qn('id')),
style.SQL_KEYWORD('FROM'), style.SQL_KEYWORD('IS NOT'),
style.SQL_TABLE(qn(f.m2m_db_table())))) style.SQL_KEYWORD('FROM'),
style.SQL_TABLE(qn(f.m2m_db_table()))))
return output return output
def savepoint_create_sql(self, sid): def savepoint_create_sql(self, sid):

View File

@ -9,10 +9,11 @@ class ContextPopException(Exception):
class Context(object): class Context(object):
"A stack container for variable context" "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 {} dict_ = dict_ or {}
self.dicts = [dict_] self.dicts = [dict_]
self.autoescape = autoescape self.autoescape = autoescape
self.current_app = current_app
def __repr__(self): def __repr__(self):
return repr(self.dicts) return repr(self.dicts)
@ -96,8 +97,8 @@ class RequestContext(Context):
Additional processors can be specified as a list of callables Additional processors can be specified as a list of callables
using the "processors" keyword argument. using the "processors" keyword argument.
""" """
def __init__(self, request, dict=None, processors=None): def __init__(self, request, dict=None, processors=None, current_app=None):
Context.__init__(self, dict) Context.__init__(self, dict, current_app=current_app)
if processors is None: if processors is None:
processors = () processors = ()
else: else:

View File

@ -367,17 +367,17 @@ class URLNode(Node):
# {% url ... as var %} construct in which cause return nothing. # {% url ... as var %} construct in which cause return nothing.
url = '' url = ''
try: 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: except NoReverseMatch, e:
if settings.SETTINGS_MODULE: if settings.SETTINGS_MODULE:
project_name = settings.SETTINGS_MODULE.split('.')[0] project_name = settings.SETTINGS_MODULE.split('.')[0]
try: try:
url = reverse(project_name + '.' + self.view_name, url = reverse(project_name + '.' + self.view_name,
args=args, kwargs=kwargs) args=args, kwargs=kwargs, current_app=context.current_app)
except NoReverseMatch: except NoReverseMatch:
if self.asvar is None: if self.asvar is None:
# Re-raise the original exception, not the one with # Re-raise the original exception, not the one with
# the path relative to the project. This makes a # the path relative to the project. This makes a
# better error message. # better error message.
raise e raise e
else: else:

View File

@ -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 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 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 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 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 implement ``get_db_prep_lookup()``. If you don't, ``get_db_prep_value`` will be

View File

@ -1,69 +1,118 @@
.. _howto-deployment-modwsgi: .. _howto-deployment-modwsgi:
========================================== ==========================================
How to use Django with Apache and mod_wsgi How to use Django with Apache and mod_wsgi
========================================== ==========================================
Deploying Django with Apache_ and `mod_wsgi`_ is the recommended way to get Deploying Django with Apache_ and `mod_wsgi`_ is the recommended way to get
Django into production. Django into production.
.. _Apache: http://httpd.apache.org/ .. _Apache: http://httpd.apache.org/
.. _mod_wsgi: http://code.google.com/p/modwsgi/ .. _mod_wsgi: http://code.google.com/p/modwsgi/
mod_wsgi is an Apache module which can be used to host any Python application mod_wsgi is an Apache module which can be used to host any Python application
which supports the `Python WSGI interface`_, including Django. Django will work which supports the `Python WSGI interface`_, including Django. Django will work
with any version of Apache which supports mod_wsgi. with any version of Apache which supports mod_wsgi.
.. _python wsgi interface: http://www.python.org/dev/peps/pep-0333/ .. _python wsgi interface: http://www.python.org/dev/peps/pep-0333/
The `official mod_wsgi documentation`_ is fantastic; it's your source for all The `official mod_wsgi documentation`_ is fantastic; it's your source for all
the details about how to use mod_wsgi. You'll probably want to start with the the details about how to use mod_wsgi. You'll probably want to start with the
`installation and configuration documentation`_. `installation and configuration documentation`_.
.. _official mod_wsgi documentation: http://code.google.com/p/modwsgi/ .. _official mod_wsgi documentation: http://code.google.com/p/modwsgi/
.. _installation and configuration documentation: http://code.google.com/p/modwsgi/wiki/InstallationInstructions .. _installation and configuration documentation: http://code.google.com/p/modwsgi/wiki/InstallationInstructions
Basic Configuration Basic Configuration
=================== ===================
Once you've got mod_wsgi installed and activated, edit your ``httpd.conf`` file Once you've got mod_wsgi installed and activated, edit your ``httpd.conf`` file
and add:: and add::
WSGIScriptAlias / /path/to/mysite/apache/django.wsgi WSGIScriptAlias / /path/to/mysite/apache/django.wsgi
The first bit above is the url you want to be serving your application at (``/`` The first bit above is the url you want to be serving your application at (``/``
indicates the root url), and the second is the location of a "WSGI file" -- see indicates the root url), and the second is the location of a "WSGI file" -- see
below -- on your system, usually inside of your project. This tells Apache below -- on your system, usually inside of your project. This tells Apache
to serve any request below the given URL using the WSGI application defined by that file. to serve any request below the given URL using the WSGI application defined by that file.
Next we'll need to actually create this WSGI application, so create the file Next we'll need to actually create this WSGI application, so create the file
mentioned in the second part of ``WSGIScriptAlias`` and add:: mentioned in the second part of ``WSGIScriptAlias`` and add::
import os import os
import sys import sys
os.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.settings' os.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.settings'
import django.core.handlers.wsgi import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler() application = django.core.handlers.wsgi.WSGIHandler()
If your project is not on your ``PYTHONPATH`` by default you can add:: If your project is not on your ``PYTHONPATH`` by default you can add::
sys.path.append('/usr/local/django') sys.path.append('/usr/local/django')
just above the final ``import`` line to place your project on the path. Remember to just above the final ``import`` line to place your project on the path. Remember to
replace 'mysite.settings' with your correct settings file, and '/usr/local/django' replace 'mysite.settings' with your correct settings file, and '/usr/local/django'
with your own project's location. with your own project's location.
See the :ref:`Apache/mod_python documentation<howto-deployment-modpython>` for Serving media files
directions on serving static media, and the `mod_wsgi documentation`_ for an ===================
explanation of other directives and configuration options you can use.
Django doesn't serve media files itself; it leaves that job to whichever Web
Details server you choose.
=======
We recommend using a separate Web server -- i.e., one that's not also running
For more details, see the `mod_wsgi documentation`_, which explains the above in Django -- for serving media. Here are some good choices:
more detail, and walks through all the various options you've got when deploying
under mod_wsgi. * lighttpd_
* Nginx_
.. _mod_wsgi documentation: http://code.google.com/p/modwsgi/wiki/IntegrationWithDjango * 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 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 on Django integration: http://code.google.com/p/modwsgi/wiki/IntegrationWithDjango

View File

@ -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 get a description of the error, a complete Python traceback, and details about
the HTTP request that caused the error. 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` To disable this behavior, just remove all entries from the :setting:`ADMINS`
setting. setting.
@ -33,12 +37,12 @@ Django can also be configured to email errors about broken links (404 "page
not found" errors). Django sends emails about 404 errors when: not found" errors). Django sends emails about 404 errors when:
* :setting:`DEBUG` is ``False`` * :setting:`DEBUG` is ``False``
* :setting:`SEND_BROKEN_LINK_EMAILS` is ``True`` * :setting:`SEND_BROKEN_LINK_EMAILS` is ``True``
* Your :setting:`MIDDLEWARE_CLASSES` setting includes ``CommonMiddleware`` * Your :setting:`MIDDLEWARE_CLASSES` setting includes ``CommonMiddleware``
(which it does by default). (which it does by default).
If those conditions are met, Django will e-mail the users listed in the If those conditions are met, Django will e-mail the users listed in the
:setting:`MANAGERS` setting whenever your code raises a 404 and the request has :setting:`MANAGERS` setting whenever your code raises a 404 and the request has
a referer. (It doesn't bother to e-mail for 404s that don't have a referer -- a referer. (It doesn't bother to e-mail for 404s that don't have a referer --

View File

@ -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 in ``django/conf/urls/defaults.py``, ``handler404`` is set to
:func:`django.views.defaults.page_not_found` by default. :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 * 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 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

View File

@ -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 anything, so you'll usually want to prepend your custom URLs to the built-in
ones. ones.
Note, however, that the ``self.my_view`` function registered above will *not* However, the ``self.my_view`` function registered above suffers from two
have any permission check done; it'll be accessible to the general public. Since problems:
this is usually not what you want, Django provides a convience wrapper to check
permissions. This wrapper is :meth:`AdminSite.admin_view` (i.e. * It will *not* perform and permission checks, so it will be accessible to
``self.admin_site.admin_view`` inside a ``ModelAdmin`` instance); use it like the general public.
so:: * 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): class MyModelAdmin(admin.ModelAdmin):
def get_urls(self): 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)) (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) .. 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(), 'osm_data': self.get_osm_info(),
} }
return super(MyModelAdmin, self).change_view(request, object_id, return super(MyModelAdmin, self).change_view(request, object_id,
extra_context=my_context)) extra_context=my_context)
``ModelAdmin`` media definitions ``ModelAdmin`` media definitions
-------------------------------- --------------------------------
@ -1228,7 +1242,7 @@ or :attr:`AdminSite.login_template` properties.
``AdminSite`` objects ``AdminSite`` objects
===================== =====================
.. class:: AdminSite .. class:: AdminSite(name=None)
A Django administrative site is represented by an instance of A Django administrative site is represented by an instance of
``django.contrib.admin.sites.AdminSite``; by default, 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 Python class), and register your models and ``ModelAdmin`` subclasses
with it instead of using the default. 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 ``AdminSite`` attributes
------------------------ ------------------------
@ -1339,10 +1361,10 @@ a pattern for your new view.
.. note:: .. note::
Any view you render that uses the admin templates, or extends the base 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 template, should provide the ``current_app`` argument to
``admin_site`` that contains the name of the :class:`AdminSite` instance. For ``RequestContext`` or ``Context`` when rendering the template. It should
:class:`AdminSite` instances, this means ``self.name``; for :class:`ModelAdmin` be set to either ``self.name`` if your view is on an ``AdminSite`` or
instances, this means ``self.admin_site.name``. ``self.admin_site.name`` if your view is on a ``ModelAdmin``.
.. _admin-reverse-urls: .. _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: The :class:`AdminSite` provides the following named URL patterns:
====================== =============================== ============= ====================== ======================== =============
Page URL name Parameters Page URL name Parameters
====================== =============================== ============= ====================== ======================== =============
Index ``admin_index`` Index ``index``
Logout ``admin_logout`` Logout ``logout``
Password change ``admin_password_change`` Password change ``password_change``
Password change done ``admin_password_change_done`` Password change done ``password_change_done``
i18n javascript ``admin_jsi18n`` i18n javascript ``jsi18n``
Application index page ``admin_app_list`` ``app_label`` Application index page ``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.
Each :class:`ModelAdmin` instance provides an additional set of named URLs: Each :class:`ModelAdmin` instance provides an additional set of named URLs:
====================== ===================================================== ============= ====================== =============================================== =============
Page URL name Parameters Page URL name Parameters
====================== ===================================================== ============= ====================== =============================================== =============
Changelist ``admin_{{ app_label }}_{{ model_name }}_changelist`` Changelist ``{{ app_label }}_{{ model_name }}_changelist``
Add ``admin_{{ app_label }}_{{ model_name }}_add`` Add ``{{ app_label }}_{{ model_name }}_add``
History ``admin_{{ app_label }}_{{ model_name }}_history`` ``object_id`` History ``{{ app_label }}_{{ model_name }}_history`` ``object_id``
Delete ``admin_{{ app_label }}_{{ model_name }}_delete`` ``object_id`` Delete ``{{ app_label }}_{{ model_name }}_delete`` ``object_id``
Change ``admin_{{ app_label }}_{{ model_name }}_change`` ``object_id`` Change ``{{ app_label }}_{{ model_name }}_change`` ``object_id``
====================== ===================================================== ============= ====================== =============================================== =============
Again, these names will be prefixed by the name of the :class:`AdminSite` in These named URLs are registered with the application namespace ``admin``, and
which they are deployed. 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 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 ``Choice`` object (from the polls application) in the default admin, you would
@ -1394,8 +1410,16 @@ call::
>>> from django.core import urlresolvers >>> from django.core import urlresolvers
>>> c = Choice.objects.get(...) >>> 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>`.

View File

@ -177,9 +177,9 @@ The ``ContentTypeManager``
.. method:: models.ContentTypeManager.clear_cache() .. method:: models.ContentTypeManager.clear_cache()
Clears an internal cache used by 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 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 probably won't ever need to call this method yourself; Django will call
it automatically when it's needed. it automatically when it's needed.

View File

@ -275,7 +275,7 @@ For each field, we describe the default widget used if you don't specify
* Default widget: ``CheckboxInput`` * Default widget: ``CheckboxInput``
* Empty value: ``False`` * Empty value: ``False``
* Normalizes to: A Python ``True`` or ``False`` value. * 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``. the field has ``required=True``.
* Error message keys: ``required`` * Error message keys: ``required``
@ -287,9 +287,10 @@ For each field, we describe the default widget used if you don't specify
.. note:: .. note::
Since all ``Field`` subclasses have ``required=True`` by default, the Since all ``Field`` subclasses have ``required=True`` by default, the
validation condition here is important. If you want to include a checkbox validation condition here is important. If you want to include a boolean
in your form that can be either checked or unchecked, you must remember to in your form that can be either ``True`` or ``False`` (e.g. a checked or
pass in ``required=False`` when creating the ``BooleanField``. unchecked checkbox), you must remember to pass in ``required=False`` when
creating the ``BooleanField``.
``CharField`` ``CharField``
~~~~~~~~~~~~~ ~~~~~~~~~~~~~
@ -328,7 +329,7 @@ Takes one extra required argument:
An iterable (e.g., a list or tuple) of 2-tuples to use as choices for this An iterable (e.g., a list or tuple) of 2-tuples to use as choices for this
field. field.
``TypedChoiceField`` ``TypedChoiceField``
~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~
@ -437,7 +438,7 @@ If no ``input_formats`` argument is provided, the default input formats are::
``min_value``, ``max_digits``, ``max_decimal_places``, ``min_value``, ``max_digits``, ``max_decimal_places``,
``max_whole_digits`` ``max_whole_digits``
Takes four optional arguments: Takes four optional arguments:
.. attribute:: DecimalField.max_value .. attribute:: DecimalField.max_value
.. attribute:: DecimalField.min_value .. attribute:: DecimalField.min_value
@ -449,7 +450,7 @@ Takes four optional arguments:
The maximum number of digits (those before the decimal point plus those The maximum number of digits (those before the decimal point plus those
after the decimal point, with leading zeros stripped) permitted in the after the decimal point, with leading zeros stripped) permitted in the
value. value.
.. attribute:: DecimalField.decimal_places .. attribute:: DecimalField.decimal_places
The maximum number of decimal places permitted. The maximum number of decimal places permitted.
@ -522,18 +523,18 @@ extra arguments; only ``path`` is required:
A regular expression pattern; only files with names matching this expression A regular expression pattern; only files with names matching this expression
will be allowed as choices. will be allowed as choices.
``FloatField`` ``FloatField``
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~
* Default widget: ``TextInput`` * Default widget: ``TextInput``
* Empty value: ``None`` * Empty value: ``None``
* Normalizes to: A Python float. * Normalizes to: A Python float.
* Validates that the given value is an float. Leading and trailing * Validates that the given value is an float. Leading and trailing
whitespace is allowed, as in Python's ``float()`` function. whitespace is allowed, as in Python's ``float()`` function.
* Error message keys: ``required``, ``invalid``, ``max_value``, * Error message keys: ``required``, ``invalid``, ``max_value``,
``min_value`` ``min_value``
Takes two optional arguments for validation, ``max_value`` and ``min_value``. Takes two optional arguments for validation, ``max_value`` and ``min_value``.
These control the range of values permitted in the field. These control the range of values permitted in the field.
``ImageField`` ``ImageField``
@ -779,10 +780,10 @@ example::
(which is ``"---------"`` by default) with the ``empty_label`` attribute, or (which is ``"---------"`` by default) with the ``empty_label`` attribute, or
you can disable the empty label entirely by setting ``empty_label`` to you can disable the empty label entirely by setting ``empty_label`` to
``None``:: ``None``::
# A custom empty label # A custom empty label
field1 = forms.ModelChoiceField(queryset=..., empty_label="(Nothing)") field1 = forms.ModelChoiceField(queryset=..., empty_label="(Nothing)")
# No empty label # No empty label
field2 = forms.ModelChoiceField(queryset=..., empty_label=None) field2 = forms.ModelChoiceField(queryset=..., empty_label=None)

View File

@ -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:: 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; FROM blog_blog;
Note that the parenthesis required by most database engines around Note that the parenthesis required by most database engines around

View File

@ -86,9 +86,16 @@ Rendering a context
Once you have a compiled ``Template`` object, you can render a context -- or Once you have a compiled ``Template`` object, you can render a context -- or
multiple contexts -- with it. The ``Context`` class lives at multiple contexts -- with it. The ``Context`` class lives at
``django.template.Context``, and the constructor takes one (optional) ``django.template.Context``, and the constructor takes two (optional)
argument: a dictionary mapping variable names to variable values. Call the arguments:
``Template`` object's ``render()`` method with the context to "fill" the
* 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:: template::
>>> from django.template import Context, Template >>> from django.template import Context, Template
@ -549,13 +556,13 @@ Here are the template loaders that come with Django:
Note that the loader performs an optimization when it is first imported: It Note that the loader performs an optimization when it is first imported: It
caches a list of which :setting:`INSTALLED_APPS` packages have a caches a list of which :setting:`INSTALLED_APPS` packages have a
``templates`` subdirectory. ``templates`` subdirectory.
This loader is enabled by default. This loader is enabled by default.
``django.template.loaders.eggs.load_template_source`` ``django.template.loaders.eggs.load_template_source``
Just like ``app_directories`` above, but it loads templates from Python Just like ``app_directories`` above, but it loads templates from Python
eggs rather than from the filesystem. eggs rather than from the filesystem.
This loader is disabled by default. This loader is disabled by default.
Django uses the template loaders in order according to the Django uses the template loaders in order according to the

View File

@ -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> <a href="{{ the_url }}">Link to optional stuff</a>
{% endif %} {% 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 .. templatetag:: widthratio
widthratio widthratio

View File

@ -86,9 +86,9 @@ displayed.
Formset validation 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 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) >>> ArticleFormSet = formset_factory(ArticleForm)
>>> formset = ArticleFormSet({}) >>> 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 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 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 = { >>> data = {
... 'form-TOTAL_FORMS': u'1', ... 'form-TOTAL_FORMS': u'2',
... 'form-INITIAL_FORMS': u'1', ... 'form-INITIAL_FORMS': u'0',
... 'form-0-title': u'Test', ... '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 = ArticleFormSet(data)
>>> formset.is_valid() >>> formset.is_valid()
False False
>>> formset.errors >>> 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 As we can see, ``formset.errors`` is a list whose entries correspond to the
expected errors. 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: .. _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 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 >>> from django.forms.formsets import BaseFormSet
>>> class BaseArticleFormSet(BaseFormSet): >>> class BaseArticleFormSet(BaseFormSet):
... def clean(self): ... 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) >>> 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() >>> formset.is_valid()
False False
>>> formset.errors
[{}, {}]
>>> formset.non_form_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 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()`` have been called. The errors will be found using the ``non_form_errors()``

View File

@ -4,6 +4,8 @@
URL dispatcher URL dispatcher
============== ==============
.. module:: django.core.urlresolvers
A clean, elegant URL scheme is an important detail in a high-quality Web 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 application. Django lets you design URLs however you want, with no framework
limitations. limitations.
@ -40,14 +42,14 @@ algorithm the system follows to determine which Python code to execute:
this is the value of the ``ROOT_URLCONF`` setting, but if the incoming this is the value of the ``ROOT_URLCONF`` setting, but if the incoming
``HttpRequest`` object has an attribute called ``urlconf``, its value ``HttpRequest`` object has an attribute called ``urlconf``, its value
will be used in place of the ``ROOT_URLCONF`` setting. will be used in place of the ``ROOT_URLCONF`` setting.
2. Django loads that Python module and looks for the variable 2. Django loads that Python module and looks for the variable
``urlpatterns``. This should be a Python list, in the format returned by ``urlpatterns``. This should be a Python list, in the format returned by
the function ``django.conf.urls.defaults.patterns()``. the function ``django.conf.urls.defaults.patterns()``.
3. Django runs through each URL pattern, in order, and stops at the first 3. Django runs through each URL pattern, in order, and stops at the first
one that matches the requested URL. one that matches the requested URL.
4. Once one of the regexes matches, Django imports and calls the given 4. Once one of the regexes matches, Django imports and calls the given
view, which is a simple Python function. The view gets passed an view, which is a simple Python function. The view gets passed an
:class:`~django.http.HttpRequest` as its first argument and any values :class:`~django.http.HttpRequest` as its first argument and any values
@ -182,11 +184,13 @@ your URLconf. This gives your module access to these objects:
patterns patterns
-------- --------
.. function:: patterns(prefix, pattern_description, ...)
A function that takes a prefix, and an arbitrary number of URL patterns, and 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. returns a list of URL patterns in the format Django needs.
The first argument to ``patterns()`` is a string ``prefix``. See 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:: The remaining arguments should be tuples in this format::
@ -222,6 +226,8 @@ url
.. versionadded:: 1.0 .. 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 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 ``patterns()``. This is convenient if you want to specify a name without the
optional extra arguments dictionary. For example:: optional extra arguments dictionary. For example::
@ -244,6 +250,8 @@ The ``prefix`` parameter has the same meaning as the first argument to
handler404 handler404
---------- ----------
.. data:: handler404
A string representing the full Python import path to the view that should be A string representing the full Python import path to the view that should be
called if none of the URL patterns match. called if none of the URL patterns match.
@ -253,6 +261,8 @@ value should suffice.
handler500 handler500
---------- ----------
.. data:: handler500
A string representing the full Python import path to the view that should be 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 called in case of server errors. Server errors happen when you have runtime
errors in view code. errors in view code.
@ -263,8 +273,17 @@ value should suffice.
include include
------- -------
A function that takes a full Python import path to another URLconf that should .. function:: include(<module or pattern_list>)
be "included" in this place. See `Including other URLconfs`_ below.
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 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 up to that point and sends the remaining string to the included URLconf for
further processing. 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/ .. _`Django Web site`: http://www.djangoproject.com/
Captured parameters Captured parameters
@ -413,6 +458,58 @@ the following example is valid::
In the above example, the captured ``"username"`` variable is passed to the In the above example, the captured ``"username"`` variable is passed to the
included URLconf, as expected. 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 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 name, will decrease the chances of collision. We recommend something like
``myapp-comment`` instead of ``comment``. ``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 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 your code, Django provides the following method (in the
``django.core.urlresolvers`` module): ``django.core.urlresolvers`` module):
.. currentmodule:: django.core.urlresolvers .. function:: reverse(viewname, urlconf=None, args=None, kwargs=None, current_app=None)
.. function:: reverse(viewname, urlconf=None, args=None, kwargs=None)
``viewname`` is either the function name (either a function reference, or the ``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 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 matching against incoming URLs and sending them off to views, but you cannot
reverse such patterns. 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 .. admonition:: Make sure your views are all correct
As part of working out which URL names map to which patterns, the 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 The :func:`django.core.urlresolvers.resolve` function can be used for resolving
URL paths to the corresponding view functions. It has the following signature: URL paths to the corresponding view functions. It has the following signature:
.. currentmodule:: django.core.urlresolvers
.. function:: resolve(path, urlconf=None) .. function:: resolve(path, urlconf=None)
``path`` is the URL path you want to resolve. As with ``reverse()`` above, you ``path`` is the URL path you want to resolve. As with ``reverse()`` above, you

View File

@ -959,11 +959,11 @@ Using the JavaScript translation catalog
To use the catalog, just pull in the dynamically generated script like this:: 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 This uses reverse URL lookup to find the URL of the JavaScript catalog view.
catalog is loaded, your JavaScript code can use the standard ``gettext`` When the catalog is loaded, your JavaScript code can use the standard
interface to access it:: ``gettext`` interface to access it::
document.write(gettext('this is to be translated')); document.write(gettext('this is to be translated'));

View File

@ -686,7 +686,13 @@ arguments at time of construction:
user accounts that are valid on your production site will not work 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 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 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() .. method:: Client.logout()

View File

@ -10,19 +10,19 @@ import models
class Admin2(admin.AdminSite): class Admin2(admin.AdminSite):
login_template = 'custom_admin/login.html' login_template = 'custom_admin/login.html'
index_template = 'custom_admin/index.html' index_template = 'custom_admin/index.html'
# A custom index view. # A custom index view.
def index(self, request, extra_context=None): def index(self, request, extra_context=None):
return super(Admin2, self).index(request, {'foo': '*bar*'}) return super(Admin2, self).index(request, {'foo': '*bar*'})
def get_urls(self): def get_urls(self):
return patterns('', return patterns('',
(r'^my_view/$', self.admin_view(self.my_view)), (r'^my_view/$', self.admin_view(self.my_view)),
) + super(Admin2, self).get_urls() ) + super(Admin2, self).get_urls()
def my_view(self, request): def my_view(self, request):
return HttpResponse("Django is a magical pony!") return HttpResponse("Django is a magical pony!")
site = Admin2(name="admin2") site = Admin2(name="admin2")
site.register(models.Article, models.ArticleAdmin) site.register(models.Article, models.ArticleAdmin)

View File

@ -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.sites import LOGIN_FORM_KEY
from django.contrib.admin.util import quote from django.contrib.admin.util import quote
from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
from django.utils.cache import get_max_age
from django.utils.html import escape from django.utils.html import escape
# local test models # 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!'}) 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) 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): def testNamedGroupFieldChoicesChangeList(self):
""" """
Ensures the admin changelist shows correct values in the relevant column 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=2).order, 13)
self.failUnlessEqual(Category.objects.get(id=3).order, 1) self.failUnlessEqual(Category.objects.get(id=3).order, 1)
self.failUnlessEqual(Category.objects.get(id=4).order, 0) 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)

View File

@ -19,7 +19,7 @@ class CarTireAdmin(admin.ModelAdmin):
return db_field.formfield(**kwargs) return db_field.formfield(**kwargs)
return super(CarTireAdmin, self).formfield_for_foreignkey(db_field, request, **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.User)
site.register(models.Car, CarAdmin) site.register(models.Car, CarAdmin)

View File

@ -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"
}
}
]

View File

@ -12,7 +12,9 @@ class Membership(models.Model):
def __unicode__(self): def __unicode__(self):
return "%s is a member of %s" % (self.person.name, self.group.name) 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): class UserMembership(models.Model):
id = models.AutoField(db_column='usermembership_id', primary_key=True)
user = models.ForeignKey(User) user = models.ForeignKey(User)
group = models.ForeignKey('Group') group = models.ForeignKey('Group')
price = models.IntegerField(default=100) price = models.IntegerField(default=100)
@ -196,4 +198,12 @@ doing a join.
# Flush the database, just to make sure we can. # Flush the database, just to make sure we can.
>>> management.call_command('flush', verbosity=0, interactive=False) >>> 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"}}]
"""} """}

View File

@ -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')),
)

View 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')),
)

View File

@ -158,4 +158,84 @@ class ReverseShortcutTests(TestCase):
res = redirect('/foo/') res = redirect('/foo/')
self.assertEqual(res['Location'], '/foo/') self.assertEqual(res['Location'], '/foo/')
res = redirect('http://example.com/') res = redirect('http://example.com/')
self.assertEqual(res['Location'], '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'))