diff --git a/django/bin/django-admin.py b/django/bin/django-admin.py
index 6b927cbde0..89297d4cf9 100755
--- a/django/bin/django-admin.py
+++ b/django/bin/django-admin.py
@@ -19,6 +19,7 @@ ACTION_MAPPING = {
'init': management.init,
'inspectdb': management.inspectdb,
'install': management.install,
+ 'installperms': management.installperms,
'runserver': management.runserver,
'sql': management.get_sql_create,
'sqlall': management.get_sql_all,
@@ -32,7 +33,7 @@ ACTION_MAPPING = {
'validate': management.validate,
}
-NO_SQL_TRANSACTION = ('adminindex', 'createcachetable', 'dbcheck', 'install', 'sqlindexes')
+NO_SQL_TRANSACTION = ('adminindex', 'createcachetable', 'dbcheck', 'install', 'installperms', 'sqlindexes')
def get_usage():
"""
diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
index 0f170409e4..ec3fdff6fd 100644
--- a/django/conf/global_settings.py
+++ b/django/conf/global_settings.py
@@ -83,8 +83,8 @@ TEMPLATE_FILE_EXTENSION = '.html'
# See the comments in django/core/template/loader.py for interface
# documentation.
TEMPLATE_LOADERS = (
-# 'django.core.template.loaders.app_directories.load_template_source',
'django.core.template.loaders.filesystem.load_template_source',
+ 'django.core.template.loaders.app_directories.load_template_source',
# 'django.core.template.loaders.eggs.load_template_source',
)
diff --git a/django/contrib/admin/media/js/urlify.js b/django/contrib/admin/media/js/urlify.js
index 1e0f04b9ab..5cdb6890c4 100644
--- a/django/contrib/admin/media/js/urlify.js
+++ b/django/contrib/admin/media/js/urlify.js
@@ -9,7 +9,7 @@ function URLify(s, num_chars) {
s = s.replace(r, '');
s = s.replace(/[^\w\s-]/g, ''); // remove unneeded chars
s = s.replace(/^\s+|\s+$/g, ''); // trim leading/trailing spaces
- s = s.replace(/\s+/g, '_'); // convert spaces to underscores
+ s = s.replace(/\s+/g, '-'); // convert spaces to hyphens
s = s.toLowerCase(); // convert to lowercase
return s.substring(0, num_chars);// trim to first num_chars chars
}
\ No newline at end of file
diff --git a/django/contrib/admin/views/doc.py b/django/contrib/admin/views/doc.py
index 166ecdff2d..e4d0f1c648 100644
--- a/django/contrib/admin/views/doc.py
+++ b/django/contrib/admin/views/doc.py
@@ -288,9 +288,9 @@ DATA_TYPE_MAPPING = {
def get_readable_field_data_type(field):
# ForeignKey is a special case. Use the field type of the relation.
- if field.__class__.__name__ == 'ForeignKey':
+ if field.get_internal_type() == 'ForeignKey':
field = field.rel.get_related_field()
- return DATA_TYPE_MAPPING[field.__class__.__name__] % field.__dict__
+ return DATA_TYPE_MAPPING[field.get_internal_type()] % field.__dict__
def extract_views_from_urlpatterns(urlpatterns, base=''):
"""
diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py
index 8ecf0a2c97..e3dd5afdcf 100644
--- a/django/contrib/admin/views/main.py
+++ b/django/contrib/admin/views/main.py
@@ -556,14 +556,14 @@ def _get_template(opts, app_label, add=False, change=False, show_delete=False, f
if not seen_collapse and 'collapse' in options.get('classes', ''):
seen_collapse = True
javascript_imports.append('%sjs/admin/CollapsedFieldsets.js' % ADMIN_MEDIA_PREFIX)
- try:
- for field_list in options['fields']:
+ for field_list in options['fields']:
+ try:
for f in field_list:
if f.rel and isinstance(f, meta.ManyToManyField) and f.rel.filter_interface:
javascript_imports.extend(['%sjs/SelectBox.js' % ADMIN_MEDIA_PREFIX, '%sjs/SelectFilter2.js' % ADMIN_MEDIA_PREFIX])
raise StopIteration
- except StopIteration:
- break
+ except StopIteration:
+ break
for j in javascript_imports:
t.append('' % j)
diff --git a/django/core/cache.py b/django/core/cache.py
index 6391304158..caeb315f05 100644
--- a/django/core/cache.py
+++ b/django/core/cache.py
@@ -13,7 +13,9 @@ settings.CACHE_BACKEND and use that to create and load a cache object.
The CACHE_BACKEND setting is a quasi-URI; examples are:
memcached://127.0.0.1:11211/ A memcached backend; the server is running
- on localhost port 11211.
+ on localhost port 11211. You can use
+ multiple memcached servers by separating
+ them with semicolons.
db://tablename/ A database backend in a table named
"tablename". This table should be created
@@ -134,7 +136,7 @@ else:
"Memcached cache backend."
def __init__(self, server, params):
_Cache.__init__(self, params)
- self._cache = memcache.Client([server])
+ self._cache = memcache.Client(server.split(';'))
def get(self, key, default=None):
val = self._cache.get(key)
diff --git a/django/core/formfields.py b/django/core/formfields.py
index 9b9f0af1d1..48238cf050 100644
--- a/django/core/formfields.py
+++ b/django/core/formfields.py
@@ -215,6 +215,7 @@ class FormField:
####################
class TextField(FormField):
+ input_type = "text"
def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=[]):
self.field_name = field_name
self.length, self.maxlength = length, maxlength
@@ -237,8 +238,8 @@ class TextField(FormField):
maxlength = 'maxlength="%s" ' % self.maxlength
if isinstance(data, unicode):
data = data.encode(DEFAULT_CHARSET)
- return '' % \
- (FORM_FIELD_ID_PREFIX + self.field_name, self.__class__.__name__, self.is_required and ' required' or '',
+ return '' % \
+ (self.input_type, FORM_FIELD_ID_PREFIX + self.field_name, self.__class__.__name__, self.is_required and ' required' or '',
self.field_name, self.length, escape(data), maxlength)
def html2python(data):
@@ -246,11 +247,7 @@ class TextField(FormField):
html2python = staticmethod(html2python)
class PasswordField(TextField):
- def render(self, data):
- # value is always blank because we never want to redisplay it
- return '' % \
- (FORM_FIELD_ID_PREFIX + self.field_name, self.__class__.__name__, self.is_required and ' required' or '',
- self.field_name)
+ input_type = "password"
class LargeTextField(TextField):
def __init__(self, field_name, rows=10, cols=40, is_required=False, validator_list=[], maxlength=None):
diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py
index 9b541b36db..190a8b02c2 100644
--- a/django/core/handlers/base.py
+++ b/django/core/handlers/base.py
@@ -48,6 +48,9 @@ class BaseHandler:
from django.core.mail import mail_admins
from django.conf.settings import DEBUG, INTERNAL_IPS, ROOT_URLCONF
+ # Reset query list per request.
+ db.db.queries = []
+
# Apply request middleware
for middleware_method in self._request_middleware:
response = middleware_method(request)
diff --git a/django/core/management.py b/django/core/management.py
index 3b973c28da..ca94683cff 100644
--- a/django/core/management.py
+++ b/django/core/management.py
@@ -345,6 +345,27 @@ The full error: %s\n""" % \
install.help_doc = "Executes ``sqlall`` for the given model module name(s) in the current database."
install.args = APP_ARGS
+def installperms(mod):
+ "Installs any permissions for the given model, if needed."
+ from django.models.auth import permissions
+ from django.models.core import packages
+ num_added = 0
+ package = packages.get_object(pk=mod._MODELS[0]._meta.app_label)
+ for klass in mod._MODELS:
+ opts = klass._meta
+ for codename, name in _get_all_permissions(opts):
+ try:
+ permissions.get_object(name__exact=name, codename__exact=codename, package__label__exact=package.label)
+ except permissions.PermissionDoesNotExist:
+ p = permissions.Permission(name=name, package=package, codename=codename)
+ p.save()
+ print "Added permission '%r'." % p
+ num_added += 1
+ if not num_added:
+ print "No permissions were added, because all necessary permissions were already installed."
+installperms.help_doc = "Installs any permissions for the given model module name(s), if needed."
+installperms.args = APP_ARGS
+
def _start_helper(app_or_project, name, directory, other_name=''):
other = {'project': 'app', 'app': 'project'}[app_or_project]
if not _is_valid_dir_name(name):
diff --git a/django/core/template/defaultfilters.py b/django/core/template/defaultfilters.py
index 746fa9b873..2604caf7f9 100644
--- a/django/core/template/defaultfilters.py
+++ b/django/core/template/defaultfilters.py
@@ -24,15 +24,15 @@ def fix_ampersands(value, _):
def floatformat(text, _):
"""
- Displays a floating point number as 34.2 (with one decimal place) - but
+ Displays a floating point number as 34.2 (with one decimal place) -- but
only if there's a point to be displayed
"""
- from math import modf
- if not text:
- return ''
- if modf(float(text))[0] < 0.1:
- return text
- return "%.1f" % float(text)
+ f = float(text)
+ m = f - int(f)
+ if m:
+ return '%.1f' % f
+ else:
+ return '%d' % int(f)
def linenumbers(value, _):
"Displays text with line numbers"
@@ -175,7 +175,7 @@ def removetags(value, tags):
"Removes a space separated list of [X]HTML tags from the output"
tags = [re.escape(tag) for tag in tags.split()]
tags_re = '(%s)' % '|'.join(tags)
- starttag_re = re.compile('<%s(>|(\s+[^>]*>))' % tags_re)
+ starttag_re = re.compile(r'<%s(/?>|(\s+[^>]*>))' % tags_re)
endtag_re = re.compile('%s>' % tags_re)
value = starttag_re.sub('', value)
value = endtag_re.sub('', value)
diff --git a/django/utils/autoreload.py b/django/utils/autoreload.py
index 039ba68cfb..04e319c1c7 100644
--- a/django/utils/autoreload.py
+++ b/django/utils/autoreload.py
@@ -37,6 +37,8 @@ def reloader_thread():
mtimes = {}
while RUN_RELOADER:
for filename in filter(lambda v: v, map(lambda m: getattr(m, "__file__", None), sys.modules.values())) + reloadFiles:
+ if not os.path.exists(filename):
+ continue # File might be in an egg, so it can't be reloaded.
if filename.endswith(".pyc"):
filename = filename[:-1]
mtime = os.stat(filename).st_mtime
diff --git a/django/utils/cache.py b/django/utils/cache.py
index fcd0825a22..631ea8f08d 100644
--- a/django/utils/cache.py
+++ b/django/utils/cache.py
@@ -21,6 +21,45 @@ import datetime, md5, re
from django.conf import settings
from django.core.cache import cache
+cc_delim_re = re.compile(r'\s*,\s*')
+def patch_cache_control(response, **kwargs):
+ """
+ This function patches the Cache-Control header by adding all
+ keyword arguments to it. The transformation is as follows:
+
+ - all keyword parameter names are turned to lowercase and
+ all _ will be translated to -
+ - if the value of a parameter is True (exatly True, not just a
+ true value), only the parameter name is added to the header
+ - all other parameters are added with their value, after applying
+ str to it.
+ """
+
+ def dictitem(s):
+ t = s.split('=',1)
+ if len(t) > 1:
+ return (t[0].lower().replace('-', '_'), t[1])
+ else:
+ return (t[0].lower().replace('-', '_'), True)
+
+ def dictvalue(t):
+ if t[1] == True:
+ return t[0]
+ else:
+ return t[0] + '=' + str(t[1])
+
+ if response.has_header('Cache-Control'):
+ print response['Cache-Control']
+ cc = cc_delim_re.split(response['Cache-Control'])
+ print cc
+ cc = dict([dictitem(el) for el in cc])
+ else:
+ cc = {}
+ for (k,v) in kwargs.items():
+ cc[k.replace('_', '-')] = v
+ cc = ', '.join([dictvalue(el) for el in cc.items()])
+ response['Cache-Control'] = cc
+
vary_delim_re = re.compile(r',\s*')
def patch_response_headers(response, cache_timeout=None):
@@ -43,8 +82,7 @@ def patch_response_headers(response, cache_timeout=None):
response['Last-Modified'] = now.strftime('%a, %d %b %Y %H:%M:%S GMT')
if not response.has_header('Expires'):
response['Expires'] = expires.strftime('%a, %d %b %Y %H:%M:%S GMT')
- if not response.has_header('Cache-Control'):
- response['Cache-Control'] = 'max-age=%d' % cache_timeout
+ patch_cache_control(response, max_age=cache_timeout)
def patch_vary_headers(response, newheaders):
"""
diff --git a/django/views/decorators/auth.py b/django/views/decorators/auth.py
index f543a6aa48..a5a28bb0b2 100644
--- a/django/views/decorators/auth.py
+++ b/django/views/decorators/auth.py
@@ -1,19 +1,22 @@
-def user_passes_test(view_func, test_func):
+def user_passes_test(test_func):
"""
Decorator for views that checks that the user passes the given test,
redirecting to the log-in page if necessary. The test should be a callable
that takes the user object and returns True if the user passes.
"""
- from django.views.auth.login import redirect_to_login
- def _checklogin(request, *args, **kwargs):
- if test_func(request.user):
- return view_func(request, *args, **kwargs)
- return redirect_to_login(request.path)
- return _checklogin
+ def _dec(view_func):
+ def _checklogin(request, *args, **kwargs):
+ from django.views.auth.login import redirect_to_login
+ if test_func(request.user):
+ return view_func(request, *args, **kwargs)
+ return redirect_to_login(request.path)
+ return _checklogin
+ return _dec
-def login_required(view_func):
+login_required = user_passes_test(lambda u: not u.is_anonymous())
+login_required.__doc__ = (
"""
Decorator for views that checks that the user is logged in, redirecting
to the log-in page if necessary.
"""
- return user_passes_test(lambda u: not u.is_anonymous())
+ )
diff --git a/django/views/decorators/cache.py b/django/views/decorators/cache.py
index 09f9a0139f..f86372cf4e 100644
--- a/django/views/decorators/cache.py
+++ b/django/views/decorators/cache.py
@@ -10,8 +10,24 @@ example, as that is unique across a Django project.
Additionally, all headers from the response's Vary header will be taken into
account on caching -- just like the middleware does.
"""
+import re
from django.utils.decorators import decorator_from_middleware
+from django.utils.cache import patch_cache_control
from django.middleware.cache import CacheMiddleware
cache_page = decorator_from_middleware(CacheMiddleware)
+
+def cache_control(**kwargs):
+
+ def _cache_controller(viewfunc):
+
+ def _cache_controlled(request, *args, **kw):
+ response = viewfunc(request, *args, **kw)
+ patch_cache_control(response, **kwargs)
+ return response
+
+ return _cache_controlled
+
+ return _cache_controller
+
diff --git a/django/views/decorators/http.py b/django/views/decorators/http.py
index 13062b630f..b9b6bac757 100644
--- a/django/views/decorators/http.py
+++ b/django/views/decorators/http.py
@@ -1,9 +1,35 @@
"""
-Decorator for views that supports conditional get on ETag and Last-Modified
-headers.
+Decorators for views based on HTTP headers.
"""
from django.utils.decorators import decorator_from_middleware
from django.middleware.http import ConditionalGetMiddleware
+from django.utils.httpwrappers import HttpResponseForbidden
conditional_page = decorator_from_middleware(ConditionalGetMiddleware)
+
+def require_http_methods(request_method_list):
+ """
+ Decorator to make a view only accept particular request methods. Usage::
+
+ @require_http_methods(["GET", "POST"])
+ def my_view(request):
+ # I can assume now that only GET or POST requests make it this far
+ # ...
+
+ Note that request methods ARE case sensitive.
+ """
+ def decorator(func):
+ def inner(request, *args, **kwargs):
+ method = request.META.get("REQUEST_METHOD", None)
+ if method not in request_method_list:
+ raise HttpResponseForbidden("REQUEST_METHOD '%s' not allowed" % method)
+ return func(request, *args, **kwargs)
+ return inner
+ return decorator
+
+require_GET = require_http_methods(["GET"])
+require_GET.__doc__ = "Decorator to require that a view only accept the GET method."
+
+require_POST = require_http_methods(["POST"])
+require_POST.__doc__ = "Decorator to require that a view only accept the POST method."
\ No newline at end of file
diff --git a/django/views/defaults.py b/django/views/defaults.py
index decc220cf7..45e5636c5a 100644
--- a/django/views/defaults.py
+++ b/django/views/defaults.py
@@ -1,10 +1,11 @@
from django.core.exceptions import Http404, ObjectDoesNotExist
from django.core.template import Context, loader
-from django.models.core import sites
+from django.models.core import sites, contenttypes
from django.utils import httpwrappers
def shortcut(request, content_type_id, object_id):
- from django.models.core import contenttypes
+ """Redirect to an object's page based on a content-type ID and an object ID"""
+ # Look up the object, making sure it's got a get_absolute_url() function.
try:
content_type = contenttypes.get_object(pk=content_type_id)
obj = content_type.get_object_for_this_type(pk=object_id)
@@ -14,25 +15,37 @@ def shortcut(request, content_type_id, object_id):
absurl = obj.get_absolute_url()
except AttributeError:
raise Http404, "%s objects don't have get_absolute_url() methods" % content_type.name
+
+ # Try to figure out the object's domain so we can do a cross-site redirect
+ # if necessary
+
+ # If the object actually defines a domain, we're done.
if absurl.startswith('http://'):
return httpwrappers.HttpResponseRedirect(absurl)
+
object_domain = None
+
+ # Next, look for an many-to-many relationship to sites
if hasattr(obj, 'get_site_list'):
site_list = obj.get_site_list()
if site_list:
object_domain = site_list[0].domain
+
+ # Next, look for a many-to-one relationship to sites
elif hasattr(obj, 'get_site'):
try:
object_domain = obj.get_site().domain
except sites.SiteDoesNotExist:
pass
- try:
- object_domain = sites.get_current().domain
- except sites.SiteDoesNotExist:
- pass
- if not object_domain:
- return httpwrappers.HttpResponseRedirect(absurl)
- return httpwrappers.HttpResponseRedirect('http://%s%s' % (object_domain, absurl))
+
+ # Then, fall back to the current site (if possible)
+ else:
+ try:
+ object_domain = sites.get_current().domain
+ except sites.SiteDoesNotExist:
+ # Finally, give up and use a URL without the domain name
+ return httpwrappers.HttpResponseRedirect(obj.get_absolute_url())
+ return httpwrappers.HttpResponseRedirect('http://%s%s' % (object_domain, obj.get_absolute_url()))
def page_not_found(request):
"""
diff --git a/docs/cache.txt b/docs/cache.txt
index f15da2660b..c1b1352bca 100644
--- a/docs/cache.txt
+++ b/docs/cache.txt
@@ -29,7 +29,9 @@ Examples:
CACHE_BACKEND Explanation
============================== ===========================================
memcached://127.0.0.1:11211/ A memcached backend; the server is running
- on localhost port 11211.
+ on localhost port 11211. You can use
+ multiple memcached servers by separating
+ them with semicolons.
db://tablename/ A database backend in a table named
"tablename". This table should be created
@@ -270,6 +272,40 @@ and a list/tuple of header names as its second argument.
.. _`HTTP Vary headers`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.44
+Controlling cache: Using Vary headers
+=====================================
+
+Another problem with caching is the privacy of data, and the question where data can
+be stored in a cascade of caches. A user usually faces two kinds of caches: his own
+browser cache (a private cache) and his providers cache (a public cache). A public cache
+is used by multiple users and controlled by someone else. This poses problems with private
+(in the sense of sensitive) data - you don't want your social security number or your
+banking account numbers stored in some public cache. So web applications need a way
+to tell the caches what data is private and what is public.
+
+Other aspects are the definition how long a page should be cached at max, or wether the
+cache should allways check for newer versions and only deliver the cache content when
+there were no changes (some caches might deliver cached content even if the server page
+changed - just because the cache copy isn't yet expired).
+
+So there are a multitude of options you can control for your pages. This is where the
+Cache-Control header (more infos in `HTTP Cache-Control headers`_) comes in. The usage
+is quite simple::
+
+ @cache_control(private=True, must_revalidate=True, max_age=3600)
+ def my_view(request):
+ ...
+
+This would define the view as private, to be revalidated on every access and cache
+copies will only be stored for 3600 seconds at max.
+
+The caching middleware already set's this header up with a max-age of the CACHE_MIDDLEWARE_SETTINGS
+setting. And the cache_page decorator does the same. The cache_control decorator correctly merges
+different values into one big header, though. But you should take into account that middlewares
+might overwrite some of your headers or set their own defaults if you don't give that header yourself.
+
+.. _`HTTP Cache-Control headers`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
+
Other optimizations
===================
diff --git a/docs/model-api.txt b/docs/model-api.txt
index fa6d8f10e6..edf94dd17c 100644
--- a/docs/model-api.txt
+++ b/docs/model-api.txt
@@ -621,6 +621,14 @@ the relationship should work. All are optional:
vertically).
``limit_choices_to`` See the description under ``ForeignKey`` above.
+
+ ``singular`` The singular name of the field. Use to name the ``get_*``
+ methods: in the example above, Django gives the ``Pizza``
+ objects a ``get_topping_list()`` method, where ``topping``
+ is the default ``singular`` value derived from the lowercase
+ version of the class being linked to. Use the singular
+ parameter to change this, which is if you want one model to
+ have multiple ``ManyToMany`` relationships to another model.
======================= ============================================================
One-to-one relationships
@@ -750,11 +758,12 @@ Here's a list of all possible ``META`` options. No options are required. Adding
``permissions``
Extra permissions to enter into the permissions table when creating this
object. Add, delete and change permissions are automatically created for
- each object. This option specifies extra permissions::
+ each object that has ``admin`` set. This example specifies an extra
+ permission, ``can_deliver_pizzas``::
permissions = (("can_deliver_pizzas", "Can deliver pizzas"),)
- This is a list of 2-tuples of
+ This is a list or tuple of 2-tuples in the format
``(permission_code, human_readable_permission_name)``.
``unique_together``
diff --git a/tests/othertests/defaultfilters.py b/tests/othertests/defaultfilters.py
new file mode 100644
index 0000000000..d440e25dd5
--- /dev/null
+++ b/tests/othertests/defaultfilters.py
@@ -0,0 +1,20 @@
+"""
+>>> floatformat(7.7, None)
+'7.7'
+>>> floatformat(7.0, None)
+'7'
+>>> floatformat(0.7, None)
+'0.7'
+>>> floatformat(0.07, None)
+'0.1'
+>>> floatformat(0.007, None)
+'0.0'
+>>> floatformat(0.0, None)
+'0'
+"""
+
+from django.core.template.defaultfilters import *
+
+if __name__ == '__main__':
+ import doctest
+ doctest.testmod()