Index: django/contrib/sessions/middleware.py
===================================================================
--- django/contrib/sessions/middleware.py	(revision 8137)
+++ django/contrib/sessions/middleware.py	(working copy)
@@ -21,12 +21,18 @@
         try:
             accessed = request.session.accessed
             modified = request.session.modified
+            destroyed = request.session.destroyed
         except AttributeError:
+            # request doesn't have 'session' attribute
+            # FIXME: is it such a good idea to hide this exception?
+            # if process_request() fails to set request.session we should
+            # raise here as well instead of silently failing,
+            # perhaps a SessionNotSetError?
             pass
         else:
             if accessed:
                 patch_vary_headers(response, ('Cookie',))
-            if modified or settings.SESSION_SAVE_EVERY_REQUEST:
+            if modified or destroyed or settings.SESSION_SAVE_EVERY_REQUEST:
                 if request.session.get_expire_at_browser_close():
                     max_age = None
                     expires = None
@@ -35,7 +41,24 @@
                     expires_time = time.time() + max_age
                     expires = cookie_date(expires_time)
                 # Save the session data and refresh the client cookie.
-                request.session.save()
+                needs_saving = modified or settings.SESSION_SAVE_EVERY_REQUEST
+                if destroyed and not needs_saving:
+                    # New session not modified after destruction.
+                    # Note that not saving here results in new key generation
+                    # in load() on the next request. If this is undesirable,
+                    # the three-branched needs_saving logic is not required.
+                    pass
+                elif destroyed and needs_saving:
+                    # New session modified after destruction.  We
+                    # need to guarantee no exception from the persistance
+                    # layer can block sending the new session cookie to
+                    # client.
+                    try:
+                        request.session.save()
+                    except:
+                        pass
+                else:
+                    request.session.save()
                 response.set_cookie(settings.SESSION_COOKIE_NAME,
                         request.session.session_key, max_age=max_age,
                         expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
Index: django/contrib/sessions/tests.py
===================================================================
--- django/contrib/sessions/tests.py	(revision 8137)
+++ django/contrib/sessions/tests.py	(working copy)
@@ -22,6 +22,29 @@
 >>> db_session.delete(db_session.session_key)
 >>> db_session.exists(db_session.session_key)
 False
+>>> db_session['foo'] = 'bar'
+>>> db_session.save()
+>>> db_session.exists(db_session.session_key)
+True
+>>> prev_key = db_session.session_key
+>>> db_session.save()
+>>> db_session.destroy()
+>>> db_session.exists(db_session.session_key)
+False
+>>> db_session.exists(prev_key)
+False
+>>> db_session.session_key == prev_key
+False
+>>> len(db_session.session_key)
+32
+>>> db_session.modified, db_session.accessed
+(False, True)
+>>> db_session['foo'] = 'bar'
+>>> db_session.modified, db_session.accessed
+(True, True)
+>>> db_session.save()
+>>> db_session.exists(db_session.session_key)
+True
 
 >>> file_session = FileSession()
 >>> file_session.modified
@@ -39,6 +62,29 @@
 >>> file_session.delete(file_session.session_key)
 >>> file_session.exists(file_session.session_key)
 False
+>>> file_session['foo'] = 'bar'
+>>> file_session.save()
+>>> file_session.exists(file_session.session_key)
+True
+>>> prev_key = file_session.session_key
+>>> file_session.save()
+>>> file_session.destroy()
+>>> file_session.exists(file_session.session_key)
+False
+>>> file_session.exists(prev_key)
+False
+>>> file_session.session_key == prev_key
+False
+>>> len(file_session.session_key)
+32
+>>> file_session.modified, file_session.accessed
+(False, True)
+>>> file_session['foo'] = 'bar'
+>>> file_session.modified, file_session.accessed
+(True, True)
+>>> file_session.save()
+>>> file_session.exists(file_session.session_key)
+True
 
 # Make sure the file backend checks for a good storage dir
 >>> settings.SESSION_FILE_PATH = "/if/this/directory/exists/you/have/a/weird/computer"
@@ -61,6 +107,29 @@
 >>> cache_session.delete(cache_session.session_key)
 >>> cache_session.exists(cache_session.session_key)
 False
+>>> cache_session['foo'] = 'bar'
+>>> cache_session.save()
+>>> cache_session.exists(cache_session.session_key)
+True
+>>> prev_key = cache_session.session_key
+>>> cache_session.save()
+>>> cache_session.destroy()
+>>> cache_session.exists(cache_session.session_key)
+False
+>>> cache_session.exists(prev_key)
+False
+>>> cache_session.session_key == prev_key
+False
+>>> len(cache_session.session_key)
+32
+>>> cache_session.modified, cache_session.accessed
+(False, True)
+>>> cache_session['foo'] = 'bar'
+>>> cache_session.modified, cache_session.accessed
+(True, True)
+>>> cache_session.save()
+>>> cache_session.exists(cache_session.session_key)
+True
 
 >>> s = SessionBase()
 >>> s._session['some key'] = 'exists' # Pre-populate the session with some data
@@ -147,8 +216,17 @@
 >>> list(i)
 [('x', 1)]
 
- 
+# test .clear()
+>>> s.modified = s.accessed = False
+>>> s.items()
+[('x', 1)]
+>>> s.clear()
+>>> s.items()
+[]
+>>> s.accessed, s.modified
+(True, True)
 
+
 #########################
 # Custom session expiry #
 #########################
Index: django/contrib/sessions/backends/base.py
===================================================================
--- django/contrib/sessions/backends/base.py	(revision 8137)
+++ django/contrib/sessions/backends/base.py	(working copy)
@@ -25,6 +25,7 @@
         self._session_key = session_key
         self.accessed = False
         self.modified = False
+        self.destroyed = False
 
     def __contains__(self, key):
         return key in self._session
@@ -53,6 +54,23 @@
         self.modified = self.modified or key in self._session
         return self._session.pop(key, *args)
 
+    def clear(self):
+        self._session.clear()
+        self.modified = True
+
+    def destroy(self):
+        self._session.clear()
+        try:
+            # remove old session, may fail
+            self.delete(self._session_key)
+        except:
+            # ignore exceptions to guarantee session key reset
+            pass
+        self._session_key = self._get_new_session_key(safe=True)
+        self.modified = False
+        self.accessed = True
+        self.destroyed = True
+
     def setdefault(self, key, value):
         if key in self._session:
             return self._session[key]
@@ -107,7 +125,7 @@
     def iteritems(self):
         return self._session.iteritems()
 
-    def _get_new_session_key(self):
+    def _get_new_session_key(self, safe=False):
         "Returns session key that isn't being used."
         # The random module is seeded when this Apache child is created.
         # Use settings.SECRET_KEY as added salt.
@@ -119,8 +137,17 @@
         while 1:
             session_key = md5.new("%s%s%s%s" % (random.randint(0, sys.maxint - 1),
                                   pid, time.time(), settings.SECRET_KEY)).hexdigest()
-            if not self.exists(session_key):
-                break
+            if safe:
+                try:
+                    if not self.exists(session_key):
+                        break
+                except:
+                    # Depends on the assumption that key collisions are rare,
+                    # see ticket #1180
+                    pass
+            else:
+                if not self.exists(session_key):
+                    break
         return session_key
 
     def _get_session_key(self):
Index: docs/sessions.txt
===================================================================
--- docs/sessions.txt	(revision 8137)
+++ docs/sessions.txt	(working copy)
@@ -106,8 +106,18 @@
 
     * ``setdefault()`` (**New in Django development version**)
 
+    * ``clear()`` (**New in Django development version**)
+
 It also has these methods:
 
+    * ``destroy()``
+
+      **New in Django development version**
+
+      Clears session data, deletes the old session from session storage and
+      creates a new empty session in exception-safe manner. This is the
+      recommended, safe way of resetting sessions on e.g. user logout.
+
     * ``set_test_cookie()``
 
       Sets a test cookie to determine whether the user's browser supports
