diff --git a/AUTHORS b/AUTHORS index f175a1034b..f5c35fb9f8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -115,6 +115,7 @@ answer newbie questions, and generally made Django that much better: Enrico A. Murat Eren Ludvig Ericson + eriks@win.tue.nl Dirk Eschler Marc Fargas Szilveszter Farkas @@ -239,6 +240,7 @@ answer newbie questions, and generally made Django that much better: Jay Parlar pavithran s Barry Pederson + permonik@mesias.brnonet.cz petr.marhoun@gmail.com pgross@thoughtworks.com phaedo @@ -317,7 +319,7 @@ answer newbie questions, and generally made Django that much better: Vlado Milton Waddams wam-djangobug@wamber.net - wangchun + Wang Chun Filip Wasilewski Dan Watson Chris Wesseling diff --git a/django/bin/compile-messages.py b/django/bin/compile-messages.py index 2838cb8aa4..8693022644 100755 --- a/django/bin/compile-messages.py +++ b/django/bin/compile-messages.py @@ -14,7 +14,8 @@ def compile_messages(locale=None): basedirs = [os.path.join('conf', 'locale'), 'locale'] if os.environ.get('DJANGO_SETTINGS_MODULE'): from django.conf import settings - basedirs += settings.LOCALE_PATHS + if hasattr(settings, 'LOCALE_PATHS'): + basedirs += settings.LOCALE_PATHS # Gather existing directories. basedirs = set(map(os.path.abspath, filter(os.path.isdir, basedirs))) diff --git a/django/bin/make-messages.py b/django/bin/make-messages.py index 11616c9ca5..44040390ea 100755 --- a/django/bin/make-messages.py +++ b/django/bin/make-messages.py @@ -74,59 +74,62 @@ def make_messages(): if os.path.exists(potfile): os.unlink(potfile) + all_files = [] for (dirpath, dirnames, filenames) in os.walk("."): - for file in filenames: - if domain == 'djangojs' and file.endswith('.js'): - if verbose: sys.stdout.write('processing file %s in %s\n' % (file, dirpath)) + all_files.extend([(dirpath, f) for f in filenames]) + all_files.sort() + for dirpath, file in all_files: + if domain == 'djangojs' and file.endswith('.js'): + if verbose: sys.stdout.write('processing file %s in %s\n' % (file, dirpath)) + src = open(os.path.join(dirpath, file), "rb").read() + src = pythonize_re.sub('\n#', src) + open(os.path.join(dirpath, '%s.py' % file), "wb").write(src) + thefile = '%s.py' % file + cmd = 'xgettext %s -d %s -L Perl --keyword=gettext_noop --keyword=gettext_lazy --keyword=ngettext_lazy:1,2 --from-code UTF-8 -o - "%s"' % ( + os.path.exists(potfile) and '--omit-header' or '', domain, os.path.join(dirpath, thefile)) + (stdin, stdout, stderr) = os.popen3(cmd, 't') + msgs = stdout.read() + errors = stderr.read() + if errors: + print "errors happened while running xgettext on %s" % file + print errors + sys.exit(8) + old = '#: '+os.path.join(dirpath, thefile)[2:] + new = '#: '+os.path.join(dirpath, file)[2:] + msgs = msgs.replace(old, new) + if msgs: + open(potfile, 'ab').write(msgs) + os.unlink(os.path.join(dirpath, thefile)) + elif domain == 'django' and (file.endswith('.py') or file.endswith('.html')): + thefile = file + if file.endswith('.html'): src = open(os.path.join(dirpath, file), "rb").read() - src = pythonize_re.sub('\n#', src) - open(os.path.join(dirpath, '%s.py' % file), "wb").write(src) thefile = '%s.py' % file - cmd = 'xgettext %s -d %s -L Perl --keyword=gettext_noop --keyword=gettext_lazy --keyword=ngettext_lazy:1,2 --from-code UTF-8 -o - "%s"' % ( - os.path.exists(potfile) and '--omit-header' or '', domain, os.path.join(dirpath, thefile)) - (stdin, stdout, stderr) = os.popen3(cmd, 't') - msgs = stdout.read() - errors = stderr.read() - if errors: - print "errors happened while running xgettext on %s" % file - print errors - sys.exit(8) + open(os.path.join(dirpath, thefile), "wb").write(templatize(src)) + if verbose: + sys.stdout.write('processing file %s in %s\n' % (file, dirpath)) + cmd = 'xgettext -d %s -L Python --keyword=gettext_noop --keyword=gettext_lazy --keyword=ngettext_lazy:1,2 --keyword=ugettext_noop --keyword=ugettext_lazy --keyword=ungettext_lazy:1,2 --from-code UTF-8 -o - "%s"' % ( + domain, os.path.join(dirpath, thefile)) + (stdin, stdout, stderr) = os.popen3(cmd, 't') + msgs = stdout.read() + errors = stderr.read() + if errors: + print "errors happened while running xgettext on %s" % file + print errors + sys.exit(8) + if thefile != file: old = '#: '+os.path.join(dirpath, thefile)[2:] new = '#: '+os.path.join(dirpath, file)[2:] msgs = msgs.replace(old, new) - if msgs: - open(potfile, 'ab').write(msgs) + if os.path.exists(potfile): + # Strip the header + msgs = '\n'.join(dropwhile(len, msgs.split('\n'))) + else: + msgs = msgs.replace('charset=CHARSET', 'charset=UTF-8') + if msgs: + open(potfile, 'ab').write(msgs) + if thefile != file: os.unlink(os.path.join(dirpath, thefile)) - elif domain == 'django' and (file.endswith('.py') or file.endswith('.html')): - thefile = file - if file.endswith('.html'): - src = open(os.path.join(dirpath, file), "rb").read() - thefile = '%s.py' % file - open(os.path.join(dirpath, thefile), "wb").write(templatize(src)) - if verbose: - sys.stdout.write('processing file %s in %s\n' % (file, dirpath)) - cmd = 'xgettext -d %s -L Python --keyword=gettext_noop --keyword=gettext_lazy --keyword=ngettext_lazy:1,2 --keyword=ugettext_noop --keyword=ugettext_lazy --keyword=ungettext_lazy:1,2 --from-code UTF-8 -o - "%s"' % ( - domain, os.path.join(dirpath, thefile)) - (stdin, stdout, stderr) = os.popen3(cmd, 't') - msgs = stdout.read() - errors = stderr.read() - if errors: - print "errors happened while running xgettext on %s" % file - print errors - sys.exit(8) - if thefile != file: - old = '#: '+os.path.join(dirpath, thefile)[2:] - new = '#: '+os.path.join(dirpath, file)[2:] - msgs = msgs.replace(old, new) - if os.path.exists(potfile): - # Strip the header - msgs = '\n'.join(dropwhile(len, msgs.split('\n'))) - else: - msgs = msgs.replace('charset=CHARSET', 'charset=UTF-8') - if msgs: - open(potfile, 'ab').write(msgs) - if thefile != file: - os.unlink(os.path.join(dirpath, thefile)) if os.path.exists(potfile): (stdin, stdout, stderr) = os.popen3('msguniq --to-code=utf-8 "%s"' % potfile, 'b') diff --git a/django/conf/locale/de/LC_MESSAGES/django.mo b/django/conf/locale/de/LC_MESSAGES/django.mo index 307976e4d5..2699d25e55 100644 Binary files a/django/conf/locale/de/LC_MESSAGES/django.mo and b/django/conf/locale/de/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/de/LC_MESSAGES/django.po b/django/conf/locale/de/LC_MESSAGES/django.po index 6978cf2d44..50fa66d43f 100644 --- a/django/conf/locale/de/LC_MESSAGES/django.po +++ b/django/conf/locale/de/LC_MESSAGES/django.po @@ -1,6 +1,7 @@ -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR , YEAR. +# Translation of django.po to German +# +# Copyright (C) 2005-2007, +# This file is distributed under the same license as the django package. # msgid "" "" @@ -1629,76 +1630,76 @@ msgstr "Webseite" msgid "flat pages" msgstr "Webseiten" -#: contrib/humanize/templatetags/humanize.py:17 +#: contrib/humanize/templatetags/humanize.py:20 msgid "th" -msgstr "" +msgstr "." -#: contrib/humanize/templatetags/humanize.py:17 +#: contrib/humanize/templatetags/humanize.py:20 msgid "st" -msgstr "" +msgstr "." -#: contrib/humanize/templatetags/humanize.py:17 +#: contrib/humanize/templatetags/humanize.py:20 msgid "nd" -msgstr "" +msgstr "." -#: contrib/humanize/templatetags/humanize.py:17 +#: contrib/humanize/templatetags/humanize.py:20 msgid "rd" -msgstr "" - -#: contrib/humanize/templatetags/humanize.py:47 -#, python-format -msgid "%(value).1f million" -msgid_plural "%(value).1f million" -msgstr[0] "" -msgstr[1] "" +msgstr "." #: contrib/humanize/templatetags/humanize.py:50 #, python-format -msgid "%(value).1f billion" -msgid_plural "%(value).1f billion" -msgstr[0] "" -msgstr[1] "" +msgid "%(value).1f million" +msgid_plural "%(value).1f million" +msgstr[0] "%(value).1f Million" +msgstr[1] "%(value).1f Millionen" #: contrib/humanize/templatetags/humanize.py:53 #, python-format +msgid "%(value).1f billion" +msgid_plural "%(value).1f billion" +msgstr[0] "%(value).1f Milliarde" +msgstr[1] "%(value).1f Milliarden" + +#: contrib/humanize/templatetags/humanize.py:56 +#, python-format msgid "%(value).1f trillion" msgid_plural "%(value).1f trillion" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "%(value).1f Billion" +msgstr[1] "%(value).1f Billionen" -#: contrib/humanize/templatetags/humanize.py:68 +#: contrib/humanize/templatetags/humanize.py:71 msgid "one" msgstr "ein" -#: contrib/humanize/templatetags/humanize.py:68 +#: contrib/humanize/templatetags/humanize.py:71 msgid "two" msgstr "zwei" -#: contrib/humanize/templatetags/humanize.py:68 +#: contrib/humanize/templatetags/humanize.py:71 msgid "three" msgstr "drei" -#: contrib/humanize/templatetags/humanize.py:68 +#: contrib/humanize/templatetags/humanize.py:71 msgid "four" msgstr "vier" -#: contrib/humanize/templatetags/humanize.py:68 +#: contrib/humanize/templatetags/humanize.py:71 msgid "five" msgstr "fünf" -#: contrib/humanize/templatetags/humanize.py:68 +#: contrib/humanize/templatetags/humanize.py:71 msgid "six" msgstr "sechs" -#: contrib/humanize/templatetags/humanize.py:68 +#: contrib/humanize/templatetags/humanize.py:71 msgid "seven" msgstr "sieben" -#: contrib/humanize/templatetags/humanize.py:68 +#: contrib/humanize/templatetags/humanize.py:71 msgid "eight" msgstr "acht" -#: contrib/humanize/templatetags/humanize.py:68 +#: contrib/humanize/templatetags/humanize.py:71 msgid "nine" msgstr "neun" diff --git a/django/contrib/auth/backends.py b/django/contrib/auth/backends.py index be6cfede11..f79929e2d2 100644 --- a/django/contrib/auth/backends.py +++ b/django/contrib/auth/backends.py @@ -1,6 +1,11 @@ from django.db import connection from django.contrib.auth.models import User +try: + set +except NameError: + from sets import Set as set # Python 2.3 fallback + class ModelBackend: """ Authenticate against django.contrib.auth.models.User diff --git a/django/contrib/sessions/models.py b/django/contrib/sessions/models.py index c086396947..8fc5e17d69 100644 --- a/django/contrib/sessions/models.py +++ b/django/contrib/sessions/models.py @@ -1,5 +1,12 @@ -import base64, md5, random, sys, datetime +import os +import sys +import time +import datetime +import base64 +import md5 +import random import cPickle as pickle + from django.db import models from django.utils.translation import ugettext_lazy as _ from django.conf import settings diff --git a/django/core/handlers/wsgi.py b/django/core/handlers/wsgi.py index 6fe24f5d13..16d1d822e0 100644 --- a/django/core/handlers/wsgi.py +++ b/django/core/handlers/wsgi.py @@ -1,16 +1,16 @@ +from threading import Lock +from pprint import pformat +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO + from django.core.handlers.base import BaseHandler from django.core import signals from django.dispatch import dispatcher from django.utils import datastructures from django.utils.encoding import force_unicode from django import http -from pprint import pformat -from shutil import copyfileobj -from threading import Lock -try: - from cStringIO import StringIO -except ImportError: - from StringIO import StringIO # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html STATUS_CODE_TEXT = { @@ -105,7 +105,8 @@ class WSGIRequest(http.HttpRequest): return '%s%s' % (self.path, self.environ.get('QUERY_STRING', '') and ('?' + self.environ.get('QUERY_STRING', '')) or '') def is_secure(self): - return 'HTTPS' in self.environ and self.environ['HTTPS'] == 'on' + return 'wsgi.url_scheme' in self.environ \ + and self.environ['wsgi.url_scheme'] == 'https' def _load_post_and_files(self): # Populates self._post and self._files diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py index e15dfccdf2..cd90fd6109 100644 --- a/django/core/management/__init__.py +++ b/django/core/management/__init__.py @@ -239,7 +239,7 @@ def setup_environ(settings_mod): project_directory, settings_filename = os.path.split(settings_mod.__file__) project_name = os.path.basename(project_directory) settings_name = os.path.splitext(settings_filename)[0] - sys.path.append(os.path.join(project_directory, '..')) + sys.path.append(os.path.join(project_directory, os.pardir)) project_module = __import__(project_name, {}, {}, ['']) sys.path.pop() diff --git a/django/core/management/commands/startapp.py b/django/core/management/commands/startapp.py index 1da2776cf5..c238e74a08 100644 --- a/django/core/management/commands/startapp.py +++ b/django/core/management/commands/startapp.py @@ -16,7 +16,7 @@ class Command(LabelCommand): directory = os.getcwd() # Determine the project_name a bit naively -- by looking at the name of # the parent directory. - project_dir = os.path.normpath(os.path.join(directory, '..')) + project_dir = os.path.normpath(os.path.join(directory, os.pardir)) parent_dir = os.path.basename(project_dir) project_name = os.path.basename(directory) if app_name == project_name: diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 67f14f9baf..017f82a28b 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -236,7 +236,7 @@ class Field(object): if self.default is not NOT_PROVIDED: if callable(self.default): return self.default() - return self.default + return force_unicode(self.default, strings_only=True) if not self.empty_strings_allowed or (self.null and settings.DATABASE_ENGINE != 'oracle'): return None return "" diff --git a/django/http/__init__.py b/django/http/__init__.py index 9a47e70592..508c6d2f3e 100644 --- a/django/http/__init__.py +++ b/django/http/__init__.py @@ -47,8 +47,9 @@ class HttpRequest(object): def get_host(self): "Returns the HTTP host using the environment or request headers." # We try three options, in order of decreasing preference. - host = self.META.get('HTTP_X_FORWARDED_HOST', '') - if 'HTTP_HOST' in self.META: + if 'HTTP_X_FORWARDED_HOST' in self.META: + host = self.META['HTTP_X_FORWARDED_HOST'] + elif 'HTTP_HOST' in self.META: host = self.META['HTTP_HOST'] else: # Reconstruct the host using the algorithm from PEP 333. diff --git a/django/newforms/widgets.py b/django/newforms/widgets.py index eb67385e53..1336a20991 100644 --- a/django/newforms/widgets.py +++ b/django/newforms/widgets.py @@ -7,6 +7,7 @@ try: except NameError: from sets import Set as set # Python 2.3 fallback +import copy from itertools import chain from django.conf import settings from django.utils.datastructures import MultiValueDict @@ -135,6 +136,12 @@ class Widget(object): else: self.attrs = {} + def __deepcopy__(self, memo): + obj = copy.copy(self) + obj.attrs = self.attrs.copy() + memo[id(self)] = obj + return obj + def render(self, name, value, attrs=None): """ Returns this Widget rendered as HTML, as a Unicode string. @@ -191,7 +198,7 @@ class PasswordInput(Input): input_type = 'password' def __init__(self, attrs=None, render_value=True): - self.attrs = attrs or {} + super(PasswordInput, self).__init__(attrs) self.render_value = render_value def render(self, name, value, attrs=None): @@ -208,8 +215,8 @@ class MultipleHiddenInput(HiddenInput): of values. """ def __init__(self, attrs=None, choices=()): + super(MultipleHiddenInput, self).__init__(attrs) # choices can be any iterable - self.attrs = attrs or {} self.choices = choices def render(self, name, value, attrs=None, choices=()): @@ -248,9 +255,9 @@ class Textarea(Widget): class CheckboxInput(Widget): def __init__(self, attrs=None, check_test=bool): + super(CheckboxInput, self).__init__(attrs) # check_test is a callable that takes a value and returns True # if the checkbox should be checked for that value. - self.attrs = attrs or {} self.check_test = check_test def render(self, name, value, attrs=None): @@ -267,7 +274,7 @@ class CheckboxInput(Widget): class Select(Widget): def __init__(self, attrs=None, choices=()): - self.attrs = attrs or {} + super(Select, self).__init__(attrs) # choices can be any iterable, but we may need to render this widget # multiple times. Thus, collapse it into a list so it can be consumed # more than once. @@ -306,8 +313,8 @@ class NullBooleanSelect(Select): class SelectMultiple(Widget): def __init__(self, attrs=None, choices=()): + super(SelectMultiple, self).__init__(attrs) # choices can be any iterable - self.attrs = attrs or {} self.choices = choices def render(self, name, value, attrs=None, choices=()): @@ -530,4 +537,3 @@ class SplitDateTimeWidget(MultiWidget): if value: return [value.date(), value.time()] return [None, None] - \ No newline at end of file diff --git a/django/oldforms/__init__.py b/django/oldforms/__init__.py index 9bb90416c4..5f7b9fc2ad 100644 --- a/django/oldforms/__init__.py +++ b/django/oldforms/__init__.py @@ -500,7 +500,7 @@ class SelectField(FormField): selected_html = u'' if smart_unicode(value) == str_data: selected_html = u' selected="selected"' - output.append(u' ' % (escape(value), selected_html, escape(display_name))) + output.append(u' ' % (escape(value), selected_html, force_unicode(escape(display_name)))) output.append(u' ') return u'\n'.join(output) @@ -612,7 +612,7 @@ class SelectMultipleField(SelectField): selected_html = u'' if smart_unicode(value) in str_data_list: selected_html = u' selected="selected"' - output.append(u' ' % (escape(value), selected_html, escape(choice))) + output.append(u' ' % (escape(value), selected_html, force_unicode(escape(choice)))) output.append(u' ') return u'\n'.join(output) diff --git a/django/utils/cache.py b/django/utils/cache.py index 2494d7839e..f192e1115c 100644 --- a/django/utils/cache.py +++ b/django/utils/cache.py @@ -23,7 +23,7 @@ import time from email.Utils import formatdate from django.conf import settings from django.core.cache import cache -from django.utils.encoding import smart_str +from django.utils.encoding import smart_str, iri_to_uri cc_delim_re = re.compile(r'\s*,\s*') @@ -57,6 +57,13 @@ def patch_cache_control(response, **kwargs): cc = dict([dictitem(el) for el in cc]) else: cc = {} + + # If there's already a max-age header but we're being asked to set a new + # max-age, use the minumum of the two ages. In practice this happens when + # a decorator and a piece of middleware both operate on a given view. + if 'max-age' in cc and 'max_age' in kwargs: + kwargs['max_age'] = min(cc['max-age'], kwargs['max_age']) + for (k,v) in kwargs.items(): cc[k.replace('_', '-')] = v cc = ', '.join([dictvalue(el) for el in cc.items()]) @@ -118,7 +125,7 @@ def _generate_cache_key(request, headerlist, key_prefix): value = request.META.get(header, None) if value is not None: ctx.update(value) - return 'views.decorators.cache.cache_page.%s.%s.%s' % (key_prefix, request.path, ctx.hexdigest()) + return 'views.decorators.cache.cache_page.%s.%s.%s' % (key_prefix, iri_to_uri(request.path), ctx.hexdigest()) def get_cache_key(request, key_prefix=None): """ @@ -132,7 +139,7 @@ def get_cache_key(request, key_prefix=None): """ if key_prefix is None: key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX - cache_key = 'views.decorators.cache.cache_header.%s.%s' % (key_prefix, request.path) + cache_key = 'views.decorators.cache.cache_header.%s.%s' % (key_prefix, iri_to_uri(request.path)) headerlist = cache.get(cache_key, None) if headerlist is not None: return _generate_cache_key(request, headerlist, key_prefix) @@ -156,7 +163,7 @@ def learn_cache_key(request, response, cache_timeout=None, key_prefix=None): key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX if cache_timeout is None: cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS - cache_key = 'views.decorators.cache.cache_header.%s.%s' % (key_prefix, request.path) + cache_key = 'views.decorators.cache.cache_header.%s.%s' % (key_prefix, iri_to_uri(request.path)) if response.has_header('Vary'): headerlist = ['HTTP_'+header.upper().replace('-', '_') for header in vary_delim_re.split(response['Vary'])] cache.set(cache_key, headerlist, cache_timeout) diff --git a/django/utils/translation/__init__.py b/django/utils/translation/__init__.py index 13fc8a847a..9ca7419e1e 100644 --- a/django/utils/translation/__init__.py +++ b/django/utils/translation/__init__.py @@ -2,6 +2,7 @@ Internationalization support. """ from django.utils.functional import lazy +from django.utils.encoding import force_unicode __all__ = ['gettext', 'gettext_noop', 'gettext_lazy', 'ngettext', 'ngettext_lazy', 'string_concat', 'activate', 'deactivate', @@ -39,7 +40,7 @@ def delayed_loader(*args, **kwargs): g['real_%s' % name] = getattr(trans, name) # Make the originally requested function call on the way out the door. - return g[caller](*args, **kwargs) + return g['real_%s' % caller](*args, **kwargs) g = globals() for name in __all__: @@ -63,14 +64,10 @@ def ugettext(message): def ungettext(singular, plural, number): return real_ungettext(singular, plural, number) -def string_concat(*strings): - return real_string_concat(*strings) - ngettext_lazy = lazy(ngettext, str) gettext_lazy = lazy(gettext, str) ungettext_lazy = lazy(ungettext, unicode) ugettext_lazy = lazy(ugettext, unicode) -string_concat = lazy(string_concat, unicode) def activate(language): return real_activate(language) @@ -108,3 +105,10 @@ def templatize(src): def deactivate_all(): return real_deactivate_all() +def string_concat(*strings): + """ + Lazy variant of string concatenation, needed for translations that are + constructed from multiple parts. + """ + return u''.join([force_unicode(s) for s in strings]) +string_concat = lazy(string_concat, unicode) diff --git a/django/utils/translation/trans_null.py b/django/utils/translation/trans_null.py index e3f89567a5..771a80e99c 100644 --- a/django/utils/translation/trans_null.py +++ b/django/utils/translation/trans_null.py @@ -13,7 +13,6 @@ ngettext_lazy = ngettext def ungettext(singular, plural, number): return force_unicode(ngettext(singular, plural, number)) -string_concat = lambda *strings: u''.join([force_unicode(el) for el in strings]) activate = lambda x: None deactivate = deactivate_all = install = lambda: None get_language = lambda: settings.LANGUAGE_CODE diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py index 5fff1ea63a..250af04e8c 100644 --- a/django/utils/translation/trans_real.py +++ b/django/utils/translation/trans_real.py @@ -516,9 +516,3 @@ def templatize(src): out.write(blankout(t.contents, 'X')) return out.getvalue() -def string_concat(*strings): - """" - Lazy variant of string concatenation, needed for translations that are - constructed from multiple parts. - """ - return u''.join([force_unicode(s) for s in strings]) diff --git a/django/views/static.py b/django/views/static.py index 3ec4ca14a1..e274057617 100644 --- a/django/views/static.py +++ b/django/views/static.py @@ -29,12 +29,12 @@ def serve(request, path, document_root=None, show_indexes=False): newpath = '' for part in path.split('/'): if not part: - # strip empty path components + # Strip empty path components. continue drive, part = os.path.splitdrive(part) head, part = os.path.split(part) if part in (os.curdir, os.pardir): - # strip '.' amd '..' in path + # Strip '.' and '..' in path. continue newpath = os.path.join(newpath, part).replace('\\', '/') if newpath and path != newpath: diff --git a/docs/databases.txt b/docs/databases.txt index 21ff4c7434..213c2d666c 100644 --- a/docs/databases.txt +++ b/docs/databases.txt @@ -163,3 +163,118 @@ storage engine, you have a couple of options. .. _AlterModelOnSyncDB: http://code.djangoproject.com/wiki/AlterModelOnSyncDB + +Oracle Notes +============ + +Django supports `Oracle Database Server`_ versions 9i and higher. Oracle +version 10g or later is required to use Django's ``regex`` and ``iregex`` query +operators. You will also need the `cx_Oracle`_ driver, version 4.3.1 or newer. + +.. _`Oracle Database Server`: http://www.oracle.com/ +.. _`cx_Oracle`: http://cx-oracle.sourceforge.net/ + +To run ``python manage.py syncdb``, you'll need to create an Oracle database +user with CREATE TABLE, CREATE SEQUENCE, CREATE PROCEDURE, and CREATE TRIGGER +privileges. To run Django's test suite, the user also needs +CREATE and DROP DATABASE and CREATE and DROP TABLESPACE privileges. + +Connecting to the Database +-------------------------- + +Your Django settings.py file should look something like this for Oracle:: + + DATABASE_ENGINE = 'oracle' + DATABASE_NAME = 'xe' + DATABASE_USER = 'a_user' + DATABASE_PASSWORD = 'a_password' + DATABASE_HOST = '' + DATABASE_PORT = '' + +If you don't use a ``tnsnames.ora`` file or a similar naming method that +recognizes the SID ("xe" in this example), then fill in both ``DATABASE_HOST`` +and ``DATABASE_PORT`` like so:: + + DATABASE_ENGINE = 'oracle' + DATABASE_NAME = 'xe' + DATABASE_USER = 'a_user' + DATABASE_PASSWORD = 'a_password' + DATABASE_HOST = 'dbprod01ned.mycompany.com' + DATABASE_PORT = '1540' + +You should supply both ``DATABASE_HOST`` and ``DATABASE_PORT``, or leave both +as empty strings. + +Tablespace Options +------------------ + +A common paradigm for optimizing performance in Oracle-based systems is the +use of `tablespaces`_ to organize disk layout. The Oracle backend supports +this use case by adding ``db_tablespace`` options to the ``Meta`` and +``Field`` classes. (When using a backend that lacks support for tablespaces, +these options are ignored.) + +.. _`tablespaces`: http://en.wikipedia.org/wiki/Tablespace + +A tablespace can be specified for the table(s) generated by a model by +supplying the ``db_tablespace`` option inside the model's ``Meta`` class. +Additionally, the ``db_tablespace`` option can be passed to a ``Field`` +constructor to specify an alternate tablespace for the ``Field``'s column +index. If no index would be created for the column, the ``db_tablespace`` +option is ignored. + +:: + + class TablespaceExample(models.Model): + name = models.CharField(maxlength=30, db_index=True, db_tablespace="indexes") + data = models.CharField(maxlength=255, db_index=True) + edges = models.ManyToManyField(to="self", db_tablespace="indexes") + + class Meta: + db_tablespace = "tables" + +In this example, the tables generated by the ``TablespaceExample`` model +(i.e., the model table and the many-to-many table) would be stored in the +``tables`` tablespace. The index for the name field and the indexes on the +many-to-many table would be stored in the ``indexes`` tablespace. The ``data`` +field would also generate an index, but no tablespace for it is specified, so +it would be stored in the model tablespace ``tables`` by default. + +Django does not create the tablespaces for you. Please refer to `Oracle's +documentation`_ for details on creating and managing tablespaces. + +.. _`Oracle's documentation`: http://download.oracle.com/docs/cd/B19306_01/server.102/b14200/statements_7003.htm#SQLRF01403 + +Naming Issues +------------- + +Oracle imposes a name length limit of 30 characters. To accommodate this, the +backend truncates database identifiers to fit, replacing the final four +characters of the truncated name with a repeatable MD5 hash value. + +NULL and Empty Strings +---------------------- + +Django generally prefers to use the empty string ('') rather than NULL, but +Oracle treats both identically. To get around this, the Oracle backend +coerces the ``null=True`` option on fields that permit the empty string as a +value. When fetching from the database, it is assumed that a NULL value in +one of these fields really means the empty string, and the data is silently +converted to reflect this assumption. + +TextField Limitations +--------------------- + +The Oracle backend stores ``TextFields`` as ``NCLOB`` columns. Oracle imposes +some limitations on the usage of such LOB columns in general: + + * LOB columns may not be used as primary keys. + + * LOB columns may not be used in indexes. + + * LOB columns may not be used in a ``SELECT DISTINCT`` list. This means that + attempting to use the ``QuerySet.distinct`` method on a model that + includes ``TextField`` columns will result in an error when run against + Oracle. A workaround to this is to keep ``TextField`` columns out of any + models that you foresee performing ``.distinct`` queries on, and to + include the ``TextField`` in a related model instead. diff --git a/docs/email.txt b/docs/email.txt index 2bad79ce33..effc5e24cf 100644 --- a/docs/email.txt +++ b/docs/email.txt @@ -317,7 +317,7 @@ To send a text and HTML combination, you could write:: subject, from_email, to = 'hello', 'from@example.com', 'to@example.com' text_content = 'This is an important message.' html_content = '

This is an important message.

' - msg = EmailMultiAlternatives(subject, text_content, from_email, to) + msg = EmailMultiAlternatives(subject, text_content, from_email, [to]) msg.attach_alternative(html_content, "text/html") msg.send() diff --git a/docs/install.txt b/docs/install.txt index 2de8529d24..95aa82b2e3 100644 --- a/docs/install.txt +++ b/docs/install.txt @@ -66,6 +66,7 @@ installed. * If you're using SQLite, you'll need pysqlite_. Use version 2.0.3 or higher. * If you're using Oracle, you'll need cx_Oracle_, version 4.3.1 or higher. + You will also want to read the database-specific notes for the `Oracle backend`_. If you plan to use Django's ``manage.py syncdb`` command to automatically create database tables for your models, you'll need to @@ -88,6 +89,7 @@ to create a temporary test database. .. _MySQL backend: ../databases/ .. _cx_Oracle: http://cx-oracle.sourceforge.net/ .. _Oracle: http://www.oracle.com/ +.. _Oracle backend: ../databases/#oracle-notes .. _testing framework: ../testing/ Remove any old versions of Django diff --git a/docs/newforms.txt b/docs/newforms.txt index 54bd6306c7..531c4fbb36 100644 --- a/docs/newforms.txt +++ b/docs/newforms.txt @@ -2388,9 +2388,9 @@ More coming soon ================ That's all the documentation for now. For more, see the file -http://code.djangoproject.com/browser/django/trunk/tests/regressiontests/forms/tests.py +http://code.djangoproject.com/browser/django/trunk/tests/regressiontests/forms -- the unit tests for ``django.newforms``. This can give you a good idea of -what's possible. +what's possible. (Each submodule there contains separate tests.) If you're really itching to learn and use this library, please be patient. We're working hard on finishing both the code and documentation. diff --git a/tests/modeltests/basic/models.py b/tests/modeltests/basic/models.py index 58770ef2ce..2f4b932fe0 100644 --- a/tests/modeltests/basic/models.py +++ b/tests/modeltests/basic/models.py @@ -152,7 +152,7 @@ TypeError: 'foo' is an invalid keyword argument for this function >>> a6 = Article(pub_date=datetime(2005, 7, 31)) >>> a6.save() >>> a6.headline -'Default headline' +u'Default headline' # For DateTimeFields, Django saves as much precision (in seconds) as you # give it. diff --git a/tests/modeltests/field_defaults/models.py b/tests/modeltests/field_defaults/models.py index 23c4733b8f..1132f1ca41 100644 --- a/tests/modeltests/field_defaults/models.py +++ b/tests/modeltests/field_defaults/models.py @@ -42,7 +42,7 @@ __test__ = {'API_TESTS':""" # Access database columns via Python attributes. >>> a.headline -'Default headline' +u'Default headline' # make sure the two dates are sufficiently close >>> d = now - a.pub_date diff --git a/tests/regressiontests/i18n/models.py b/tests/regressiontests/i18n/models.py index e69de29bb2..e1eefb8477 100644 --- a/tests/regressiontests/i18n/models.py +++ b/tests/regressiontests/i18n/models.py @@ -0,0 +1,12 @@ +from django.db import models +from django.utils.translation import ugettext_lazy as _ + +class TestModel(models.Model): + text = models.CharField(max_length=10, default=_('Anything')) + +__test__ = {'API_TESTS': ''' +>>> tm = TestModel() +>>> tm.save() +''' +} + diff --git a/tests/regressiontests/i18n/tests.py b/tests/regressiontests/i18n/tests.py index 8a7d2bee3e..99204451e4 100644 --- a/tests/regressiontests/i18n/tests.py +++ b/tests/regressiontests/i18n/tests.py @@ -30,4 +30,12 @@ True >>> s4 = ugettext_lazy('Some other string') >>> s == s4 False + +unicode(string_concat(...)) should not raise a TypeError - #4796 + +>>> import django.utils.translation +>>> reload(django.utils.translation) + +>>> unicode(django.utils.translation.string_concat("dja", "ngo")) +u'django' """