Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

queryset-refactor: Merged to [6340]

git-svn-id: http://code.djangoproject.com/svn/django/branches/queryset-refactor@6341 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 28a4aa6f49b11881d43a585f118a3d0537ba9084 1 parent ca33d30
Adrian Holovaty authored September 15, 2007
1  AUTHORS
@@ -87,6 +87,7 @@ answer newbie questions, and generally made Django that much better:
87 87
     Matt Croydon <http://www.postneo.com/>
88 88
     flavio.curella@gmail.com
89 89
     Jure Cuhalev <gandalf@owca.info>
  90
+    John D'Agostino <john.dagostino@gmail.com>
90 91
     dackze+django@gmail.com
91 92
     David Danier <goliath.mailinglist@gmx.de>
92 93
     Dirk Datzert <dummy@habmalnefrage.de>
14  django/conf/global_settings.py
@@ -271,12 +271,14 @@
271 271
 # SESSIONS #
272 272
 ############
273 273
 
274  
-SESSION_COOKIE_NAME = 'sessionid'         # Cookie name. This can be whatever you want.
275  
-SESSION_COOKIE_AGE = 60 * 60 * 24 * 7 * 2 # Age of cookie, in seconds (default: 2 weeks).
276  
-SESSION_COOKIE_DOMAIN = None              # A string like ".lawrence.com", or None for standard domain cookie.
277  
-SESSION_COOKIE_SECURE = False             # Whether the session cookie should be secure (https:// only).
278  
-SESSION_SAVE_EVERY_REQUEST = False        # Whether to save the session data on every request.
279  
-SESSION_EXPIRE_AT_BROWSER_CLOSE = False   # Whether sessions expire when a user closes his browser.
  274
+SESSION_COOKIE_NAME = 'sessionid'                       # Cookie name. This can be whatever you want.
  275
+SESSION_COOKIE_AGE = 60 * 60 * 24 * 7 * 2               # Age of cookie, in seconds (default: 2 weeks).
  276
+SESSION_COOKIE_DOMAIN = None                            # A string like ".lawrence.com", or None for standard domain cookie.
  277
+SESSION_COOKIE_SECURE = False                           # Whether the session cookie should be secure (https:// only).
  278
+SESSION_SAVE_EVERY_REQUEST = False                      # Whether to save the session data on every request.
  279
+SESSION_EXPIRE_AT_BROWSER_CLOSE = False                 # Whether sessions expire when a user closes his browser.
  280
+SESSION_ENGINE = 'django.contrib.sessions.backends.db'  # The module to store session data
  281
+SESSION_FILE_PATH = '/tmp/'                             # Directory to store session files if using the file session module
280 282
 
281 283
 #########
282 284
 # CACHE #
4  django/contrib/auth/handlers/modpython.py
@@ -10,6 +10,10 @@ def authenhandler(req, **kwargs):
10 10
     # that so that the following import works
11 11
     os.environ.update(req.subprocess_env)
12 12
 
  13
+    # apache 2.2 requires a call to req.get_basic_auth_pw() before 
  14
+    # req.user and friends are available.
  15
+    req.get_basic_auth_pw()
  16
+
13 17
     # check for PythonOptions
14 18
     _str_to_bool = lambda s: s.lower() in ('1', 'true', 'on', 'yes')
15 19
 
53  django/contrib/auth/models.py
@@ -15,25 +15,43 @@
15 15
 except NameError:
16 16
     from sets import Set as set   # Python 2.3 fallback
17 17
 
  18
+def get_hexdigest(algorithm, salt, raw_password):
  19
+    """
  20
+    Returns a string of the hexdigest of the given plaintext password and salt
  21
+    using the given algorithm ('md5', 'sha1' or 'crypt').
  22
+    """
  23
+    raw_password, salt = smart_str(raw_password), smart_str(salt)
  24
+    if algorithm == 'crypt':
  25
+        try:
  26
+            import crypt
  27
+        except ImportError:
  28
+            raise ValueError('"crypt" password algorithm not supported in this environment')
  29
+        return crypt.crypt(raw_password, salt)
  30
+    # The rest of the supported algorithms are supported by hashlib, but
  31
+    # hashlib is only available in Python 2.5.
  32
+    try:
  33
+        import hashlib
  34
+    except ImportError:
  35
+        if algorithm == 'md5':
  36
+            import md5
  37
+            return md5.new(salt + raw_password).hexdigest()
  38
+        elif algorithm == 'sha1':
  39
+            import sha
  40
+            return sha.new(salt + raw_password).hexdigest()
  41
+    else:
  42
+        if algorithm == 'md5':
  43
+            return hashlib.md5(salt + raw_password).hexdigest()
  44
+        elif algorithm == 'sha1':
  45
+            return hashlib.sha1(salt + raw_password).hexdigest()
  46
+    raise ValueError("Got unknown password algorithm type in password.")
  47
+
18 48
 def check_password(raw_password, enc_password):
19 49
     """
20 50
     Returns a boolean of whether the raw_password was correct. Handles
21 51
     encryption formats behind the scenes.
22 52
     """
23 53
     algo, salt, hsh = enc_password.split('$')
24  
-    if algo == 'md5':
25  
-        import md5
26  
-        return hsh == md5.new(smart_str(salt + raw_password)).hexdigest()
27  
-    elif algo == 'sha1':
28  
-        import sha
29  
-        return hsh == sha.new(smart_str(salt + raw_password)).hexdigest()
30  
-    elif algo == 'crypt':
31  
-        try:
32  
-            import crypt
33  
-        except ImportError:
34  
-            raise ValueError, "Crypt password algorithm not supported in this environment."
35  
-        return hsh == crypt.crypt(smart_str(raw_password), smart_str(salt))
36  
-    raise ValueError, "Got unknown password algorithm type in password."
  54
+    return hsh == get_hexdigest(algo, salt, raw_password)
37 55
 
38 56
 class SiteProfileNotAvailable(Exception):
39 57
     pass
@@ -162,10 +180,10 @@ def get_full_name(self):
162 180
         return full_name.strip()
163 181
 
164 182
     def set_password(self, raw_password):
165  
-        import sha, random
  183
+        import random
166 184
         algo = 'sha1'
167  
-        salt = sha.new(str(random.random())).hexdigest()[:5]
168  
-        hsh = sha.new(salt + smart_str(raw_password)).hexdigest()
  185
+        salt = get_hexdigest(algo, str(random.random()), str(random.random()))[:5]
  186
+        hsh = get_hexdigest(algo, salt, raw_password)
169 187
         self.password = '%s$%s$%s' % (algo, salt, hsh)
170 188
 
171 189
     def check_password(self, raw_password):
@@ -176,8 +194,7 @@ def check_password(self, raw_password):
176 194
         # Backwards-compatibility check. Older passwords won't include the
177 195
         # algorithm or salt.
178 196
         if '$' not in self.password:
179  
-            import md5
180  
-            is_correct = (self.password == md5.new(smart_str(raw_password)).hexdigest())
  197
+            is_correct = (self.password == get_hexdigest('md5', '', raw_password))
181 198
             if is_correct:
182 199
                 # Convert the password to the new, more secure format.
183 200
                 self.set_password(raw_password)
143  django/contrib/sessions/backends/base.py
... ...
@@ -0,0 +1,143 @@
  1
+import base64
  2
+import md5
  3
+import os
  4
+import random
  5
+import sys
  6
+import time
  7
+from django.conf import settings
  8
+from django.core.exceptions import SuspiciousOperation
  9
+
  10
+try:
  11
+    import cPickle as pickle
  12
+except ImportError:
  13
+    import pickle
  14
+
  15
+class SessionBase(object):
  16
+    """
  17
+    Base class for all Session classes.
  18
+    """
  19
+
  20
+    TEST_COOKIE_NAME = 'testcookie'
  21
+    TEST_COOKIE_VALUE = 'worked'
  22
+
  23
+    def __init__(self, session_key=None):
  24
+        self._session_key = session_key
  25
+        self.accessed = False
  26
+        self.modified = False
  27
+
  28
+    def __contains__(self, key):
  29
+        return key in self._session
  30
+
  31
+    def __getitem__(self, key):
  32
+        return self._session[key]
  33
+
  34
+    def __setitem__(self, key, value):
  35
+        self._session[key] = value
  36
+        self.modified = True
  37
+
  38
+    def __delitem__(self, key):
  39
+        del self._session[key]
  40
+        self.modified = True
  41
+
  42
+    def keys(self):
  43
+        return self._session.keys()
  44
+
  45
+    def items(self):
  46
+        return self._session.items()
  47
+
  48
+    def get(self, key, default=None):
  49
+        return self._session.get(key, default)
  50
+
  51
+    def pop(self, key, *args):
  52
+        return self._session.pop(key, *args)
  53
+
  54
+    def set_test_cookie(self):
  55
+        self[self.TEST_COOKIE_NAME] = self.TEST_COOKIE_VALUE
  56
+
  57
+    def test_cookie_worked(self):
  58
+        return self.get(self.TEST_COOKIE_NAME) == self.TEST_COOKIE_VALUE
  59
+
  60
+    def delete_test_cookie(self):
  61
+        del self[self.TEST_COOKIE_NAME]
  62
+        
  63
+    def encode(self, session_dict):
  64
+        "Returns the given session dictionary pickled and encoded as a string."
  65
+        pickled = pickle.dumps(session_dict, pickle.HIGHEST_PROTOCOL)
  66
+        pickled_md5 = md5.new(pickled + settings.SECRET_KEY).hexdigest()
  67
+        return base64.encodestring(pickled + pickled_md5)
  68
+
  69
+    def decode(self, session_data):
  70
+        encoded_data = base64.decodestring(session_data)
  71
+        pickled, tamper_check = encoded_data[:-32], encoded_data[-32:]
  72
+        if md5.new(pickled + settings.SECRET_KEY).hexdigest() != tamper_check:
  73
+            raise SuspiciousOperation("User tampered with session cookie.")
  74
+        try:
  75
+            return pickle.loads(pickled)
  76
+        # Unpickling can cause a variety of exceptions. If something happens,
  77
+        # just return an empty dictionary (an empty session).
  78
+        except:
  79
+            return {}
  80
+        
  81
+    def _get_new_session_key(self):
  82
+        "Returns session key that isn't being used."
  83
+        # The random module is seeded when this Apache child is created.
  84
+        # Use settings.SECRET_KEY as added salt.
  85
+        while 1:
  86
+            session_key = md5.new("%s%s%s%s" % (random.randint(0, sys.maxint - 1), 
  87
+                                  os.getpid(), time.time(), settings.SECRET_KEY)).hexdigest()
  88
+            if not self.exists(session_key):
  89
+                break
  90
+        return session_key
  91
+        
  92
+    def _get_session_key(self):
  93
+        if self._session_key:
  94
+            return self._session_key
  95
+        else:
  96
+            self._session_key = self._get_new_session_key()
  97
+            return self._session_key
  98
+    
  99
+    def _set_session_key(self, session_key):
  100
+        self._session_key = session_key
  101
+    
  102
+    session_key = property(_get_session_key, _set_session_key)
  103
+
  104
+    def _get_session(self):
  105
+        # Lazily loads session from storage.
  106
+        self.accessed = True
  107
+        try:
  108
+            return self._session_cache
  109
+        except AttributeError:
  110
+            if self.session_key is None:
  111
+                self._session_cache = {}
  112
+            else:
  113
+                self._session_cache = self.load()
  114
+        return self._session_cache
  115
+
  116
+    _session = property(_get_session)
  117
+    
  118
+    # Methods that child classes must implement.
  119
+    
  120
+    def exists(self, session_key):
  121
+        """
  122
+        Returns True if the given session_key already exists.
  123
+        """
  124
+        raise NotImplementedError
  125
+
  126
+    def save(self):
  127
+        """
  128
+        Saves the session data.
  129
+        """
  130
+        raise NotImplementedError
  131
+
  132
+    def delete(self, session_key):
  133
+        """
  134
+        Clears out the session data under this key.
  135
+        """
  136
+        raise NotImplementedError
  137
+
  138
+    def load(self):
  139
+        """
  140
+        Loads the session data and returns a dictionary.
  141
+        """
  142
+        raise NotImplementedError
  143
+        
26  django/contrib/sessions/backends/cache.py
... ...
@@ -0,0 +1,26 @@
  1
+from django.conf import settings
  2
+from django.contrib.sessions.backends.base import SessionBase
  3
+from django.core.cache import cache
  4
+
  5
+class SessionStore(SessionBase):
  6
+    """
  7
+    A cache-based session store. 
  8
+    """
  9
+    def __init__(self, session_key=None):
  10
+        self._cache = cache
  11
+        super(SessionStore, self).__init__(session_key)
  12
+        
  13
+    def load(self):
  14
+        session_data = self._cache.get(self.session_key)
  15
+        return session_data or {}
  16
+
  17
+    def save(self):
  18
+        self._cache.set(self.session_key, self._session, settings.SESSION_COOKIE_AGE)
  19
+
  20
+    def exists(self, session_key):
  21
+        if self._cache.get(session_key):
  22
+            return True
  23
+        return False
  24
+        
  25
+    def delete(self, session_key):
  26
+        self._cache.delete(session_key)
49  django/contrib/sessions/backends/db.py
... ...
@@ -0,0 +1,49 @@
  1
+from django.conf import settings
  2
+from django.contrib.sessions.models import Session
  3
+from django.contrib.sessions.backends.base import SessionBase
  4
+from django.core.exceptions import SuspiciousOperation
  5
+import datetime
  6
+
  7
+class SessionStore(SessionBase):
  8
+    """
  9
+    Implements database session store
  10
+    """
  11
+    def __init__(self, session_key=None):
  12
+        super(SessionStore, self).__init__(session_key)
  13
+    
  14
+    def load(self):
  15
+        try:
  16
+            s = Session.objects.get(
  17
+                session_key = self.session_key, 
  18
+                expire_date__gt=datetime.datetime.now()
  19
+            )
  20
+            return self.decode(s.session_data)
  21
+        except (Session.DoesNotExist, SuspiciousOperation):
  22
+            
  23
+            # Create a new session_key for extra security.
  24
+            self.session_key = self._get_new_session_key()
  25
+            self._session_cache = {}
  26
+
  27
+            # Save immediately to minimize collision
  28
+            self.save()
  29
+            return {}
  30
+            
  31
+    def exists(self, session_key):
  32
+        try:
  33
+            Session.objects.get(session_key=session_key)
  34
+        except Session.DoesNotExist:
  35
+            return False
  36
+        return True
  37
+            
  38
+    def save(self):
  39
+        Session.objects.create(
  40
+            session_key = self.session_key,
  41
+            session_data = self.encode(self._session),
  42
+            expire_date = datetime.datetime.now() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE)
  43
+        )
  44
+    
  45
+    def delete(self, session_key):
  46
+        try:
  47
+            Session.objects.get(session_key=session_key).delete()
  48
+        except Session.DoesNotExist:
  49
+            pass
67  django/contrib/sessions/backends/file.py
... ...
@@ -0,0 +1,67 @@
  1
+import os
  2
+from django.conf import settings
  3
+from django.contrib.sessions.backends.base import SessionBase
  4
+from django.core.exceptions import SuspiciousOperation
  5
+
  6
+class SessionStore(SessionBase):
  7
+    """
  8
+    Implements a file based session store.
  9
+    """
  10
+    def __init__(self, session_key=None):
  11
+        self.storage_path = settings.SESSION_FILE_PATH
  12
+        self.file_prefix = settings.SESSION_COOKIE_NAME    
  13
+        super(SessionStore, self).__init__(session_key)
  14
+    
  15
+    def _key_to_file(self, session_key=None):
  16
+        """
  17
+        Get the file associated with this session key.
  18
+        """
  19
+        if session_key is None:
  20
+            session_key = self.session_key
  21
+        
  22
+        # Make sure we're not vulnerable to directory traversal. Session keys
  23
+        # should always be md5s, so they should never contain directory components.
  24
+        if os.path.sep in session_key:
  25
+            raise SuspiciousOperation("Invalid characters (directory components) in session key")
  26
+            
  27
+        return os.path.join(self.storage_path, self.file_prefix + session_key)
  28
+            
  29
+    def load(self):
  30
+        session_data = {}
  31
+        try:
  32
+            session_file = open(self._key_to_file(), "rb")
  33
+            try:
  34
+                session_data = self.decode(session_file.read())
  35
+            except(EOFError, SuspiciousOperation):
  36
+                self._session_key = self._get_new_session_key()
  37
+                self._session_cache = {}
  38
+                self.save()
  39
+            finally:
  40
+                session_file.close()
  41
+        except(IOError):
  42
+            pass
  43
+        return session_data
  44
+
  45
+    def save(self):
  46
+        try:
  47
+            f = open(self._key_to_file(self.session_key), "wb")
  48
+            try:
  49
+                f.write(self.encode(self._session))
  50
+            finally:
  51
+                f.close()
  52
+        except(IOError, EOFError):
  53
+            pass
  54
+
  55
+    def exists(self, session_key):
  56
+        if os.path.exists(self._key_to_file(session_key)):
  57
+            return True
  58
+        return False
  59
+        
  60
+    def delete(self, session_key):
  61
+        try:
  62
+            os.unlink(self._key_to_file(session_key))
  63
+        except OSError:
  64
+            pass
  65
+            
  66
+    def clean(self):
  67
+        pass
89  django/contrib/sessions/middleware.py
... ...
@@ -1,6 +1,4 @@
1 1
 from django.conf import settings
2  
-from django.contrib.sessions.models import Session
3  
-from django.core.exceptions import SuspiciousOperation
4 2
 from django.utils.cache import patch_vary_headers
5 3
 from email.Utils import formatdate
6 4
 import datetime
@@ -9,73 +7,11 @@
9 7
 TEST_COOKIE_NAME = 'testcookie'
10 8
 TEST_COOKIE_VALUE = 'worked'
11 9
 
12  
-class SessionWrapper(object):
13  
-    def __init__(self, session_key):
14  
-        self.session_key = session_key
15  
-        self.accessed = False
16  
-        self.modified = False
17  
-
18  
-    def __contains__(self, key):
19  
-        return key in self._session
20  
-
21  
-    def __getitem__(self, key):
22  
-        return self._session[key]
23  
-
24  
-    def __setitem__(self, key, value):
25  
-        self._session[key] = value
26  
-        self.modified = True
27  
-
28  
-    def __delitem__(self, key):
29  
-        del self._session[key]
30  
-        self.modified = True
31  
-
32  
-    def keys(self):
33  
-        return self._session.keys()
34  
-
35  
-    def items(self):
36  
-        return self._session.items()
37  
-
38  
-    def get(self, key, default=None):
39  
-        return self._session.get(key, default)
40  
-
41  
-    def pop(self, key, *args):
42  
-        self.modified = self.modified or key in self._session
43  
-        return self._session.pop(key, *args)
44  
-
45  
-    def set_test_cookie(self):
46  
-        self[TEST_COOKIE_NAME] = TEST_COOKIE_VALUE
47  
-
48  
-    def test_cookie_worked(self):
49  
-        return self.get(TEST_COOKIE_NAME) == TEST_COOKIE_VALUE
50  
-
51  
-    def delete_test_cookie(self):
52  
-        del self[TEST_COOKIE_NAME]
53  
-
54  
-    def _get_session(self):
55  
-        # Lazily loads session from storage.
56  
-        self.accessed = True
57  
-        try:
58  
-            return self._session_cache
59  
-        except AttributeError:
60  
-            if self.session_key is None:
61  
-                self._session_cache = {}
62  
-            else:
63  
-                try:
64  
-                    s = Session.objects.get(session_key=self.session_key,
65  
-                        expire_date__gt=datetime.datetime.now())
66  
-                    self._session_cache = s.get_decoded()
67  
-                except (Session.DoesNotExist, SuspiciousOperation):
68  
-                    self._session_cache = {}
69  
-                    # Set the session_key to None to force creation of a new
70  
-                    # key, for extra security.
71  
-                    self.session_key = None
72  
-            return self._session_cache
73  
-
74  
-    _session = property(_get_session)
75  
-
76 10
 class SessionMiddleware(object):
  11
+
77 12
     def process_request(self, request):
78  
-        request.session = SessionWrapper(request.COOKIES.get(settings.SESSION_COOKIE_NAME, None))
  13
+      engine = __import__(settings.SESSION_ENGINE, {}, {}, [''])
  14
+      request.session = engine.SessionStore(request.COOKIES.get(settings.SESSION_COOKIE_NAME, None))
79 15
 
80 16
     def process_response(self, request, response):
81 17
         # If request.session was modified, or if response.session was set, save
@@ -89,25 +25,22 @@ def process_response(self, request, response):
89 25
             if accessed:
90 26
                 patch_vary_headers(response, ('Cookie',))
91 27
             if modified or settings.SESSION_SAVE_EVERY_REQUEST:
92  
-                if request.session.session_key:
93  
-                    session_key = request.session.session_key
94  
-                else:
95  
-                    obj = Session.objects.get_new_session_object()
96  
-                    session_key = obj.session_key
97  
-
98 28
                 if settings.SESSION_EXPIRE_AT_BROWSER_CLOSE:
99 29
                     max_age = None
100 30
                     expires = None
101 31
                 else:
102 32
                     max_age = settings.SESSION_COOKIE_AGE
103 33
                     rfcdate = formatdate(time.time() + settings.SESSION_COOKIE_AGE)
  34
+                    
104 35
                     # Fixed length date must have '-' separation in the format
105 36
                     # DD-MMM-YYYY for compliance with Netscape cookie standard
106  
-                    expires = (rfcdate[:7] + "-" + rfcdate[8:11]
107  
-                               + "-" + rfcdate[12:26] + "GMT")
108  
-                new_session = Session.objects.save(session_key, request.session._session,
109  
-                    datetime.datetime.now() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE))
110  
-                response.set_cookie(settings.SESSION_COOKIE_NAME, session_key,
  37
+                    expires = datetime.datetime.strftime(datetime.datetime.utcnow() + \
  38
+                              datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE), "%a, %d-%b-%Y %H:%M:%S GMT")
  39
+
  40
+                # Save the seesion data and refresh the client cookie.
  41
+                request.session.save()
  42
+                response.set_cookie(settings.SESSION_COOKIE_NAME, request.session.session_key,
111 43
                     max_age=max_age, expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
112 44
                     secure=settings.SESSION_COOKIE_SECURE or None)
  45
+                    
113 46
         return response
3  django/contrib/sessions/models.py
... ...
@@ -1,4 +1,4 @@
1  
-import base64, md5, random, sys, datetime, os, time
  1
+import base64, md5, random, sys, datetime
2 2
 import cPickle as pickle
3 3
 from django.db import models
4 4
 from django.utils.translation import ugettext_lazy as _
@@ -74,6 +74,7 @@ class Session(models.Model):
74 74
     session_data = models.TextField(_('session data'))
75 75
     expire_date = models.DateTimeField(_('expire date'))
76 76
     objects = SessionManager()
  77
+
77 78
     class Meta:
78 79
         db_table = 'django_session'
79 80
         verbose_name = _('session')
60  django/contrib/sessions/tests.py
... ...
@@ -1,35 +1,59 @@
1 1
 r"""
2  
->>> s = SessionWrapper(None)
3 2
 
4  
-Inject data into the session cache.
5  
->>> s._session_cache = {}
6  
->>> s._session_cache['some key'] = 'exists'
  3
+>>> from django.contrib.sessions.backends.db import SessionStore as DatabaseSession
  4
+>>> from django.contrib.sessions.backends.cache import SessionStore as CacheSession
  5
+>>> from django.contrib.sessions.backends.file import SessionStore as FileSession
7 6
 
8  
->>> s.accessed
  7
+>>> db_session = DatabaseSession()
  8
+>>> db_session.modified
9 9
 False
10  
->>> s.modified
11  
-False
12  
-
13  
->>> s.pop('non existant key', 'does not exist')
  10
+>>> db_session['cat'] = "dog"
  11
+>>> db_session.modified
  12
+True
  13
+>>> db_session.pop('cat')
  14
+'dog'
  15
+>>> db_session.pop('some key', 'does not exist')
14 16
 'does not exist'
15  
->>> s.accessed
  17
+>>> db_session.save()
  18
+>>> db_session.exists(db_session.session_key)
16 19
 True
17  
->>> s.modified
  20
+>>> db_session.delete(db_session.session_key)
  21
+>>> db_session.exists(db_session.session_key)
18 22
 False
19 23
 
20  
->>> s.pop('some key')
21  
-'exists'
22  
->>> s.accessed
  24
+>>> file_session = FileSession()
  25
+>>> file_session.modified
  26
+False
  27
+>>> file_session['cat'] = "dog"
  28
+>>> file_session.modified
23 29
 True
24  
->>> s.modified
  30
+>>> file_session.pop('cat')
  31
+'dog'
  32
+>>> file_session.pop('some key', 'does not exist')
  33
+'does not exist'
  34
+>>> file_session.save()
  35
+>>> file_session.exists(file_session.session_key)
25 36
 True
  37
+>>> file_session.delete(file_session.session_key)
  38
+>>> file_session.exists(file_session.session_key)
  39
+False
26 40
 
27  
->>> s.pop('some key', 'does not exist')
  41
+>>> cache_session = CacheSession()
  42
+>>> cache_session.modified
  43
+False
  44
+>>> cache_session['cat'] = "dog"
  45
+>>> cache_session.modified
  46
+True
  47
+>>> cache_session.pop('cat')
  48
+'dog'
  49
+>>> cache_session.pop('some key', 'does not exist')
28 50
 'does not exist'
  51
+>>> cache_session.save()
  52
+>>> cache_session.delete(cache_session.session_key)
  53
+>>> cache_session.exists(cache_session.session_key)
  54
+False
29 55
 """
30 56
 
31  
-from django.contrib.sessions.middleware import SessionWrapper
32  
-
33 57
 if __name__ == '__main__':
34 58
     import doctest
35 59
     doctest.testmod()
55  django/db/__init__.py
... ...
@@ -1,6 +1,9 @@
  1
+import os
1 2
 from django.conf import settings
2 3
 from django.core import signals
  4
+from django.core.exceptions import ImproperlyConfigured
3 5
 from django.dispatch import dispatcher
  6
+from django.utils.functional import curry
4 7
 
5 8
 __all__ = ('backend', 'connection', 'DatabaseError', 'IntegrityError')
6 9
 
@@ -8,25 +11,43 @@
8 11
     settings.DATABASE_ENGINE = 'dummy'
9 12
 
10 13
 try:
11  
-    backend = __import__('django.db.backends.%s.base' % settings.DATABASE_ENGINE, {}, {}, [''])
  14
+    # Most of the time, the database backend will be one of the official 
  15
+    # backends that ships with Django, so look there first.
  16
+    _import_path = 'django.db.backends.'
  17
+    backend = __import__('%s%s.base' % (_import_path, settings.DATABASE_ENGINE), {}, {}, [''])
12 18
 except ImportError, e:
13  
-    # The database backend wasn't found. Display a helpful error message
14  
-    # listing all possible database backends.
15  
-    from django.core.exceptions import ImproperlyConfigured
16  
-    import os
17  
-    backend_dir = os.path.join(__path__[0], 'backends')
18  
-    available_backends = [f for f in os.listdir(backend_dir) if not f.startswith('_') and not f.startswith('.') and not f.endswith('.py') and not f.endswith('.pyc')]
19  
-    available_backends.sort()
20  
-    if settings.DATABASE_ENGINE not in available_backends:
21  
-        raise ImproperlyConfigured, "%r isn't an available database backend. Available options are: %s" % \
22  
-            (settings.DATABASE_ENGINE, ", ".join(map(repr, available_backends)))
23  
-    else:
24  
-        raise # If there's some other error, this must be an error in Django itself.
25  
-
26  
-get_introspection_module = lambda: __import__('django.db.backends.%s.introspection' % settings.DATABASE_ENGINE, {}, {}, [''])
27  
-get_creation_module = lambda: __import__('django.db.backends.%s.creation' % settings.DATABASE_ENGINE, {}, {}, [''])
28  
-runshell = lambda: __import__('django.db.backends.%s.client' % settings.DATABASE_ENGINE, {}, {}, ['']).runshell()
  19
+    # If the import failed, we might be looking for a database backend 
  20
+    # distributed external to Django. So we'll try that next.
  21
+    try:
  22
+        _import_path = ''
  23
+        backend = __import__('%s.base' % settings.DATABASE_ENGINE, {}, {}, [''])
  24
+    except ImportError, e_user:
  25
+        # The database backend wasn't found. Display a helpful error message
  26
+        # listing all possible (built-in) database backends.
  27
+        backend_dir = os.path.join(__path__[0], 'backends')
  28
+        available_backends = [f for f in os.listdir(backend_dir) if not f.startswith('_') and not f.startswith('.') and not f.endswith('.py') and not f.endswith('.pyc')]
  29
+        available_backends.sort()
  30
+        if settings.DATABASE_ENGINE not in available_backends:
  31
+            raise ImproperlyConfigured, "%r isn't an available database backend. Available options are: %s" % \
  32
+                (settings.DATABASE_ENGINE, ", ".join(map(repr, available_backends)))
  33
+        else:
  34
+            raise # If there's some other error, this must be an error in Django itself.
29 35
 
  36
+def _import_database_module(import_path='', module_name=''):
  37
+    """Lazyily import a database module when requested."""
  38
+    return __import__('%s%s.%s' % (_import_path, settings.DATABASE_ENGINE, module_name), {}, {}, [''])
  39
+
  40
+# We don't want to import the introspect/creation modules unless 
  41
+# someone asks for 'em, so lazily load them on demmand.
  42
+get_introspection_module = curry(_import_database_module, _import_path, 'introspection')
  43
+get_creation_module = curry(_import_database_module, _import_path, 'creation')
  44
+
  45
+# We want runshell() to work the same way, but we have to treat it a
  46
+# little differently (since it just runs instead of returning a module like
  47
+# the above) and wrap the lazily-loaded runshell() method.
  48
+runshell = lambda: _import_database_module(_import_path, "client").runshell()
  49
+
  50
+# Convenient aliases for backend bits.
30 51
 connection = backend.DatabaseWrapper(**settings.DATABASE_OPTIONS)
31 52
 DatabaseError = backend.DatabaseError
32 53
 IntegrityError = backend.IntegrityError
23  django/test/client.py
@@ -4,8 +4,6 @@
4 4
 from urlparse import urlparse
5 5
 from django.conf import settings
6 6
 from django.contrib.auth import authenticate, login
7  
-from django.contrib.sessions.models import Session
8  
-from django.contrib.sessions.middleware import SessionWrapper
9 7
 from django.core.handlers.base import BaseHandler
10 8
 from django.core.handlers.wsgi import WSGIRequest
11 9
 from django.core.signals import got_request_exception
@@ -132,9 +130,10 @@ def store_exc_info(self, *args, **kwargs):
132 130
     def _session(self):
133 131
         "Obtain the current session variables"
134 132
         if 'django.contrib.sessions' in settings.INSTALLED_APPS:
  133
+            engine = __import__(settings.SESSION_ENGINE, {}, {}, [''])
135 134
             cookie = self.cookies.get(settings.SESSION_COOKIE_NAME, None)
136 135
             if cookie:
137  
-                return SessionWrapper(cookie.value)
  136
+                return engine.SessionStore(cookie.value)
138 137
         return {}
139 138
     session = property(_session)
140 139
 
@@ -247,24 +246,23 @@ def login(self, **credentials):
247 246
         """
248 247
         user = authenticate(**credentials)
249 248
         if user and user.is_active and 'django.contrib.sessions' in settings.INSTALLED_APPS:
250  
-            obj = Session.objects.get_new_session_object()
  249
+            engine = __import__(settings.SESSION_ENGINE, {}, {}, [''])
251 250
 
252 251
             # Create a fake request to store login details
253 252
             request = HttpRequest()
254  
-            request.session = SessionWrapper(obj.session_key)
  253
+            request.session = engine.SessionStore()
255 254
             login(request, user)
256 255
 
257 256
             # Set the cookie to represent the session
258  
-            self.cookies[settings.SESSION_COOKIE_NAME] = obj.session_key
  257
+            self.cookies[settings.SESSION_COOKIE_NAME] = request.session.session_key
259 258
             self.cookies[settings.SESSION_COOKIE_NAME]['max-age'] = None
260 259
             self.cookies[settings.SESSION_COOKIE_NAME]['path'] = '/'
261 260
             self.cookies[settings.SESSION_COOKIE_NAME]['domain'] = settings.SESSION_COOKIE_DOMAIN
262 261
             self.cookies[settings.SESSION_COOKIE_NAME]['secure'] = settings.SESSION_COOKIE_SECURE or None
263 262
             self.cookies[settings.SESSION_COOKIE_NAME]['expires'] = None
264 263
 
265  
-            # Set the session values
266  
-            Session.objects.save(obj.session_key, request.session._session,
267  
-                datetime.datetime.now() + datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE))
  264
+            # Save the session values
  265
+            request.session.save()   
268 266
 
269 267
             return True
270 268
         else:
@@ -275,9 +273,6 @@ def logout(self):
275 273
 
276 274
         Causes the authenticated user to be logged out.
277 275
         """
278  
-        try:
279  
-            Session.objects.get(session_key=self.cookies['sessionid'].value).delete()
280  
-        except KeyError:
281  
-            pass
282  
-
  276
+        session = __import__(settings.SESSION_ENGINE, {}, {}, ['']).SessionStore()
  277
+        session.delete(session_key=self.cookies['sessionid'].value)
283 278
         self.cookies = SimpleCookie()
2  django/views/i18n.py
@@ -16,7 +16,7 @@ def set_language(request):
16 16
     redirect to the page in the request (the 'next' parameter) without changing
17 17
     any state.
18 18
     """
19  
-    next = request.GET.get('next', None)
  19
+    next = request.REQUEST.get('next', None)
20 20
     if not next:
21 21
         next = request.META.get('HTTP_REFERER', None)
22 22
     if not next:
47  docs/apache_auth.txt
@@ -21,14 +21,57 @@ file, you'll need to use mod_python's ``PythonAuthenHandler`` directive along
21 21
 with the standard ``Auth*`` and ``Require`` directives::
22 22
 
23 23
     <Location /example/>
24  
-        AuthType basic
  24
+        AuthType Basic
25 25
         AuthName "example.com"
26 26
         Require valid-user
27 27
 
28 28
         SetEnv DJANGO_SETTINGS_MODULE mysite.settings
29 29
         PythonAuthenHandler django.contrib.auth.handlers.modpython
30 30
     </Location>
31  
-
  31
+    
  32
+.. admonition:: Using the authentication handler with Apache 2.2
  33
+
  34
+    If you're using Apache 2.2, you'll need to take a couple extra steps.
  35
+    
  36
+    You'll need to ensure that ``mod_auth_basic`` and ``mod_authz_user``
  37
+    are loaded. These might be compiled staticly into Apache, or you might
  38
+    need to use ``LoadModule`` to load them dynamically (as shown in the
  39
+    example at the bottom of this note).
  40
+        
  41
+    You'll also need to insert configuration directives that prevent Apache
  42
+    from trying to use other authentication modules. Depnding on which other
  43
+    authentication modules you have loaded, you might need one or more of
  44
+    the following directives::
  45
+    
  46
+        AuthBasicAuthoritative Off
  47
+        AuthDefaultAuthoritative Off
  48
+        AuthzLDAPAuthoritative Off
  49
+        AuthzDBMAuthoritative Off
  50
+        AuthzDefaultAuthoritative Off
  51
+        AuthzGroupFileAuthoritative Off
  52
+        AuthzOwnerAuthoritative Off
  53
+        AuthzUserAuthoritative Off
  54
+        
  55
+    A complete configuration, with differences between Apache 2.0 and
  56
+    Apache 2.2 marked in bold, would look something like:
  57
+    
  58
+    .. parsed-literal::
  59
+    
  60
+        **LoadModule auth_basic_module modules/mod_auth_basic.so**
  61
+        **LoadModule authz_user_module modules/mod_authz_user.so**
  62
+    
  63
+        ...
  64
+    
  65
+        <Location /exmaple/>
  66
+            AuthType Basic
  67
+            AuthName "example.com"
  68
+            **AuthBasicAuthoritative Off**
  69
+            Require valid-user
  70
+
  71
+            SetEnv DJANGO_SETTINGS_MODULE mysite.settings
  72
+            PythonAuthenHandler django.contrib.auth.handlers.modpython
  73
+        </Location>
  74
+        
32 75
 By default, the authentication handler will limit access to the ``/example/``
33 76
 location to users marked as staff members.  You can use a set of
34 77
 ``PythonOption`` directives to modify this behavior:
9  docs/cache.txt
@@ -524,6 +524,15 @@ the value of the ``CACHE_MIDDLEWARE_SETTINGS`` setting. If you use a custom
524 524
 ``max_age`` in a ``cache_control`` decorator, the decorator will take
525 525
 precedence, and the header values will be merged correctly.)
526 526
 
  527
+If you want to use headers to disable caching altogether, 
  528
+``django.views.decorators.never_cache`` is a view decorator that adds
  529
+headers to ensure the response won't be cached by browsers or other caches. Example::
  530
+
  531
+    from django.views.decorators.cache import never_cache
  532
+    @never_cache
  533
+    def myview(request):
  534
+        ...
  535
+
527 536
 .. _`Cache-Control spec`: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
528 537
 
529 538
 Other optimizations
258  docs/contenttypes.txt
... ...
@@ -0,0 +1,258 @@
  1
+==========================
  2
+The contenttypes framework
  3
+==========================
  4
+
  5
+Django includes a "contenttypes" application that can track all of
  6
+the models installed in your Django-powered project, providing a
  7
+high-level, generic interface for working with your models.
  8
+
  9
+Overview
  10
+========
  11
+
  12
+At the heart of the contenttypes application is the ``ContentType``
  13
+model, which lives at
  14
+``django.contrib.contenttypes.models.ContentType``. Instances of
  15
+``ContentType`` represent and store information about the models
  16
+installed in your project, and new instances of ``ContentType`` are
  17
+automatically created whenever new models are installed.
  18
+
  19
+Instances of ``ContentType`` have methods for returning the model
  20
+classes they represent and for querying objects from those models.
  21
+``ContentType`` also has a `custom manager`_ that adds methods for
  22
+working with ``ContentType`` and for obtaining instances of
  23
+``ContentType`` for a particular model.
  24
+
  25
+Relations between your models and ``ContentType`` can also be used to
  26
+enable "generic" relationships between an instance of one of your
  27
+models and instances of any model you have installed.
  28
+
  29
+.. _custom manager: ../model-api/#custom-managers
  30
+
  31
+Installing the contenttypes framework
  32
+=====================================
  33
+
  34
+The contenttypes framework is included in the default
  35
+``INSTALLED_APPS`` list created by ``django-admin.py startproject``,
  36
+but if you've removed it or if you manually set up your
  37
+``INSTALLED_APPS`` list, you can enable it by adding
  38
+``'django.contrib.contenttypes'`` to your ``INSTALLED_APPS`` setting.
  39
+
  40
+It's generally a good idea to have the contenttypes framework
  41
+installed; several of Django's other bundled applications require it:
  42
+
  43
+    * The admin application uses it to log the history of each object
  44
+      added or changed through the admin interface.
  45
+
  46
+    * Django's `authentication framework`_ uses it to tie user permissions
  47
+      to specific models.
  48
+
  49
+    * Django's comments system (``django.contrib.comments``) uses it to
  50
+      "attach" comments to any installed model.
  51
+
  52
+.. _authentication framework: ../authentication/
  53
+
  54
+The ``ContentType`` model
  55
+=========================
  56
+
  57
+Each instance of ``ContentType`` has three fields which, taken
  58
+together, uniquely describe an installed model:
  59
+
  60
+    ``app_label``
  61
+        The name of the application the model is part of. This is taken from
  62
+        the ``app_label`` attribute of the model, and includes only the *last*
  63
+        part of the application's Python import path;
  64
+        "django.contrib.contenttypes", for example, becomes an ``app_label``
  65
+        of "contenttypes".
  66
+
  67
+    ``model``
  68
+        The name of the model class.
  69
+
  70
+    ``name``
  71
+        The human-readable name of the model. This is taken from
  72
+        `the verbose_name attribute`_ of the model.
  73
+
  74
+Let's look at an example to see how this works. If you already have
  75
+the contenttypes application installed, and then add `the sites
  76
+application`_ to your ``INSTALLED_APPS`` setting and run ``manage.py
  77
+syncdb`` to install it, the model ``django.contrib.sites.models.Site``
  78
+will be installed into your database. Along with it a new instance
  79
+of ``ContentType`` will be created with the following values:
  80
+
  81
+    * ``app_label`` will be set to ``'sites'`` (the last part of the Python
  82
+      path "django.contrib.sites").
  83
+
  84
+    * ``model`` will be set to ``'site'``.
  85
+
  86
+    * ``name`` will be set to ``'site'``.
  87
+
  88
+.. _the verbose_name attribute: ../model-api/#verbose_name
  89
+.. _the sites application: ../sites/
  90
+
  91
+Methods on ``ContentType`` instances
  92
+====================================
  93
+
  94
+Each ``ContentType`` instance has methods that allow you to get from a
  95
+``ContentType`` instance to the model it represents, or to retrieve objects
  96
+from that model:
  97
+
  98
+    ``get_object_for_this_type(**kwargs)``
  99
+        Takes a set of valid `lookup arguments`_ for the model the
  100
+        ``ContentType`` represents, and does `a get() lookup`_ on that
  101
+        model, returning the corresponding object.
  102
+
  103
+    ``model_class()``
  104
+        Returns the model class represented by this ``ContentType``
  105
+        instance.
  106
+
  107
+For example, we could look up the ``ContentType`` for the ``User`` model::
  108
+
  109
+    >>> from django.contrib.contenttypes.models import ContentType
  110
+    >>> user_type = ContentType.objects.get(app_label="auth", model="user")
  111
+    >>> user_type
  112
+    <ContentType: user>
  113
+
  114
+And then use it to query for a particular ``User``, or to get access
  115
+to the ``User`` model class::
  116
+
  117
+    >>> user_type.model_class()
  118
+    <class 'django.contrib.auth.models.User'>
  119
+    >>> user_type.get_object_for_this_type(username='Guido')
  120
+    <User: Guido>
  121
+
  122
+Together, ``get_object_for_this_type`` and ``model_class`` enable two
  123
+extremely important use cases:
  124
+
  125
+    1. Using these methods, you can write high-level generic code that
  126
+       performs queries on any installed model -- instead of importing and
  127
+       using a single specific model class, you can pass an ``app_label``
  128
+       and ``model`` into a ``ContentType`` lookup at runtime, and then
  129
+       work with the model class or retrieve objects from it.
  130
+
  131
+    2. You can relate another model to ``ContentType`` as a way of tying
  132
+       instances of it to particular model classes, and use these methods
  133
+       to get access to those model classes.
  134
+
  135
+Several of Django's bundled applications make use of the latter
  136
+technique. For example, `the permissions system`_ in Django's
  137
+authentication framework uses a ``Permission`` model with a foreign
  138
+key to ``ContentType``; this lets ``Permission`` represent concepts
  139
+like "can add blog entry" or "can delete news story".
  140
+
  141
+.. _lookup arguments: ../db-api/#field-lookups
  142
+.. _a get() lookup: ../db-api/#get-kwargs
  143
+.. _the permissions system: ../authentication/#permissions
  144
+
  145
+The ``ContentTypeManager``
  146
+--------------------------
  147
+
  148
+``ContentType`` also has a custom manager, ``ContentTypeManager``,
  149
+which adds the following methods:
  150
+
  151
+    ``clear_cache()``
  152
+        Clears an internal cache used by ``ContentType`` to keep track of which
  153
+        models for which it has created ``ContentType`` instances. You probably
  154
+        won't ever need to call this method yourself; Django will call it
  155
+        automatically when it's needed.
  156
+
  157
+    ``get_for_model(model)``
  158
+        Takes either a model class or an instance of a model, and returns the
  159
+        ``ContentType`` instance representing that model.
  160
+
  161
+The ``get_for_model`` method is especially useful when you know you
  162
+need to work with a ``ContentType`` but don't want to go to the
  163
+trouble of obtaining the model's metadata to perform a manual lookup::
  164
+
  165
+    >>> from django.contrib.auth.models import User
  166
+    >>> user_type = ContentType.objects.get_for_model(User)
  167
+    >>> user_type
  168
+    <ContentType: user>
  169
+
  170
+Generic relations
  171
+=================
  172
+
  173
+Adding a foreign key from one of your own models to ``ContentType``
  174
+allows your model to effectively tie itself to another model class, as
  175
+in the example of the ``Permission`` model above. But it's possible to
  176
+go one step further and use ``ContentType`` to enable truly generic
  177
+(sometimes called "polymorphic") relationships between models.
  178
+
  179
+A simple example is a tagging system, which might look like this::
  180
+
  181
+    from django.db import models
  182
+    from django.contrib.contenttypes.models import ContentType
  183
+    from django.contrib.contenttypes import generic
  184
+
  185
+    class TaggedItem(models.Model):
  186
+        tag = models.SlugField()
  187
+        content_type = models.ForeignKey(ContentType)
  188
+        object_id = models.PositiveIntegerField()
  189
+        content_object = generic.GenericForeignKey('content_type', 'object_id')
  190
+
  191
+        def __unicode__(self):
  192
+            return self.tag
  193
+
  194
+A normal ``ForeignKey`` can only "point to" one other model, which
  195
+means that if the ``TaggedItem`` model used a ``ForeignKey`` it would have to
  196
+choose one and only one model to store tags for. The contenttypes
  197
+application provides a special field type --
  198
+``django.contrib.contenttypes.generic.GenericForeignKey`` -- which
  199
+works around this and allows the relationship to be with any
  200
+model. There are three parts to setting up a ``GenericForeignKey``:
  201
+
  202
+    1. Give your model a ``ForeignKey`` to ``ContentType``.
  203
+
  204
+    2. Give your model a field that can store a primary-key value from the
  205
+       models you'll be relating to. (For most models, this means an
  206
+       ``IntegerField`` or ``PositiveIntegerField``.)
  207
+
  208
+    3. Give your model a ``GenericForeignKey``, and pass it the names of
  209
+       the two fields described above. If these fields are named
  210
+       "content_type" and "object_id", you can omit this -- those are the
  211
+       default field names ``GenericForeignKey`` will look for.
  212
+
  213
+This will enable an API similar to the one used for a normal ``ForeignKey``;
  214
+each ``TaggedItem`` will have a ``content_object`` field that returns the
  215
+object it's related to, and you can also assign to that field or use it when
  216
+creating a ``TaggedItem``::
  217
+
  218
+    >>> from django.contrib.models.auth import User
  219
+    >>> guido = User.objects.get(username='Guido')
  220
+    >>> t = TaggedItem(content_object=guido, tag='bdfl')
  221
+    >>> t.save()
  222
+    >>> t.content_object
  223
+    <User: Guido>
  224
+
  225
+Reverse generic relations
  226
+-------------------------
  227
+
  228
+If you know which models you'll be using most often, you can also add
  229
+a "reverse" generic relationship to enable an additional API. For example::
  230
+
  231
+    class Bookmark(models.Model):
  232
+        url = models.URLField()
  233
+        tags = generic.GenericRelation(TaggedItem)
  234
+
  235
+``Bookmark`` instances will each have a ``tags`` attribute, which can
  236
+be used to retrieve their associated ``TaggedItems``::
  237
+
  238
+    >>> b = Bookmark('http://www.djangoproject.com/')
  239
+    >>> b.save()
  240
+    >>> t1 = TaggedItem(content_object=b, tag='django')
  241
+    >>> t1.save()
  242
+    >>> t2 = TaggedItem(content_object=b, tag='python')
  243
+    >>> t2.save()
  244
+    >>> b.tags.all()
  245
+    [<TaggedItem: django>, <TaggedItem: python>]
  246
+
  247
+If you don't add the reverse relationship, you can do the lookup manually::
  248
+
  249
+    >>> b = Bookmark.objects.get(url='http://www.djangoproject.com/)
  250
+    >>> bookmark_type = ContentType.objects.get_for_model(b)
  251
+    >>> TaggedItem.objects.filter(content_type__pk=bookmark_type.id,
  252
+    ...                           object_id=b.id)
  253
+    [<TaggedItem: django>, <TaggedItem: python>]
  254
+
  255
+Note that if you delete an object that has a ``GenericRelation``, any objects
  256
+which have a ``GenericForeignKey`` pointing at it will be deleted as well. In
  257
+the example above, this means that if a ``Bookmark`` object were deleted, any
  258
+``TaggedItem`` objects pointing at it would be deleted at the same time.
17  docs/db-api.txt
@@ -951,6 +951,23 @@ Example::
951 951
 
952 952
 If you pass ``in_bulk()`` an empty list, you'll get an empty dictionary.
953 953
 
  954
+``iterator()``
  955
+~~~~~~~~~~~~
  956
+
  957
+Evaluates the ``QuerySet`` (by performing the query) and returns an
  958
+`iterator`_ over the results. A ``QuerySet`` typically reads all of
  959
+its results and instantiates all of the corresponding objects the
  960
+first time you access it; ``iterator()`` will instead read results and
  961
+instantiate objects in discrete chunks, yielding them one at a
  962
+time. For a ``QuerySet`` which returns a large number of objects, this
  963
+often results in better performance and a significant reduction in
  964
+memory use.
  965
+