diff --git a/AUTHORS b/AUTHORS index 6a7f22ada4f..2904bd0d997 100644 --- a/AUTHORS +++ b/AUTHORS @@ -33,6 +33,7 @@ The PRIMARY AUTHORS are (and/or have been): * Florian Apolloner * Jeremy Dunck * Bryan Veloso + * Preston Holmes More information on the main contributors to Django can be found in docs/internals/committers.txt. diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst new file mode 100644 index 00000000000..3b1734157d9 --- /dev/null +++ b/CONTRIBUTING.rst @@ -0,0 +1,16 @@ +====================== +Contributing to Django +====================== + +As an open source project, Django welcomes contributions of many forms. + +Examples of contributions include: + +* Code patches +* Documentation improvements +* Bug reports and patch reviews + +Extensive contribution guidelines are available in the repository at +``docs/internals/contributing/``, or online at: + +https://docs.djangoproject.com/en/dev/internals/contributing/ diff --git a/django/contrib/auth/admin.py b/django/contrib/auth/admin.py index ccf940d16d9..5c08b0615f7 100644 --- a/django/contrib/auth/admin.py +++ b/django/contrib/auth/admin.py @@ -54,7 +54,7 @@ class UserAdmin(admin.ModelAdmin): add_form = UserCreationForm change_password_form = AdminPasswordChangeForm list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff') - list_filter = ('is_staff', 'is_superuser', 'is_active') + list_filter = ('is_staff', 'is_superuser', 'is_active', 'groups') search_fields = ('username', 'first_name', 'last_name', 'email') ordering = ('username',) filter_horizontal = ('user_permissions',) diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py index 748fb603cf1..abcc7ceafc2 100644 --- a/django/contrib/auth/models.py +++ b/django/contrib/auth/models.py @@ -28,7 +28,7 @@ def update_last_login(sender, user, **kwargs): the user logging in. """ user.last_login = timezone.now() - user.save() + user.save(update_fields=['last_login']) user_logged_in.connect(update_last_login) diff --git a/django/contrib/auth/tests/signals.py b/django/contrib/auth/tests/signals.py index 53057452d23..c597aa9ed00 100644 --- a/django/contrib/auth/tests/signals.py +++ b/django/contrib/auth/tests/signals.py @@ -1,6 +1,8 @@ from django.contrib.auth import signals +from django.contrib.auth.models import User from django.contrib.auth.tests.utils import skipIfCustomUser from django.test import TestCase +from django.test.client import RequestFactory from django.test.utils import override_settings @@ -49,3 +51,16 @@ class SignalTestCase(TestCase): self.client.get('/logout/next_page/') self.assertEqual(len(self.logged_out), 1) self.assertEqual(self.logged_out[0].username, 'testclient') + + def test_update_last_login(self): + """Ensure that only `last_login` is updated in `update_last_login`""" + user = User.objects.get(pk=3) + old_last_login = user.last_login + + user.username = "This username shouldn't get saved" + request = RequestFactory().get('/login') + signals.user_logged_in.send(sender=user.__class__, request=request, + user=user) + user = User.objects.get(pk=3) + self.assertEqual(user.username, 'staff') + self.assertNotEqual(user.last_login, old_last_login) diff --git a/django/contrib/gis/templates/gis/admin/openlayers.js b/django/contrib/gis/templates/gis/admin/openlayers.js index f54b75e2583..a67980da403 100644 --- a/django/contrib/gis/templates/gis/admin/openlayers.js +++ b/django/contrib/gis/templates/gis/admin/openlayers.js @@ -109,10 +109,12 @@ OpenLayers.Projection.addTransform("EPSG:4326", "EPSG:3857", OpenLayers.Layer.Sp {% autoescape off %}{% for item in map_options.items %} '{{ item.0 }}' : {{ item.1 }}{% if not forloop.last %},{% endif %} {% endfor %}{% endautoescape %} };{% endblock %} // The admin map for this geometry field. + {% block map_creation %} {{ module }}.map = new OpenLayers.Map('{{ id }}_map', options); // Base Layer {{ module }}.layers.base = {% block base_layer %}new OpenLayers.Layer.WMS("{{ wms_name }}", "{{ wms_url }}", {layers: '{{ wms_layer }}'{{ wms_options|safe }}});{% endblock %} {{ module }}.map.addLayer({{ module }}.layers.base); + {% endblock %} {% block extra_layers %}{% endblock %} {% if is_linestring %}OpenLayers.Feature.Vector.style["default"]["strokeWidth"] = 3; // Default too thin for linestrings. {% endif %} {{ module }}.layers.vector = new OpenLayers.Layer.Vector(" {{ field_name }}"); diff --git a/django/contrib/gis/tests/geo3d/tests.py b/django/contrib/gis/tests/geo3d/tests.py index dfd0e496ff1..0aba38f5cab 100644 --- a/django/contrib/gis/tests/geo3d/tests.py +++ b/django/contrib/gis/tests/geo3d/tests.py @@ -1,16 +1,17 @@ -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals import os import re -from django.utils.unittest import TestCase from django.contrib.gis.db.models import Union, Extent3D from django.contrib.gis.geos import GEOSGeometry, Point, Polygon from django.contrib.gis.utils import LayerMapping, LayerMapError +from django.test import TestCase from .models import (City3D, Interstate2D, Interstate3D, InterstateProj2D, InterstateProj3D, Point2D, Point3D, MultiPoint3D, Polygon2D, Polygon3D) + data_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', 'data')) city_file = os.path.join(data_path, 'cities', 'cities.shp') vrt_file = os.path.join(data_path, 'test_vrt', 'test_vrt.vrt') @@ -46,12 +47,11 @@ interstate_data = ( # Bounding box polygon for inner-loop of Houston (in projected coordinate # system 32140), with elevation values from the National Elevation Dataset # (see above). -bbox_wkt = 'POLYGON((941527.97 4225693.20,962596.48 4226349.75,963152.57 4209023.95,942051.75 4208366.38,941527.97 4225693.20))' -bbox_z = (21.71, 13.21, 9.12, 16.40, 21.71) -def gen_bbox(): - bbox_2d = GEOSGeometry(bbox_wkt, srid=32140) - bbox_3d = Polygon(tuple((x, y, z) for (x, y), z in zip(bbox_2d[0].coords, bbox_z)), srid=32140) - return bbox_2d, bbox_3d +bbox_data = ( + 'POLYGON((941527.97 4225693.20,962596.48 4226349.75,963152.57 4209023.95,942051.75 4208366.38,941527.97 4225693.20))', + (21.71, 13.21, 9.12, 16.40, 21.71) +) + class Geo3DTest(TestCase): """ @@ -63,20 +63,7 @@ class Geo3DTest(TestCase): http://postgis.refractions.net/documentation/manual-1.4/ch08.html#PostGIS_3D_Functions """ - def test01_3d(self): - "Test the creation of 3D models." - # 3D models for the rest of the tests will be populated in here. - # For each 3D data set create model (and 2D version if necessary), - # retrieve, and assert geometry is in 3D and contains the expected - # 3D values. - for name, pnt_data in city_data: - x, y, z = pnt_data - pnt = Point(x, y, z, srid=4326) - City3D.objects.create(name=name, point=pnt) - city = City3D.objects.get(name=name) - self.assertTrue(city.point.hasz) - self.assertEqual(z, city.point.z) - + def _load_interstate_data(self): # Interstate (2D / 3D and Geographic/Projected variants) for name, line, exp_z in interstate_data: line_3d = GEOSGeometry(line, srid=4269) @@ -90,26 +77,51 @@ class Geo3DTest(TestCase): Interstate2D.objects.create(name=name, line=line_2d) InterstateProj2D.objects.create(name=name, line=line_2d) - # Retrieving and making sure it's 3D and has expected - # Z values -- shouldn't change because of coordinate system. + def _load_city_data(self): + for name, pnt_data in city_data: + City3D.objects.create(name=name, point=Point(*pnt_data, srid=4326)) + + def _load_polygon_data(self): + bbox_wkt, bbox_z = bbox_data + bbox_2d = GEOSGeometry(bbox_wkt, srid=32140) + bbox_3d = Polygon(tuple((x, y, z) for (x, y), z in zip(bbox_2d[0].coords, bbox_z)), srid=32140) + Polygon2D.objects.create(name='2D BBox', poly=bbox_2d) + Polygon3D.objects.create(name='3D BBox', poly=bbox_3d) + + def test_3d_hasz(self): + """ + Make sure data is 3D and has expected Z values -- shouldn't change + because of coordinate system. + """ + self._load_interstate_data() + for name, line, exp_z in interstate_data: interstate = Interstate3D.objects.get(name=name) interstate_proj = InterstateProj3D.objects.get(name=name) for i in [interstate, interstate_proj]: self.assertTrue(i.line.hasz) self.assertEqual(exp_z, tuple(i.line.z)) - # Creating 3D Polygon. - bbox2d, bbox3d = gen_bbox() - Polygon2D.objects.create(name='2D BBox', poly=bbox2d) - Polygon3D.objects.create(name='3D BBox', poly=bbox3d) + self._load_city_data() + for name, pnt_data in city_data: + city = City3D.objects.get(name=name) + z = pnt_data[2] + self.assertTrue(city.point.hasz) + self.assertEqual(z, city.point.z) + + def test_3d_polygons(self): + """ + Test the creation of polygon 3D models. + """ + self._load_polygon_data() p3d = Polygon3D.objects.get(name='3D BBox') self.assertTrue(p3d.poly.hasz) - self.assertEqual(bbox3d, p3d.poly) - - def test01a_3d_layermapping(self): - "Testing LayerMapping on 3D models." - from .models import Point2D, Point3D + self.assertIsInstance(p3d.poly, Polygon) + self.assertEqual(p3d.poly.srid, 32140) + def test_3d_layermapping(self): + """ + Testing LayerMapping on 3D models. + """ point_mapping = {'point' : 'POINT'} mpoint_mapping = {'mpoint' : 'MULTIPOINT'} @@ -134,34 +146,46 @@ class Geo3DTest(TestCase): lm.save() self.assertEqual(3, MultiPoint3D.objects.count()) - def test02a_kml(self): - "Test GeoQuerySet.kml() with Z values." + def test_kml(self): + """ + Test GeoQuerySet.kml() with Z values. + """ + self._load_city_data() h = City3D.objects.kml(precision=6).get(name='Houston') # KML should be 3D. # `SELECT ST_AsKML(point, 6) FROM geo3d_city3d WHERE name = 'Houston';` ref_kml_regex = re.compile(r'^-95.363\d+,29.763\d+,18$') self.assertTrue(ref_kml_regex.match(h.kml)) - def test02b_geojson(self): - "Test GeoQuerySet.geojson() with Z values." + def test_geojson(self): + """ + Test GeoQuerySet.geojson() with Z values. + """ + self._load_city_data() h = City3D.objects.geojson(precision=6).get(name='Houston') # GeoJSON should be 3D # `SELECT ST_AsGeoJSON(point, 6) FROM geo3d_city3d WHERE name='Houston';` ref_json_regex = re.compile(r'^{"type":"Point","coordinates":\[-95.363151,29.763374,18(\.0+)?\]}$') self.assertTrue(ref_json_regex.match(h.geojson)) - def test03a_union(self): - "Testing the Union aggregate of 3D models." + def test_union(self): + """ + Testing the Union aggregate of 3D models. + """ # PostGIS query that returned the reference EWKT for this test: # `SELECT ST_AsText(ST_Union(point)) FROM geo3d_city3d;` + self._load_city_data() ref_ewkt = 'SRID=4326;MULTIPOINT(-123.305196 48.462611 15,-104.609252 38.255001 1433,-97.521157 34.464642 380,-96.801611 32.782057 147,-95.363151 29.763374 18,-95.23506 38.971823 251,-87.650175 41.850385 181,174.783117 -41.315268 14)' ref_union = GEOSGeometry(ref_ewkt) union = City3D.objects.aggregate(Union('point'))['point__union'] self.assertTrue(union.hasz) self.assertEqual(ref_union, union) - def test03b_extent(self): - "Testing the Extent3D aggregate for 3D models." + def test_extent(self): + """ + Testing the Extent3D aggregate for 3D models. + """ + self._load_city_data() # `SELECT ST_Extent3D(point) FROM geo3d_city3d;` ref_extent3d = (-123.305196, -41.315268, 14,174.783117, 48.462611, 1433) extent1 = City3D.objects.aggregate(Extent3D('point'))['point__extent3d'] @@ -174,8 +198,11 @@ class Geo3DTest(TestCase): for e3d in [extent1, extent2]: check_extent3d(e3d) - def test04_perimeter(self): - "Testing GeoQuerySet.perimeter() on 3D fields." + def test_perimeter(self): + """ + Testing GeoQuerySet.perimeter() on 3D fields. + """ + self._load_polygon_data() # Reference query for values below: # `SELECT ST_Perimeter3D(poly), ST_Perimeter2D(poly) FROM geo3d_polygon3d;` ref_perim_3d = 76859.2620451 @@ -188,12 +215,15 @@ class Geo3DTest(TestCase): Polygon3D.objects.perimeter().get(name='3D BBox').perimeter.m, tol) - def test05_length(self): - "Testing GeoQuerySet.length() on 3D fields." + def test_length(self): + """ + Testing GeoQuerySet.length() on 3D fields. + """ # ST_Length_Spheroid Z-aware, and thus does not need to use # a separate function internally. # `SELECT ST_Length_Spheroid(line, 'SPHEROID["GRS 1980",6378137,298.257222101]') # FROM geo3d_interstate[2d|3d];` + self._load_interstate_data() tol = 3 ref_length_2d = 4368.1721949481 ref_length_3d = 4368.62547052088 @@ -217,16 +247,22 @@ class Geo3DTest(TestCase): InterstateProj3D.objects.length().get(name='I-45').length.m, tol) - def test06_scale(self): - "Testing GeoQuerySet.scale() on Z values." + def test_scale(self): + """ + Testing GeoQuerySet.scale() on Z values. + """ + self._load_city_data() # Mapping of City name to reference Z values. zscales = (-3, 4, 23) for zscale in zscales: for city in City3D.objects.scale(1.0, 1.0, zscale): self.assertEqual(city_dict[city.name][2] * zscale, city.scale.z) - def test07_translate(self): - "Testing GeoQuerySet.translate() on Z values." + def test_translate(self): + """ + Testing GeoQuerySet.translate() on Z values. + """ + self._load_city_data() ztranslations = (5.23, 23, -17) for ztrans in ztranslations: for city in City3D.objects.translate(0, 0, ztrans): diff --git a/django/contrib/gis/tests/geoapp/tests.py b/django/contrib/gis/tests/geoapp/tests.py index cd3cec3074b..7fc870f64b7 100644 --- a/django/contrib/gis/tests/geoapp/tests.py +++ b/django/contrib/gis/tests/geoapp/tests.py @@ -520,8 +520,8 @@ class GeoQuerySetTest(TestCase): if oracle: # No precision parameter for Oracle :-/ gml_regex = re.compile(r'^-104.60925\d+,38.25500\d+ ') - elif spatialite: - # Spatialite has extra colon in SrsName + elif spatialite and connection.ops.spatial_version < (3, 0, 0): + # Spatialite before 3.0 has extra colon in SrsName gml_regex = re.compile(r'^-104.609251\d+,38.255001') else: gml_regex = re.compile(r'^-104\.60925\d+,38\.255001') diff --git a/django/contrib/gis/tests/layermap/tests.py b/django/contrib/gis/tests/layermap/tests.py index 85b8d0c8b5d..4912e645c24 100644 --- a/django/contrib/gis/tests/layermap/tests.py +++ b/django/contrib/gis/tests/layermap/tests.py @@ -8,6 +8,7 @@ from django.contrib.gis.gdal import DataSource from django.contrib.gis.tests.utils import mysql from django.contrib.gis.utils.layermapping import (LayerMapping, LayerMapError, InvalidDecimal, MissingForeignKey) +from django.db import router from django.test import TestCase from .models import ( @@ -26,6 +27,7 @@ NAMES = ['Bexar', 'Galveston', 'Harris', 'Honolulu', 'Pueblo'] NUMS = [1, 2, 1, 19, 1] # Number of polygons for each. STATES = ['Texas', 'Texas', 'Texas', 'Hawaii', 'Colorado'] + class LayerMapTest(TestCase): def test_init(self): @@ -281,3 +283,31 @@ class LayerMapTest(TestCase): lm.save(silent=True, strict=True) self.assertEqual(City.objects.count(), 3) self.assertEqual(City.objects.all().order_by('name_txt')[0].name_txt, "Houston") + + +class OtherRouter(object): + def db_for_read(self, model, **hints): + return 'other' + + def db_for_write(self, model, **hints): + return self.db_for_read(model, **hints) + + def allow_relation(self, obj1, obj2, **hints): + return None + + def allow_syncdb(self, db, model): + return True + + +class LayerMapRouterTest(TestCase): + + def setUp(self): + self.old_routers = router.routers + router.routers = [OtherRouter()] + + def tearDown(self): + router.routers = self.old_routers + + def test_layermapping_default_db(self): + lm = LayerMapping(City, city_shp, city_mapping) + self.assertEqual(lm.using, 'other') diff --git a/django/contrib/gis/utils/layermapping.py b/django/contrib/gis/utils/layermapping.py index e898f6de2eb..9511815426b 100644 --- a/django/contrib/gis/utils/layermapping.py +++ b/django/contrib/gis/utils/layermapping.py @@ -9,7 +9,7 @@ import sys from decimal import Decimal from django.core.exceptions import ObjectDoesNotExist -from django.db import connections, DEFAULT_DB_ALIAS +from django.db import connections, router from django.contrib.gis.db.models import GeometryField from django.contrib.gis.gdal import (CoordTransform, DataSource, OGRException, OGRGeometry, OGRGeomType, SpatialReference) @@ -67,7 +67,7 @@ class LayerMapping(object): def __init__(self, model, data, mapping, layer=0, source_srs=None, encoding=None, transaction_mode='commit_on_success', - transform=True, unique=None, using=DEFAULT_DB_ALIAS): + transform=True, unique=None, using=None): """ A LayerMapping object is initialized using the given Model (not an instance), a DataSource (or string path to an OGR-supported data file), and a mapping @@ -81,8 +81,8 @@ class LayerMapping(object): self.ds = data self.layer = self.ds[layer] - self.using = using - self.spatial_backend = connections[using].ops + self.using = using if using is not None else router.db_for_write(model) + self.spatial_backend = connections[self.using].ops # Setting the mapping & model attributes. self.mapping = mapping diff --git a/django/contrib/sessions/tests.py b/django/contrib/sessions/tests.py index 9aa602f4166..fc2d8753d7c 100644 --- a/django/contrib/sessions/tests.py +++ b/django/contrib/sessions/tests.py @@ -12,6 +12,7 @@ from django.contrib.sessions.backends.file import SessionStore as FileSession from django.contrib.sessions.backends.signed_cookies import SessionStore as CookieSession from django.contrib.sessions.models import Session from django.contrib.sessions.middleware import SessionMiddleware +from django.core.cache import DEFAULT_CACHE_ALIAS from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation from django.http import HttpResponse from django.test import TestCase, RequestFactory @@ -133,6 +134,9 @@ class SessionTestsMixin(object): self.assertTrue(self.session.modified) def test_save(self): + if (hasattr(self.session, '_cache') and + 'DummyCache' in settings.CACHES[DEFAULT_CACHE_ALIAS]['BACKEND']): + raise unittest.SkipTest("Session saving tests require a real cache backend") self.session.save() self.assertTrue(self.session.exists(self.session.session_key)) @@ -296,6 +300,8 @@ class CacheDBSessionTests(SessionTestsMixin, TestCase): backend = CacheDBSession + @unittest.skipIf('DummyCache' in settings.CACHES[DEFAULT_CACHE_ALIAS]['BACKEND'], + "Session saving tests require a real cache backend") def test_exists_searches_cache_first(self): self.session.save() with self.assertNumQueries(0): diff --git a/django/core/files/storage.py b/django/core/files/storage.py index 0b300cd31e4..650373f0c36 100644 --- a/django/core/files/storage.py +++ b/django/core/files/storage.py @@ -192,7 +192,10 @@ class FileSystemStorage(Storage): else: # This fun binary flag incantation makes os.open throw an # OSError if the file already exists before we open it. - fd = os.open(full_path, os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, 'O_BINARY', 0)) + flags = (os.O_WRONLY | os.O_CREAT | os.O_EXCL | + getattr(os, 'O_BINARY', 0)) + # The current umask value is masked out by os.open! + fd = os.open(full_path, flags, 0o666) try: locks.lock(fd, locks.LOCK_EX) _file = None diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py index 791382bac05..ba6e8f0be73 100644 --- a/django/core/handlers/base.py +++ b/django/core/handlers/base.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals +import logging import sys import types @@ -7,10 +8,9 @@ from django import http from django.core import signals from django.utils.encoding import force_text from django.utils.importlib import import_module -from django.utils.log import getLogger from django.utils import six -logger = getLogger('django.request') +logger = logging.getLogger('django.request') class BaseHandler(object): diff --git a/django/core/handlers/wsgi.py b/django/core/handlers/wsgi.py index 7a32a3dac74..7d2ee44e80e 100644 --- a/django/core/handlers/wsgi.py +++ b/django/core/handlers/wsgi.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals +import logging import sys from io import BytesIO from threading import Lock @@ -10,9 +11,8 @@ from django.core.handlers import base from django.core.urlresolvers import set_script_prefix from django.utils import datastructures from django.utils.encoding import force_str, force_text, iri_to_uri -from django.utils.log import getLogger -logger = getLogger('django.request') +logger = logging.getLogger('django.request') # See http://www.iana.org/assignments/http-status-codes diff --git a/django/core/mail/backends/locmem.py b/django/core/mail/backends/locmem.py index 642bfc49fb0..6826d09ee5f 100644 --- a/django/core/mail/backends/locmem.py +++ b/django/core/mail/backends/locmem.py @@ -20,5 +20,7 @@ class EmailBackend(BaseEmailBackend): def send_messages(self, messages): """Redirect messages to the dummy outbox""" + for message in messages: # .message() triggers header validation + message.message() mail.outbox.extend(messages) return len(messages) diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py index c8b88d5619c..5837922061b 100644 --- a/django/db/backends/postgresql_psycopg2/base.py +++ b/django/db/backends/postgresql_psycopg2/base.py @@ -3,6 +3,7 @@ PostgreSQL database backend for Django. Requires psycopg 2: http://initd.org/projects/psycopg2 """ +import logging import sys from django.db import utils @@ -14,7 +15,6 @@ from django.db.backends.postgresql_psycopg2.creation import DatabaseCreation from django.db.backends.postgresql_psycopg2.version import get_version from django.db.backends.postgresql_psycopg2.introspection import DatabaseIntrospection from django.utils.encoding import force_str -from django.utils.log import getLogger from django.utils.safestring import SafeText, SafeBytes from django.utils import six from django.utils.timezone import utc @@ -33,7 +33,7 @@ psycopg2.extensions.register_type(psycopg2.extensions.UNICODE) psycopg2.extensions.register_adapter(SafeBytes, psycopg2.extensions.QuotedString) psycopg2.extensions.register_adapter(SafeText, psycopg2.extensions.QuotedString) -logger = getLogger('django.db.backends') +logger = logging.getLogger('django.db.backends') def utc_tzinfo_factory(offset): if offset != 0: diff --git a/django/db/backends/util.py b/django/db/backends/util.py index 75d4d07a665..e029c42899d 100644 --- a/django/db/backends/util.py +++ b/django/db/backends/util.py @@ -3,15 +3,15 @@ from __future__ import unicode_literals import datetime import decimal import hashlib +import logging from time import time from django.conf import settings from django.utils.encoding import force_bytes -from django.utils.log import getLogger from django.utils.timezone import utc -logger = getLogger('django.db.backends') +logger = logging.getLogger('django.db.backends') class CursorWrapper(object): diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index 28d24048581..f06d6b11a4c 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -609,8 +609,12 @@ class SQLCompiler(object): restricted = False for f, model in opts.get_fields_with_model(): + # The get_fields_with_model() returns None for fields that live + # in the field's local model. So, for those fields we want to use + # the f.model - that is the field's local model. + field_model = model or f.model if not select_related_descend(f, restricted, requested, - only_load.get(model or self.query.model)): + only_load.get(field_model)): continue # The "avoid" set is aliases we want to avoid just for this # particular branch of the recursion. They aren't permanently diff --git a/django/middleware/common.py b/django/middleware/common.py index bb24977ce86..0ec17fbe92e 100644 --- a/django/middleware/common.py +++ b/django/middleware/common.py @@ -1,4 +1,5 @@ import hashlib +import logging import re from django.conf import settings @@ -6,9 +7,9 @@ from django import http from django.core.mail import mail_managers from django.utils.http import urlquote from django.core import urlresolvers -from django.utils.log import getLogger -logger = getLogger('django.request') + +logger = logging.getLogger('django.request') class CommonMiddleware(object): diff --git a/django/middleware/csrf.py b/django/middleware/csrf.py index c9e8d73c821..b2eb0df3f5f 100644 --- a/django/middleware/csrf.py +++ b/django/middleware/csrf.py @@ -7,6 +7,7 @@ against request forgeries from other sites. from __future__ import unicode_literals import hashlib +import logging import re import random @@ -15,10 +16,10 @@ from django.core.urlresolvers import get_callable from django.utils.cache import patch_vary_headers from django.utils.encoding import force_text from django.utils.http import same_origin -from django.utils.log import getLogger from django.utils.crypto import constant_time_compare, get_random_string -logger = getLogger('django.request') + +logger = logging.getLogger('django.request') REASON_NO_REFERER = "Referer checking failed - no Referer." REASON_BAD_REFERER = "Referer checking failed - %s does not match %s." diff --git a/django/test/testcases.py b/django/test/testcases.py index e5788ccd768..2b1ef912b68 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -649,6 +649,7 @@ class TransactionTestCase(SimpleTestCase): self.assertEqual(response.status_code, status_code, msg_prefix + "Couldn't retrieve content: Response code was %d" " (expected %d)" % (response.status_code, status_code)) + text = force_text(text, encoding=response._charset) content = response.content.decode(response._charset) if html: content = assert_and_parse_html(self, content, None, @@ -684,6 +685,7 @@ class TransactionTestCase(SimpleTestCase): self.assertEqual(response.status_code, status_code, msg_prefix + "Couldn't retrieve content: Response code was %d" " (expected %d)" % (response.status_code, status_code)) + text = force_text(text, encoding=response._charset) content = response.content.decode(response._charset) if html: content = assert_and_parse_html(self, content, None, diff --git a/django/utils/dateformat.py b/django/utils/dateformat.py index 6a91a370e5b..b2586ba1ff0 100644 --- a/django/utils/dateformat.py +++ b/django/utils/dateformat.py @@ -110,8 +110,8 @@ class TimeFormat(Formatter): return '%02d' % self.data.second def u(self): - "Microseconds" - return self.data.microsecond + "Microseconds; i.e. '000000' to '999999'" + return '%06d' %self.data.microsecond class DateFormat(TimeFormat): diff --git a/django/utils/html.py b/django/utils/html.py index 2b669cc8ecc..cc8372906bc 100644 --- a/django/utils/html.py +++ b/django/utils/html.py @@ -42,29 +42,26 @@ def escape(text): return mark_safe(force_text(text).replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"').replace("'", ''')) escape = allow_lazy(escape, six.text_type) -_base_js_escapes = ( - ('\\', '\\u005C'), - ('\'', '\\u0027'), - ('"', '\\u0022'), - ('>', '\\u003E'), - ('<', '\\u003C'), - ('&', '\\u0026'), - ('=', '\\u003D'), - ('-', '\\u002D'), - (';', '\\u003B'), - ('\u2028', '\\u2028'), - ('\u2029', '\\u2029') -) +_js_escapes = { + ord('\\'): '\\u005C', + ord('\''): '\\u0027', + ord('"'): '\\u0022', + ord('>'): '\\u003E', + ord('<'): '\\u003C', + ord('&'): '\\u0026', + ord('='): '\\u003D', + ord('-'): '\\u002D', + ord(';'): '\\u003B', + ord('\u2028'): '\\u2028', + ord('\u2029'): '\\u2029' +} # Escape every ASCII character with a value less than 32. -_js_escapes = (_base_js_escapes + - tuple([('%c' % z, '\\u%04X' % z) for z in range(32)])) +_js_escapes.update((ord('%c' % z), '\\u%04X' % z) for z in range(32)) def escapejs(value): """Hex encodes characters for use in JavaScript strings.""" - for bad, good in _js_escapes: - value = mark_safe(force_text(value).replace(bad, good)) - return value + return mark_safe(force_text(value).translate(_js_escapes)) escapejs = allow_lazy(escapejs, six.text_type) def conditional_escape(text): diff --git a/django/utils/numberformat.py b/django/utils/numberformat.py index d51b2308230..6a31237f135 100644 --- a/django/utils/numberformat.py +++ b/django/utils/numberformat.py @@ -21,12 +21,10 @@ def format(number, decimal_sep, decimal_pos=None, grouping=0, thousand_sep='', if isinstance(number, int) and not use_grouping and not decimal_pos: return mark_safe(six.text_type(number)) # sign - if float(number) < 0: - sign = '-' - else: - sign = '' + sign = '' str_number = six.text_type(number) if str_number[0] == '-': + sign = '-' str_number = str_number[1:] # decimal part if '.' in str_number: @@ -48,4 +46,3 @@ def format(number, decimal_sep, decimal_pos=None, grouping=0, thousand_sep='', int_part_gd += digit int_part = int_part_gd[::-1] return sign + int_part + dec_part - diff --git a/django/views/decorators/http.py b/django/views/decorators/http.py index d5c4bff7443..410979e1e4b 100644 --- a/django/views/decorators/http.py +++ b/django/views/decorators/http.py @@ -2,18 +2,18 @@ Decorators for views based on HTTP headers. """ +import logging from calendar import timegm from functools import wraps from django.utils.decorators import decorator_from_middleware, available_attrs from django.utils.http import http_date, parse_http_date_safe, parse_etags, quote_etag -from django.utils.log import getLogger from django.middleware.http import ConditionalGetMiddleware from django.http import HttpResponseNotAllowed, HttpResponseNotModified, HttpResponse conditional_page = decorator_from_middleware(ConditionalGetMiddleware) -logger = getLogger('django.request') +logger = logging.getLogger('django.request') def require_http_methods(request_method_list): diff --git a/django/views/generic/base.py b/django/views/generic/base.py index e11412ba4d2..d2349e1fca0 100644 --- a/django/views/generic/base.py +++ b/django/views/generic/base.py @@ -1,14 +1,15 @@ from __future__ import unicode_literals +import logging from functools import update_wrapper + from django import http from django.core.exceptions import ImproperlyConfigured from django.template.response import TemplateResponse -from django.utils.log import getLogger from django.utils.decorators import classonlymethod from django.utils import six -logger = getLogger('django.request') +logger = logging.getLogger('django.request') class ContextMixin(object): diff --git a/django/views/generic/dates.py b/django/views/generic/dates.py index 52e13a4533a..e1b0eb99fe5 100644 --- a/django/views/generic/dates.py +++ b/django/views/generic/dates.py @@ -377,7 +377,7 @@ class BaseDateListView(MultipleObjectMixin, DateMixin, View): """ return self.date_list_period - def get_date_list(self, queryset, date_type=None): + def get_date_list(self, queryset, date_type=None, ordering='ASC'): """ Get a date list by calling `queryset.dates()`, checking along the way for empty lists that aren't allowed. @@ -387,7 +387,7 @@ class BaseDateListView(MultipleObjectMixin, DateMixin, View): if date_type is None: date_type = self.get_date_list_period() - date_list = queryset.dates(date_field, date_type)[::-1] + date_list = queryset.dates(date_field, date_type, ordering) if date_list is not None and not date_list and not allow_empty: name = force_text(queryset.model._meta.verbose_name_plural) raise Http404(_("No %(verbose_name_plural)s available") % @@ -409,7 +409,7 @@ class BaseArchiveIndexView(BaseDateListView): Return (date_list, items, extra_context) for this request. """ qs = self.get_dated_queryset(ordering='-%s' % self.get_date_field()) - date_list = self.get_date_list(qs) + date_list = self.get_date_list(qs, ordering='DESC') if not date_list: qs = qs.none() diff --git a/docs/howto/custom-template-tags.txt b/docs/howto/custom-template-tags.txt index 5b27af82d69..70b6288beed 100644 --- a/docs/howto/custom-template-tags.txt +++ b/docs/howto/custom-template-tags.txt @@ -760,8 +760,6 @@ A few things to note about the ``simple_tag`` helper function: * If the argument was a template variable, our function is passed the current value of the variable, not the variable itself. -.. versionadded:: 1.3 - If your template tag needs to access the current context, you can use the ``takes_context`` argument when registering your tag: diff --git a/docs/howto/error-reporting.txt b/docs/howto/error-reporting.txt index 64af2a09803..78e797b6075 100644 --- a/docs/howto/error-reporting.txt +++ b/docs/howto/error-reporting.txt @@ -44,8 +44,6 @@ setting. .. seealso:: - .. versionadded:: 1.3 - Server error emails are sent using the logging framework, so you can customize this behavior by :doc:`customizing your logging configuration `. @@ -99,8 +97,6 @@ The best way to disable this behavior is to set .. seealso:: - .. versionadded:: 1.3 - 404 errors are logged using the logging framework. By default, these log records are ignored, but you can use them for error reporting by writing a handler and :doc:`configuring logging ` appropriately. diff --git a/docs/howto/static-files.txt b/docs/howto/static-files.txt index f8c591891de..964b5fab610 100644 --- a/docs/howto/static-files.txt +++ b/docs/howto/static-files.txt @@ -2,8 +2,6 @@ Managing static files ===================== -.. versionadded:: 1.3 - Django developers mostly concern themselves with the dynamic parts of web applications -- the views and templates that render anew for each request. But web applications have other parts: the static files (images, CSS, diff --git a/docs/internals/committers.txt b/docs/internals/committers.txt index ca56d36880e..7900dd8cd0d 100644 --- a/docs/internals/committers.txt +++ b/docs/internals/committers.txt @@ -407,6 +407,18 @@ Jeremy Dunck .. _vlogger: http://youtube.com/bryanveloso/ .. _shoutcaster: http://twitch.tv/vlogalonstar/ +`Preston Holmes`_ + Preston is a recovering neuroscientist who originally discovered Django as + part of a sweeping move to Python from a grab bag of half a dozen + languages. He was drawn to Django's balance of practical batteries included + philosophy, care and thought in code design, and strong open source + community. In addition to his current job in private progressive education, + Preston contributes some developer time to local non-profits. + + Preston lives with his family and animal menagerie in Santa Barbara, CA, USA. + +.. _Preston Holmes: http://www.ptone.com/ + Specialists ----------- diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 45febb7c6b6..4e341c6953a 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -134,7 +134,7 @@ these changes. * The function-based generic view modules will be removed in favor of their class-based equivalents, outlined :doc:`here - `: + `. * The :class:`~django.core.servers.basehttp.AdminMediaHandler` will be removed. In its place use diff --git a/docs/misc/api-stability.txt b/docs/misc/api-stability.txt index 2839ee3594a..4f232e795bd 100644 --- a/docs/misc/api-stability.txt +++ b/docs/misc/api-stability.txt @@ -155,8 +155,6 @@ Certain APIs are explicitly marked as "internal" in a couple of ways: Local flavors ------------- -.. versionchanged:: 1.3 - :mod:`django.contrib.localflavor` contains assorted pieces of code that are useful for particular countries or cultures. This data is local in nature, and is subject to change on timelines that will diff --git a/docs/ref/class-based-views/generic-date-based.txt b/docs/ref/class-based-views/generic-date-based.txt index 64b269f5147..c6af23e421b 100644 --- a/docs/ref/class-based-views/generic-date-based.txt +++ b/docs/ref/class-based-views/generic-date-based.txt @@ -87,16 +87,24 @@ YearArchiveView * ``year``: A :class:`~datetime.date` object representing the given year. + .. versionchanged:: 1.5 + + Previously, this returned a string. + * ``next_year``: A :class:`~datetime.date` object representing the first day of the next year, according to :attr:`~BaseDateListView.allow_empty` and :attr:`~DateMixin.allow_future`. + .. versionadded:: 1.5 + * ``previous_year``: A :class:`~datetime.date` object representing the first day of the previous year, according to :attr:`~BaseDateListView.allow_empty` and :attr:`~DateMixin.allow_future`. + .. versionadded:: 1.5 + **Notes** * Uses a default ``template_name_suffix`` of ``_archive_year``. diff --git a/docs/ref/class-based-views/mixins-date-based.txt b/docs/ref/class-based-views/mixins-date-based.txt index 01181ebb6c6..561e525e704 100644 --- a/docs/ref/class-based-views/mixins-date-based.txt +++ b/docs/ref/class-based-views/mixins-date-based.txt @@ -318,12 +318,16 @@ BaseDateListView Returns the aggregation period for ``date_list``. Returns :attr:`~BaseDateListView.date_list_period` by default. - .. method:: get_date_list(queryset, date_type=None) + .. method:: get_date_list(queryset, date_type=None, ordering='ASC') Returns the list of dates of type ``date_type`` for which ``queryset`` contains entries. For example, ``get_date_list(qs, 'year')`` will return the list of years for which ``qs`` has entries. If ``date_type`` isn't provided, the result of - :meth:`BaseDateListView.get_date_list_period` is used. See - :meth:`~django.db.models.query.QuerySet.dates()` for the ways that the - ``date_type`` argument can be used. + :meth:`~BaseDateListView.get_date_list_period` is used. ``date_type`` + and ``ordering`` are simply passed to + :meth:`QuerySet.dates()`. + + .. versionchanged:: 1.5 + The ``ordering`` parameter was added, and the default order was + changed to ascending. diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 66a5a2cc4f2..2aabc559086 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -129,8 +129,6 @@ subclass:: date_hierarchy = 'pub_date' - .. versionadded:: 1.3 - This will intelligently populate itself based on available data, e.g. if all the dates are in one month, it'll show the day-level drill-down only. @@ -576,8 +574,6 @@ subclass:: class PersonAdmin(ModelAdmin): list_filter = ('is_staff', 'company') - .. versionadded:: 1.3 - Field names in ``list_filter`` can also span relations using the ``__`` lookup, for example:: @@ -748,8 +744,6 @@ subclass:: .. attribute:: ModelAdmin.paginator - .. versionadded:: 1.3 - The paginator class to be used for pagination. By default, :class:`django.core.paginator.Paginator` is used. If the custom paginator class doesn't have the same constructor interface as @@ -966,8 +960,6 @@ templates used by the :class:`ModelAdmin` views: .. method:: ModelAdmin.delete_model(self, request, obj) - .. versionadded:: 1.3 - The ``delete_model`` method is given the ``HttpRequest`` and a model instance. Use this method to do pre- or post-delete operations. @@ -1213,8 +1205,6 @@ templates used by the :class:`ModelAdmin` views: .. method:: ModelAdmin.get_paginator(queryset, per_page, orphans=0, allow_empty_first_page=True) - .. versionadded:: 1.3 - Returns an instance of the paginator to use for this view. By default, instantiates an instance of :attr:`paginator`. @@ -1295,8 +1285,6 @@ on your ``ModelAdmin``:: } js = ("my_code.js",) -.. versionchanged:: 1.3 - The :doc:`staticfiles app ` prepends :setting:`STATIC_URL` (or :setting:`MEDIA_URL` if :setting:`STATIC_URL` is ``None``) to any media paths. The same rules apply as :ref:`regular media @@ -1394,18 +1382,15 @@ adds some of its own (the shared features are actually defined in the - :attr:`~ModelAdmin.exclude` - :attr:`~ModelAdmin.filter_horizontal` - :attr:`~ModelAdmin.filter_vertical` +- :attr:`~ModelAdmin.ordering` - :attr:`~ModelAdmin.prepopulated_fields` +- :meth:`~ModelAdmin.queryset` - :attr:`~ModelAdmin.radio_fields` - :attr:`~ModelAdmin.readonly_fields` - :attr:`~InlineModelAdmin.raw_id_fields` - :meth:`~ModelAdmin.formfield_for_foreignkey` - :meth:`~ModelAdmin.formfield_for_manytomany` -.. versionadded:: 1.3 - -- :attr:`~ModelAdmin.ordering` -- :meth:`~ModelAdmin.queryset` - .. versionadded:: 1.4 - :meth:`~ModelAdmin.has_add_permission` @@ -1813,8 +1798,6 @@ Templates can override or extend base admin templates as described in .. attribute:: AdminSite.login_form - .. versionadded:: 1.3 - Subclass of :class:`~django.contrib.auth.forms.AuthenticationForm` that will be used by the admin site login view. diff --git a/docs/ref/contrib/comments/example.txt b/docs/ref/contrib/comments/example.txt index e78d83c35da..2bff778c2fe 100644 --- a/docs/ref/contrib/comments/example.txt +++ b/docs/ref/contrib/comments/example.txt @@ -152,27 +152,6 @@ enable it in your project's ``urls.py``: Now you should have the latest comment feeds being served off ``/feeds/latest/``. -.. versionchanged:: 1.3 - -Prior to Django 1.3, the LatestCommentFeed was deployed using the -syndication feed view: - -.. code-block:: python - - from django.conf.urls import patterns - from django.contrib.comments.feeds import LatestCommentFeed - - feeds = { - 'latest': LatestCommentFeed, - } - - urlpatterns = patterns('', - # ... - (r'^feeds/(?P.*)/$', 'django.contrib.syndication.views.feed', - {'feed_dict': feeds}), - # ... - ) - Moderation ========== diff --git a/docs/ref/contrib/comments/moderation.txt b/docs/ref/contrib/comments/moderation.txt index f03c7fda0d5..39b3ea7913a 100644 --- a/docs/ref/contrib/comments/moderation.txt +++ b/docs/ref/contrib/comments/moderation.txt @@ -136,10 +136,6 @@ Simply subclassing :class:`CommentModerator` and changing the values of these options will automatically enable the various moderation methods for any models registered using the subclass. -.. versionchanged:: 1.3 - -``moderate_after`` and ``close_after`` now accept 0 as a valid value. - Adding custom moderation methods -------------------------------- diff --git a/docs/ref/contrib/contenttypes.txt b/docs/ref/contrib/contenttypes.txt index e98da6e4298..dfbeabc3024 100644 --- a/docs/ref/contrib/contenttypes.txt +++ b/docs/ref/contrib/contenttypes.txt @@ -423,8 +423,6 @@ pointing at it will be deleted as well. In the example above, this means that if a ``Bookmark`` object were deleted, any ``TaggedItem`` objects pointing at it would be deleted at the same time. -.. versionadded:: 1.3 - Unlike :class:`~django.db.models.ForeignKey`, :class:`~django.contrib.contenttypes.generic.GenericForeignKey` does not accept an :attr:`~django.db.models.ForeignKey.on_delete` argument to customize this diff --git a/docs/ref/contrib/flatpages.txt b/docs/ref/contrib/flatpages.txt index 3de449708f8..38cedc40fe7 100644 --- a/docs/ref/contrib/flatpages.txt +++ b/docs/ref/contrib/flatpages.txt @@ -239,8 +239,6 @@ template. Getting a list of :class:`~django.contrib.flatpages.models.FlatPage` objects in your templates ============================================================================================== -.. versionadded:: 1.3 - The flatpages app provides a template tag that allows you to iterate over all of the available flatpages on the :ref:`current site `. diff --git a/docs/ref/contrib/gis/admin.txt b/docs/ref/contrib/gis/admin.txt index aa6ba58630c..d1a9fc1dcb3 100644 --- a/docs/ref/contrib/gis/admin.txt +++ b/docs/ref/contrib/gis/admin.txt @@ -45,7 +45,7 @@ GeoDjango's admin site .. attribute:: openlayers_url Link to the URL of the OpenLayers JavaScript. Defaults to - ``'http://openlayers.org/api/2.8/OpenLayers.js'``. + ``'http://openlayers.org/api/2.11/OpenLayers.js'``. .. attribute:: modifiable diff --git a/docs/ref/contrib/gis/geos.txt b/docs/ref/contrib/gis/geos.txt index f4e706d2757..b569a74fe35 100644 --- a/docs/ref/contrib/gis/geos.txt +++ b/docs/ref/contrib/gis/geos.txt @@ -163,6 +163,11 @@ WKB / EWKB ``buffer`` GeoJSON ``str`` or ``unicode`` ============= ====================== +.. note:: + + The new 3D/4D WKT notation with an intermediary Z or M (like + ``POINT Z (3, 4, 5)``) is only supported with GEOS 3.3.0 or later. + Properties ~~~~~~~~~~ @@ -232,8 +237,6 @@ Returns a boolean indicating whether the geometry is valid. .. attribute:: GEOSGeometry.valid_reason -.. versionadded:: 1.3 - Returns a string describing the reason why a geometry is invalid. .. attribute:: GEOSGeometry.srid @@ -530,8 +533,6 @@ corresponding to the SRID of the geometry or ``None``. .. method:: GEOSGeometry.transform(ct, clone=False) -.. versionchanged:: 1.3 - Transforms the geometry according to the given coordinate transformation paramter (``ct``), which may be an integer SRID, spatial reference WKT string, a PROJ.4 string, a :class:`~django.contrib.gis.gdal.SpatialReference` object, or a diff --git a/docs/ref/contrib/gis/testing.txt b/docs/ref/contrib/gis/testing.txt index d12c884a1b7..86979f0308a 100644 --- a/docs/ref/contrib/gis/testing.txt +++ b/docs/ref/contrib/gis/testing.txt @@ -134,8 +134,6 @@ your settings:: GeoDjango tests =============== -.. versionchanged:: 1.3 - GeoDjango's test suite may be run in one of two ways, either by itself or with the rest of :ref:`Django's unit tests `. diff --git a/docs/ref/contrib/localflavor.txt b/docs/ref/contrib/localflavor.txt index 4595f51d9ef..0d1319ec613 100644 --- a/docs/ref/contrib/localflavor.txt +++ b/docs/ref/contrib/localflavor.txt @@ -267,8 +267,6 @@ Austria (``at``) Belgium (``be``) ================ -.. versionadded:: 1.3 - .. class:: be.forms.BEPhoneNumberField A form field that validates input as a Belgium phone number, with one of @@ -658,11 +656,6 @@ Indonesia (``id``) A ``Select`` widget that uses a list of Indonesian provinces as its choices. -.. versionchanged:: 1.3 - The province "Nanggroe Aceh Darussalam (NAD)" has been removed - from the province list in favor of the new official designation - "Aceh (ACE)". - .. class:: id.forms.IDPhoneNumberField A form field that validates input as an Indonesian telephone number. diff --git a/docs/ref/contrib/sitemaps.txt b/docs/ref/contrib/sitemaps.txt index 2393a4a9a32..ef6c64dc61d 100644 --- a/docs/ref/contrib/sitemaps.txt +++ b/docs/ref/contrib/sitemaps.txt @@ -330,8 +330,6 @@ with a caching decorator -- you must name your sitemap view and pass Template customization ====================== -.. versionadded:: 1.3 - If you wish to use a different template for each sitemap or sitemap index available on your site, you may specify it by passing a ``template_name`` parameter to the ``sitemap`` and ``index`` views via the URLconf:: diff --git a/docs/ref/contrib/sites.txt b/docs/ref/contrib/sites.txt index 8fc434ba9b5..8bb7b27f32a 100644 --- a/docs/ref/contrib/sites.txt +++ b/docs/ref/contrib/sites.txt @@ -159,8 +159,6 @@ the :class:`~django.contrib.sites.models.Site` model's manager has a else: # Do something else. -.. versionchanged:: 1.3 - For code which relies on getting the current domain but cannot be certain that the sites framework will be installed for any given project, there is a utility function :func:`~django.contrib.sites.models.get_current_site` that @@ -169,12 +167,10 @@ the sites framework is installed) or a RequestSite instance (if it is not). This allows loose coupling with the sites framework and provides a usable fallback for cases where it is not installed. -.. versionadded:: 1.3 - .. function:: get_current_site(request) Checks if contrib.sites is installed and returns either the current - :class:`~django.contrib.sites.models.Site` object or a + :class:`~django.contrib.sites.models.Site` object or a :class:`~django.contrib.sites.models.RequestSite` object based on the request. @@ -437,7 +433,7 @@ fallback when the database-backed sites framework is not available. Sets the ``name`` and ``domain`` attributes to the value of :meth:`~django.http.HttpRequest.get_host`. - + A :class:`~django.contrib.sites.models.RequestSite` object has a similar interface to a normal :class:`~django.contrib.sites.models.Site` object, except diff --git a/docs/ref/contrib/staticfiles.txt b/docs/ref/contrib/staticfiles.txt index cbe8ad54b87..3a74797145d 100644 --- a/docs/ref/contrib/staticfiles.txt +++ b/docs/ref/contrib/staticfiles.txt @@ -5,8 +5,6 @@ The staticfiles app .. module:: django.contrib.staticfiles :synopsis: An app for handling static files. -.. versionadded:: 1.3 - ``django.contrib.staticfiles`` collects static files from each of your applications (and any other places you specify) into a single location that can easily be served in production. diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 5ff7ecba2c7..467e32c86d2 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -176,8 +176,6 @@ records to dump. If you're using a :ref:`custom manager ` as the default manager and it filters some of the available records, not all of the objects will be dumped. -.. versionadded:: 1.3 - The :djadminopt:`--all` option may be provided to specify that ``dumpdata`` should use Django's base manager, dumping records which might otherwise be filtered or modified by a custom manager. @@ -195,18 +193,10 @@ easy for humans to read, so you can use the ``--indent`` option to pretty-print the output with a number of indentation spaces. The :djadminopt:`--exclude` option may be provided to prevent specific -applications from being dumped. - -.. versionadded:: 1.3 - -The :djadminopt:`--exclude` option may also be provided to prevent specific -models (specified as in the form of ``appname.ModelName``) from being dumped. - -In addition to specifying application names, you can provide a list of -individual models, in the form of ``appname.Model``. If you specify a model -name to ``dumpdata``, the dumped output will be restricted to that model, -rather than the entire application. You can also mix application names and -model names. +applications or models (specified as in the form of ``appname.ModelName``) from +being dumped. If you specify a model name to ``dumpdata``, the dumped output +will be restricted to that model, rather than the entire application. You can +also mix application names and model names. The :djadminopt:`--database` option can be used to specify the database from which data will be dumped. @@ -463,8 +453,6 @@ Use the ``--no-default-ignore`` option to disable the default values of .. django-admin-option:: --no-wrap -.. versionadded:: 1.3 - Use the ``--no-wrap`` option to disable breaking long message lines into several lines in language files. @@ -640,15 +628,11 @@ machines on your network. To make your development server viewable to other machines on the network, use its own IP address (e.g. ``192.168.2.1``) or ``0.0.0.0`` or ``::`` (with IPv6 enabled). -.. versionchanged:: 1.3 - You can provide an IPv6 address surrounded by brackets (e.g. ``[200a::1]:8000``). This will automatically enable IPv6 support. A hostname containing ASCII-only characters can also be used. -.. versionchanged:: 1.3 - If the :doc:`staticfiles` contrib app is enabled (default in new projects) the :djadmin:`runserver` command will be overriden with an own :djadmin:`runserver` command. @@ -674,8 +658,6 @@ development server. .. django-admin-option:: --ipv6, -6 -.. versionadded:: 1.3 - Use the ``--ipv6`` (or shorter ``-6``) option to tell Django to use IPv6 for the development server. This changes the default IP address from ``127.0.0.1`` to ``::1``. @@ -1113,8 +1095,6 @@ To run on 1.2.3.4:7000 with a ``test`` fixture:: django-admin.py testserver --addrport 1.2.3.4:7000 test -.. versionadded:: 1.3 - The :djadminopt:`--noinput` option may be provided to suppress all user prompts. diff --git a/docs/ref/files/storage.txt b/docs/ref/files/storage.txt index b3f89098475..f9bcf9b61e8 100644 --- a/docs/ref/files/storage.txt +++ b/docs/ref/files/storage.txt @@ -18,7 +18,7 @@ Django provides two convenient ways to access the current storage class: .. function:: get_storage_class([import_path=None]) Returns a class or module which implements the storage API. - + When called without the ``import_path`` parameter ``get_storage_class`` will return the current default storage system as defined by :setting:`DEFAULT_FILE_STORAGE`. If ``import_path`` is provided, @@ -35,9 +35,9 @@ The FileSystemStorage Class basic file storage on a local filesystem. It inherits from :class:`~django.core.files.storage.Storage` and provides implementations for all the public methods thereof. - + .. note:: - + The :class:`FileSystemStorage.delete` method will not raise raise an exception if the given file name does not exist. @@ -53,16 +53,12 @@ The Storage Class .. method:: accessed_time(name) - .. versionadded:: 1.3 - Returns a ``datetime`` object containing the last accessed time of the file. For storage systems that aren't able to return the last accessed time this will raise ``NotImplementedError`` instead. .. method:: created_time(name) - .. versionadded:: 1.3 - Returns a ``datetime`` object containing the creation time of the file. For storage systems that aren't able to return the creation time this will raise ``NotImplementedError`` instead. @@ -100,8 +96,6 @@ The Storage Class .. method:: modified_time(name) - .. versionadded:: 1.3 - Returns a ``datetime`` object containing the last modified time. For storage systems that aren't able to return the last modified time, this will raise ``NotImplementedError`` instead. diff --git a/docs/ref/forms/api.txt b/docs/ref/forms/api.txt index 777d73e0150..23234252777 100644 --- a/docs/ref/forms/api.txt +++ b/docs/ref/forms/api.txt @@ -658,8 +658,6 @@ those classes as an argument:: .. method:: BoundField.value() - .. versionadded:: 1.3 - Use this method to render the raw value of this field as it would be rendered by a ``Widget``:: diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index 7c06bf97eef..9f3dc68b4da 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -704,8 +704,6 @@ For each field, we describe the default widget used if you don't specify ``TypedMultipleChoiceField`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. versionadded:: 1.3 - .. class:: TypedMultipleChoiceField(**kwargs) Just like a :class:`MultipleChoiceField`, except :class:`TypedMultipleChoiceField` diff --git a/docs/ref/forms/widgets.txt b/docs/ref/forms/widgets.txt index 4724cbdec2e..3c458930fa7 100644 --- a/docs/ref/forms/widgets.txt +++ b/docs/ref/forms/widgets.txt @@ -294,11 +294,6 @@ These widgets make use of the HTML elements ``input`` and ``textarea``. Determines whether the widget will have a value filled in when the form is re-displayed after a validation error (default is ``False``). - .. versionchanged:: 1.3 - The default value for - :attr:`~PasswordInput.render_value` was - changed from ``True`` to ``False`` - ``HiddenInput`` ~~~~~~~~~~~~~~~ @@ -532,8 +527,6 @@ File upload widgets .. class:: ClearableFileInput - .. versionadded:: 1.3 - File upload input: ````, with an additional checkbox input to clear the field's value, if the field is not required and has initial data. diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index 8b3c31f0294..4797e8b26bb 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -1023,8 +1023,6 @@ define the details of how the relation works. The field on the related object that the relation is to. By default, Django uses the primary key of the related object. -.. versionadded:: 1.3 - .. attribute:: ForeignKey.on_delete When an object referenced by a :class:`ForeignKey` is deleted, Django by @@ -1081,6 +1079,9 @@ the model is related. This works exactly the same as it does for :class:`ForeignKey`, including all the options regarding :ref:`recursive ` and :ref:`lazy ` relationships. +Related objects can be added, removed, or created with the field's +:class:`~django.db.models.fields.related.RelatedManager`. + Database Representation ~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 8ec7cfc7917..749a979db6a 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -505,15 +505,8 @@ followed (optionally) by any output-affecting methods (such as ``values()``), but it doesn't really matter. This is your chance to really flaunt your individualism. -.. versionchanged:: 1.3 - -The ``values()`` method previously did not return anything for -:class:`~django.db.models.ManyToManyField` attributes and would raise an error -if you tried to pass this type of field to it. - -This restriction has been lifted, and you can now also refer to fields on -related models with reverse relations through ``OneToOneField``, ``ForeignKey`` -and ``ManyToManyField`` attributes:: +You can also refer to fields on related models with reverse relations through +``OneToOneField``, ``ForeignKey`` and ``ManyToManyField`` attributes:: Blog.objects.values('name', 'entry__headline') [{'name': 'My blog', 'entry__headline': 'An entry'}, @@ -1664,10 +1657,9 @@ For example:: # This will delete all Blogs and all of their Entry objects. blogs.delete() -.. versionadded:: 1.3 - This cascade behavior is customizable via the - :attr:`~django.db.models.ForeignKey.on_delete` argument to the - :class:`~django.db.models.ForeignKey`. +This cascade behavior is customizable via the +:attr:`~django.db.models.ForeignKey.on_delete` argument to the +:class:`~django.db.models.ForeignKey`. The ``delete()`` method does a bulk delete and does not call any ``delete()`` methods on your models. It does, however, emit the diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt index 21e99de10d9..cc2a351d8e4 100644 --- a/docs/ref/request-response.txt +++ b/docs/ref/request-response.txt @@ -42,8 +42,6 @@ All attributes should be considered read-only, unless stated otherwise below. data in different ways than conventional HTML forms: binary images, XML payload etc. For processing conventional form data, use ``HttpRequest.POST``. - .. versionadded:: 1.3 - You can also read from an HttpRequest using a file-like interface. See :meth:`HttpRequest.read()`. @@ -305,8 +303,6 @@ Methods .. method:: HttpRequest.xreadlines() .. method:: HttpRequest.__iter__() - .. versionadded:: 1.3 - Methods implementing a file-like interface for reading from an HttpRequest instance. This makes it possible to consume an incoming request in a streaming fashion. A common use-case would be to process a @@ -509,9 +505,6 @@ In addition, ``QueryDict`` has the following methods: >>> q.urlencode() 'a=2&b=3&b=5' - .. versionchanged:: 1.3 - The ``safe`` parameter was added. - Optionally, urlencode can be passed characters which do not require encoding. For example:: @@ -648,12 +641,6 @@ Methods .. method:: HttpResponse.set_cookie(key, value='', max_age=None, expires=None, path='/', domain=None, secure=None, httponly=True) - .. versionchanged:: 1.3 - - The possibility of specifying a ``datetime.datetime`` object in - ``expires``, and the auto-calculation of ``max_age`` in such case - was added. The ``httponly`` argument was also added. - .. versionchanged:: 1.4 The default value for httponly was changed from ``False`` to ``True``. diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 6c289d4662c..a909c12665b 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -124,8 +124,6 @@ The model to use to represent a User. See :ref:`auth-custom-user`. CACHES ------ -.. versionadded:: 1.3 - Default:: { @@ -166,12 +164,6 @@ backend class (i.e. ``mypackage.backends.whatever.WhateverCache``). Writing a whole new cache backend from scratch is left as an exercise to the reader; see the other backends for examples. -.. note:: - Prior to Django 1.3, you could use a URI based version of the backend - name to reference the built-in cache backends (e.g., you could use - ``'db://tablename'`` to refer to the database backend). This format has - been deprecated, and will be removed in Django 1.5. - .. setting:: CACHES-KEY_FUNCTION KEY_FUNCTION @@ -533,8 +525,6 @@ Only supported for the ``mysql`` backend (see the `MySQL manual`_ for details). TEST_DEPENDENCIES ~~~~~~~~~~~~~~~~~ -.. versionadded:: 1.3 - Default: ``['default']``, for all databases other than ``default``, which has no dependencies. @@ -1261,8 +1251,6 @@ the ``locale`` directory (i.e. ``'/path/to/locale'``). LOGGING ------- -.. versionadded:: 1.3 - Default: A logging configuration dictionary. A data structure containing configuration information. The contents of @@ -1277,8 +1265,6 @@ email log handler; all other log messages are given to a NullHandler. LOGGING_CONFIG -------------- -.. versionadded:: 1.3 - Default: ``'django.utils.log.dictConfig'`` A path to a callable that will be used to configure logging in the @@ -1370,13 +1356,11 @@ MEDIA_URL Default: ``''`` (Empty string) URL that handles the media served from :setting:`MEDIA_ROOT`, used -for :doc:`managing stored files `. +for :doc:`managing stored files `. It must end in a slash if set +to a non-empty value. Example: ``"http://media.example.com/"`` -.. versionchanged:: 1.3 - It must end in a slash if set to a non-empty value. - MESSAGE_LEVEL ------------- @@ -1895,10 +1879,6 @@ A tuple of callables that are used to populate the context in ``RequestContext`` These callables take a request object as their argument and return a dictionary of items to be merged into the context. -.. versionadded:: 1.3 - The ``django.core.context_processors.static`` context processor - was added in this release. - .. versionadded:: 1.4 The ``django.core.context_processors.tz`` context processor was added in this release. @@ -2159,8 +2139,6 @@ See also :setting:`TIME_ZONE`, :setting:`USE_I18N` and :setting:`USE_L10N`. USE_X_FORWARDED_HOST -------------------- -.. versionadded:: 1.3.1 - Default: ``False`` A boolean that specifies whether to use the X-Forwarded-Host header in diff --git a/docs/ref/signals.txt b/docs/ref/signals.txt index b2f2e85abc9..0db540370dd 100644 --- a/docs/ref/signals.txt +++ b/docs/ref/signals.txt @@ -46,7 +46,7 @@ pre_init .. ^^^^^^^ this :module: hack keeps Sphinx from prepending the module. -Whenever you instantiate a Django model,, this signal is sent at the beginning +Whenever you instantiate a Django model, this signal is sent at the beginning of the model's :meth:`~django.db.models.Model.__init__` method. Arguments sent with this signal: @@ -118,8 +118,6 @@ Arguments sent with this signal: records in the database as the database might not be in a consistent state yet. -.. versionadded:: 1.3 - ``using`` The database alias being used. @@ -155,8 +153,6 @@ Arguments sent with this signal: records in the database as the database might not be in a consistent state yet. -.. versionadded:: 1.3 - ``using`` The database alias being used. @@ -183,8 +179,6 @@ Arguments sent with this signal: ``instance`` The actual instance being deleted. -.. versionadded:: 1.3 - ``using`` The database alias being used. @@ -209,8 +203,6 @@ Arguments sent with this signal: Note that the object will no longer be in the database, so be very careful what you do with this instance. -.. versionadded:: 1.3 - ``using`` The database alias being used. @@ -271,8 +263,6 @@ Arguments sent with this signal: For the ``pre_clear`` and ``post_clear`` actions, this is ``None``. -.. versionadded:: 1.3 - ``using`` The database alias being used. @@ -287,13 +277,22 @@ like this:: # ... toppings = models.ManyToManyField(Topping) -If we would do something like this: +If we connected a handler like this:: + + def toppings_changed(sender, **kwargs): + # Do something + pass + + m2m_changed.connect(toppings_changed, sender=Pizza.toppings.through) + +and then did something like this:: >>> p = Pizza.object.create(...) >>> t = Topping.objects.create(...) >>> p.toppings.add(t) -the arguments sent to a :data:`m2m_changed` handler would be: +the arguments sent to a :data:`m2m_changed` handler (``topppings_changed`` in +the example above) would be: ============== ============================================================ Argument Value diff --git a/docs/ref/template-response.txt b/docs/ref/template-response.txt index 9e09077adcf..d9b7130362d 100644 --- a/docs/ref/template-response.txt +++ b/docs/ref/template-response.txt @@ -2,8 +2,6 @@ TemplateResponse and SimpleTemplateResponse =========================================== -.. versionadded:: 1.3 - .. module:: django.template.response :synopsis: Classes dealing with lazy-rendered HTTP responses. diff --git a/docs/ref/templates/api.txt b/docs/ref/templates/api.txt index 48bd3467880..f29d2acc121 100644 --- a/docs/ref/templates/api.txt +++ b/docs/ref/templates/api.txt @@ -160,11 +160,6 @@ it. Example:: >>> t.render(Context({"person": PersonClass2})) "My name is Samantha." -.. versionchanged:: 1.3 - Previously, only variables that originated with an attribute lookup would - be called by the template system. This change was made for consistency - across lookup types. - Callable variables are slightly more complex than variables which only require straight lookups. Here are some things to keep in mind: @@ -448,11 +443,6 @@ If :setting:`TEMPLATE_CONTEXT_PROCESSORS` contains this processor, every ``django.contrib.auth.context_processors.PermWrapper``, representing the permissions that the currently logged-in user has. -.. versionchanged:: 1.3 - Prior to version 1.3, ``PermWrapper`` was located in - ``django.contrib.auth.context_processors``. - - django.core.context_processors.debug ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -491,8 +481,6 @@ django.core.context_processors.static .. function:: django.core.context_processors.static -.. versionadded:: 1.3 - If :setting:`TEMPLATE_CONTEXT_PROCESSORS` contains this processor, every ``RequestContext`` will contain a variable ``STATIC_URL``, providing the value of the :setting:`STATIC_URL` setting. diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 072eebf69fb..07ac2849050 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -156,8 +156,6 @@ In this syntax, each value gets interpreted as a literal string, and there's no way to specify variable values. Or literal commas. Or spaces. Did we mention you shouldn't use this syntax in any new projects? -.. versionadded:: 1.3 - By default, when you use the ``as`` keyword with the cycle tag, the usage of ``{% cycle %}`` that declares the cycle will itself output the first value in the cycle. This could be a problem if you want to @@ -676,9 +674,6 @@ including it. This example produces the output ``"Hello, John"``: {{ greeting }}, {{ person|default:"friend" }}! -.. versionchanged:: 1.3 - Additional context and exclusive context. - You can pass additional context to the template using keyword arguments:: {% include "name_snippet.html" with person="Jane" greeting="Hello" %} @@ -710,8 +705,6 @@ registered in ``somelibrary`` and ``otherlibrary`` located in package {% load somelibrary package.otherlibrary %} -.. versionchanged:: 1.3 - You can also selectively load individual filters or tags from a library, using the ``from`` argument. In this example, the template tags/filters named ``foo`` and ``bar`` will be loaded from ``somelibrary``:: @@ -1076,9 +1069,6 @@ which is rounded up to 88). with ^^^^ -.. versionchanged:: 1.3 - New keyword argument format and multiple variable assignments. - Caches a complex variable under a simpler name. This is useful when accessing an "expensive" method (e.g., one that hits the database) multiple times. @@ -1261,7 +1251,7 @@ S English ordinal suffix for day of the ``'st'``, ``'nd'``, month, 2 characters. t Number of days in the given month. ``28`` to ``31`` T Time zone of this machine. ``'EST'``, ``'MDT'`` -u Microseconds. ``0`` to ``999999`` +u Microseconds. ``000000`` to ``999999`` U Seconds since the Unix Epoch (January 1 1970 00:00:00 UTC). w Day of the week, digits without ``'0'`` (Sunday) to ``'6'`` (Saturday) @@ -2126,8 +2116,6 @@ For example:: If ``value`` is ``"http://www.example.org/foo?a=b&c=d"``, the output will be ``"http%3A//www.example.org/foo%3Fa%3Db%26c%3Dd"``. -.. versionadded:: 1.3 - An optional argument containing the characters which should not be escaped can be provided. diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index bbd7fec1e3a..df8d89c185c 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -133,6 +133,8 @@ Django 1.5 also includes several smaller improvements worth noting: * The :ref:`receiver ` decorator is now able to connect to more than one signal by supplying a list of signals. +* In the admin, you can now filter users by groups which they are members of. + * :meth:`QuerySet.bulk_create() ` now has a batch_size argument. By default the batch_size is unlimited except for SQLite where @@ -167,6 +169,21 @@ year|date:"Y" }}``. ``next_year`` and ``previous_year`` were also added in the context. They are calculated according to ``allow_empty`` and ``allow_future``. +Context in year and month archive class-based views +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:class:`~django.views.generic.dates.YearArchiveView` and +:class:`~django.views.generic.dates.MonthArchiveView` were documented to +provide a ``date_list`` sorted in ascending order in the context, like their +function-based predecessors, but it actually was in descending order. In 1.5, +the documented order was restored. You may want to add (or remove) the +``reversed`` keyword when you're iterating on ``date_list`` in a template:: + + {% for date in date_list reversed %} + +:class:`~django.views.generic.dates.ArchiveIndexView` still provides a +``date_list`` in descending order. + Context in TemplateView ~~~~~~~~~~~~~~~~~~~~~~~ @@ -362,6 +379,11 @@ Miscellaneous function at :func:`django.utils.text.slugify`. Similarly, ``remove_tags`` is available at :func:`django.utils.html.remove_tags`. +* Uploaded files are no longer created as executable by default. If you need + them to be executeable change :setting:`FILE_UPLOAD_PERMISSIONS` to your + needs. The new default value is `0666` (octal) and the current umask value + is first masked out. + Features deprecated in 1.5 ========================== diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt index 355fe3cfea6..2eb98e383ba 100644 --- a/docs/topics/auth.txt +++ b/docs/topics/auth.txt @@ -872,8 +872,6 @@ How to log a user out Login and logout signals ------------------------ -.. versionadded:: 1.3 - The auth framework uses two :doc:`signals ` that can be used for notification when a user logs in or out. @@ -972,8 +970,6 @@ The login_required decorator context variable which stores the redirect path will use the value of ``redirect_field_name`` as its key rather than ``"next"`` (the default). - .. versionadded:: 1.3 - :func:`~django.contrib.auth.decorators.login_required` also takes an optional ``login_url`` parameter. Example:: @@ -1201,9 +1197,6 @@ includes a few other useful built-in views located in that can be used to reset the password, and sending that link to the user's registered email address. - .. versionchanged:: 1.3 - The ``from_email`` argument was added. - .. versionchanged:: 1.4 Users flagged with an unusable password (see :meth:`~django.contrib.auth.models.User.set_unusable_password()` @@ -1684,10 +1677,6 @@ The currently logged-in user's permissions are stored in the template variable :class:`django.contrib.auth.context_processors.PermWrapper`, which is a template-friendly proxy of permissions. -.. versionchanged:: 1.3 - Prior to version 1.3, ``PermWrapper`` was located in - ``django.core.context_processors``. - In the ``{{ perms }}`` object, single-attribute lookup is a proxy to :meth:`User.has_module_perms `. This example would display ``True`` if the logged-in user had any permissions @@ -2263,8 +2252,6 @@ for example, to control anonymous access. Authorization for inactive users ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. versionchanged:: 1.3 - An inactive user is a one that is authenticated but has its attribute ``is_active`` set to ``False``. However this does not mean they are not authorized to do anything. For example they are allowed to activate their diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt index f13238e3427..e80ac85bd87 100644 --- a/docs/topics/cache.txt +++ b/docs/topics/cache.txt @@ -51,13 +51,6 @@ Your cache preference goes in the :setting:`CACHES` setting in your settings file. Here's an explanation of all available values for :setting:`CACHES`. -.. versionchanged:: 1.3 - The settings used to configure caching changed in Django 1.3. In - Django 1.2 and earlier, you used a single string-based - :setting:`CACHE_BACKEND` setting to configure caches. This has - been replaced with the new dictionary-based :setting:`CACHES` - setting. - .. _memcached: Memcached @@ -83,9 +76,6 @@ two most common are `python-memcached`_ and `pylibmc`_. .. _`python-memcached`: ftp://ftp.tummy.com/pub/python-memcached/ .. _`pylibmc`: http://sendapatch.se/projects/pylibmc/ -.. versionchanged:: 1.3 - Support for ``pylibmc`` was added. - To use Memcached with Django: * Set :setting:`BACKEND ` to @@ -673,12 +663,27 @@ dictionaries, lists of model objects, and so forth. (Most common Python objects can be pickled; refer to the Python documentation for more information about pickling.) +Accessing the cache +------------------- + The cache module, ``django.core.cache``, has a ``cache`` object that's automatically created from the ``'default'`` entry in the :setting:`CACHES` setting:: >>> from django.core.cache import cache +If you have multiple caches defined in :setting:`CACHES`, then you can use +:func:`django.core.cache.get_cache` to retrieve a cache object for any key:: + + >>> from django.core.cache import get_cache + >>> cache = get_cache('alternate') + +If the named key does not exist, :exc:`InvalidCacheBackendError` will be raised. + + +Basic usage +----------- + The basic interface is ``set(key, value, timeout)`` and ``get(key)``:: >>> cache.set('my_key', 'hello, world!', 30) @@ -686,7 +691,7 @@ The basic interface is ``set(key, value, timeout)`` and ``get(key)``:: 'hello, world!' The ``timeout`` argument is optional and defaults to the ``timeout`` -argument of the ``'default'`` backend in :setting:`CACHES` setting +argument of the appropriate backend in the :setting:`CACHES` setting (explained above). It's the number of seconds the value should be stored in the cache. @@ -785,8 +790,6 @@ nonexistent cache key.:: Cache key prefixing ------------------- -.. versionadded:: 1.3 - If you are sharing a cache instance between servers, or between your production and development environments, it's possible for data cached by one server to be used by another server. If the format of cached @@ -807,8 +810,6 @@ collisions in cache values. Cache versioning ---------------- -.. versionadded:: 1.3 - When you change running code that uses cached values, you may need to purge any existing cached values. The easiest way to do this is to flush the entire cache, but this can lead to the loss of cache values @@ -856,8 +857,6 @@ keys unaffected. Continuing our previous example:: Cache key transformation ------------------------ -.. versionadded:: 1.3 - As described in the previous two sections, the cache key provided by a user is not used verbatim -- it is combined with the cache prefix and key version to provide a final cache key. By default, the three parts @@ -878,8 +877,6 @@ be used instead of the default key combining function. Cache key warnings ------------------ -.. versionadded:: 1.3 - Memcached, the most commonly-used production cache backend, does not allow cache keys longer than 250 characters or containing whitespace or control characters, and using such keys will cause an exception. To encourage @@ -966,10 +963,6 @@ mechanism should take into account when building its cache key. For example, if the contents of a Web page depend on a user's language preference, the page is said to "vary on language." -.. versionchanged:: 1.3 - In Django 1.3 the full request path -- including the query -- is used - to create the cache keys, instead of only the path component in Django 1.2. - By default, Django's cache system creates its cache keys using the requested path and query -- e.g., ``"/stories/2005/?order_by=author"``. This means every request to that URL will use the same cached version, regardless of user-agent diff --git a/docs/topics/class-based-views/generic-display.txt b/docs/topics/class-based-views/generic-display.txt index 0d4cb6244d6..10279c0f638 100644 --- a/docs/topics/class-based-views/generic-display.txt +++ b/docs/topics/class-based-views/generic-display.txt @@ -4,11 +4,6 @@ Class-based generic views ========================= -.. note:: - Prior to Django 1.3, generic views were implemented as functions. The - function-based implementation has been removed in favor of the - class-based approach described here. - Writing Web applications can be monotonous, because we repeat certain patterns again and again. Django tries to take away some of that monotony at the model and template layers, but Web developers also experience this boredom at the view diff --git a/docs/topics/class-based-views/generic-editing.txt b/docs/topics/class-based-views/generic-editing.txt index 23d346a32a8..7bae3c692d6 100644 --- a/docs/topics/class-based-views/generic-editing.txt +++ b/docs/topics/class-based-views/generic-editing.txt @@ -203,3 +203,43 @@ Note that you'll need to :ref:`decorate this view` using :func:`~django.contrib.auth.decorators.login_required`, or alternatively handle unauthorised users in the :meth:`form_valid()`. + +AJAX example +------------ + +Here is a simple example showing how you might go about implementing a form that +works for AJAX requests as well as 'normal' form POSTs:: + + import json + + from django.http import HttpResponse + from django.views.generic.edit import CreateView + from django.views.generic.detail import SingleObjectTemplateResponseMixin + + class AjaxableResponseMixin(object): + """ + Mixin to add AJAX support to a form. + Must be used with an object-based FormView (e.g. CreateView) + """ + def render_to_json_response(self, context, **response_kwargs): + data = json.dumps(context) + response_kwargs['content_type'] = 'application/json' + return HttpResponse(data, **response_kwargs) + + def form_invalid(self, form): + if self.request.is_ajax(): + return self.render_to_json_response(form.errors, status=400) + else: + return super(AjaxableResponseMixin, self).form_invalid(form) + + def form_valid(self, form): + if self.request.is_ajax(): + data = { + 'pk': form.instance.pk, + } + return self.render_to_json_response(data) + else: + return super(AjaxableResponseMixin, self).form_valid(form) + + class AuthorCreate(AjaxableResponseMixin, CreateView): + model = Author diff --git a/docs/topics/class-based-views/index.txt b/docs/topics/class-based-views/index.txt index 2d3e00ab4cd..a7382218926 100644 --- a/docs/topics/class-based-views/index.txt +++ b/docs/topics/class-based-views/index.txt @@ -2,8 +2,6 @@ Class-based views ================= -.. versionadded:: 1.3 - A view is a callable which takes a request and returns a response. This can be more than just a function, and Django provides an example of some classes which can be used as views. These allow you diff --git a/docs/topics/class-based-views/mixins.txt b/docs/topics/class-based-views/mixins.txt index f07769fb8a3..f349c236266 100644 --- a/docs/topics/class-based-views/mixins.txt +++ b/docs/topics/class-based-views/mixins.txt @@ -2,8 +2,6 @@ Using mixins with class-based views =================================== -.. versionadded:: 1.3 - .. caution:: This is an advanced topic. A working knowledge of :doc:`Django's diff --git a/docs/topics/db/queries.txt b/docs/topics/db/queries.txt index 5385b2a72d2..dd160656c73 100644 --- a/docs/topics/db/queries.txt +++ b/docs/topics/db/queries.txt @@ -633,8 +633,6 @@ issue the query:: >>> Entry.objects.filter(authors__name=F('blog__name')) -.. versionadded:: 1.3 - For date and date/time fields, you can add or subtract a :class:`~datetime.timedelta` object. The following would return all entries that were modified more than 3 days after they were published:: @@ -876,7 +874,6 @@ it. For example:: # This will delete the Blog and all of its Entry objects. b.delete() -.. versionadded:: 1.3 This cascade behavior is customizable via the :attr:`~django.db.models.ForeignKey.on_delete` argument to the :class:`~django.db.models.ForeignKey`. diff --git a/docs/topics/db/sql.txt b/docs/topics/db/sql.txt index 19daffd4647..310dcb5ae67 100644 --- a/docs/topics/db/sql.txt +++ b/docs/topics/db/sql.txt @@ -242,7 +242,7 @@ By default, the Python DB API will return results without their field names, which means you end up with a ``list`` of values, rather than a ``dict``. At a small performance cost, you can return results as a ``dict`` by using something like this:: - + def dictfetchall(cursor): "Returns all rows from a cursor as a dict" desc = cursor.description @@ -256,7 +256,7 @@ Here is an example of the difference between the two:: >>> cursor.execute("SELECT id, parent_id from test LIMIT 2"); >>> cursor.fetchall() ((54360982L, None), (54360880L, None)) - + >>> cursor.execute("SELECT id, parent_id from test LIMIT 2"); >>> dictfetchall(cursor) [{'parent_id': None, 'id': 54360982L}, {'parent_id': None, 'id': 54360880L}] @@ -273,11 +273,6 @@ transaction containing those calls is closed correctly. See :ref:`the notes on the requirements of Django's transaction handling ` for more details. -.. versionchanged:: 1.3 - -Prior to Django 1.3, it was necessary to manually mark a transaction -as dirty using ``transaction.set_dirty()`` when using raw SQL calls. - Connections and cursors ----------------------- diff --git a/docs/topics/db/transactions.txt b/docs/topics/db/transactions.txt index 99283546642..4a52c5af358 100644 --- a/docs/topics/db/transactions.txt +++ b/docs/topics/db/transactions.txt @@ -66,9 +66,6 @@ database cursor (which is mapped to its own database connection internally). Controlling transaction management in views =========================================== -.. versionchanged:: 1.3 - Transaction management context managers are new in Django 1.3. - For most people, implicit request-based transactions work wonderfully. However, if you need more fine-grained control over how transactions are managed, you can use a set of functions in ``django.db.transaction`` to control transactions on a @@ -195,8 +192,6 @@ managers, too. Requirements for transaction handling ===================================== -.. versionadded:: 1.3 - Django requires that every transaction that is opened is closed before the completion of a request. If you are using :func:`autocommit` (the default commit mode) or :func:`commit_on_success`, this will be done diff --git a/docs/topics/email.txt b/docs/topics/email.txt index 0cc476e02c6..b3d7254e7f9 100644 --- a/docs/topics/email.txt +++ b/docs/topics/email.txt @@ -119,8 +119,6 @@ The "From:" header of the email will be the value of the This method exists for convenience and readability. -.. versionchanged:: 1.3 - If ``html_message`` is provided, the resulting email will be a :mimetype:`multipart/alternative` email with ``message`` as the :mimetype:`text/plain` content type and ``html_message`` as the @@ -236,9 +234,6 @@ following parameters (in the given order, if positional arguments are used). All parameters are optional and can be set at any time prior to calling the ``send()`` method. -.. versionchanged:: 1.3 - The ``cc`` argument was added. - * ``subject``: The subject line of the email. * ``body``: The body text. This should be a plain text message. diff --git a/docs/topics/forms/formsets.txt b/docs/topics/forms/formsets.txt index 2a83172e17a..7c1771b758f 100644 --- a/docs/topics/forms/formsets.txt +++ b/docs/topics/forms/formsets.txt @@ -35,19 +35,9 @@ display two blank forms:: >>> ArticleFormSet = formset_factory(ArticleForm, extra=2) -.. versionchanged:: 1.3 - -Prior to Django 1.3, formset instances were not iterable. To render -the formset you iterated over the ``forms`` attribute:: - - >>> formset = ArticleFormSet() - >>> for form in formset.forms: - ... print(form.as_table()) - -Iterating over ``formset.forms`` will render the forms in the order -they were created. The default formset iterator also renders the forms -in this order, but you can change this order by providing an alternate -implementation for the :meth:`__iter__()` method. +Iterating over the ``formset`` will render the forms in the order they were +created. You can change this order by providing an alternate implementation for +the :meth:`__iter__()` method. Formsets can also be indexed into, which returns the corresponding form. If you override ``__iter__``, you will need to also override ``__getitem__`` to have diff --git a/docs/topics/forms/media.txt b/docs/topics/forms/media.txt index 29a7829799e..98e70e5e778 100644 --- a/docs/topics/forms/media.txt +++ b/docs/topics/forms/media.txt @@ -195,8 +195,6 @@ return values for dynamic media properties. Paths in media definitions -------------------------- -.. versionchanged:: 1.3 - Paths used to specify media can be either relative or absolute. If a path starts with ``/``, ``http://`` or ``https://``, it will be interpreted as an absolute path, and left as-is. All other paths will be prepended with the value diff --git a/docs/topics/http/middleware.txt b/docs/topics/http/middleware.txt index fe92bc59a9d..a8347e52a04 100644 --- a/docs/topics/http/middleware.txt +++ b/docs/topics/http/middleware.txt @@ -117,8 +117,6 @@ middleware is always called on every response. ``process_template_response`` ----------------------------- -.. versionadded:: 1.3 - .. method:: process_template_response(self, request, response) ``request`` is an :class:`~django.http.HttpRequest` object. ``response`` is a diff --git a/docs/topics/http/shortcuts.txt b/docs/topics/http/shortcuts.txt index 10be353e80d..0dc38b14592 100644 --- a/docs/topics/http/shortcuts.txt +++ b/docs/topics/http/shortcuts.txt @@ -17,8 +17,6 @@ introduce controlled coupling for convenience's sake. .. function:: render(request, template_name[, dictionary][, context_instance][, content_type][, status][, current_app]) - .. versionadded:: 1.3 - Combines a given template with a given context dictionary and returns an :class:`~django.http.HttpResponse` object with that rendered text. diff --git a/docs/topics/http/urls.txt b/docs/topics/http/urls.txt index 4503bbd6ef1..99afa132797 100644 --- a/docs/topics/http/urls.txt +++ b/docs/topics/http/urls.txt @@ -55,7 +55,8 @@ algorithm the system follows to determine which Python code to execute: one that matches the requested URL. 4. Once one of the regexes matches, Django imports and calls the given - view, which is a simple Python function. The view gets passed an + view, which is a simple Python function (or a :doc:`class based view + `). The view gets passed an :class:`~django.http.HttpRequest` as its first argument and any values captured in the regex as remaining arguments. @@ -673,6 +674,15 @@ The style you use is up to you. Note that if you use this technique -- passing objects rather than strings -- the view prefix (as explained in "The view prefix" above) will have no effect. +Note that :doc:`class based views` must be +imported:: + + from mysite.views import ClassBasedView + + urlpatterns = patterns('', + (r'^myview/$', ClassBasedView.as_view()), + ) + .. _naming-url-patterns: Naming URL patterns @@ -970,13 +980,6 @@ A :class:`ResolverMatch` object can also be assigned to a triple:: func, args, kwargs = resolve('/some/path/') -.. versionchanged:: 1.3 - Triple-assignment exists for backwards-compatibility. Prior to - Django 1.3, :func:`~django.core.urlresolvers.resolve` returned a - triple containing (view function, arguments, keyword arguments); - the :class:`ResolverMatch` object (as well as the namespace and pattern - information it provides) is not available in earlier Django releases. - One possible use of :func:`~django.core.urlresolvers.resolve` would be to test whether a view would raise a ``Http404`` error before redirecting to it:: diff --git a/docs/topics/i18n/formatting.txt b/docs/topics/i18n/formatting.txt index b09164769e4..fc3f37de323 100644 --- a/docs/topics/i18n/formatting.txt +++ b/docs/topics/i18n/formatting.txt @@ -80,8 +80,6 @@ Template tags localize ~~~~~~~~ -.. versionadded:: 1.3 - Enables or disables localization of template variables in the contained block. @@ -116,8 +114,6 @@ Template filters localize ~~~~~~~~ -.. versionadded:: 1.3 - Forces localization of a single value. For example:: @@ -136,8 +132,6 @@ tag. unlocalize ~~~~~~~~~~ -.. versionadded:: 1.3 - Forces a single value to be printed without localization. For example:: diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt index a7f48fe1fd4..aaf728b1af3 100644 --- a/docs/topics/i18n/translation.txt +++ b/docs/topics/i18n/translation.txt @@ -134,8 +134,6 @@ translations wouldn't be able to reorder placeholder text. Comments for translators ------------------------ -.. versionadded:: 1.3 - If you would like to give translators hints about a translatable string, you can add a comment prefixed with the ``Translators`` keyword on the line preceding the string, e.g.:: @@ -255,8 +253,6 @@ cardinality of the elements at play. Contextual markers ------------------ -.. versionadded:: 1.3 - Sometimes words have several meanings, such as ``"May"`` in English, which refers to a month name and to a verb. To enable translators to translate these words correctly in different contexts, you can use the @@ -436,8 +432,6 @@ Localized names of languages .. function:: get_language_info -.. versionadded:: 1.3 - The ``get_language_info()`` function provides detailed information about languages:: @@ -535,9 +529,6 @@ using the ``context`` keyword: ``blocktrans`` template tag --------------------------- -.. versionchanged:: 1.3 - New keyword argument format. - Contrarily to the :ttag:`trans` tag, the ``blocktrans`` tag allows you to mark complex sentences consisting of literals and variable content for translation by making use of placeholders:: @@ -664,8 +655,6 @@ string, so they don't need to be aware of translations. translator might translate the string ``"yes,no"`` as ``"ja,nein"`` (keeping the comma intact). -.. versionadded:: 1.3 - You can also retrieve information about any of the available languages using provided template tags and filters. To get information about a single language, use the ``{% get_language_info %}`` tag:: @@ -787,10 +776,6 @@ directories listed in :setting:`LOCALE_PATHS` have the highest precedence with the ones appearing first having higher precedence than the ones appearing later. -.. versionchanged:: 1.3 - Directories listed in :setting:`LOCALE_PATHS` weren't included in the - lookup algorithm until version 1.3. - Using the JavaScript translation catalog ---------------------------------------- diff --git a/docs/topics/logging.txt b/docs/topics/logging.txt index 28baf875226..94236babd63 100644 --- a/docs/topics/logging.txt +++ b/docs/topics/logging.txt @@ -2,8 +2,6 @@ Logging ======= -.. versionadded:: 1.3 - .. module:: django.utils.log :synopsis: Logging tools for Django applications diff --git a/docs/topics/signals.txt b/docs/topics/signals.txt index db1bcb03df8..1078d0372c0 100644 --- a/docs/topics/signals.txt +++ b/docs/topics/signals.txt @@ -132,10 +132,6 @@ Now, our ``my_callback`` function will be called each time a request finishes. Note that ``receiver`` can also take a list of signals to connect a function to. -.. versionadded:: 1.3 - -The ``receiver`` decorator was added in Django 1.3. - .. versionchanged:: 1.5 The ability to pass a list of signals was added. diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index c4c73733f59..117dfbe5912 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -73,8 +73,6 @@ module defines tests in class-based approach. .. admonition:: unittest2 - .. versionchanged:: 1.3 - Python 2.7 introduced some major changes to the unittest library, adding some extremely useful features. To ensure that every Django project can benefit from these new features, Django ships with a @@ -436,8 +434,6 @@ two databases. Controlling creation order for test databases ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. versionadded:: 1.3 - By default, Django will always create the ``default`` database first. However, no guarantees are made on the creation order of any other databases in your test setup. @@ -512,6 +508,13 @@ file, all Django tests run with :setting:`DEBUG`\=False. This is to ensure that the observed output of your code matches what will be seen in a production setting. +Caches are not cleared after each test, and running "manage.py test fooapp" can +insert data from the tests into the cache of a live system if you run your +tests in production because, unlike databases, a separate "test cache" is not +used. This behavior `may change`_ in the future. + +.. _may change: https://code.djangoproject.com/ticket/11505 + Understanding the test output ----------------------------- @@ -1001,8 +1004,6 @@ Specifically, a ``Response`` object has the following attributes: The HTTP status of the response, as an integer. See :rfc:`2616#section-10` for a full list of HTTP status codes. - .. versionadded:: 1.3 - .. attribute:: templates A list of ``Template`` instances used to render the final content, in @@ -1089,8 +1090,6 @@ The request factory .. class:: RequestFactory -.. versionadded:: 1.3 - The :class:`~django.test.client.RequestFactory` shares the same API as the test client. However, instead of behaving like a browser, the RequestFactory provides a way to generate a request instance that can @@ -1327,8 +1326,6 @@ This means, instead of instantiating a ``Client`` in each test:: Customizing the test client ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. versionadded:: 1.3 - .. attribute:: TestCase.client_class If you want to use a different ``Client`` class (for example, a subclass @@ -1708,8 +1705,6 @@ your test suite. .. method:: TestCase.assertQuerysetEqual(qs, values, transform=repr, ordered=True) - .. versionadded:: 1.3 - Asserts that a queryset ``qs`` returns a particular list of values ``values``. The comparison of the contents of ``qs`` and ``values`` is performed using @@ -1730,8 +1725,6 @@ your test suite. .. method:: TestCase.assertNumQueries(num, func, *args, **kwargs) - .. versionadded:: 1.3 - Asserts that when ``func`` is called with ``*args`` and ``**kwargs`` that ``num`` database queries are executed. @@ -1854,8 +1847,6 @@ Skipping tests .. currentmodule:: django.test -.. versionadded:: 1.3 - The unittest library provides the :func:`@skipIf ` and :func:`@skipUnless ` decorators to allow you to skip tests if you know ahead of time that those tests are going to fail under certain diff --git a/tests/regressiontests/defer_regress/models.py b/tests/regressiontests/defer_regress/models.py index d02adccde9d..444649ac921 100644 --- a/tests/regressiontests/defer_regress/models.py +++ b/tests/regressiontests/defer_regress/models.py @@ -52,6 +52,9 @@ class SimpleItem(models.Model): class Feature(models.Model): item = models.ForeignKey(SimpleItem) +class SpecialFeature(models.Model): + feature = models.ForeignKey(Feature) + class ItemAndSimpleItem(models.Model): item = models.ForeignKey(Item) simple = models.ForeignKey(SimpleItem) diff --git a/tests/regressiontests/defer_regress/tests.py b/tests/regressiontests/defer_regress/tests.py index 53bb59f5b35..c77ca32135d 100644 --- a/tests/regressiontests/defer_regress/tests.py +++ b/tests/regressiontests/defer_regress/tests.py @@ -9,7 +9,7 @@ from django.db.models.loading import cache from django.test import TestCase from .models import (ResolveThis, Item, RelatedItem, Child, Leaf, Proxy, - SimpleItem, Feature, ItemAndSimpleItem) + SimpleItem, Feature, ItemAndSimpleItem, SpecialFeature) class DeferRegressionTest(TestCase): @@ -115,6 +115,7 @@ class DeferRegressionTest(TestCase): RelatedItem, ResolveThis, SimpleItem, + SpecialFeature, ] ) @@ -152,6 +153,7 @@ class DeferRegressionTest(TestCase): "RelatedItem_Deferred_item_id", "ResolveThis", "SimpleItem", + "SpecialFeature", ] ) @@ -197,6 +199,18 @@ class DeferRegressionTest(TestCase): self.assertEqual(obj.item, item2) self.assertEqual(obj.item_id, item2.id) + def test_only_with_select_related(self): + # Test for #17485. + item = SimpleItem.objects.create(name='first', value=47) + feature = Feature.objects.create(item=item) + SpecialFeature.objects.create(feature=feature) + + qs = Feature.objects.only('item__name').select_related('item') + self.assertEqual(len(qs), 1) + + qs = SpecialFeature.objects.only('feature__item__name').select_related('feature__item') + self.assertEqual(len(qs), 1) + def test_deferred_class_factory(self): from django.db.models.query_utils import deferred_class_factory new_class = deferred_class_factory(Item, diff --git a/tests/regressiontests/file_storage/tests.py b/tests/regressiontests/file_storage/tests.py index 281041e651f..6b57ad6160e 100644 --- a/tests/regressiontests/file_storage/tests.py +++ b/tests/regressiontests/file_storage/tests.py @@ -4,6 +4,7 @@ from __future__ import absolute_import, unicode_literals import errno import os import shutil +import sys import tempfile import time from datetime import datetime, timedelta @@ -23,6 +24,7 @@ from django.core.files.uploadedfile import UploadedFile from django.test import SimpleTestCase from django.utils import six from django.utils import unittest +from django.test.utils import override_settings from ..servers.tests import LiveServerBase # Try to import PIL in either of the two ways it can end up installed. @@ -433,22 +435,29 @@ class FileSaveRaceConditionTest(unittest.TestCase): self.storage.delete('conflict') self.storage.delete('conflict_1') +@unittest.skipIf(sys.platform.startswith('win'), "Windows only partially supports umasks and chmod.") class FileStoragePermissions(unittest.TestCase): def setUp(self): - self.old_perms = settings.FILE_UPLOAD_PERMISSIONS - settings.FILE_UPLOAD_PERMISSIONS = 0o666 + self.umask = 0o027 + self.old_umask = os.umask(self.umask) self.storage_dir = tempfile.mkdtemp() self.storage = FileSystemStorage(self.storage_dir) def tearDown(self): - settings.FILE_UPLOAD_PERMISSIONS = self.old_perms shutil.rmtree(self.storage_dir) + os.umask(self.old_umask) + @override_settings(FILE_UPLOAD_PERMISSIONS=0o654) def test_file_upload_permissions(self): name = self.storage.save("the_file", ContentFile("data")) actual_mode = os.stat(self.storage.path(name))[0] & 0o777 - self.assertEqual(actual_mode, 0o666) + self.assertEqual(actual_mode, 0o654) + @override_settings(FILE_UPLOAD_PERMISSIONS=None) + def test_file_upload_default_permissions(self): + fname = self.storage.save("some_file", ContentFile("data")) + mode = os.stat(self.storage.path(fname))[0] & 0o777 + self.assertEqual(mode, 0o666 & ~self.umask) class FileStoragePathParsing(unittest.TestCase): def setUp(self): diff --git a/tests/regressiontests/generic_views/dates.py b/tests/regressiontests/generic_views/dates.py index c2fa71b376a..0c565daf9f3 100644 --- a/tests/regressiontests/generic_views/dates.py +++ b/tests/regressiontests/generic_views/dates.py @@ -23,29 +23,30 @@ requires_tz_support = skipUnless(TZ_SUPPORT, "time zone, but your operating system isn't able to do that.") +def _make_books(n, base_date): + for i in range(n): + b = Book.objects.create( + name='Book %d' % i, + slug='book-%d' % i, + pages=100+i, + pubdate=base_date - datetime.timedelta(days=i)) + class ArchiveIndexViewTests(TestCase): fixtures = ['generic-views-test-data.json'] urls = 'regressiontests.generic_views.urls' - def _make_books(self, n, base_date): - for i in range(n): - b = Book.objects.create( - name='Book %d' % i, - slug='book-%d' % i, - pages=100+i, - pubdate=base_date - datetime.timedelta(days=1)) def test_archive_view(self): res = self.client.get('/dates/books/') self.assertEqual(res.status_code, 200) - self.assertEqual(res.context['date_list'], Book.objects.dates('pubdate', 'year')[::-1]) + self.assertEqual(list(res.context['date_list']), list(Book.objects.dates('pubdate', 'year', 'DESC'))) self.assertEqual(list(res.context['latest']), list(Book.objects.all())) self.assertTemplateUsed(res, 'generic_views/book_archive.html') def test_archive_view_context_object_name(self): res = self.client.get('/dates/books/context_object_name/') self.assertEqual(res.status_code, 200) - self.assertEqual(res.context['date_list'], Book.objects.dates('pubdate', 'year')[::-1]) + self.assertEqual(list(res.context['date_list']), list(Book.objects.dates('pubdate', 'year', 'DESC'))) self.assertEqual(list(res.context['thingies']), list(Book.objects.all())) self.assertFalse('latest' in res.context) self.assertTemplateUsed(res, 'generic_views/book_archive.html') @@ -65,14 +66,14 @@ class ArchiveIndexViewTests(TestCase): def test_archive_view_template(self): res = self.client.get('/dates/books/template_name/') self.assertEqual(res.status_code, 200) - self.assertEqual(res.context['date_list'], Book.objects.dates('pubdate', 'year')[::-1]) + self.assertEqual(list(res.context['date_list']), list(Book.objects.dates('pubdate', 'year', 'DESC'))) self.assertEqual(list(res.context['latest']), list(Book.objects.all())) self.assertTemplateUsed(res, 'generic_views/list.html') def test_archive_view_template_suffix(self): res = self.client.get('/dates/books/template_name_suffix/') self.assertEqual(res.status_code, 200) - self.assertEqual(res.context['date_list'], Book.objects.dates('pubdate', 'year')[::-1]) + self.assertEqual(list(res.context['date_list']), list(Book.objects.dates('pubdate', 'year', 'DESC'))) self.assertEqual(list(res.context['latest']), list(Book.objects.all())) self.assertTemplateUsed(res, 'generic_views/book_detail.html') @@ -82,13 +83,13 @@ class ArchiveIndexViewTests(TestCase): def test_archive_view_by_month(self): res = self.client.get('/dates/books/by_month/') self.assertEqual(res.status_code, 200) - self.assertEqual(res.context['date_list'], Book.objects.dates('pubdate', 'month')[::-1]) + self.assertEqual(list(res.context['date_list']), list(Book.objects.dates('pubdate', 'month', 'DESC'))) def test_paginated_archive_view(self): - self._make_books(20, base_date=datetime.date.today()) + _make_books(20, base_date=datetime.date.today()) res = self.client.get('/dates/books/paginated/') self.assertEqual(res.status_code, 200) - self.assertEqual(res.context['date_list'], Book.objects.dates('pubdate', 'year')[::-1]) + self.assertEqual(list(res.context['date_list']), list(Book.objects.dates('pubdate', 'year', 'DESC'))) self.assertEqual(list(res.context['latest']), list(Book.objects.all()[0:10])) self.assertTemplateUsed(res, 'generic_views/book_archive.html') @@ -99,7 +100,7 @@ class ArchiveIndexViewTests(TestCase): def test_paginated_archive_view_does_not_load_entire_table(self): # Regression test for #18087 - self._make_books(20, base_date=datetime.date.today()) + _make_books(20, base_date=datetime.date.today()) # 1 query for years list + 1 query for books with self.assertNumQueries(2): self.client.get('/dates/books/') @@ -124,6 +125,13 @@ class ArchiveIndexViewTests(TestCase): res = self.client.get('/dates/booksignings/') self.assertEqual(res.status_code, 200) + def test_date_list_order(self): + """date_list should be sorted descending in index""" + _make_books(5, base_date=datetime.date(2011, 12, 25)) + res = self.client.get('/dates/books/') + self.assertEqual(res.status_code, 200) + self.assertEqual(list(res.context['date_list']), list(reversed(sorted(res.context['date_list'])))) + class YearArchiveViewTests(TestCase): fixtures = ['generic-views-test-data.json'] @@ -202,6 +210,12 @@ class YearArchiveViewTests(TestCase): res = self.client.get('/dates/booksignings/2008/') self.assertEqual(res.status_code, 200) + def test_date_list_order(self): + """date_list should be sorted ascending in year view""" + _make_books(10, base_date=datetime.date(2011, 12, 25)) + res = self.client.get('/dates/books/2011/') + self.assertEqual(list(res.context['date_list']), list(sorted(res.context['date_list']))) + class MonthArchiveViewTests(TestCase): fixtures = ['generic-views-test-data.json'] @@ -322,6 +336,12 @@ class MonthArchiveViewTests(TestCase): res = self.client.get('/dates/booksignings/2008/apr/') self.assertEqual(res.status_code, 200) + def test_date_list_order(self): + """date_list should be sorted ascending in month view""" + _make_books(10, base_date=datetime.date(2011, 12, 25)) + res = self.client.get('/dates/books/2011/dec/') + self.assertEqual(list(res.context['date_list']), list(sorted(res.context['date_list']))) + class WeekArchiveViewTests(TestCase): fixtures = ['generic-views-test-data.json'] diff --git a/tests/regressiontests/logging_tests/tests.py b/tests/regressiontests/logging_tests/tests.py index 4ae3c1b1a9e..f444e0ff460 100644 --- a/tests/regressiontests/logging_tests/tests.py +++ b/tests/regressiontests/logging_tests/tests.py @@ -1,13 +1,14 @@ from __future__ import unicode_literals import copy +import logging import warnings from django.conf import compat_patch_logging_config from django.core import mail from django.test import TestCase, RequestFactory from django.test.utils import override_settings -from django.utils.log import CallbackFilter, RequireDebugFalse, getLogger +from django.utils.log import CallbackFilter, RequireDebugFalse # logging config prior to using filter with mail_admins @@ -153,7 +154,7 @@ class AdminEmailHandlerTest(TestCase): token1 = 'ping' token2 = 'pong' - logger = getLogger('django.request') + logger = logging.getLogger('django.request') admin_email_handler = self.get_admin_email_handler(logger) # Backup then override original filters orig_filters = admin_email_handler.filters @@ -184,7 +185,7 @@ class AdminEmailHandlerTest(TestCase): token1 = 'ping' token2 = 'pong' - logger = getLogger('django.request') + logger = logging.getLogger('django.request') admin_email_handler = self.get_admin_email_handler(logger) # Backup then override original filters orig_filters = admin_email_handler.filters @@ -222,7 +223,7 @@ class AdminEmailHandlerTest(TestCase): self.assertEqual(len(mail.outbox), 0) - logger = getLogger('django.request') + logger = logging.getLogger('django.request') logger.error(message) self.assertEqual(len(mail.outbox), 1) @@ -247,7 +248,7 @@ class AdminEmailHandlerTest(TestCase): self.assertEqual(len(mail.outbox), 0) - logger = getLogger('django.request') + logger = logging.getLogger('django.request') logger.error(message) self.assertEqual(len(mail.outbox), 1) diff --git a/tests/regressiontests/mail/tests.py b/tests/regressiontests/mail/tests.py index 3e9ae846502..33898cc1d54 100644 --- a/tests/regressiontests/mail/tests.py +++ b/tests/regressiontests/mail/tests.py @@ -498,6 +498,11 @@ class LocmemBackendTests(BaseEmailBackendTests, TestCase): connection2.send_messages([email]) self.assertEqual(len(mail.outbox), 2) + def test_validate_multiline_headers(self): + # Ticket #18861 - Validate emails when using the locmem backend + with self.assertRaises(BadHeaderError): + send_mail('Subject\nMultiline', 'Content', 'from@example.com', ['to@example.com']) + class FileBackendTests(BaseEmailBackendTests, TestCase): email_backend = 'django.core.mail.backends.filebased.EmailBackend' diff --git a/tests/regressiontests/model_permalink/models.py b/tests/regressiontests/model_permalink/models.py index 4823fd46c7a..dacf2a3fb3c 100644 --- a/tests/regressiontests/model_permalink/models.py +++ b/tests/regressiontests/model_permalink/models.py @@ -1,6 +1,13 @@ from django.db import models +def set_attr(name, value): + def wrapper(function): + setattr(function, name, value) + return function + return wrapper + + class Guitarist(models.Model): name = models.CharField(max_length=50) slug = models.CharField(max_length=50) @@ -9,3 +16,9 @@ class Guitarist(models.Model): def url(self): "Returns the URL for this guitarist." return ('guitarist_detail', [self.slug]) + + @models.permalink + @set_attr('attribute', 'value') + def url_with_attribute(self): + "Returns the URL for this guitarist and holds an attribute" + return ('guitarist_detail', [self.slug]) diff --git a/tests/regressiontests/model_permalink/tests.py b/tests/regressiontests/model_permalink/tests.py index 8286f6811a4..049f338c2ed 100644 --- a/tests/regressiontests/model_permalink/tests.py +++ b/tests/regressiontests/model_permalink/tests.py @@ -16,3 +16,12 @@ class PermalinkTests(TestCase): "Methods using the @permalink decorator retain their docstring." g = Guitarist(name='Adrien Moignard', slug='adrienmoignard') self.assertEqual(g.url.__doc__, "Returns the URL for this guitarist.") + + def test_wrapped_attribute(self): + """ + Methods using the @permalink decorator can have attached attributes + from other decorators + """ + g = Guitarist(name='Adrien Moignard', slug='adrienmoignard') + self.assertTrue(hasattr(g.url_with_attribute, 'attribute')) + self.assertEqual(g.url_with_attribute.attribute, 'value') diff --git a/tests/regressiontests/test_client_regress/tests.py b/tests/regressiontests/test_client_regress/tests.py index d80293b3580..9deb8a4755b 100644 --- a/tests/regressiontests/test_client_regress/tests.py +++ b/tests/regressiontests/test_client_regress/tests.py @@ -16,6 +16,7 @@ from django.test import Client, TestCase from django.test.client import encode_file, RequestFactory from django.test.utils import ContextList, override_settings, str_prefix from django.template.response import SimpleTemplateResponse +from django.utils.translation import ugettext_lazy from django.http import HttpResponse @@ -129,6 +130,14 @@ class AssertContainsTests(TestCase): self.assertNotContains(r, 'はたけ') self.assertNotContains(r, b'\xe3\x81\xaf\xe3\x81\x9f\xe3\x81\x91'.decode('utf-8')) + def test_nontext_contains(self): + r = self.client.get('/test_client_regress/no_template_view/') + self.assertContains(r, ugettext_lazy('once')) + + def test_nontext_not_contains(self): + r = self.client.get('/test_client_regress/no_template_view/') + self.assertNotContains(r, ugettext_lazy('never')) + def test_assert_contains_renders_template_response(self): """ Test that we can pass in an unrendered SimpleTemplateReponse without throwing an error. diff --git a/tests/regressiontests/utils/dateformat.py b/tests/regressiontests/utils/dateformat.py index 0f18bb2a4d9..0f4fd67f6f1 100644 --- a/tests/regressiontests/utils/dateformat.py +++ b/tests/regressiontests/utils/dateformat.py @@ -72,6 +72,11 @@ class DateFormatTests(unittest.TestCase): self.assertEqual(dateformat.format(my_birthday, 'a'), 'p.m.') + def test_microsecond(self): + # Regression test for #18951 + dt = datetime(2009, 5, 16, microsecond=123) + self.assertEqual(dateformat.format(dt, 'u'), '000123') + def test_date_formats(self): my_birthday = datetime(1979, 7, 8, 22, 00) timestamp = datetime(2008, 5, 19, 11, 45, 23, 123456) diff --git a/tests/regressiontests/utils/numberformat.py b/tests/regressiontests/utils/numberformat.py new file mode 100644 index 00000000000..f9d9031e488 --- /dev/null +++ b/tests/regressiontests/utils/numberformat.py @@ -0,0 +1,47 @@ +from unittest import TestCase +from django.utils.numberformat import format as nformat +from sys import float_info + + +class TestNumberFormat(TestCase): + + def test_format_number(self): + self.assertEqual(nformat(1234, '.'), '1234') + self.assertEqual(nformat(1234.2, '.'), '1234.2') + self.assertEqual(nformat(1234, '.', decimal_pos=2), '1234.00') + self.assertEqual(nformat(1234, '.', grouping=2, thousand_sep=','), + '1234') + self.assertEqual(nformat(1234, '.', grouping=2, thousand_sep=',', + force_grouping=True), '12,34') + self.assertEqual(nformat(-1234.33, '.', decimal_pos=1), '-1234.3') + + def test_format_string(self): + self.assertEqual(nformat('1234', '.'), '1234') + self.assertEqual(nformat('1234.2', '.'), '1234.2') + self.assertEqual(nformat('1234', '.', decimal_pos=2), '1234.00') + self.assertEqual(nformat('1234', '.', grouping=2, thousand_sep=','), + '1234') + self.assertEqual(nformat('1234', '.', grouping=2, thousand_sep=',', + force_grouping=True), '12,34') + self.assertEqual(nformat('-1234.33', '.', decimal_pos=1), '-1234.3') + + def test_large_number(self): + most_max = ('{0}179769313486231570814527423731704356798070567525844996' + '598917476803157260780028538760589558632766878171540458953' + '514382464234321326889464182768467546703537516986049910576' + '551282076245490090389328944075868508455133942304583236903' + '222948165808559332123348274797826204144723168738177180919' + '29988125040402618412485836{1}') + most_max2 = ('{0}35953862697246314162905484746340871359614113505168999' + '31978349536063145215600570775211791172655337563430809179' + '07028764928468642653778928365536935093407075033972099821' + '15310256415249098018077865788815173701691026788460916647' + '38064458963316171186642466965495956524082894463374763543' + '61838599762500808052368249716736') + int_max = int(float_info.max) + self.assertEqual(nformat(int_max, '.'), most_max.format('', '8')) + self.assertEqual(nformat(int_max + 1, '.'), most_max.format('', '9')) + self.assertEqual(nformat(int_max * 2, '.'), most_max2.format('')) + self.assertEqual(nformat(0 - int_max, '.'), most_max.format('-', '8')) + self.assertEqual(nformat(-1 - int_max, '.'), most_max.format('-', '9')) + self.assertEqual(nformat(-2 * int_max, '.'), most_max2.format('-')) diff --git a/tests/regressiontests/utils/tests.py b/tests/regressiontests/utils/tests.py index 061c669eb7a..47af07561a5 100644 --- a/tests/regressiontests/utils/tests.py +++ b/tests/regressiontests/utils/tests.py @@ -21,6 +21,7 @@ from .http import TestUtilsHttp from .ipv6 import TestUtilsIPv6 from .jslex import JsToCForGettextTest, JsTokensTest from .module_loading import CustomLoader, DefaultLoader, EggLoader +from .numberformat import TestNumberFormat from .os_utils import SafeJoinTests from .regex_helper import NormalizeTests from .simplelazyobject import TestUtilsSimpleLazyObject