diff --git a/AUTHORS b/AUTHORS index 58179e111a..4ba21fe0d9 100644 --- a/AUTHORS +++ b/AUTHORS @@ -202,6 +202,7 @@ answer newbie questions, and generally made Django that much better: Kieran Holland Sung-Jin Hong Leo "hylje" Honkanen + Tareque Hossain Richard House Robert Rock Howard John Huddleston @@ -241,6 +242,7 @@ answer newbie questions, and generally made Django that much better: Igor Kolar Gasper Koren Martin Kosír + Arthur Koziel Meir Kriheli Bruce Kroeze krzysiek.pawlik@silvermedia.pl @@ -401,7 +403,7 @@ answer newbie questions, and generally made Django that much better: Vasiliy Stavenko Thomas Steinacher Johan C. Stöver - nowell strite + Nowell Strite Thomas Stromberg Pascal Varet SuperJared diff --git a/django/conf/locale/he/LC_MESSAGES/django.po b/django/conf/locale/he/LC_MESSAGES/django.po index 53b40132ba..ed048c76f2 100644 --- a/django/conf/locale/he/LC_MESSAGES/django.po +++ b/django/conf/locale/he/LC_MESSAGES/django.po @@ -391,7 +391,7 @@ msgstr "הוספת %s" #: contrib/admin/options.py:1003 #, python-format msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "הפריט %(name)s עם המקש %(key)r אינו קיים." +msgstr "הפריט %(name)s עם המפתח הראשי %(key)r אינו קיים." #: contrib/admin/options.py:860 #, python-format diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py index abcff14cd1..5f397ecb01 100644 --- a/django/contrib/admin/sites.py +++ b/django/contrib/admin/sites.py @@ -300,7 +300,7 @@ class AdminSite(object): user = authenticate(username=username, password=password) if user is None: message = ERROR_MESSAGE - if u'@' in username: + if username is not None and u'@' in username: # Mistakenly entered e-mail address instead of username? Look it up. try: user = User.objects.get(email=username) diff --git a/django/contrib/admin/templates/admin/edit_inline/tabular.html b/django/contrib/admin/templates/admin/edit_inline/tabular.html index ecc14a46d3..211573ebfc 100644 --- a/django/contrib/admin/templates/admin/edit_inline/tabular.html +++ b/django/contrib/admin/templates/admin/edit_inline/tabular.html @@ -15,6 +15,7 @@ {% if inline_admin_formset.formset.can_delete %}{% trans "Delete?" %}{% endif %} + {% for inline_admin_form in inline_admin_formset %} {% if inline_admin_form.form.non_field_errors %} {{ inline_admin_form.form.non_field_errors }} @@ -57,7 +58,7 @@ {% endfor %} - + diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py index d97c36a207..9a4ce3b266 100644 --- a/django/contrib/admin/templatetags/admin_list.py +++ b/django/contrib/admin/templatetags/admin_list.py @@ -22,7 +22,7 @@ def paginator_number(cl,i): elif i == cl.page_num: return mark_safe(u'%d ' % (i+1)) else: - return mark_safe(u'%d ' % (cl.get_query_string({PAGE_VAR: i}), (i == cl.paginator.num_pages-1 and ' class="end"' or ''), i+1)) + return mark_safe(u'%d ' % (escape(cl.get_query_string({PAGE_VAR: i})), (i == cl.paginator.num_pages-1 and ' class="end"' or ''), i+1)) paginator_number = register.simple_tag(paginator_number) def pagination(cl): @@ -265,7 +265,7 @@ def date_hierarchy(cl): day_lookup = cl.params.get(day_field) year_month_format, month_day_format = get_partial_date_formats() - link = lambda d: mark_safe(cl.get_query_string(d, [field_generic])) + link = lambda d: cl.get_query_string(d, [field_generic]) if year_lookup and month_lookup and day_lookup: day = datetime.date(int(year_lookup), int(month_lookup), int(day_lookup)) diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py index eacea44a31..fb5acb5295 100644 --- a/django/contrib/admin/widgets.py +++ b/django/contrib/admin/widgets.py @@ -7,6 +7,7 @@ import copy from django import forms from django.forms.widgets import RadioFieldRenderer from django.forms.util import flatatt +from django.utils.html import escape from django.utils.text import truncate_words from django.utils.translation import ugettext as _ from django.utils.safestring import mark_safe @@ -148,7 +149,7 @@ class ForeignKeyRawIdWidget(forms.TextInput): def label_for_value(self, value): key = self.rel.get_related_field().name obj = self.rel.to._default_manager.get(**{key: value}) - return ' %s' % truncate_words(obj, 14) + return ' %s' % escape(truncate_words(obj, 14)) class ManyToManyRawIdWidget(ForeignKeyRawIdWidget): """ diff --git a/django/contrib/auth/decorators.py b/django/contrib/auth/decorators.py index b80aca10a3..798e596978 100644 --- a/django/contrib/auth/decorators.py +++ b/django/contrib/auth/decorators.py @@ -1,11 +1,12 @@ try: - from functools import update_wrapper + from functools import update_wrapper, wraps except ImportError: - from django.utils.functional import update_wrapper # Python 2.3, 2.4 fallback. + from django.utils.functional import update_wrapper, wraps # Python 2.3, 2.4 fallback. from django.contrib.auth import REDIRECT_FIELD_NAME from django.http import HttpResponseRedirect from django.utils.http import urlquote +from django.utils.decorators import auto_adapt_to_methods def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME): """ @@ -13,9 +14,19 @@ def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIE 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. """ - def decorate(view_func): - return _CheckLogin(view_func, test_func, login_url, redirect_field_name) - return decorate + if not login_url: + from django.conf import settings + login_url = settings.LOGIN_URL + + def decorator(view_func): + def _wrapped_view(request, *args, **kwargs): + if test_func(request.user): + return view_func(request, *args, **kwargs) + path = urlquote(request.get_full_path()) + tup = login_url, redirect_field_name, path + return HttpResponseRedirect('%s?%s=%s' % tup) + return wraps(view_func)(_wrapped_view) + return auto_adapt_to_methods(decorator) def login_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME): """ @@ -36,46 +47,3 @@ def permission_required(perm, login_url=None): enabled, redirecting to the log-in page if necessary. """ return user_passes_test(lambda u: u.has_perm(perm), login_url=login_url) - -class _CheckLogin(object): - """ - Class that checks that the user passes the given test, redirecting to - the log-in page if necessary. If the test is passed, the view function - is invoked. The test should be a callable that takes the user object - and returns True if the user passes. - - We use a class here so that we can define __get__. This way, when a - _CheckLogin object is used as a method decorator, the view function - is properly bound to its instance. - """ - def __init__(self, view_func, test_func, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME): - if not login_url: - from django.conf import settings - login_url = settings.LOGIN_URL - self.view_func = view_func - self.test_func = test_func - self.login_url = login_url - self.redirect_field_name = redirect_field_name - - # We can't blindly apply update_wrapper because it udpates __dict__ and - # if the view function is already a _CheckLogin object then - # self.test_func and friends will get stomped. However, we also can't - # *not* update the wrapper's dict because then view function attributes - # don't get updated into the wrapper. So we need to split the - # difference: don't let update_wrapper update __dict__, but then update - # the (parts of) __dict__ that we care about ourselves. - update_wrapper(self, view_func, updated=()) - for k in view_func.__dict__: - if k not in self.__dict__: - self.__dict__[k] = view_func.__dict__[k] - - def __get__(self, obj, cls=None): - view_func = self.view_func.__get__(obj, cls) - return _CheckLogin(view_func, self.test_func, self.login_url, self.redirect_field_name) - - def __call__(self, request, *args, **kwargs): - if self.test_func(request.user): - return self.view_func(request, *args, **kwargs) - path = urlquote(request.get_full_path()) - tup = self.login_url, self.redirect_field_name, path - return HttpResponseRedirect('%s?%s=%s' % tup) diff --git a/django/contrib/auth/tests/__init__.py b/django/contrib/auth/tests/__init__.py index 9a381ef93d..14428d0fc8 100644 --- a/django/contrib/auth/tests/__init__.py +++ b/django/contrib/auth/tests/__init__.py @@ -10,10 +10,6 @@ from django.contrib.auth.tests.tokens import TOKEN_GENERATOR_TESTS __test__ = { 'BASIC_TESTS': BASIC_TESTS, - 'PASSWORDRESET_TESTS': PasswordResetTest, 'FORM_TESTS': FORM_TESTS, 'TOKEN_GENERATOR_TESTS': TOKEN_GENERATOR_TESTS, - 'CHANGEPASSWORD_TESTS': ChangePasswordTest, - 'LOGIN_TESTS': LoginTest, - 'LOGOUT_TESTS': LogoutTest, } diff --git a/django/contrib/comments/admin.py b/django/contrib/comments/admin.py index 3b1fb14bcc..c2f8e564f4 100644 --- a/django/contrib/comments/admin.py +++ b/django/contrib/comments/admin.py @@ -20,6 +20,7 @@ class CommentsAdmin(admin.ModelAdmin): list_filter = ('submit_date', 'site', 'is_public', 'is_removed') date_hierarchy = 'submit_date' ordering = ('-submit_date',) + raw_id_fields = ('user',) search_fields = ('comment', 'user__username', 'user_name', 'user_email', 'user_url', 'ip_address') # Only register the default admin if the model is the built-in comment model diff --git a/django/contrib/gis/db/models/sql/aggregates.py b/django/contrib/gis/db/models/sql/aggregates.py index 370182c721..b534288891 100644 --- a/django/contrib/gis/db/models/sql/aggregates.py +++ b/django/contrib/gis/db/models/sql/aggregates.py @@ -16,7 +16,7 @@ def convert_geom(wkt, geo_field): if SpatialBackend.postgis: def convert_extent(box): - # Box text will be something like "BOX(-90.0 30.0, -85.0 40.0)"; + # Box text will be something like "BOX(-90.0 30.0, -85.0 40.0)"; # parsing out and returning as a 4-tuple. ll, ur = box[4:-1].split(',') xmin, ymin = map(float, ll.split()) @@ -32,19 +32,28 @@ elif SpatialBackend.oracle: def convert_extent(clob): if clob: - # Oracle returns a polygon for the extent, we construct - # the 4-tuple from the coordinates in the polygon. - poly = SpatialBackend.Geometry(clob.read()) - shell = poly.shell - ll, ur = shell[0], shell[2] + # Generally, Oracle returns a polygon for the extent -- however, + # it can return a single point if there's only one Point in the + # table. + ext_geom = SpatialBackend.Geometry(clob.read()) + gtype = str(ext_geom.geom_type) + if gtype == 'Polygon': + # Construct the 4-tuple from the coordinates in the polygon. + shell = ext_geom.shell + ll, ur = shell[0][:2], shell[2][:2] + elif gtype == 'Point': + ll = ext_geom.coords[:2] + ur = ll + else: + raise Exception('Unexpected geometry type returned for extent: %s' % gtype) xmin, ymin = ll xmax, ymax = ur return (xmin, ymin, xmax, ymax) else: return None - + def convert_geom(clob, geo_field): - if clob: + if clob: return SpatialBackend.Geometry(clob.read(), geo_field.srid) else: return None @@ -73,7 +82,7 @@ class GeoAggregate(Aggregate): self.extra.setdefault('tolerance', 0.05) # Can't use geographic aggregates on non-geometry fields. - if not isinstance(self.source, GeometryField): + if not isinstance(self.source, GeometryField): raise ValueError('Geospatial aggregates only allowed on geometry fields.') # Making sure the SQL function is available for this spatial backend. @@ -87,7 +96,7 @@ class Collect(GeoAggregate): class Extent(GeoAggregate): is_extent = True sql_function = SpatialBackend.extent - + if SpatialBackend.oracle: # Have to change Extent's attributes here for Oracle. Extent.conversion_class = GeomField diff --git a/django/contrib/gis/management/commands/inspectdb.py b/django/contrib/gis/management/commands/inspectdb.py index d4fe210953..365bb24063 100644 --- a/django/contrib/gis/management/commands/inspectdb.py +++ b/django/contrib/gis/management/commands/inspectdb.py @@ -131,7 +131,7 @@ class Command(InspectCommand): if srid != 4326: extra_params['srid'] = srid else: try: - field_type = connection.introspection.data_types_reverse[row[1]] + field_type = connection.introspection.get_field_type(row[1], row) except KeyError: field_type = 'TextField' comment_notes.append('This field type is a guess.') diff --git a/django/contrib/gis/shortcuts.py b/django/contrib/gis/shortcuts.py index e62e2b6bc8..a6fb8927d0 100644 --- a/django/contrib/gis/shortcuts.py +++ b/django/contrib/gis/shortcuts.py @@ -1,4 +1,5 @@ import cStringIO, zipfile +from django.conf import settings from django.http import HttpResponse from django.template import loader @@ -6,7 +7,7 @@ def compress_kml(kml): "Returns compressed KMZ from the given KML string." kmz = cStringIO.StringIO() zf = zipfile.ZipFile(kmz, 'a', zipfile.ZIP_DEFLATED) - zf.writestr('doc.kml', kml) + zf.writestr('doc.kml', kml.encode(settings.DEFAULT_CHARSET)) zf.close() kmz.seek(0) return kmz.read() diff --git a/django/contrib/gis/tests/geoapp/test_regress.py b/django/contrib/gis/tests/geoapp/test_regress.py index ef8d44d988..043dace769 100644 --- a/django/contrib/gis/tests/geoapp/test_regress.py +++ b/django/contrib/gis/tests/geoapp/test_regress.py @@ -1,6 +1,7 @@ import os, unittest from django.contrib.gis.db.backend import SpatialBackend -from django.contrib.gis.tests.utils import no_mysql, no_oracle, no_postgis +from django.contrib.gis.tests.utils import no_mysql, no_oracle, no_postgis, no_spatialite +from django.contrib.gis.shortcuts import render_to_kmz from models import City class GeoRegressionTests(unittest.TestCase): @@ -16,3 +17,19 @@ class GeoRegressionTests(unittest.TestCase): self.assertEqual(pnt, City.objects.get(name='Pueblo').point) City.objects.filter(name='Pueblo').update(point=bak) self.assertEqual(bak, City.objects.get(name='Pueblo').point) + + def test02_kmz(self): + "Testing `render_to_kmz` with non-ASCII data, see #11624." + name = '\xc3\x85land Islands'.decode('iso-8859-1') + places = [{'name' : name, + 'description' : name, + 'kml' : '5.0,23.0' + }] + kmz = render_to_kmz('gis/kml/placemarks.kml', {'places' : places}) + + @no_spatialite + def test03_extent(self): + "Testing `extent` on a table with a single point, see #11827." + pnt = City.objects.get(name='Pueblo').point + ref_ext = (pnt.x, pnt.y, pnt.x, pnt.y) + self.assertEqual(ref_ext, City.objects.filter(name='Pueblo').extent()) diff --git a/django/contrib/gis/tests/relatedapp/tests.py b/django/contrib/gis/tests/relatedapp/tests.py index 2bdf29304d..0e905274d4 100644 --- a/django/contrib/gis/tests/relatedapp/tests.py +++ b/django/contrib/gis/tests/relatedapp/tests.py @@ -279,11 +279,11 @@ class RelatedGeoModelTest(unittest.TestCase): 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") + # 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'] @@ -293,7 +293,6 @@ class RelatedGeoModelTest(unittest.TestCase): self.assertEqual(4, len(coll)) self.assertEqual(ref_geom, coll) - # TODO: Related tests for KML, GML, and distance lookups. def suite(): diff --git a/django/contrib/localflavor/fr/forms.py b/django/contrib/localflavor/fr/forms.py index 8d9d9d7771..4cd84102a8 100644 --- a/django/contrib/localflavor/fr/forms.py +++ b/django/contrib/localflavor/fr/forms.py @@ -27,7 +27,7 @@ class FRPhoneNumberField(Field): '0X XX XX XX XX'. """ default_error_messages = { - 'invalid': u'Phone numbers must be in 0X XX XX XX XX format.', + 'invalid': _('Phone numbers must be in 0X XX XX XX XX format.'), } def clean(self, value): diff --git a/django/core/handlers/modpython.py b/django/core/handlers/modpython.py index c6dcf23e9a..b1e3e17227 100644 --- a/django/core/handlers/modpython.py +++ b/django/core/handlers/modpython.py @@ -134,8 +134,8 @@ class ModPythonRequest(http.HttpRequest): if not hasattr(self, '_meta'): self._meta = { 'AUTH_TYPE': self._req.ap_auth_type, - 'CONTENT_LENGTH': self._req.clength, # This may be wrong - 'CONTENT_TYPE': self._req.content_type, # This may be wrong + 'CONTENT_LENGTH': self._req.headers_in.get('content-length', 0), + 'CONTENT_TYPE': self._req.headers_in.get('content-type'), 'GATEWAY_INTERFACE': 'CGI/1.1', 'PATH_INFO': self.path_info, 'PATH_TRANSLATED': None, # Not supported diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py index 026e426862..60dcf727e4 100644 --- a/django/core/management/__init__.py +++ b/django/core/management/__init__.py @@ -261,6 +261,82 @@ class ManagementUtility(object): sys.exit(1) return klass + def autocomplete(self): + """ + Output completion suggestions for BASH. + + The output of this function is passed to BASH's `COMREPLY` variable and + treated as completion suggestions. `COMREPLY` expects a space + separated string as the result. + + The `COMP_WORDS` and `COMP_CWORD` BASH environment variables are used + to get information about the cli input. Please refer to the BASH + man-page for more information about this variables. + + Subcommand options are saved as pairs. A pair consists of + the long option string (e.g. '--exclude') and a boolean + value indicating if the option requires arguments. When printing to + stdout, a equal sign is appended to options which require arguments. + + Note: If debugging this function, it is recommended to write the debug + output in a separate file. Otherwise the debug output will be treated + and formatted as potential completion suggestions. + """ + # Don't complete if user hasn't sourced bash_completion file. + if not os.environ.has_key('DJANGO_AUTO_COMPLETE'): + return + + cwords = os.environ['COMP_WORDS'].split()[1:] + cword = int(os.environ['COMP_CWORD']) + + try: + curr = cwords[cword-1] + except IndexError: + curr = '' + + subcommands = get_commands().keys() + ['help'] + options = [('--help', None)] + + # subcommand + if cword == 1: + print ' '.join(filter(lambda x: x.startswith(curr), subcommands)) + # subcommand options + # special case: the 'help' subcommand has no options + elif cwords[0] in subcommands and cwords[0] != 'help': + subcommand_cls = self.fetch_command(cwords[0]) + # special case: 'runfcgi' stores additional options as + # 'key=value' pairs + if cwords[0] == 'runfcgi': + from django.core.servers.fastcgi import FASTCGI_OPTIONS + options += [(k, 1) for k in FASTCGI_OPTIONS] + # special case: add the names of installed apps to options + elif cwords[0] in ('dumpdata', 'reset', 'sql', 'sqlall', + 'sqlclear', 'sqlcustom', 'sqlindexes', + 'sqlreset', 'sqlsequencereset', 'test'): + try: + from django.conf import settings + # Get the last part of the dotted path as the app name. + options += [(a.split('.')[-1], 0) for a in settings.INSTALLED_APPS] + except ImportError: + # Fail silently if DJANGO_SETTINGS_MODULE isn't set. The + # user will find out once they execute the command. + pass + options += [(s_opt.get_opt_string(), s_opt.nargs) for s_opt in + subcommand_cls.option_list] + # filter out previously specified options from available options + prev_opts = [x.split('=')[0] for x in cwords[1:cword-1]] + options = filter(lambda (x, v): x not in prev_opts, options) + + # filter options by current input + options = [(k, v) for k, v in options if k.startswith(curr)] + for option in options: + opt_label = option[0] + # append '=' to options which require args + if option[1]: + opt_label += '=' + print opt_label + sys.exit(1) + def execute(self): """ Given the command-line arguments, this figures out which subcommand is @@ -272,6 +348,7 @@ class ManagementUtility(object): parser = LaxOptionParser(usage="%prog subcommand [options] [args]", version=get_version(), option_list=BaseCommand.option_list) + self.autocomplete() try: options, args = parser.parse_args(self.argv) handle_default_options(options) diff --git a/django/core/management/base.py b/django/core/management/base.py index b5efdbc108..76b9c0967c 100644 --- a/django/core/management/base.py +++ b/django/core/management/base.py @@ -398,7 +398,9 @@ def copy_helper(style, app_or_project, name, directory, other_name=''): if subdir.startswith('.'): del subdirs[i] for f in files: - if f.endswith('.pyc'): + if not f.endswith('.py'): + # Ignore .pyc, .pyo, .py.class etc, as they cause various + # breakages. continue path_old = os.path.join(d, f) path_new = os.path.join(top_dir, relative_dir, f.replace('%s_name' % app_or_project, name)) diff --git a/django/core/management/commands/inspectdb.py b/django/core/management/commands/inspectdb.py index 3fb50b6119..8592ecb130 100644 --- a/django/core/management/commands/inspectdb.py +++ b/django/core/management/commands/inspectdb.py @@ -82,7 +82,7 @@ class Command(NoArgsCommand): extra_params['db_column'] = column_name else: try: - field_type = connection.introspection.data_types_reverse[row[1]] + field_type = connection.introspection.get_field_type(row[1], row) except KeyError: field_type = 'TextField' comment_notes.append('This field type is a guess.') diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py index 9639a982df..ae9ed3bcea 100644 --- a/django/core/urlresolvers.py +++ b/django/core/urlresolvers.py @@ -287,8 +287,17 @@ class RegexURLResolver(object): candidate = result % unicode_kwargs if re.search(u'^%s' % pattern, candidate, re.UNICODE): return candidate + # lookup_view can be URL label, or dotted path, or callable, Any of + # these can be passed in at the top, but callables are not friendly in + # error messages. + m = getattr(lookup_view, '__module__', None) + n = getattr(lookup_view, '__name__', None) + if m is not None and n is not None: + lookup_view_s = "%s.%s" % (m, n) + else: + lookup_view_s = lookup_view raise NoReverseMatch("Reverse for '%s' with arguments '%s' and keyword " - "arguments '%s' not found." % (lookup_view, args, kwargs)) + "arguments '%s' not found." % (lookup_view_s, args, kwargs)) def resolve(path, urlconf=None): return get_resolver(urlconf).resolve(path) diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index 8bf29032c1..4aa04da321 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -480,6 +480,14 @@ class BaseDatabaseIntrospection(object): def __init__(self, connection): self.connection = connection + def get_field_type(self, data_type, description): + """Hook for a database backend to use the cursor description to + match a Django field type to a database column. + + For Oracle, the column data_type on its own is insufficient to + distinguish between a FloatField and IntegerField, for example.""" + return self.data_types_reverse[data_type] + def table_name_converter(self, name): """Apply a conversion to the name for the purposes of comparison. diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index dd29e4c766..29e0ff04a3 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -36,6 +36,14 @@ DatabaseError = Database.DatabaseError IntegrityError = Database.IntegrityError +# Check whether cx_Oracle was compiled with the WITH_UNICODE option. This will +# also be True in Python 3.0. +if int(Database.version.split('.', 1)[0]) >= 5 and not hasattr(Database, 'UNICODE'): + convert_unicode = force_unicode +else: + convert_unicode = smart_str + + class DatabaseFeatures(BaseDatabaseFeatures): empty_fetchmany_value = () needs_datetime_string_cast = False @@ -176,10 +184,10 @@ WHEN (new.%(col_name)s IS NULL) return "RETURNING %s INTO %%s", (InsertIdVar(),) def savepoint_create_sql(self, sid): - return "SAVEPOINT " + self.quote_name(sid) + return convert_unicode("SAVEPOINT " + self.quote_name(sid)) def savepoint_rollback_sql(self, sid): - return "ROLLBACK TO SAVEPOINT " + self.quote_name(sid) + return convert_unicode("ROLLBACK TO SAVEPOINT " + self.quote_name(sid)) def sql_flush(self, style, tables, sequences): # Return a list of 'TRUNCATE x;', 'TRUNCATE y;', @@ -310,7 +318,7 @@ class DatabaseWrapper(BaseDatabaseWrapper): def _cursor(self): cursor = None if not self._valid_connection(): - conn_string = self._connect_string() + conn_string = convert_unicode(self._connect_string()) self.connection = Database.connect(conn_string, **self.settings_dict['DATABASE_OPTIONS']) cursor = FormatStylePlaceholderCursor(self.connection) # Set oracle date to ansi date format. This only needs to execute @@ -361,7 +369,8 @@ class OracleParam(object): if hasattr(param, 'bind_parameter'): self.smart_str = param.bind_parameter(cursor) else: - self.smart_str = smart_str(param, cursor.charset, strings_only) + self.smart_str = convert_unicode(param, cursor.charset, + strings_only) if hasattr(param, 'input_size'): # If parameter has `input_size` attribute, use that. self.input_size = param.input_size @@ -429,7 +438,7 @@ class FormatStylePlaceholderCursor(object): # is being passed to SQL*Plus. if query.endswith(';') or query.endswith('/'): query = query[:-1] - query = smart_str(query, self.charset) % tuple(args) + query = convert_unicode(query % tuple(args), self.charset) self._guess_input_sizes([params]) try: return self.cursor.execute(query, self._param_generator(params)) @@ -451,7 +460,7 @@ class FormatStylePlaceholderCursor(object): # is being passed to SQL*Plus. if query.endswith(';') or query.endswith('/'): query = query[:-1] - query = smart_str(query, self.charset) % tuple(args) + query = convert_unicode(query % tuple(args), self.charset) formatted = [self._format_params(i) for i in params] self._guess_input_sizes(formatted) try: diff --git a/django/db/backends/oracle/introspection.py b/django/db/backends/oracle/introspection.py index 543e84a8f3..0b4f61a360 100644 --- a/django/db/backends/oracle/introspection.py +++ b/django/db/backends/oracle/introspection.py @@ -26,6 +26,14 @@ class DatabaseIntrospection(BaseDatabaseIntrospection): except AttributeError: pass + def get_field_type(self, data_type, description): + # If it's a NUMBER with scale == 0, consider it an IntegerField + if data_type == cx_Oracle.NUMBER and description[5] == 0: + return 'IntegerField' + else: + return super(DatabaseIntrospection, self).get_field_type( + data_type, description) + def get_table_list(self, cursor): "Returns a list of table names in the current database." cursor.execute("SELECT TABLE_NAME FROM USER_TABLES") diff --git a/django/db/models/sql/expressions.py b/django/db/models/sql/expressions.py index b7f1cf29b5..3309710c6c 100644 --- a/django/db/models/sql/expressions.py +++ b/django/db/models/sql/expressions.py @@ -62,7 +62,7 @@ class SQLEvaluator(object): else: sql, params = '%s', (child,) - if hasattr(child, 'children') > 1: + if len(getattr(child, 'children', [])) > 1: format = '(%s)' else: format = '%s' diff --git a/django/forms/fields.py b/django/forms/fields.py index affe3879b9..0aef355d0f 100644 --- a/django/forms/fields.py +++ b/django/forms/fields.py @@ -29,7 +29,6 @@ from django.utils.encoding import smart_unicode, smart_str from util import ErrorList, ValidationError from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateInput, DateTimeInput, TimeInput, SplitDateTimeWidget, SplitHiddenDateTimeWidget -from django.core.files.uploadedfile import SimpleUploadedFile as UploadedFile __all__ = ( 'Field', 'CharField', 'IntegerField', @@ -422,7 +421,7 @@ class RegexField(CharField): email_re = re.compile( r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # dot-atom r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string - r')@(?:[A-Z0-9]+(?:-*[A-Z0-9]+)*\.)+[A-Z]{2,6}$', re.IGNORECASE) # domain + r')@(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?$', re.IGNORECASE) # domain class EmailField(RegexField): default_error_messages = { @@ -533,7 +532,7 @@ class ImageField(FileField): url_re = re.compile( r'^https?://' # http:// or https:// - r'(?:(?:[A-Z0-9]+(?:-*[A-Z0-9]+)*\.)+[A-Z]{2,6}|' #domain... + r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|' #domain... r'localhost|' #localhost... r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip r'(?::\d+)?' # optional port diff --git a/django/forms/forms.py b/django/forms/forms.py index 1b9faa7e4d..0b7c2e2338 100644 --- a/django/forms/forms.py +++ b/django/forms/forms.py @@ -243,13 +243,13 @@ class BaseForm(StrAndUnicode): value = getattr(self, 'clean_%s' % name)() self.cleaned_data[name] = value except ValidationError, e: - self._errors[name] = e.messages + self._errors[name] = self.error_class(e.messages) if name in self.cleaned_data: del self.cleaned_data[name] try: self.cleaned_data = self.clean() except ValidationError, e: - self._errors[NON_FIELD_ERRORS] = e.messages + self._errors[NON_FIELD_ERRORS] = self.error_class(e.messages) if self._errors: delattr(self, 'cleaned_data') diff --git a/django/forms/widgets.py b/django/forms/widgets.py index cecafa880b..b1d2cb7cda 100644 --- a/django/forms/widgets.py +++ b/django/forms/widgets.py @@ -275,9 +275,10 @@ class FileInput(Input): class Textarea(Widget): def __init__(self, attrs=None): # The 'rows' and 'cols' attributes are required for HTML correctness. - self.attrs = {'cols': '40', 'rows': '10'} + default_attrs = {'cols': '40', 'rows': '10'} if attrs: - self.attrs.update(attrs) + default_attrs.update(attrs) + super(Textarea, self).__init__(default_attrs) def render(self, name, value, attrs=None): if value is None: value = '' diff --git a/django/utils/datastructures.py b/django/utils/datastructures.py index ce2424065d..85cdd443f8 100644 --- a/django/utils/datastructures.py +++ b/django/utils/datastructures.py @@ -117,7 +117,7 @@ class SortedDict(dict): return iter(self.keyOrder) def values(self): - return [super(SortedDict, self).__getitem__(k) for k in self.keyOrder] + return map(super(SortedDict, self).__getitem__, self.keyOrder) def itervalues(self): for key in self.keyOrder: diff --git a/django/utils/decorators.py b/django/utils/decorators.py index 8fc4c1d96a..4636a2d040 100644 --- a/django/utils/decorators.py +++ b/django/utils/decorators.py @@ -2,60 +2,86 @@ import types try: - from functools import wraps + from functools import wraps, update_wrapper except ImportError: - from django.utils.functional import wraps # Python 2.3, 2.4 fallback. + from django.utils.functional import wraps, update_wrapper # Python 2.3, 2.4 fallback. + +class MethodDecoratorAdaptor(object): + """ + Generic way of creating decorators that adapt to being + used on methods + """ + def __init__(self, decorator, func): + update_wrapper(self, func) + # NB: update the __dict__ first, *then* set + # our own .func and .decorator, in case 'func' is actually + # another MethodDecoratorAdaptor object, which has its + # 'func' and 'decorator' attributes in its own __dict__ + self.decorator = decorator + self.func = func + def __call__(self, *args, **kwargs): + return self.decorator(self.func)(*args, **kwargs) + def __get__(self, instance, owner): + return self.decorator(self.func.__get__(instance, owner)) + +def auto_adapt_to_methods(decorator): + """ + Takes a decorator function, and returns a decorator-like callable that can + be used on methods as well as functions. + """ + def adapt(func): + return MethodDecoratorAdaptor(decorator, func) + return wraps(decorator)(adapt) + +def decorator_from_middleware_with_args(middleware_class): + """ + Like decorator_from_middleware, but returns a function + that accepts the arguments to be passed to the middleware_class. + Use like:: + + cache_page = decorator_from_middleware_with_args(CacheMiddleware) + # ... + + @cache_page(3600) + def my_view(request): + # ... + """ + return make_middleware_decorator(middleware_class) def decorator_from_middleware(middleware_class): """ Given a middleware class (not an instance), returns a view decorator. This - lets you use middleware functionality on a per-view basis. + lets you use middleware functionality on a per-view basis. The middleware + is created with no params passed. """ - def _decorator_from_middleware(*args, **kwargs): - # For historical reasons, these "decorators" are also called as - # dec(func, *args) instead of dec(*args)(func). We handle both forms - # for backwards compatibility. - has_func = True - try: - view_func = kwargs.pop('view_func') - except KeyError: - if len(args): - view_func, args = args[0], args[1:] - else: - has_func = False - if not (has_func and isinstance(view_func, types.FunctionType)): - # We are being called as a decorator. - if has_func: - args = (view_func,) + args - middleware = middleware_class(*args, **kwargs) + return make_middleware_decorator(middleware_class)() - def decorator_func(fn): - return _decorator_from_middleware(fn, *args, **kwargs) - return decorator_func - - middleware = middleware_class(*args, **kwargs) - - def _wrapped_view(request, *args, **kwargs): - if hasattr(middleware, 'process_request'): - result = middleware.process_request(request) - if result is not None: - return result - if hasattr(middleware, 'process_view'): - result = middleware.process_view(request, view_func, args, kwargs) - if result is not None: - return result - try: - response = view_func(request, *args, **kwargs) - except Exception, e: - if hasattr(middleware, 'process_exception'): - result = middleware.process_exception(request, e) +def make_middleware_decorator(middleware_class): + def _make_decorator(*m_args, **m_kwargs): + middleware = middleware_class(*m_args, **m_kwargs) + def _decorator(view_func): + def _wrapped_view(request, *args, **kwargs): + if hasattr(middleware, 'process_request'): + result = middleware.process_request(request) if result is not None: return result - raise - if hasattr(middleware, 'process_response'): - result = middleware.process_response(request, response) - if result is not None: - return result - return response - return wraps(view_func)(_wrapped_view) - return _decorator_from_middleware + if hasattr(middleware, 'process_view'): + result = middleware.process_view(request, view_func, args, kwargs) + if result is not None: + return result + try: + response = view_func(request, *args, **kwargs) + except Exception, e: + if hasattr(middleware, 'process_exception'): + result = middleware.process_exception(request, e) + if result is not None: + return result + raise + if hasattr(middleware, 'process_response'): + result = middleware.process_response(request, response) + if result is not None: + return result + return response + return wraps(view_func)(_wrapped_view) + return auto_adapt_to_methods(_decorator) + return _make_decorator diff --git a/django/views/decorators/cache.py b/django/views/decorators/cache.py index 8b620c1345..f051f096a0 100644 --- a/django/views/decorators/cache.py +++ b/django/views/decorators/cache.py @@ -16,11 +16,37 @@ try: except ImportError: from django.utils.functional import wraps # Python 2.3, 2.4 fallback. -from django.utils.decorators import decorator_from_middleware +from django.utils.decorators import decorator_from_middleware_with_args, auto_adapt_to_methods from django.utils.cache import patch_cache_control, add_never_cache_headers from django.middleware.cache import CacheMiddleware -cache_page = decorator_from_middleware(CacheMiddleware) +def cache_page(*args, **kwargs): + # We need backwards compatibility with code which spells it this way: + # def my_view(): pass + # my_view = cache_page(my_view, 123) + # and this way: + # my_view = cache_page(123)(my_view) + # and this: + # my_view = cache_page(my_view, 123, key_prefix="foo") + # and this: + # my_view = cache_page(123, key_prefix="foo")(my_view) + # and possibly this way (?): + # my_view = cache_page(123, my_view) + + # We also add some asserts to give better error messages in case people are + # using other ways to call cache_page that no longer work. + key_prefix = kwargs.pop('key_prefix', None) + assert not kwargs, "The only keyword argument accepted is key_prefix" + if len(args) > 1: + assert len(args) == 2, "cache_page accepts at most 2 arguments" + if callable(args[0]): + return decorator_from_middleware_with_args(CacheMiddleware)(cache_timeout=args[1], key_prefix=key_prefix)(args[0]) + elif callable(args[1]): + return decorator_from_middleware_with_args(CacheMiddleware)(cache_timeout=args[0], key_prefix=key_prefix)(args[1]) + else: + assert False, "cache_page must be passed either a single argument (timeout) or a view function and a timeout" + else: + return decorator_from_middleware_with_args(CacheMiddleware)(cache_timeout=args[0], key_prefix=key_prefix) def cache_control(**kwargs): @@ -33,7 +59,7 @@ def cache_control(**kwargs): return wraps(viewfunc)(_cache_controlled) - return _cache_controller + return auto_adapt_to_methods(_cache_controller) def never_cache(view_func): """ @@ -45,3 +71,4 @@ def never_cache(view_func): add_never_cache_headers(response) return response return wraps(view_func)(_wrapped_view_func) +never_cache = auto_adapt_to_methods(never_cache) diff --git a/docs/howto/static-files.txt b/docs/howto/static-files.txt index 43062bebb6..9e6511a743 100644 --- a/docs/howto/static-files.txt +++ b/docs/howto/static-files.txt @@ -126,7 +126,7 @@ Here's the default ``static/directory_index.html`` template: listings. The template that was loaded had to be called ``static/directory_listing`` (with no ``.html`` extension). For backwards compatibility with earlier versions, Django will still load templates with - the older (no extension) name, but it will prefer a the + the older (no extension) name, but it will prefer the ``directory_index.html`` version. Limiting use to DEBUG=True diff --git a/docs/internals/index.txt b/docs/internals/index.txt index 1cbbb87f06..4f9007705e 100644 --- a/docs/internals/index.txt +++ b/docs/internals/index.txt @@ -23,3 +23,4 @@ the hood". committers release-process deprecation + svn diff --git a/docs/internals/svn.txt b/docs/internals/svn.txt index 7d010a7cfa..372fbd1202 100644 --- a/docs/internals/svn.txt +++ b/docs/internals/svn.txt @@ -114,14 +114,9 @@ not; in either case there comes a time when the branch is no longer being actively worked on by any developer. At this point the branch is considered closed. -Unfortunately, Subversion has no standard way of indicating -this. Generally, you can recognize a dead branch by viewing it through -the web interface, which lists the date of the most recent change to -the branch. Branches which have gone more than a month or two with no -activity can usually be assumed to be closed. In the future, the -layout of branches in the repository may be rearranged to make it -easier to tell which branches are still active (e.g., by moving closed -or abandoned branches into the ``django/branches/attic`` directory). +Unfortunately, Subversion has no standard way of indicating this. As a +workaround, branches of Django which are closed and no longer +maintained are moved into the directory ``django/branches/attic``. For reference, the following are branches whose code eventually became part of Django itself, and so are no longer separately maintained: @@ -184,6 +179,9 @@ were never finished: * ``sqlalchemy`` +All of the above-mentioned branches now reside in +``django/branches/attic``. + Support and bugfix branches --------------------------- @@ -201,7 +199,7 @@ will be created there approximately one month after each new Django release. For example, shortly after the release of Django 1.0, the branch ``django/branches/releases/1.0.X`` was created to receive bug fixes, and shortly after the release of Django 1.1 the branch -``django/branches/releases/1.1.X`` will be created. +``django/branches/releases/1.1.X`` was created. Prior to the Django 1.0 release, these branches were maintaind within the top-level ``django/branches`` directory, and so the following diff --git a/docs/intro/tutorial01.txt b/docs/intro/tutorial01.txt index ef65dae38d..99aebff2c5 100644 --- a/docs/intro/tutorial01.txt +++ b/docs/intro/tutorial01.txt @@ -644,8 +644,7 @@ Save these changes and start a new Python interactive shell by running >>> Poll.objects.filter(question__startswith='What') [] - # Get the poll whose year is 2007. Of course, if you're going through this - # tutorial in another year, change as appropriate. + # Get the poll whose year is 2007. >>> Poll.objects.get(pub_date__year=2007) diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 584672e4f0..c1e05eda1d 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -25,21 +25,26 @@ Django's admin interface. Overview ======== -There are five steps in activating the Django admin site: +There are six steps in activating the Django admin site: - 1. Add ``django.contrib.admin`` to your ``INSTALLED_APPS`` setting. + 1. Add ``'django.contrib.admin'`` to your :setting:`INSTALLED_APPS` + setting. - 2. Determine which of your application's models should be editable in the + 2. Admin has two dependencies - ``django.contrib.auth`` and + ``django.contrib.contenttypes``. If these applications are not + in your :setting:`INSTALLED_APPS` list, add them. + + 3. Determine which of your application's models should be editable in the admin interface. - 3. For each of those models, optionally create a ``ModelAdmin`` class that + 4. For each of those models, optionally create a ``ModelAdmin`` class that encapsulates the customized admin functionality and options for that particular model. - 4. Instantiate an ``AdminSite`` and tell it about each of your models and + 5. Instantiate an ``AdminSite`` and tell it about each of your models and ``ModelAdmin`` classes. - 5. Hook the ``AdminSite`` instance into your URLconf. + 6. Hook the ``AdminSite`` instance into your URLconf. Other topics ------------ @@ -845,7 +850,7 @@ the admin application URL dispatching handler to render the pages that deal with model instances CRUD operations. As a result, completely overriding these methods will significantly change the behavior of the admin application. -One comon reason for overriding these methods is to augment the context data +One common reason for overriding these methods is to augment the context data that is provided to the template that renders the view. In the following example, the change view is overridden so that the rendered template is provided some extra mapping data that would not otherwise be available:: diff --git a/docs/ref/contrib/comments/index.txt b/docs/ref/contrib/comments/index.txt index f6e1553bac..880be34101 100644 --- a/docs/ref/contrib/comments/index.txt +++ b/docs/ref/contrib/comments/index.txt @@ -24,13 +24,13 @@ Quick start guide To get started using the ``comments`` app, follow these steps: - #. Install the comments framework by adding ``'django.contrib.comments'`` to + #. Install the comments framework by adding ``'django.contrib.comments'`` to :setting:`INSTALLED_APPS`. #. Run ``manage.py syncdb`` so that Django will create the comment tables. #. Add the comment app's URLs to your project's ``urls.py``: - + .. code-block:: python urlpatterns = patterns('', @@ -41,9 +41,9 @@ To get started using the ``comments`` app, follow these steps: #. Use the `comment template tags`_ below to embed comments in your templates. - + You might also want to examine :ref:`ref-contrib-comments-settings`. - + Comment template tags ===================== @@ -67,20 +67,20 @@ different ways you can specify which object to attach to: #. Refer to the object directly -- the more common method. Most of the time, you'll have some object in the template's context you want to attach the comment to; you can simply use that object. - - For example, in a blog entry page that has a variable named ``entry``, + + For example, in a blog entry page that has a variable named ``entry``, you could use the following to load the number of comments:: - + {% get_comment_count for entry as comment_count %}. - + #. Refer to the object by content-type and object id. You'd use this method if you, for some reason, don't actually have direct access to the object. - + Following the above example, if you knew the object ID was ``14`` but didn't have access to the actual object, you could do something like:: - + {% get_comment_count for blog.entry 14 as comment_count %} - + In the above, ``blog.entry`` is the app label and (lower-cased) model name of the model class. @@ -89,7 +89,7 @@ different ways you can specify which object to attach to: Displaying comments ------------------- -To get a the list of comments for some object, use :ttag:`get_comment_list`:: +To get the list of comments for some object, use :ttag:`get_comment_list`:: {% get_comment_list for [object] as [varname] %} @@ -99,7 +99,7 @@ For example:: {% for comment in comment_list %} ... {% endfor %} - + This returns a list of :class:`~django.contrib.comments.models.Comment` objects; see :ref:`the comment model documentation ` for details. @@ -116,9 +116,9 @@ To count comments attached to an object, use :ttag:`get_comment_count`:: For example:: {% get_comment_count for event as comment_count %} - +

This event has {{ comment_count }} comments.

- + Displaying the comment post form -------------------------------- @@ -153,7 +153,7 @@ If you want more control over the look and feel of the comment form, you use use you can use in the template:: {% get_comment_form for [object] as [varname] %} - + A complete form might look like:: {% get_comment_form for event as form %} @@ -164,7 +164,7 @@ A complete form might look like:: - + Be sure to read the `notes on the comment form`_, below, for some special considerations you'll need to make if you're using this approach. @@ -185,7 +185,7 @@ Redirecting after the comment post To specify the URL you want to redirect to after the comment has been posted, you can include a hidden form input called ``next`` in your comment form. For example:: - + .. _notes-on-the-comment-form: @@ -198,24 +198,24 @@ should know about: * It contains a number of hidden fields that contain timestamps, information about the object the comment should be attached to, and a "security hash" - used to validate this information. If someone tampers with this data -- + used to validate this information. If someone tampers with this data -- something comment spammers will try -- the comment submission will fail. - + If you're rendering a custom comment form, you'll need to make sure to pass these values through unchanged. - + * The timestamp is used to ensure that "reply attacks" can't continue very long. Users who wait too long between requesting the form and posting a comment will have their submissions refused. - + * The comment form includes a "honeypot_" field. It's a trap: if any data is entered in that field, the comment will be considered spam (spammers often automatically fill in all fields in an attempt to make valid submissions). - + The default form hides this field with a piece of CSS and further labels it with a warning field; if you use the comment form with a custom template you should be sure to do the same. - + .. _honeypot: http://en.wikipedia.org/wiki/Honeypot_(computing) More information @@ -230,4 +230,4 @@ More information upgrade custom forms - moderation \ No newline at end of file + moderation diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index 7cdbe66df8..ec47fbb1a9 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -256,7 +256,6 @@ Here's a sample configuration which uses a MySQL option file:: } } - # my.cnf [client] database = DATABASE_NAME @@ -481,6 +480,10 @@ version of the driver should **not** be used with Django; ``cx_Oracle`` 5.0.1 resolved this issue, so if you'd like to use a more recent ``cx_Oracle``, use version 5.0.1. +``cx_Oracle`` 5.0.1 or greater can optionally be compiled with the +``WITH_UNICODE`` environment variable. This is recommended but not +required. + .. _`Oracle Database Server`: http://www.oracle.com/ .. _`cx_Oracle`: http://cx-oracle.sourceforge.net/ diff --git a/docs/ref/forms/widgets.txt b/docs/ref/forms/widgets.txt index 9735c8181f..1fc2bfa85d 100644 --- a/docs/ref/forms/widgets.txt +++ b/docs/ref/forms/widgets.txt @@ -24,6 +24,13 @@ commonly used groups of widgets: Password input: ```` + Takes one optional argument: + + .. attribute:: PasswordInput.render_value + + Determines whether the widget will have a value filled in when the + form is re-displayed after a validation error (default is ``True``). + .. class:: HiddenInput Hidden input: ```` @@ -88,6 +95,14 @@ commonly used groups of widgets: Checkbox: ```` + Takes one optional argument: + + .. attribute:: CheckboxInput.check_test + + A callable that takes the value of the CheckBoxInput + and returns ``True`` if the checkbox should be checked for + that value. + .. class:: Select Select widget: ```` @@ -144,6 +159,16 @@ commonly used groups of widgets: .. versionchanged:: 1.1 The ``date_format`` and ``time_format`` arguments were not supported in Django 1.0. +.. class:: SelectDateWidget + + Wrapper around three select widgets: one each for month, day, and year. + Note that this widget lives in a separate file from the standard widgets. + + .. code-block:: python + + from django.forms.extras.widgets import SelectDateWidget + + date = forms.DateField(widget=SelectDateWidget()) Specifying widgets ------------------ diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index 177df12862..0cb5be4b92 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -844,7 +844,7 @@ define the details of how the relation works. current date/time to be chosen. Instead of a dictionary this can also be a :class:`~django.db.models.Q` - object (an object with a :meth:`get_sql` method) for more complex queries. + object for more :ref:`complex queries `. ``limit_choices_to`` has no effect on the inline FormSets that are created to display related objects in the admin. diff --git a/docs/ref/models/options.txt b/docs/ref/models/options.txt index 4725a8d146..ee8968c40b 100644 --- a/docs/ref/models/options.txt +++ b/docs/ref/models/options.txt @@ -19,6 +19,17 @@ Available ``Meta`` options If ``True``, this model will be an :ref:`abstract base class `. +``app_label`` +------------- + +.. attribute:: Options.app_label + +If a model exists outside of the standard :file:`models.py` (for instance, if +the app's models are in submodules of ``myapp.models``), the model must define +which app it is part of:: + + app_label = 'myapp' + ``db_table`` ------------ diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index f58e5ed53a..fd9af3fccb 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -20,7 +20,7 @@ Throughout this reference we'll use the :ref:`example weblog models When QuerySets are evaluated ============================ -Internally, a ``QuerySet`` can be constructed, filter, sliced, and generally +Internally, a ``QuerySet`` can be constructed, filtered, sliced, and generally passed around without actually hitting the database. No database activity actually occurs until you do something to evaluate the queryset. @@ -1530,9 +1530,10 @@ regex Case-sensitive regular expression match. -The regular expression syntax is that of the database backend in use. In the -case of SQLite, which doesn't natively support regular-expression lookups, the -syntax is that of Python's ``re`` module. +The regular expression syntax is that of the database backend in use. +In the case of SQLite, which has no built in regular expression support, +this feature is provided by a (Python) user-defined REGEXP function, and +the regular expression syntax is therefore that of Python's ``re`` module. Example:: diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt index 8701c76235..77d991bc1f 100644 --- a/docs/ref/request-response.txt +++ b/docs/ref/request-response.txt @@ -232,16 +232,7 @@ Methods Returns ``True`` if the request was made via an ``XMLHttpRequest``, by checking the ``HTTP_X_REQUESTED_WITH`` header for the string - ``'XMLHttpRequest'``. The following major JavaScript libraries all send this - header: - - * jQuery - * Dojo - * MochiKit - * MooTools - * Prototype - * YUI - + ``'XMLHttpRequest'``. Most modern JavaScript libraries send this header. If you write your own XMLHttpRequest call (on the browser side), you'll have to set this header manually if you want ``is_ajax()`` to work. @@ -271,7 +262,7 @@ a subclass of dictionary. Exceptions are outlined here: Returns the value for the given key. If the key has more than one value, ``__getitem__()`` returns the last value. Raises - ``django.utils.datastructure.MultiValueDictKeyError`` if the key does not + ``django.utils.datastructures.MultiValueDictKeyError`` if the key does not exist. (This is a subclass of Python's standard ``KeyError``, so you can stick to catching ``KeyError``.) @@ -319,7 +310,7 @@ a subclass of dictionary. Exceptions are outlined here: >>> q = QueryDict('a=1&a=2&a=3') >>> q.items() [('a', '3')] - + .. method:: QueryDict.iteritems() Just like the standard dictionary ``iteritems()`` method. Like diff --git a/docs/ref/signals.txt b/docs/ref/signals.txt index 87e1ce3b77..cc203f1817 100644 --- a/docs/ref/signals.txt +++ b/docs/ref/signals.txt @@ -8,6 +8,9 @@ A list of all the signals that Django sends. .. seealso:: + See the documentation on the :ref:`signal dispatcher ` for + information regarding how to register for and receive signals. + The :ref:`comment framework ` sends a :ref:`set of comment-related signals `. @@ -98,7 +101,7 @@ pre_save .. data:: django.db.models.signals.pre_save :module: - + This is sent at the beginning of a model's :meth:`~django.db.models.Model.save` method. @@ -114,8 +117,8 @@ post_save --------- .. data:: django.db.models.signals.post_save - :module: - + :module: + Like :data:`pre_save`, but sent at the end of the :meth:`~django.db.models.Model.save` method. @@ -135,7 +138,7 @@ pre_delete .. data:: django.db.models.signals.pre_delete :module: - + Sent at the beginning of a model's :meth:`~django.db.models.Model.delete` method. @@ -151,8 +154,8 @@ post_delete ----------- .. data:: django.db.models.signals.post_delete - :module: - + :module: + Like :data:`pre_delete`, but sent at the end of the :meth:`~django.db.models.Model.delete` method. @@ -172,7 +175,7 @@ class_prepared .. data:: django.db.models.signals.class_prepared :module: - + Sent whenever a model class has been "prepared" -- that is, once model has been defined and registered with Django's model system. Django uses this signal internally; it's not generally used in third-party applications. @@ -241,8 +244,8 @@ request_started --------------- .. data:: django.core.signals.request_started - :module: - + :module: + Sent when Django begins processing an HTTP request. Arguments sent with this signal: @@ -258,7 +261,7 @@ request_finished .. data:: django.core.signals.request_finished :module: - + Sent when Django finishes processing an HTTP request. Arguments sent with this signal: @@ -271,7 +274,7 @@ got_request_exception .. data:: django.core.signals.got_request_exception :module: - + This signal is sent whenever Django encounters an exception while processing an incoming HTTP request. Arguments sent with this signal: @@ -295,7 +298,7 @@ template_rendered .. data:: django.test.signals.template_rendered :module: - + Sent when the test system renders a template. This signal is not emitted during normal operation of a Django server -- it is only available during testing. diff --git a/docs/releases/1.1.txt b/docs/releases/1.1.txt index 5af2e17449..cd68a922b7 100644 --- a/docs/releases/1.1.txt +++ b/docs/releases/1.1.txt @@ -21,7 +21,7 @@ Django has a policy of :ref:`API stability `. This means that, in general, code you develop against Django 1.0 should continue to work against 1.1 unchanged. However, we do sometimes make backwards-incompatible changes if they're necessary to resolve bugs, and there are a handful of such -(minor) changes between Django 1.0 and Django 1.1. +(minor) changes between Django 1.0 and Django 1.1. Before upgrading to Django 1.1 you should double-check that the following changes don't impact you, and upgrade your code if they do. @@ -41,12 +41,12 @@ However, **users on 64-bit platforms may experience some problems** using the would generate a 64-bit, 16 character digest in the constraint name; for example:: - ALTER TABLE myapp_sometable ADD CONSTRAINT object_id_refs_id_5e8f10c132091d1e FOREIGN KEY ... - + ALTER TABLE myapp_sometable ADD CONSTRAINT object_id_refs_id_5e8f10c132091d1e FOREIGN KEY ... + Following this change, all platforms, regardless of word size, will generate a 32-bit, 8 character digest in the constraint name; for example:: - ALTER TABLE myapp_sometable ADD CONSTRAINT object_id_refs_id_32091d1e FOREIGN KEY ... + ALTER TABLE myapp_sometable ADD CONSTRAINT object_id_refs_id_32091d1e FOREIGN KEY ... As a result of this change, you will not be able to use the :djadmin:`reset` management command on any table made by a 64-bit machine. This is because the @@ -94,13 +94,13 @@ other than raise a ``DeprecationWarning``. If you've been relying on this middleware, the easiest upgrade path is: * Examine `the code as it existed before it was removed`__. - + * Verify that it works correctly with your upstream proxy, modifying it to support your particular proxy (if necessary). - + * Introduce your modified version of ``SetRemoteAddrFromForwardedFor`` as a piece of middleware in your own project. - + __ http://code.djangoproject.com/browser/django/trunk/django/middleware/http.py?rev=11000#L33 Names of uploaded files are available later @@ -115,7 +115,7 @@ it was available in a model's pre-save signal handler. In Django 1.1 the file is saved as part of saving the model in the database, so the actual file name used on disk cannot be relied on until *after* the model -has been saved saved. +has been saved. Changes to how model formsets are saved --------------------------------------- @@ -132,8 +132,8 @@ public methods. Fixed the ``join`` filter's escaping behavior --------------------------------------------- -The :ttag:`join` filter no longer escapes the literal value that is -passed in for the connector. +The :ttag:`join` filter no longer escapes the literal value that is +passed in for the connector. This is backwards incompatible for the special situation of the literal string containing one of the five special HTML characters. Thus, if you were writing @@ -157,13 +157,13 @@ One feature has been marked as deprecated in Django 1.1: * You should no longer use ``AdminSite.root()`` to register that admin views. That is, if your URLconf contains the line:: - + (r'^admin/(.*)', admin.site.root), - + You should change it to read:: - + (r'^admin/', include(admin.site.urls)), - + You should begin to remove use of this features from your code immediately. ``AdminSite.root`` will will raise a ``PendingDeprecationWarning`` if used in @@ -289,7 +289,6 @@ test client: * The test :class:`Client` now can automatically follow redirects with the ``follow`` argument to :meth:`Client.get` and :meth:`Client.post`. This makes testing views that issue redirects simpler. - * It's now easier to get at the template context in the response returned the test client: you'll simply access the context as ``request.context[key]``. The old way, which treats ``request.context`` as @@ -351,13 +350,13 @@ features: * Support for SpatiaLite_ -- a spatial database for SQLite -- as a spatial backend. - + * Geographic aggregates (``Collect``, ``Extent``, ``MakeLine``, ``Union``) and ``F`` expressions. - + * New ``GeoQuerySet`` methods: ``collect``, ``geojson``, and ``snap_to_grid``. - + * A new list interface methods for ``GEOSGeometry`` objects. For more details, see the `GeoDjango documentation`_. @@ -412,23 +411,23 @@ Other new features and changes introduced since Django 1.0 include: * The :djadmin:`dumpdata` management command now accepts individual model names as arguments, allowing you to export the data just from particular models. - + * There's a new :tfilter:`safeseq` template filter which works just like :tfilter:`safe` for lists, marking each item in the list as safe. - + * :ref:`Cache backends ` now support ``incr()`` and ``decr()`` commands to increment and decrement the value of a cache key. On cache backends that support atomic increment/decrement -- most notably, the memcached backend -- these operations will be atomic, and quite fast. - + * Django now can :ref:`easily delegate authentication to the web server ` via a new authentication backend that supports the standard ``REMOTE_USER`` environment variable used for this purpose. - + * There's a new :func:`django.shortcuts.redirect` function that makes it easier to issue redirects given an object, a view name, or a URL. - + * The ``postgresql_psycopg2`` backend now supports :ref:`native PostgreSQL autocommit `. This is an advanced, PostgreSQL-specific feature, that can make certain read-heavy applications a good deal @@ -448,7 +447,7 @@ mailing list: join the discussions! Django's online documentation also includes pointers on how to contribute to -Django: +Django: * :ref:`How to contribute to Django ` diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt index 7858e44962..6a62be8c32 100644 --- a/docs/topics/auth.txt +++ b/docs/topics/auth.txt @@ -29,13 +29,16 @@ Installation Authentication support is bundled as a Django application in ``django.contrib.auth``. To install it, do the following: - 1. Put ``'django.contrib.auth'`` in your :setting:`INSTALLED_APPS` setting. + 1. Put ``'django.contrib.auth'`` and ``'django.contrib.contenttypes'`` in + your :setting:`INSTALLED_APPS` setting. + (The :class:`~django.contrib.auth.models.Permisson` model in + :mod:`django.contrib.auth` depends on :mod:`django.contrib.contenttypes`.) 2. Run the command ``manage.py syncdb``. Note that the default :file:`settings.py` file created by -:djadmin:`django-admin.py startproject` includes ``'django.contrib.auth'`` in -:setting:`INSTALLED_APPS` for convenience. If your :setting:`INSTALLED_APPS` -already contains ``'django.contrib.auth'``, feel free to run +:djadmin:`django-admin.py startproject` includes ``'django.contrib.auth'`` and +``'django.contrib.contenttypes'`` in :setting:`INSTALLED_APPS` for convenience. +If your :setting:`INSTALLED_APPS` already contains these apps, feel free to run :djadmin:`manage.py syncdb` again; you can run that command as many times as you'd like, and each time it'll only install what's needed. @@ -209,14 +212,15 @@ Methods .. method:: models.User.has_perm(perm) Returns ``True`` if the user has the specified permission, where perm is - in the format ``"."``. If the - user is inactive, this method will always return ``False``. + in the format ``"."``. + If the user is inactive, this method will always return ``False``. .. method:: models.User.has_perms(perm_list) Returns ``True`` if the user has each of the specified permissions, - where each perm is in the format ``"package.codename"``. If the user is - inactive, this method will always return ``False``. + where each perm is in the format + ``"."``. If the user is inactive, + this method will always return ``False``. .. method:: models.User.has_module_perms(package_name) @@ -686,8 +690,10 @@ The login_required decorator * If the user isn't logged in, redirect to :setting:`settings.LOGIN_URL ` (``/accounts/login/`` by - default), passing the current absolute URL in the query string as - ``next`` or the value of ``redirect_field_name``. For example: + default), passing the current absolute URL in the query string. The + name of the GET argument is determined by the ``redirect_field_name`` + argument provided to the decorator. The default argument name is + ``next``. For example: ``/accounts/login/?next=/polls/3/``. * If the user is logged in, execute the view normally. The view code is @@ -723,14 +729,14 @@ the following line to your URLconf:: * ``next``: The URL to redirect to after successful login. This may contain a query string, too. - + * ``site``: The current :class:`~django.contrib.sites.models.Site`, according to the :setting:`SITE_ID` setting. If you don't have the site framework installed, this will be set to an instance of :class:`~django.contrib.sites.models.RequestSite`, which derives the site name and domain from the current :class:`~django.http.HttpRequest`. - + * ``site_name``: An alias for ``site.name``. If you don't have the site framework installed, this will be set to the value of :attr:`request.META['SERVER_NAME'] `. @@ -742,11 +748,11 @@ the following line to your URLconf:: :file:`myapp/login.html` instead:: (r'^accounts/login/$', 'django.contrib.auth.views.login', {'template_name': 'myapp/login.html'}), - + You can also specify the name of the ``GET`` field which contains the URL to redirect to after login by passing ``redirect_field_name`` to the view. By default, the field is called ``next``. - + Here's a sample :file:`registration/login.html` template you can use as a starting point. It assumes you have a :file:`base.html` template that defines a ``content`` block: @@ -800,7 +806,7 @@ includes a few other useful built-in views located in * ``template_name``: The full name of a template to display after logging the user out. This will default to :file:`registration/logged_out.html` if no argument is supplied. - + * ``redirect_field_name``: The name of a ``GET`` field containing the URL to redirect to after log out. Overrides ``next_page`` if the given ``GET`` parameter is passed. @@ -859,17 +865,17 @@ includes a few other useful built-in views located in * ``email_template_name``: The full name of a template to use for generating the e-mail with the new password. This will default to :file:`registration/password_reset_email.html` if not supplied. - + * ``password_reset_form``: Form that will be used to set the password. Defaults to ``SetPasswordForm``. - + * ``token_generator``: Instance of the class to check the password. This will default to ``default_token_generator``, it's an instance of ``django.contrib.auth.tokens.PasswordResetTokenGenerator``. - + * ``post_reset_redirect``: The URL to redirect to after a successful password change. - + **Template context:** * ``form``: The form for resetting the user's password. @@ -897,11 +903,11 @@ includes a few other useful built-in views located in * ``login_url``: The URL of the login page to redirect to. This will default to :setting:`settings.LOGIN_URL ` if not supplied. - + * ``redirect_field_name``: The name of a ``GET`` field containing the URL to redirect to after log out. Overrides ``next`` if the given ``GET`` parameter is passed. - + .. function:: password_reset_confirm(request[, uidb36, token, template_name, token_generator, set_password_form, post_reset_redirect]) Presents a form for entering a new password. @@ -926,7 +932,7 @@ includes a few other useful built-in views located in Presents a view which informs the user that the password has been successfully changed. - **Optional arguments:** + **Optional arguments:** * ``template_name``: The full name of a template to display the view. This will default to :file:`registration/password_reset_complete.html`. @@ -1057,8 +1063,8 @@ The permission_required decorator my_view = permission_required('polls.can_vote')(my_view) As for the :meth:`User.has_perm` method, permission names take the form - ``"."`` (i.e. ``polls.choice`` for - a ``Choice`` model in the ``polls`` application). + ``"."`` (i.e. ``polls.can_vote`` for a + permission on a model in the ``polls`` application). Note that :func:`~django.contrib.auth.decorators.permission_required()` also takes an optional ``login_url`` parameter. Example:: diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt index 174dbae121..c9fd1b4012 100644 --- a/docs/topics/cache.txt +++ b/docs/topics/cache.txt @@ -361,6 +361,17 @@ then requests to ``/foo/1/`` and ``/foo/23/`` will be cached separately, as you may expect. But once a particular URL (e.g., ``/foo/23/``) has been requested, subsequent requests to that URL will use the cache. +``cache_page`` can also take an optional keyword argument, ``key_prefix``, which +works in the same way as the ``CACHE_MIDDLEWARE_KEY_PREFIX`` setting for the +middleware. It can be used like this:: + + my_view = cache_page(my_view, 60 * 15, key_prefix="site1") + +Or, using Python 2.4's decorator syntax:: + + @cache_page(60 * 15, key_prefix="site1") + def my_view(request): + Specifying per-view cache in the URLconf ---------------------------------------- diff --git a/docs/topics/db/queries.txt b/docs/topics/db/queries.txt index 5e353b2ec3..968ea7fbc8 100644 --- a/docs/topics/db/queries.txt +++ b/docs/topics/db/queries.txt @@ -622,6 +622,8 @@ To avoid this problem, simply save the ``QuerySet`` and reuse it:: >>> print [p.headline for p in queryset] # Evaluate the query set. >>> print [p.pub_date for p in queryset] # Re-use the cache from the evaluation. +.. _complex-lookups-with-q: + Complex lookups with Q objects ============================== diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt index add581268b..c5aa9c8a77 100644 --- a/docs/topics/forms/modelforms.txt +++ b/docs/topics/forms/modelforms.txt @@ -371,6 +371,35 @@ parameter when declaring the form field:: ... class Meta: ... model = Article +.. note:: + + If you explicitly instantiate a form field like this, Django assumes that you + want to completely define its behavior; therefore, default attributes (such as + ``max_length`` or ``required``) are not drawn from the corresponding model. If + you want to maintain the behavior specified in the model, you must set the + relevant arguments explicitly when declaring the form field. + + For example, if the ``Article`` model looks like this:: + + class Article(models.Model): + headline = models.CharField(max_length=200, null=True, blank=True, + help_text="Use puns liberally") + content = models.TextField() + + and you want to do some custom validation for ``headline``, while keeping + the ``blank`` and ``help_text`` values as specified, you might define + ``ArticleForm`` like this:: + + class ArticleForm(ModelForm): + headline = MyFormField(max_length=200, required=False, + help_text="Use puns liberally") + + class Meta: + model = Article + + See the :ref:`form field documentation ` for more information + on fields and their arguments. + Changing the order of fields ---------------------------- @@ -512,6 +541,12 @@ Then, pass your ``BaseAuthorFormSet`` class to the factory function:: >>> AuthorFormSet = modelformset_factory(Author, formset=BaseAuthorFormSet) +If you want to return a formset that doesn't include *any* pre-existing +instances of the model, you can specify an empty QuerySet:: + + >>> AuthorFormSet(queryset=Author.objects.none()) + + Controlling which fields are used with ``fields`` and ``exclude`` ----------------------------------------------------------------- diff --git a/docs/topics/generic-views.txt b/docs/topics/generic-views.txt index c7d751a86b..c48c858f0b 100644 --- a/docs/topics/generic-views.txt +++ b/docs/topics/generic-views.txt @@ -64,7 +64,7 @@ code! --, actually the ``direct_to_template`` view simply grabs information from the extra-parameters dictionary and uses that information when rendering the view. -Because this generic view -- and all the others -- is a regular view functions +Because this generic view -- and all the others -- is a regular view function like any other, we can reuse it inside our own views. As an example, let's extend our "about" example to map URLs of the form ``/about//`` to statically rendered ``about/.html``. We'll do this by first modifying @@ -150,7 +150,7 @@ be using these models:: publisher = models.ForeignKey(Publisher) publication_date = models.DateField() -To build a list page of all books, we'd use a URLconf along these lines:: +To build a list page of all publishers, we'd use a URLconf along these lines:: from django.conf.urls.defaults import * from django.views.generic import list_detail @@ -176,7 +176,7 @@ version of the model's name. .. highlightlang:: html+django This template will be rendered against a context containing a variable called -``object_list`` that contains all the book objects. A very simple template +``object_list`` that contains all the publisher objects. A very simple template might look like the following:: {% extends "base.html" %} @@ -217,7 +217,7 @@ Making "friendly" template contexts You might have noticed that our sample publisher list template stores all the books in a variable named ``object_list``. While this works just fine, it isn't all that "friendly" to template authors: they have to "just know" that they're -dealing with books here. A better name for that variable would be +dealing with publishers here. A better name for that variable would be ``publisher_list``; that variable's content is pretty obvious. We can change the name of that variable easily with the ``template_object_name`` @@ -241,14 +241,14 @@ Adding extra context -------------------- Often you simply need to present some extra information beyond that provided by -the generic view. For example, think of showing a list of all the other -publishers on each publisher detail page. The ``object_detail`` generic view -provides the publisher to the context, but it seems there's no way to get a list -of *all* publishers in that template. +the generic view. For example, think of showing a list of all the books on each +publisher detail page. The ``object_detail`` generic view provides the +publisher to the context, but it seems there's no way to get additional +information in that template. But there is: all generic views take an extra optional parameter, ``extra_context``. This is a dictionary of extra objects that will be added to -the template's context. So, to provide the list of all publishers on the detail +the template's context. So, to provide the list of all books on the detail detail view, we'd use an info dict like this: .. parsed-literal:: @@ -268,9 +268,9 @@ generic view. It's very handy. However, there's actually a subtle bug here -- can you spot it? The problem has to do with when the queries in ``extra_context`` are evaluated. -Because this example puts ``Publisher.objects.all()`` in the URLconf, it will +Because this example puts ``Book.objects.all()`` in the URLconf, it will be evaluated only once (when the URLconf is first loaded). Once you add or -remove publishers, you'll notice that the generic view doesn't reflect those +remove books, you'll notice that the generic view doesn't reflect those changes until you reload the Web server (see :ref:`caching-and-querysets` for more information about when QuerySets are cached and evaluated). diff --git a/docs/topics/http/sessions.txt b/docs/topics/http/sessions.txt index d3956504c7..3b1429b440 100644 --- a/docs/topics/http/sessions.txt +++ b/docs/topics/http/sessions.txt @@ -453,6 +453,20 @@ Default: ``'sessionid'`` The name of the cookie to use for sessions. This can be whatever you want. +SESSION_COOKIE_PATH +------------------- + +.. versionadded:: 1.0 + +Default: ``'/'`` + +The path set on the session cookie. This should either match the URL path of +your Django installation or be parent of that path. + +This is useful if you have multiple Django instances running under the same +hostname. They can use different cookie paths, and each instance will only see +its own session cookie. + SESSION_COOKIE_SECURE --------------------- diff --git a/docs/topics/http/shortcuts.txt b/docs/topics/http/shortcuts.txt index 2bc35a3b9f..e8039fe253 100644 --- a/docs/topics/http/shortcuts.txt +++ b/docs/topics/http/shortcuts.txt @@ -147,7 +147,7 @@ will be returned:: ``get_object_or_404`` ===================== -.. function:: get_object_or_404(object, *args, **kwargs) +.. function:: get_object_or_404(klass, *args, **kwargs) Calls :meth:`~django.db.models.QuerySet.get()` on a given model manager, but it raises ``django.http.Http404`` instead of the model's diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index fc30be6ae2..23cbdbc98e 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -515,7 +515,7 @@ arguments at time of construction: >>> c = Client() >>> c.get('/customers/details/?name=fred&age=7') - If you provide URL both an encoded GET data and a data argument, + If you provide a URL with both an encoded GET data and a data argument, the data argument will take precedence. If you set ``follow`` to ``True`` the client will follow any redirects @@ -627,7 +627,7 @@ arguments at time of construction: .. versionadded:: 1.1 - Makes an PUT request on the provided ``path`` and returns a + Makes a PUT request on the provided ``path`` and returns a ``Response`` object. Useful for testing RESTful interfaces. Acts just like :meth:`Client.post` except with the PUT request method. @@ -1127,11 +1127,11 @@ Django, such as your machine's mail server, if you're running one.) During test running, each outgoing e-mail is saved in ``django.core.mail.outbox``. This is a simple list of all -:class:`<~django.core.mail.EmailMessage>` instances that have been sent. +:class:`~django.core.mail.EmailMessage` instances that have been sent. It does not exist under normal execution conditions, i.e., when you're not running unit tests. The outbox is created during test setup, along with the -dummy :class:`<~django.core.mail.SMTPConnection>`. When the test framework is -torn down, the standard :class:`<~django.core.mail.SMTPConnection>` class is +dummy :class:`~django.core.mail.SMTPConnection`. When the test framework is +torn down, the standard :class:`~django.core.mail.SMTPConnection` class is restored, and the test outbox is destroyed. The ``outbox`` attribute is a special attribute that is created *only* when diff --git a/extras/django_bash_completion b/extras/django_bash_completion index b805af9e6f..420553fcfa 100755 --- a/extras/django_bash_completion +++ b/extras/django_bash_completion @@ -31,136 +31,10 @@ # # To uninstall, just remove the line from your .bash_profile and .bashrc. -# Enable extended pattern matching operators. -shopt -s extglob - _django_completion() { - local cur prev opts actions action_shell_opts action_runfcgi_opts - COMPREPLY=() - cur="${COMP_WORDS[COMP_CWORD]}" - prev="${COMP_WORDS[COMP_CWORD-1]}" - - # Standalone options - opts="--help --settings --pythonpath --noinput --noreload --format --indent --verbosity --adminmedia --version --locale --domain" - # Actions - actions="createcachetable createsuperuser compilemessages \ - dbshell diffsettings dumpdata flush inspectdb loaddata \ - makemessages reset runfcgi runserver shell sql sqlall sqlclear \ - sqlcustom sqlflush sqlindexes sqlreset sqlsequencereset startapp \ - startproject syncdb test validate" - # Action's options - action_shell_opts="--plain" - action_runfcgi_opts="host port socket method maxspare minspare maxchildren daemonize pidfile workdir" - - if [[ # django-admin.py, django-admin, ./manage, manage.py - ( ${COMP_CWORD} -eq 1 && - ( ${COMP_WORDS[0]} == django-admin.py || - ${COMP_WORDS[0]} == django-admin || - ${COMP_WORDS[0]} == ./manage.py || - ${COMP_WORDS[0]} == manage.py ) ) - || - # python manage.py, /some/path/python manage.py (if manage.py exists) - ( ${COMP_CWORD} -eq 2 && - ( $( basename -- ${COMP_WORDS[0]} ) == python?([1-9]\.[0-9]) ) && - ( $( basename -- ${COMP_WORDS[1]} ) == manage.py) && - ( -r ${COMP_WORDS[1]} ) ) - || - ( ${COMP_CWORD} -eq 2 && - ( $( basename -- ${COMP_WORDS[0]} ) == python?([1-9]\.[0-9]) ) && - ( $( basename -- ${COMP_WORDS[1]} ) == django-admin.py) && - ( -r ${COMP_WORDS[1]} ) ) - || - ( ${COMP_CWORD} -eq 2 && - ( $( basename -- ${COMP_WORDS[0]} ) == python?([1-9]\.[0-9]) ) && - ( $( basename -- ${COMP_WORDS[1]} ) == django-admin) && - ( -r ${COMP_WORDS[1]} ) ) ]] ; then - - case ${cur} in - -*) - COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) - action=$COMPREPLY - return 0 - ;; - *) - COMPREPLY=( $(compgen -W "${actions}" -- ${cur}) ) - action=$COMPREPLY - return 0 - ;; - esac - else - case ${prev} in - dumpdata|reset| \ - sql|sqlall|sqlclear|sqlcustom|sqlindexes| \ - sqlreset|sqlsequencereset|test) - # App completion - settings="" - # If settings.py in the PWD, use that - if [ -e settings.py ] ; then - settings="$PWD/settings.py" - else - # Use the ENV variable if it is set - if [ $DJANGO_SETTINGS_MODULE ] ; then - settings=$DJANGO_SETTINGS_MODULE - fi - fi - # Couldn't find settings so return nothing - if [ -z $settings ] ; then - COMPREPLY=() - # Otherwise inspect settings.py file - else - apps=`sed -n "/INSTALLED_APPS = (/,/)/p" $settings | \ - grep -v "django.contrib" | - sed -n "s/^[ ]*'\(.*\.\)*\(.*\)'.*$/\2 /pg" | \ - tr -d "\n"` - COMPREPLY=( $(compgen -W "${apps}" -- ${cur}) ) - fi - return 0 - ;; - - createcachetable|cleanup|compilemessages|dbshell| \ - diffsettings|inspectdb|makemessages| \ - runserver|startapp|startproject|syncdb| \ - validate) - COMPREPLY=() - return 0 - ;; - shell) - COMPREPLY=( $(compgen -W "$action_shell_opts" -- ${cur}) ) - return 0 - ;; - runfcgi) - COMPREPLY=( $(compgen -W "$action_runfcgi_opts" -- ${cur}) ) - return 0 - ;; - host*|port*|socket*|method*|maxspare*|minspare*|maxchildren*|daemonize*|pidfile*|workdir*) - if [ "$action" == "runfcgi" ] ; then - COMPREPLY=( $(compgen -W "$action_runfcgi_opts" -- ${cur}) ) - return 0 - fi - return 0 - ;; - *) - #COMPREPLY=( $(compgen -W "auth core" -- ${cur}) ) - COMPREPLY=() - return 0 - ;; - esac - fi + COMPREPLY=( $( COMP_WORDS="${COMP_WORDS[*]}" \ + COMP_CWORD=$COMP_CWORD \ + DJANGO_AUTO_COMPLETE=1 $1 ) ) } - -complete -F _django_completion django-admin.py manage.py django-admin - -# Support for multiple interpreters. -unset pythons -if command -v whereis &>/dev/null; then - python_interpreters=$(whereis python | cut -d " " -f 2-) - for python in $python_interpreters; do - pythons="${pythons} $(basename -- $python)" - done - pythons=$(echo $pythons | tr " " "\n" | sort -u | tr "\n" " ") -else - pythons=python -fi - -complete -F _django_completion -o default $pythons +complete -F _django_completion -o default django-admin.py manage.py diff --git a/tests/modeltests/expressions/models.py b/tests/modeltests/expressions/models.py index 27daabad71..b515682a9c 100644 --- a/tests/modeltests/expressions/models.py +++ b/tests/modeltests/expressions/models.py @@ -56,6 +56,16 @@ __test__ = {'API_TESTS': """ >>> company_query [{'num_chairs': 2302, 'name': u'Example Inc.', 'num_employees': 2300}, {'num_chairs': 5, 'name': u'Foobar Ltd.', 'num_employees': 3}, {'num_chairs': 34, 'name': u'Test GmbH', 'num_employees': 32}] +# Law of order of operations is followed +>>> _ =company_query.update(num_chairs=F('num_employees') + 2 * F('num_employees')) +>>> company_query +[{'num_chairs': 6900, 'name': u'Example Inc.', 'num_employees': 2300}, {'num_chairs': 9, 'name': u'Foobar Ltd.', 'num_employees': 3}, {'num_chairs': 96, 'name': u'Test GmbH', 'num_employees': 32}] + +# Law of order of operations can be overridden by parentheses +>>> _ =company_query.update(num_chairs=((F('num_employees') + 2) * F('num_employees'))) +>>> company_query +[{'num_chairs': 5294600, 'name': u'Example Inc.', 'num_employees': 2300}, {'num_chairs': 15, 'name': u'Foobar Ltd.', 'num_employees': 3}, {'num_chairs': 1088, 'name': u'Test GmbH', 'num_employees': 32}] + # The relation of a foreign key can become copied over to an other foreign key. >>> Company.objects.update(point_of_contact=F('ceo')) 3 diff --git a/tests/regressiontests/admin_views/fixtures/admin-views-person.xml b/tests/regressiontests/admin_views/fixtures/admin-views-person.xml index 77928a834b..ff00fd2169 100644 --- a/tests/regressiontests/admin_views/fixtures/admin-views-person.xml +++ b/tests/regressiontests/admin_views/fixtures/admin-views-person.xml @@ -6,7 +6,7 @@ True - Grace Hooper + Grace Hopper 1 False diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py index a4abbd0005..7273d3f320 100644 --- a/tests/regressiontests/admin_views/tests.py +++ b/tests/regressiontests/admin_views/tests.py @@ -353,6 +353,9 @@ class AdminViewPermissionsTest(TestCase): LOGIN_FORM_KEY: 1, 'username': 'joepublic', 'password': 'secret'} + self.no_username_login = { + LOGIN_FORM_KEY: 1, + 'password': 'secret'} def testLogin(self): """ @@ -416,6 +419,14 @@ class AdminViewPermissionsTest(TestCase): # Login.context is a list of context dicts we just need to check the first one. self.assert_(login.context[0].get('error_message')) + # Requests without username should not return 500 errors. + request = self.client.get('/test_admin/admin/') + self.failUnlessEqual(request.status_code, 200) + login = self.client.post('/test_admin/admin/', self.no_username_login) + self.failUnlessEqual(login.status_code, 200) + # Login.context is a list of context dicts we just need to check the first one. + self.assert_(login.context[0].get('error_message')) + def testLoginSuccessfullyRedirectsToOriginalUrl(self): request = self.client.get('/test_admin/admin/') self.failUnlessEqual(request.status_code, 200) @@ -897,7 +908,7 @@ class AdminViewListEditable(TestCase): self.client.post('/test_admin/admin/admin_views/person/', data) self.failUnlessEqual(Person.objects.get(name="John Mauchly").alive, False) - self.failUnlessEqual(Person.objects.get(name="Grace Hooper").gender, 2) + self.failUnlessEqual(Person.objects.get(name="Grace Hopper").gender, 2) # test a filtered page data = { diff --git a/tests/regressiontests/backends/tests.py b/tests/regressiontests/backends/tests.py index 06ab09765f..9f4ea3be77 100644 --- a/tests/regressiontests/backends/tests.py +++ b/tests/regressiontests/backends/tests.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # Unit and doctests for specific database backends. import unittest -from django.db import connection, DEFAULT_DB_ALIAS +from django.db import backend, connection, DEFAULT_DB_ALIAS from django.db.backends.signals import connection_created from django.conf import settings @@ -11,19 +11,20 @@ class Callproc(unittest.TestCase): # If the backend is Oracle, test that we can call a standard # stored procedure through our cursor wrapper. if settings.DATABASES[DEFAULT_DB_ALIAS]['DATABASE_ENGINE'] == 'oracle': + convert_unicode = backend.convert_unicode cursor = connection.cursor() - cursor.callproc('DBMS_SESSION.SET_IDENTIFIER', - ['_django_testing!',]) + cursor.callproc(convert_unicode('DBMS_SESSION.SET_IDENTIFIER'), + [convert_unicode('_django_testing!'),]) return True else: return True - + class LongString(unittest.TestCase): def test_long_string(self): # If the backend is Oracle, test that we can save a text longer # than 4000 chars and read it properly - if settings.DATABASES[DEFAULT_DB_ALIAS]['DATABASE_ENGINE'] == 'oracle': + if settings.DATABASE_ENGINE == 'oracle': c = connection.cursor() c.execute('CREATE TABLE ltext ("TEXT" NCLOB)') long_str = ''.join([unicode(x) for x in xrange(4000)]) diff --git a/tests/regressiontests/decorators/tests.py b/tests/regressiontests/decorators/tests.py index 3c58637f1a..b81a5d665a 100644 --- a/tests/regressiontests/decorators/tests.py +++ b/tests/regressiontests/decorators/tests.py @@ -1,11 +1,16 @@ from unittest import TestCase from sys import version_info +try: + from functools import wraps +except ImportError: + from django.utils.functional import wraps # Python 2.3, 2.4 fallback. -from django.http import HttpResponse +from django.http import HttpResponse, HttpRequest from django.utils.functional import allow_lazy, lazy, memoize from django.views.decorators.http import require_http_methods, require_GET, require_POST from django.views.decorators.vary import vary_on_headers, vary_on_cookie from django.views.decorators.cache import cache_page, never_cache, cache_control +from django.utils.decorators import auto_adapt_to_methods from django.contrib.auth.decorators import login_required, permission_required, user_passes_test from django.contrib.admin.views.decorators import staff_member_required @@ -84,4 +89,65 @@ class DecoratorsTest(TestCase): response = callback(request) self.assertEqual(response, ['test2', 'test1']) - + + def test_cache_page_new_style(self): + """ + Test that we can call cache_page the new way + """ + def my_view(request): + return "response" + my_view_cached = cache_page(123)(my_view) + self.assertEqual(my_view_cached(HttpRequest()), "response") + my_view_cached2 = cache_page(123, key_prefix="test")(my_view) + self.assertEqual(my_view_cached2(HttpRequest()), "response") + + def test_cache_page_old_style(self): + """ + Test that we can call cache_page the old way + """ + def my_view(request): + return "response" + my_view_cached = cache_page(my_view, 123) + self.assertEqual(my_view_cached(HttpRequest()), "response") + my_view_cached2 = cache_page(my_view, 123, key_prefix="test") + self.assertEqual(my_view_cached2(HttpRequest()), "response") + +class MethodDecoratorAdapterTests(TestCase): + def test_auto_adapt_to_methods(self): + """ + Test that auto_adapt_to_methods actually works. + """ + # Need 2 decorators with auto_adapt_to_methods, + # to check it plays nicely with composing itself. + + def my_decorator(func): + def wrapped(*args, **kwargs): + # need to ensure that the first arg isn't 'self' + self.assertEqual(args[0], "test") + return "my_decorator:" + func(*args, **kwargs) + wrapped.my_decorator_custom_attribute = True + return wraps(func)(wrapped) + my_decorator = auto_adapt_to_methods(my_decorator) + + def my_decorator2(func): + def wrapped(*args, **kwargs): + # need to ensure that the first arg isn't 'self' + self.assertEqual(args[0], "test") + return "my_decorator2:" + func(*args, **kwargs) + wrapped.my_decorator2_custom_attribute = True + return wraps(func)(wrapped) + my_decorator2 = auto_adapt_to_methods(my_decorator2) + + class MyClass(object): + def my_method(self, *args, **kwargs): + return "my_method:%r %r" % (args, kwargs) + my_method = my_decorator2(my_decorator(my_method)) + + obj = MyClass() + self.assertEqual(obj.my_method("test", 123, name='foo'), + "my_decorator2:my_decorator:my_method:('test', 123) {'name': 'foo'}") + self.assertEqual(obj.my_method.__name__, 'my_method') + self.assertEqual(getattr(obj.my_method, 'my_decorator_custom_attribute', False), + True) + self.assertEqual(getattr(obj.my_method, 'my_decorator2_custom_attribute', False), + True) diff --git a/tests/regressiontests/forms/error_messages.py b/tests/regressiontests/forms/error_messages.py index ec91b57a06..b7224dbde0 100644 --- a/tests/regressiontests/forms/error_messages.py +++ b/tests/regressiontests/forms/error_messages.py @@ -358,4 +358,42 @@ ValidationError: [u'NOT A LIST OF VALUES'] Traceback (most recent call last): ... ValidationError: [u'4 IS INVALID CHOICE'] + +# Subclassing ErrorList ####################################################### + +>>> from django.utils.safestring import mark_safe +>>> +>>> class TestForm(Form): +... first_name = CharField() +... last_name = CharField() +... birthday = DateField() +... +... def clean(self): +... raise ValidationError("I like to be awkward.") +... +>>> class CustomErrorList(util.ErrorList): +... def __unicode__(self): +... return self.as_divs() +... def as_divs(self): +... if not self: return u'' +... return mark_safe(u'
%s
' +... % ''.join([u'

%s

' % e for e in self])) +... + +This form should print errors the default way. + +>>> form1 = TestForm({'first_name': 'John'}) +>>> print form1['last_name'].errors +
  • This field is required.
+>>> print form1.errors['__all__'] +
  • I like to be awkward.
+ +This one should wrap error groups in the customized way. + +>>> form2 = TestForm({'first_name': 'John'}, error_class=CustomErrorList) +>>> print form2['last_name'].errors +

This field is required.

+>>> print form2.errors['__all__'] +

I like to be awkward.

+ """ diff --git a/tests/regressiontests/forms/fields.py b/tests/regressiontests/forms/fields.py index 9d9d7227b9..9d407d9ea7 100644 --- a/tests/regressiontests/forms/fields.py +++ b/tests/regressiontests/forms/fields.py @@ -767,6 +767,13 @@ u'example@valid-----hyphens.com' >>> f.clean('example@valid-with-hyphens.com') u'example@valid-with-hyphens.com' +# Check for runaway regex security problem. This will take for-freeking-ever +# if the security fix isn't in place. +>>> f.clean('viewx3dtextx26qx3d@yahoo.comx26latlngx3d15854521645943074058') +Traceback (most recent call last): + ... +ValidationError: [u'Enter a valid e-mail address.'] + >>> f = EmailField(required=False) >>> f.clean('') u'' @@ -972,6 +979,32 @@ ValidationError: [u'Enter a valid URL.'] Traceback (most recent call last): ... ValidationError: [u'Enter a valid URL.'] +>>> f.clean('.') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid URL.'] +>>> f.clean('com.') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid URL.'] +>>> f.clean('http://example.com.') +u'http://example.com./' +>>> f.clean('example.com.') +u'http://example.com./' + +# hangs "forever" if catastrophic backtracking in ticket:#11198 not fixed +>>> f.clean('http://%s' % ("X"*200,)) +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid URL.'] + +# a second test, to make sure the problem is really addressed, even on +# domains that don't fail the domain label length check in the regex +>>> f.clean('http://%s' % ("X"*60,)) +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid URL.'] + >>> f.clean('http://.com') Traceback (most recent call last): ... diff --git a/tests/regressiontests/introspection/tests.py b/tests/regressiontests/introspection/tests.py index b7cd2404da..1a2042992f 100644 --- a/tests/regressiontests/introspection/tests.py +++ b/tests/regressiontests/introspection/tests.py @@ -76,7 +76,7 @@ class IntrospectionTests(TestCase): def test_get_table_description_types(self): cursor = connection.cursor() desc = connection.introspection.get_table_description(cursor, Reporter._meta.db_table) - self.assertEqual([datatype(r[1]) for r in desc], + self.assertEqual([datatype(r[1], r) for r in desc], ['IntegerField', 'CharField', 'CharField', 'CharField']) # Regression test for #9991 - 'real' types in postgres @@ -86,7 +86,7 @@ class IntrospectionTests(TestCase): cursor.execute("CREATE TABLE django_ixn_real_test_table (number REAL);") desc = connection.introspection.get_table_description(cursor, 'django_ixn_real_test_table') cursor.execute('DROP TABLE django_ixn_real_test_table;') - self.assertEqual(datatype(desc[0][1]), 'FloatField') + self.assertEqual(datatype(desc[0][1], desc[0]), 'FloatField') def test_get_relations(self): cursor = connection.cursor() @@ -104,9 +104,10 @@ class IntrospectionTests(TestCase): indexes = connection.introspection.get_indexes(cursor, Article._meta.db_table) self.assertEqual(indexes['reporter_id'], {'unique': False, 'primary_key': False}) -def datatype(dbtype): + +def datatype(dbtype, description): """Helper to convert a data type into a string.""" - dt = connection.introspection.data_types_reverse[dbtype] + dt = connection.introspection.get_field_type(dbtype, description) if type(dt) is tuple: return dt[0] else: diff --git a/tests/regressiontests/queries/models.py b/tests/regressiontests/queries/models.py index b19bb9f51b..403f4c169d 100644 --- a/tests/regressiontests/queries/models.py +++ b/tests/regressiontests/queries/models.py @@ -67,6 +67,9 @@ class Author(models.Model): num = models.IntegerField(unique=True) extra = models.ForeignKey(ExtraInfo) + class Meta: + ordering = ['name'] + def __unicode__(self): return self.name diff --git a/tests/regressiontests/templates/filters.py b/tests/regressiontests/templates/filters.py index e8b1dbe135..2b448574f7 100644 --- a/tests/regressiontests/templates/filters.py +++ b/tests/regressiontests/templates/filters.py @@ -196,7 +196,7 @@ def get_filter_tests(): 'filter-force-escape05': ('{% autoescape off %}{{ a|force_escape|escape }}{% endautoescape %}', {"a": "x&y"}, u"x&y"), 'filter-force-escape06': ('{{ a|force_escape|escape }}', {"a": "x&y"}, u"x&y"), 'filter-force-escape07': ('{% autoescape off %}{{ a|escape|force_escape }}{% endautoescape %}', {"a": "x&y"}, u"x&y"), - 'filter-force-escape07': ('{{ a|escape|force_escape }}', {"a": "x&y"}, u"x&y"), + 'filter-force-escape08': ('{{ a|escape|force_escape }}', {"a": "x&y"}, u"x&y"), # The contents in "linebreaks" and "linebreaksbr" are escaped # according to the current autoescape setting.