diff --git a/django/contrib/formtools/wizard.py b/django/contrib/formtools/wizard.py
new file mode 100644
index 0000000..4fc7f88
--- /dev/null
+++ b/django/contrib/formtools/wizard.py
@@ -0,0 +1,235 @@
+"""
+FormWizard class -- implements a multi-page form, validating between each
+step and storing the form's state as HTML hidden fields so that no state is
+stored on the server side.
+"""
+
+from django import newforms as forms
+from django.conf import settings
+from django.http import Http404
+from django.shortcuts import render_to_response
+from django.template.context import RequestContext
+import cPickle as pickle
+import md5
+
+class FormWizard(object):
+    # Dictionary of extra template context variables.
+    extra_context = {}
+
+    # The HTML (and POST data) field name for the "step" variable.
+    step_field_name="wizard_step"
+
+    # METHODS SUBCLASSES SHOULDN'T OVERRIDE ###################################
+
+    def __init__(self, form_list, initial=None):
+        "form_list should be a list of Form classes (not instances)."
+        self.form_list = form_list[:]
+        self.initial = initial or {}
+        self.step = 0 # A zero-based counter keeping track of which step we're in.
+
+    def __repr__(self):
+        return "step: %d\nform_list: %s\ninitial_data: %s" % (self.step, self.form_list, self.initial)
+
+    def get_form(self, step, data=None):
+        "Helper method that returns the Form instance for the given step."
+        return self.form_list[step](data, prefix=self.prefix_for_step(step), initial=self.initial.get(step, None))
+
+    def num_steps(self):
+        "Helper method that returns the number of steps."
+        # You might think we should just set "self.form_list = len(form_list)"
+        # in __init__(), but this calculation needs to be dynamic, because some
+        # hook methods might alter self.form_list.
+        return len(self.form_list)
+
+    def __call__(self, request, *args, **kwargs):
+        """
+        Main method that does all the hard work, conforming to the Django view
+        interface.
+        """
+        if 'extra_context' in kwargs:
+            self.extra_context.update(kwargs['extra_context'])
+        current_step = self.determine_step(request, *args, **kwargs)
+
+        # Sanity check.
+        if current_step >= self.num_steps():
+            raise Http404('Step %s does not exist' % current_step)
+
+        # For each previous step, verify the hash and process.
+        # TODO: Move "hash_%d" to a method to make it configurable.
+        for i in range(current_step):
+            form = self.get_form(i, request.POST)
+            if request.POST.get("hash_%d" % i, '') != self.security_hash(request, form):
+                return self.render_hash_failure(request, i)
+            self.process_step(request, form, i)
+
+        # Process the current step. If it's valid, go to the next step or call
+        # done(), depending on whether any steps remain.
+        if request.method == 'POST':
+            form = self.get_form(current_step, request.POST)
+        else:
+            form = self.get_form(current_step)
+        if form.is_valid():
+            self.process_step(request, form, current_step)
+            next_step = current_step + 1
+
+            # If this was the last step, validate all of the forms one more
+            # time, as a sanity check, and call done().
+            num = self.num_steps()
+            if next_step == num:
+                final_form_list = [self.get_form(i, request.POST) for i in range(num)]
+
+                # Validate all the forms. If any of them fail validation, that
+                # must mean the validator relied on some other input, such as
+                # an external Web site.
+                for i, f in enumerate(final_form_list):
+                    if not f.is_valid():
+                        return self.render_revalidation_failure(request, i, f)
+                return self.done(request, final_form_list)
+
+            # Otherwise, move along to the next step.
+            else:
+                form = self.get_form(next_step)
+                current_step = next_step
+
+        return self.render(form, request, current_step)
+
+    def render(self, form, request, step, context=None):
+        "Renders the given Form object, returning an HttpResponse."
+        old_data = request.POST
+        prev_fields = []
+        if old_data:
+            hidden = forms.HiddenInput()
+            # Collect all data from previous steps and render it as HTML hidden fields.
+            for i in range(step):
+                old_form = self.get_form(i, old_data)
+                hash_name = 'hash_%s' % i
+                prev_fields.extend([bf.as_hidden() for bf in old_form])
+                prev_fields.append(hidden.render(hash_name, old_data.get(hash_name, self.security_hash(request, old_form))))
+        return self.render_template(request, form, ''.join(prev_fields), step, context)
+
+    # METHODS SUBCLASSES MIGHT OVERRIDE IF APPROPRIATE ########################
+
+    def prefix_for_step(self, step):
+        "Given the step, returns a Form prefix to use."
+        return str(step)
+
+    def render_hash_failure(self, request, step):
+        """
+        Hook for rendering a template if a hash check failed.
+
+        step is the step that failed. Any previous step is guaranteed to be
+        valid.
+
+        This default implementation simply renders the form for the given step,
+        but subclasses may want to display an error message, etc.
+        """
+        return self.render(self.get_form(step), request, step, context={'wizard_error': 'We apologize, but your form has expired. Please continue filling out the form from this page.'})
+
+    def render_revalidation_failure(self, request, step, form):
+        """
+        Hook for rendering a template if final revalidation failed.
+
+        It is highly unlikely that this point would ever be reached, but See
+        the comment in __call__() for an explanation.
+        """
+        return self.render(form, request, step)
+
+    def security_hash(self, request, form):
+        """
+        Calculates the security hash for the given HttpRequest and Form instances.
+
+        This creates a list of the form field names/values in a deterministic
+        order, pickles the result with the SECRET_KEY setting and takes an md5
+        hash of that.
+
+        Subclasses may want to take into account request-specific information,
+        such as the IP address.
+        """
+        data = [(bf.name, bf.data) for bf in form] + [settings.SECRET_KEY]
+        # Use HIGHEST_PROTOCOL because it's the most efficient. It requires
+        # Python 2.3, but Django requires 2.3 anyway, so that's OK.
+        pickled = pickle.dumps(data, protocol=pickle.HIGHEST_PROTOCOL)
+        return md5.new(pickled).hexdigest()
+
+    def determine_step(self, request, *args, **kwargs):
+        """
+        Given the request object and whatever *args and **kwargs were passed to
+        __call__(), returns the current step (which is zero-based).
+
+        Note that the result should not be trusted. It may even be a completely
+        invalid number. It's not the job of this method to validate it.
+        """
+        if not request.POST:
+            return 0
+        try:
+            step = int(request.POST.get(self.step_field_name, 0))
+        except ValueError:
+            return 0
+        return step
+
+    def get_template(self, step):
+        """
+        Hook for specifying the name of the template to use for a given step.
+
+        Note that this can return a tuple of template names if you'd like to
+        use the template system's select_template() hook.
+        """
+        return 'forms/wizard.html'
+
+    def render_template(self, request, form, previous_fields, step, context=None):
+        """
+        Renders the template for the given step, returning an HttpResponse object.
+
+        Override this method if you want to add a custom context, return a
+        different MIME type, etc. If you only need to override the template
+        name, use get_template() instead.
+
+        The template will be rendered with the following context:
+            step_field -- The name of the hidden field containing the step.
+            step0      -- The current step (zero-based).
+            step       -- The current step (one-based).
+            form       -- The Form instance for the current step (either empty
+                          or with errors).
+            previous_fields -- A string representing every previous data field,
+                          plus hashes for completed forms, all in the form of
+                          hidden fields. Note that you'll need to run this
+                          through the "safe" template filter, to prevent
+                          auto-escaping, because it's raw HTML.
+        """
+        context = context or {}
+        context.update(self.extra_context)
+        return render_to_response(self.get_template(self.step), dict(context,
+            step_field=self.step_field_name,
+            step0=step,
+            step=step + 1,
+            step_count=self.num_steps(),
+            form=form,
+            previous_fields=previous_fields
+        ), context_instance=RequestContext(request))
+
+    def process_step(self, request, form, step):
+        """
+        Hook for modifying the FormWizard's internal state, given a fully
+        validated Form object. The Form is guaranteed to have clean, valid
+        data.
+
+        This method should *not* modify any of that data. Rather, it might want
+        to set self.extra_context or dynamically alter self.form_list, based on
+        previously submitted forms.
+
+        Note that this method is called every time a page is rendered for *all*
+        submitted steps.
+        """
+        pass
+
+    # METHODS SUBCLASSES MUST OVERRIDE ########################################
+
+    def done(self, request, form_list):
+        """
+        Hook for doing something with the validated data. This is responsible
+        for the final processing.
+
+        form_list is a list of Form instances, each containing clean, valid
+        data.
+        """
+        raise NotImplementedError("Your %s class has not defined a done() method, which is required." % self.__class__.__name__)
diff --git a/django/views/generic/create_update.py b/django/views/generic/create_update.py
index 46e92fe..e9a2dc9 100644
--- a/django/views/generic/create_update.py
+++ b/django/views/generic/create_update.py
@@ -7,6 +7,196 @@ from django.template import RequestContext
 from django.http import Http404, HttpResponse, HttpResponseRedirect
 from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
 from django.utils.translation import ugettext
+from django import newforms as forms
+from django.newforms.models import ModelFormMetaclass, ModelForm
+
+
+# New-sytle, class based generic views #######################################
+
+class BaseView(object):
+    """
+    Base class for generic object creation and update view.
+
+    Templates: ``<app_label>/<model_name>_form.html``
+    Context:
+        form
+            the ``ModelForm`` instance for the object
+    """
+    def __init__(self, model, post_save_redirect=None):
+        self.model = model
+        self.post_save_redirect = None
+
+    def __call__(self, request):
+        return self.main(request, self.get_instance(request))
+
+    def main(self, request, instance):
+        Form = self.get_form(request)
+        if request.POST:
+            form = Form(request.POST, request.FILES, instance=instance)
+            if form.is_valid():
+                new_object = self.save(request, form)
+                return self.on_success(request, new_object)
+        else:
+            form = Form()
+        rendered_template = self.get_rendered_template(request, instance, form)
+        return HttpResponse(rendered_template)
+
+    def get_form(self, request):
+        """
+        Returns a ``ModelForm`` class to be used in this view.
+        """
+        # TODO: we should be able to construct a ModelForm without creating
+        # and passing in a temporary inner class
+        class Meta:
+            model = self.model
+        class_name = self.model.__name__ + 'Form'
+        return ModelFormMetaclass(class_name, (ModelForm,), {'Meta': Meta})
+
+    def get_context(self, request, instance, form=None):
+        """
+        Returns a ``Context`` instance to be used when rendering this view.
+        """
+        return RequestContext(request, {'form': form, 'object': instance})
+
+    def get_template(self, request):
+        """
+        Returns the template to be used when rendering this view. Those who
+        wish to use a custom template loader should do so here.
+        """
+        opts = self.model._meta
+        template_name = "%s/%s_form.html" % (opts.app_label, opts.object_name.lower())
+        return loader.get_template(template_name)
+
+    def get_rendered_template(self, request, instance, form=None):
+        """
+        Returns a rendered template. This will be passed as the sole argument
+        to HttpResponse()
+        """
+        template = self.get_template(request)
+        context = self.get_context(request, instance, form)
+        return template.render(context)
+
+    def save(self, request, form):
+        """
+        Saves the object represented by the given ``form``. This method will
+        only be called if the form is valid, and should in most cases return
+        an HttpResponseRediect. It's return value will be the return value
+        for the view on success.
+        """
+        return form.save()
+
+    def on_success(self, request, new_object):
+        """
+        Returns an HttpResonse, generally an HttpResponse redirect. This will
+        be the final return value of the view and will only be called if the
+        object was saved successfuly.
+        """
+        if request.user.is_authenticated():
+            message = self.get_message(request, new_object)
+            request.user.message_set.create(message=message)
+        # Redirect to the new object: first by trying post_save_redirect,
+        # then by obj.get_absolute_url; fail if neither works.
+        if self.post_save_redirect:
+            return HttpResponseRedirect(post_save_redirect % new_object.__dict__)
+        elif hasattr(new_object, 'get_absolute_url'):
+            return HttpResponseRedirect(new_object.get_absolute_url())
+        else:
+            raise ImproperlyConfigured("No URL to redirect to from generic create view.")
+
+class AddView(BaseView):
+    def get_instance(self, request):
+        """
+        Returns the object instance to create.
+        """
+        return self.model()
+
+    def get_message(self, request, new_object):
+        return ugettext("The %(verbose_name)s was created successfully.") % {"verbose_name": self.model._meta.verbose_name}
+
+class ChangeView(BaseView):
+    def __init__(self, model, post_save_redirect=None, slug_field='slug'):
+        self.slug_field = slug_field
+        super(ChangeView, self).__init__(model, post_save_redirect=post_save_redirect)
+
+    def __call__(self, request, object_id=None, slug=None):
+        return self.main(request, self.get_instance(request, object_id, slug))
+
+    def get_query_set(self, request):
+        """
+        Returns a queryset to use when trying to look up the object to change.
+        """
+        return self.model._default_manager.get_query_set()
+
+    def get_instance(self, request, object_id=None, slug=None):
+        """
+        Returns the object to be changed, or raises a 404 if it doesn't exist.
+        """
+        # Look up the object to be edited
+        lookup_kwargs = {}
+        if object_id:
+            lookup_kwargs['pk'] = object_id
+        elif slug and self.slug_field:
+            lookup_kwargs['%s__exact' % slug_field] = slug
+        else:
+            raise AttributeError("Generic view must be called with either an object_id or a slug/slug_field")
+        try:
+            return self.get_query_set(request).get(**lookup_kwargs)
+        except ObjectDoesNotExist:
+            raise Http404, "No %s found for %s" % (self.model._meta.verbose_name, lookup_kwargs)
+
+    def get_message(self, request, new_object):
+        return ugettext("The %(verbose_name)s was updated successfully.") % {"verbose_name": self.model._meta.verbose_name}
+
+class DeleteView(ChangeView):
+    def __init__(self, model, post_save_redirect=None, slug_field='slug'):
+        self.model = model
+        self.slug_field = slug_field
+        self.post_save_redirect = post_save_redirect
+
+    def main(self, request, instance):
+        if request.method == 'POST':
+            self.delete(instance)
+            return self.on_success(request, instance)
+        rendered_template = self.get_rendered_template(request, instance)
+        response = HttpResponse(rendered_template)
+        populate_xheaders(request, response, self.model, instance.pk)
+        return response
+
+    def get_context(self, request, instance, form=None):
+        """
+        Returns a ``Context`` instance to be used when rendering this view.
+        """
+        return RequestContext(request, {'object': instance})
+
+    def get_template(self, request):
+        opts = self.model._meta
+        template_name = "%s/%s_confirm_delete.html" % (opts.app_label, opts.object_name.lower())
+        return loader.get_template(template_name)
+
+    def delete(request, instance):
+        """
+        Deletes the given instance. Subclasses that wish to veto deletion
+        should do so here.
+        """
+        instance.delete()
+
+    def on_success(self, request, new_object):
+        """
+        Redirects to self.post_save_redirect after setting a message if the
+        user is logged in.
+        
+        This method is only called if saving the object was successful.
+        """
+        if request.user.is_authenticated():
+            message = self.get_message(request, new_object)
+            request.user.message_set.create(message=message)
+        return HttpResponseRedirect(self.post_save_redirect)
+
+    def get_message(self, request, new_object):
+        return ugettext("The %(verbose_name)s was deleted.") % {"verbose_name": model._meta.verbose_name}
+
+
+# Classic generic views ######################################################
 
 def create_object(request, model, template_name=None,
         template_loader=loader, extra_context=None, post_save_redirect=None,
diff --git a/docs/authentication.txt b/docs/authentication.txt
index ade2d71..9167458 100644
--- a/docs/authentication.txt
+++ b/docs/authentication.txt
@@ -309,7 +309,7 @@ with that ``User``.
 
 For more information, see `Chapter 12 of the Django book`_.
 
-.. _Chapter 12 of the Django book: http://www.djangobook.com/en/beta/chapter12/#cn226
+.. _Chapter 12 of the Django book: http://www.djangobook.com/en/1.0/chapter12/#cn222
 
 Authentication in Web requests
 ==============================
diff --git a/docs/i18n.txt b/docs/i18n.txt
index bb6cf74..8da19cd 100644
--- a/docs/i18n.txt
+++ b/docs/i18n.txt
@@ -547,7 +547,7 @@ following this algorithm:
 
     * First, it looks for a ``django_language`` key in the the current user's
       `session`_.
-    * Failing that, it looks for a cookie that is named according to your ``LANGUAGE_COOKIE_NAME`` setting (the default name is: ``django_language``).
+    * Failing that, it looks for a cookie that is named according to your ``LANGUAGE_COOKIE_NAME`` setting. (The default name is ``django_language``, and this setting is new in the Django development version. In Django version 0.96 and before, the cookie's name is hard-coded to ``django_language``.)
     * Failing that, it looks at the ``Accept-Language`` HTTP header. This
       header is sent by your browser and tells the server which language(s) you
       prefer, in order by priority. Django tries each language in the header
@@ -719,8 +719,9 @@ Activate this view by adding the following line to your URLconf::
 The view expects to be called via the ``POST`` method, with a ``language``
 parameter set in request. If session support is enabled, the view
 saves the language choice in the user's session. Otherwise, it saves the
-language choice in a cookie that is by default named ``django_language``
-(the name can be changed through the ``LANGUAGE_COOKIE_NAME`` setting).
+language choice in a cookie that is by default named ``django_language``.
+(The name can be changed through the ``LANGUAGE_COOKIE_NAME`` setting if you're
+using the Django development version.)
 
 After setting the language choice, Django redirects the user, following this
 algorithm:
diff --git a/docs/model-api.txt b/docs/model-api.txt
index 66fa63e..4901a9a 100644
--- a/docs/model-api.txt
+++ b/docs/model-api.txt
@@ -788,10 +788,10 @@ Note, however, that this only refers to models in the same models.py file -- you
 cannot use a string to reference a model defined in another application or
 imported from elsewhere.
 
-**New in Django development version:** to refer to models defined in another
-application, you must instead explicitially specify the application label. That
-is, if the ``Manufacturer`` model above is defined in another application called
-``production``, you'd need to use::
+**New in Django development version:** To refer to models defined in another
+application, you must instead explicitly specify the application label. For
+example, if the ``Manufacturer`` model above is defined in another application
+called ``production``, you'd need to use::
 
     class Car(models.Model):
         manufacturer = models.ForeignKey('production.Manufacturer')
@@ -1022,6 +1022,8 @@ See the `One-to-one relationship model example`_ for a full example.
 Custom field types
 ------------------
 
+**New in Django development version**
+
 If one of the existing model fields cannot be used to fit your purposes, or if
 you wish to take advantage of some less common database column types, you can
 create your own field class. Full coverage of creating your own fields is
diff --git a/docs/settings.txt b/docs/settings.txt
index ace893f..77e3c66 100644
--- a/docs/settings.txt
+++ b/docs/settings.txt
@@ -582,13 +582,14 @@ in standard language format. For example, U.S. English is ``"en-us"``. See the
 LANGUAGE_COOKIE_NAME
 --------------------
 
+**New in Django development version**
+
 Default: ``'django_language'``
 
 The name of the cookie to use for the language cookie. This can be whatever
-you want (but should be different from SESSION_COOKIE_NAME). See the
+you want (but should be different from ``SESSION_COOKIE_NAME``). See the
 `internationalization docs`_ for details.
 
-
 LANGUAGES
 ---------
 
diff --git a/tests/regressiontests/views/tests/__init__.py b/tests/regressiontests/views/tests/__init__.py
index 2c8c5b4..f0f6f59 100644
--- a/tests/regressiontests/views/tests/__init__.py
+++ b/tests/regressiontests/views/tests/__init__.py
@@ -1,4 +1,5 @@
 from defaults import *
 from i18n import *
 from static import *
+from generic.create_update import *
 from generic.date_based import *
\ No newline at end of file
diff --git a/tests/regressiontests/views/tests/generic/create_update.py b/tests/regressiontests/views/tests/generic/create_update.py
new file mode 100644
index 0000000..7cdbb9e
--- /dev/null
+++ b/tests/regressiontests/views/tests/generic/create_update.py
@@ -0,0 +1,44 @@
+# coding: utf-8 
+from django.test import TestCase 
+from regressiontests.views.models import Article, Author
+
+class AddViewTest(TestCase):
+
+    def test_initial(self):
+        response = self.client.get('/views/create_update/add/')
+        self.assertEqual(response.status_code, 200) 
+        self.assertEqual(response.context['form']._meta.model, Author)
+        self.assertEqual(response.context['object'].name, "")
+
+    def test_submit(self):
+        response = self.client.post('/views/create_update/add/', {
+            'name': 'Boris',
+        })
+        self.assertEqual(response.status_code, 302)
+
+class ChangeViewTest(TestCase):
+    fixtures = ['testdata.json']
+
+    def test_initial(self):
+        response = self.client.get('/views/create_update/1/change/')
+        self.assertEqual(response.status_code, 200) 
+        self.assertEqual(response.context['form']._meta.model, Author)
+        self.assertEqual(response.context['object'].name, "Boris")
+
+    def test_submit(self):
+        response = self.client.post('/views/create_update/1/change/', {
+            'name': 'Jack Kerouac',
+        })
+        self.assertEqual(response.status_code, 302)
+
+class DeleteViewTest(TestCase):
+    fixtures = ['testdata.json']
+
+    def test_initial(self):
+        response = self.client.get('/views/create_update/1/delete/')
+        self.assertEqual(response.status_code, 200) 
+        self.assertEqual(response.context['object'].name, "Boris")
+
+    def test_submit(self):
+        response = self.client.post('/views/create_update/1/delete/', {})
+        self.assertEqual(response.status_code, 302)
diff --git a/tests/regressiontests/views/urls.py b/tests/regressiontests/views/urls.py
index 5ef0c51..ebd5721 100644
--- a/tests/regressiontests/views/urls.py
+++ b/tests/regressiontests/views/urls.py
@@ -1,6 +1,7 @@
 from os import path
 
 from django.conf.urls.defaults import *
+from django.views.generic.create_update import AddView, ChangeView, DeleteView
 
 from models import *
 import views
@@ -9,6 +10,10 @@ base_dir = path.dirname(path.abspath(__file__))
 media_dir = path.join(base_dir, 'media')
 locale_dir = path.join(base_dir, 'locale')
 
+author_add = AddView(Author)
+author_change = ChangeView(Author)
+author_delete = DeleteView(Author)
+
 js_info_dict = {
     'domain': 'djangojs',
     'packages': ('regressiontests.views',),
@@ -34,8 +39,14 @@ urlpatterns = patterns('',
     
     # Static views
     (r'^site_media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': media_dir}),
-    
-	# Date-based generic views
+
+    # Create/Update generic views
+    (r'create_update/add/', author_add),
+    (r'create_update/(?P<object_id>\d+)/change/', author_change),
+    (r'create_update/(?P<object_id>\d+)/delete/', author_delete),
+
+
+    # Date-based generic views
     (r'^date_based/object_detail/(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/(?P<slug>[-\w]+)/$', 
         'django.views.generic.date_based.object_detail', 
         dict(slug_field='slug', **date_based_info_dict)), 
diff --git a/tests/templates/views/author_confirm_delete.html b/tests/templates/views/author_confirm_delete.html
new file mode 100644
index 0000000..3f8ff55
--- /dev/null
+++ b/tests/templates/views/author_confirm_delete.html
@@ -0,0 +1 @@
+This template intentionally left blank
\ No newline at end of file
diff --git a/tests/templates/views/author_form.html b/tests/templates/views/author_form.html
new file mode 100644
index 0000000..3f8ff55
--- /dev/null
+++ b/tests/templates/views/author_form.html
@@ -0,0 +1 @@
+This template intentionally left blank
\ No newline at end of file
