Ticket #3639: create_update_newforms4.diff

File create_update_newforms4.diff, 21.6 KB (added by Brian Rosner, 16 years ago)

updated to r6963 and using ModelForm now. Backwards Incompatible.

  • django/views/generic/create_update.py

    diff --git a/django/views/generic/create_update.py b/django/views/generic/create_update.py
    index 46e92fe..3532820 100644
    a b  
    11from django.core.xheaders import populate_xheaders
    22from django.template import loader
    3 from django import oldforms
    4 from django.db.models import FileField
     3from django import newforms as forms
    54from django.contrib.auth.views import redirect_to_login
    65from django.template import RequestContext
    76from django.http import Http404, HttpResponse, HttpResponseRedirect
    87from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
    98from django.utils.translation import ugettext
    109
    11 def create_object(request, model, template_name=None,
     10def create_object(request, form_class, template_name=None,
    1211        template_loader=loader, extra_context=None, post_save_redirect=None,
    13         login_required=False, follow=None, context_processors=None):
     12        login_required=False, context_processors=None):
    1413    """
    1514    Generic object-creation function.
    1615
    def create_object(request, model, template_name=None,  
    2221    if extra_context is None: extra_context = {}
    2322    if login_required and not request.user.is_authenticated():
    2423        return redirect_to_login(request.path)
    25 
    26     manipulator = model.AddManipulator(follow=follow)
    27     if request.POST:
    28         # If data was POSTed, we're trying to create a new object
    29         new_data = request.POST.copy()
    30 
    31         if model._meta.has_field_type(FileField):
    32             new_data.update(request.FILES)
    33 
    34         # Check for errors
    35         errors = manipulator.get_validation_errors(new_data)
    36         manipulator.do_html2python(new_data)
    37 
    38         if not errors:
    39             # No errors -- this means we can save the data!
    40             new_object = manipulator.save(new_data)
    41 
     24   
     25    opts = form_class._meta
     26   
     27    if request.method == 'POST':
     28        form = form_class(request.POST, request.FILES)
     29       
     30        if form.is_valid():
     31            new_object = form.save()
     32           
    4233            if request.user.is_authenticated():
    43                 request.user.message_set.create(message=ugettext("The %(verbose_name)s was created successfully.") % {"verbose_name": model._meta.verbose_name})
     34                request.user.message_set.create(message=ugettext("The %(verbose_name)s was created successfully.") % {"verbose_name": opts.model._meta.verbose_name})
    4435
    4536            # Redirect to the new object: first by trying post_save_redirect,
    4637            # then by obj.get_absolute_url; fail if neither works.
    def create_object(request, model, template_name=None,  
    5142            else:
    5243                raise ImproperlyConfigured("No URL to redirect to from generic create view.")
    5344    else:
    54         # No POST, so we want a brand new form without any data or errors
    55         errors = {}
    56         new_data = manipulator.flatten_data()
     45        form = form_class()
    5746
    58     # Create the FormWrapper, template, context, response
    59     form = oldforms.FormWrapper(manipulator, new_data, errors)
     47    # Create the template, context, response
    6048    if not template_name:
    61         template_name = "%s/%s_form.html" % (model._meta.app_label, model._meta.object_name.lower())
     49        template_name = "%s/%s_form.html" % (opts.model._meta.app_label, opts.model._meta.object_name.lower())
    6250    t = template_loader.get_template(template_name)
    6351    c = RequestContext(request, {
    6452        'form': form,
    def create_object(request, model, template_name=None,  
    7058            c[key] = value
    7159    return HttpResponse(t.render(c))
    7260
    73 def update_object(request, model, object_id=None, slug=None,
     61def update_object(request, form_class, object_id=None, slug=None,
    7462        slug_field='slug', template_name=None, template_loader=loader,
    7563        extra_context=None, post_save_redirect=None,
    76         login_required=False, follow=None, context_processors=None,
     64        login_required=False, context_processors=None,
    7765        template_object_name='object'):
    7866    """
    7967    Generic object-update function.
    def update_object(request, model, object_id=None, slug=None,  
    8977    if login_required and not request.user.is_authenticated():
    9078        return redirect_to_login(request.path)
    9179
     80    opts = form_class._meta
     81   
    9282    # Look up the object to be edited
    9383    lookup_kwargs = {}
    9484    if object_id:
    95         lookup_kwargs['%s__exact' % model._meta.pk.name] = object_id
     85        lookup_kwargs['%s__exact' % opts.model._meta.pk.name] = object_id
    9686    elif slug and slug_field:
    9787        lookup_kwargs['%s__exact' % slug_field] = slug
    9888    else:
    9989        raise AttributeError("Generic edit view must be called with either an object_id or a slug/slug_field")
    10090    try:
    101         object = model.objects.get(**lookup_kwargs)
     91        object = opts.model.objects.get(**lookup_kwargs)
    10292    except ObjectDoesNotExist:
    103         raise Http404, "No %s found for %s" % (model._meta.verbose_name, lookup_kwargs)
     93        raise Http404, "No %s found for %s" % (opts.model._meta.verbose_name, lookup_kwargs)
    10494
    105     manipulator = model.ChangeManipulator(getattr(object, object._meta.pk.attname), follow=follow)
    106 
    107     if request.POST:
    108         new_data = request.POST.copy()
    109         if model._meta.has_field_type(FileField):
    110             new_data.update(request.FILES)
    111         errors = manipulator.get_validation_errors(new_data)
    112         manipulator.do_html2python(new_data)
    113         if not errors:
    114             object = manipulator.save(new_data)
     95    if request.method == 'POST':
     96        form = form_class(request.POST, request.FILES, instance=object)
     97        if form.is_valid():
     98            object = form.save()
    11599
    116100            if request.user.is_authenticated():
    117                 request.user.message_set.create(message=ugettext("The %(verbose_name)s was updated successfully.") % {"verbose_name": model._meta.verbose_name})
     101                request.user.message_set.create(message=ugettext("The %(verbose_name)s was updated successfully.") % {"verbose_name": opts.model._meta.verbose_name})
    118102
    119103            # Do a post-after-redirect so that reload works, etc.
    120104            if post_save_redirect:
    def update_object(request, model, object_id=None, slug=None,  
    124108            else:
    125109                raise ImproperlyConfigured("No URL to redirect to from generic create view.")
    126110    else:
    127         errors = {}
    128         # This makes sure the form acurate represents the fields of the place.
    129         new_data = manipulator.flatten_data()
     111        form = form_class(instance=object)
    130112
    131     form = oldforms.FormWrapper(manipulator, new_data, errors)
    132113    if not template_name:
    133         template_name = "%s/%s_form.html" % (model._meta.app_label, model._meta.object_name.lower())
     114        template_name = "%s/%s_form.html" % (opts.model._meta.app_label, opts.model._meta.object_name.lower())
    134115    t = template_loader.get_template(template_name)
    135116    c = RequestContext(request, {
    136117        'form': form,
    def update_object(request, model, object_id=None, slug=None,  
    142123        else:
    143124            c[key] = value
    144125    response = HttpResponse(t.render(c))
    145     populate_xheaders(request, response, model, getattr(object, object._meta.pk.attname))
     126    populate_xheaders(request, response, opts.model, getattr(object, object._meta.pk.attname))
    146127    return response
    147128
    148129def delete_object(request, model, post_delete_redirect,
  • docs/generic_views.txt

    diff --git a/docs/generic_views.txt b/docs/generic_views.txt
    index 1718789..090c738 100644
    a b Create/update/delete generic views  
    894894The ``django.views.generic.create_update`` module contains a set of functions
    895895for creating, editing and deleting objects.
    896896
     897**New in Django development version:**
     898
     899``django.views.generic.create_update.create_object`` and
     900``django.views.generic.create_update.update_object`` now use `newforms`_ to
     901build and display the form.
     902
     903.. _newforms: ../newforms/
     904
    897905``django.views.generic.create_update.create_object``
    898906----------------------------------------------------
    899907
    900908**Description:**
    901909
    902910A page that displays a form for creating an object, redisplaying the form with
    903 validation errors (if there are any) and saving the object. This uses the
    904 automatic manipulators that come with Django models.
     911validation errors (if there are any) and saving the object.
    905912
    906913**Required arguments:**
    907914
    908     * ``model``: The Django model class of the object that the form will
    909       create.
     915    * ``form_class``: A ``django.newforms.ModelForm`` class that will
     916      represent the model as a form. See `ModelForm docs`_ for more information.
    910917
    911918**Optional arguments:**
    912919
    If ``template_name`` isn't specified, this view will use the template  
    947954
    948955In addition to ``extra_context``, the template's context will be:
    949956
    950     * ``form``: A ``django.oldforms.FormWrapper`` instance representing the form
    951       for editing the object. This lets you refer to form fields easily in the
     957    * ``form``: A ``django.newforms.ModelForm`` instance representing the form
     958      for creating the object. This lets you refer to form fields easily in the
    952959      template system.
    953960
    954       For example, if ``model`` has two fields, ``name`` and ``address``::
     961      For example, if the model has two fields, ``name`` and ``address``::
    955962
    956963          <form action="" method="post">
    957           <p><label for="id_name">Name:</label> {{ form.name }}</p>
    958           <p><label for="id_address">Address:</label> {{ form.address }}</p>
     964          <p>{{ form.name.label_tag }} {{ form.name }}</p>
     965          <p>{{ form.address.label_tag }} {{ form.address }}</p>
    959966          </form>
    960967
    961       See the `manipulator and formfield documentation`_ for more information
    962       about using ``FormWrapper`` objects in templates.
     968      See the `newforms documentation`_ for more information about using
     969      ``Form`` objects in templates.
    963970
    964971.. _authentication system: ../authentication/
    965 .. _manipulator and formfield documentation: ../forms/
     972.. _ModelForm docs: ../newforms/modelforms
     973.. _newforms documentation: ../newforms/
    966974
    967975``django.views.generic.create_update.update_object``
    968976----------------------------------------------------
    object. This uses the automatic manipulators that come with Django models.  
    975983
    976984**Required arguments:**
    977985
    978     * ``model``: The Django model class of the object that the form will
    979       create.
     986    * ``form_class``: A ``django.newforms.ModelForm`` class that will
     987      represent the model as a form. See `ModelForm docs`_ for more information.
    980988
    981989    * Either ``object_id`` or (``slug`` *and* ``slug_field``) is required.
    982990
    If ``template_name`` isn't specified, this view will use the template  
    10291037
    10301038In addition to ``extra_context``, the template's context will be:
    10311039
    1032     * ``form``: A ``django.oldforms.FormWrapper`` instance representing the form
     1040    * ``form``: A ``django.newforms.ModelForm`` instance representing the form
    10331041      for editing the object. This lets you refer to form fields easily in the
    10341042      template system.
    10351043
    1036       For example, if ``model`` has two fields, ``name`` and ``address``::
     1044      For example, if the model has two fields, ``name`` and ``address``::
    10371045
    10381046          <form action="" method="post">
    1039           <p><label for="id_name">Name:</label> {{ form.name }}</p>
    1040           <p><label for="id_address">Address:</label> {{ form.address }}</p>
     1047          <p>{{ form.name.label_tag }} {{ form.name }}</p>
     1048          <p>{{ form.address.label_tag }} {{ form.address }}</p>
    10411049          </form>
    1042 
    1043       See the `manipulator and formfield documentation`_ for more information
    1044       about using ``FormWrapper`` objects in templates.
     1050     
     1051      See the `newforms documentation`_ for more information about using
     1052      ``Form`` objects in templates.
    10451053
    10461054    * ``object``: The original object being edited. This variable's name
    10471055      depends on the ``template_object_name`` parameter, which is ``'object'``
  • new file tests/regressiontests/views/forms.py

    diff --git a/tests/regressiontests/views/forms.py b/tests/regressiontests/views/forms.py
    new file mode 100644
    index 0000000..60dd539
    - +  
     1
     2from datetime import datetime
     3
     4from django import newforms as forms
     5from models import Author, Article
     6
     7
     8class ArticleForm(forms.ModelForm):
     9    """
     10    A form bound to the Article model
     11    """
     12   
     13    class Meta:
     14        model = Article
     15
     16
     17class CustomSlugArticleForm(forms.ModelForm):
     18    """
     19    A ModelForm with a custom save method to modify how the form is saved.
     20    """
     21   
     22    class Meta:
     23        model = Article
     24        fields = ("title",)
     25   
     26    def save(self, commit=True):
     27        instance = super(CustomSlugArticleForm, self).save(commit=False)
     28        instance.slug = 'some-other-slug'
     29        instance.author = Author.objects.get(pk=1)
     30        # this would normally be done through the default value of the field
     31        # this is just here to prove that it can be done in a save method
     32        # override.
     33        instance.date_created = datetime.now()
     34        if commit:
     35            instance.save()
     36        return instance
  • tests/regressiontests/views/tests/__init__.py

    diff --git a/tests/regressiontests/views/tests/__init__.py b/tests/regressiontests/views/tests/__init__.py
    index 2c8c5b4..e076fa8 100644
    a b  
    11from defaults import *
    22from i18n import *
    33from static import *
    4 from generic.date_based import *
    5  No newline at end of file
     4from generic.date_based import *
     5from generic.create_update import *
     6 No newline at end of file
  • new file tests/regressiontests/views/tests/generic/create_update.py

    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..6676185
    - +  
     1
     2import datetime
     3from django.test import TestCase
     4from django.contrib.auth.views import redirect_to_login
     5from regressiontests.views.models import Article, Author
     6
     7class CreateObjectTest(TestCase):
     8    fixtures = ['testdata.json']
     9   
     10    def test_not_logged_in(self):
     11        """Verifies the user is logged in through the login_required kwarg"""
     12        view_url = '/views/create_update/member/create/article/'
     13        response = self.client.get(view_url)
     14        self.assertRedirects(response, 'http://testserver/accounts/login/?next=%s' % view_url)
     15   
     16    def test_create_article_display_page(self):
     17        """Ensures the generic view returned the page and contains a form."""
     18        view_url = '/views/create_update/create/article/'
     19        response = self.client.get(view_url)
     20        self.assertEqual(response.status_code, 200)
     21        if not response.context.get('form'):
     22            self.fail('No form found in the response.')
     23   
     24    def test_create_article_with_errors(self):
     25        """POSTs a form that contain validation errors."""
     26        view_url = '/views/create_update/create/article/'
     27        response = self.client.post(view_url, {
     28            'title': 'My First Article',
     29        })
     30        self.assertFormError(response, 'form', 'slug', [u'This field is required.'])
     31   
     32    def test_create_article(self):
     33        """Creates a new article through the generic view and ensures it gets
     34        redirected to the correct URL defined by post_save_redirect"""
     35        view_url = '/views/create_update/create/article/'
     36        response = self.client.post(view_url, {
     37            'title': 'My First Article',
     38            'slug': 'my-first-article',
     39            'author': '1',
     40            'date_created': datetime.datetime(2007, 6, 25),
     41        })
     42        self.assertRedirects(response,
     43            'http://testserver/views/create_update/view/article/my-first-article/',
     44            target_status_code=404)
     45   
     46    def test_create_custom_save_article(self):
     47        """Creates a new article with a save method override to adjust the slug
     48        before committing to the database."""
     49        view_url = '/views/create_update/create_custom/article/'
     50        response = self.client.post(view_url, {
     51            'title': 'Test Article',
     52        })
     53        self.assertRedirects(response,
     54            'http://testserver/views/create_update/view/article/some-other-slug/',
     55            target_status_code=404)
     56
     57class UpdateDeleteObjectTest(TestCase):
     58    fixtures = ['testdata.json']
     59   
     60    def test_update_object_form_display(self):
     61        """Verifies that the form was created properly and with initial values."""
     62        response = self.client.get('/views/create_update/update/article/old_article/')
     63        self.assertEquals(unicode(response.context['form']['title']),
     64            u'<input id="id_title" type="text" name="title" value="Old Article" maxlength="100" />')
     65   
     66    def test_update_object(self):
     67        """Verifies the form POST data and performs a redirect to
     68        post_save_redirect"""
     69        response = self.client.post('/views/create_update/update/article/old_article/', {
     70            'title': 'Another Article',
     71            'slug': 'another-article-slug',
     72            'author': 1,
     73            'date_created': datetime.datetime(2007, 6, 25),
     74        })
     75        self.assertRedirects(response,
     76            'http://testserver/views/create_update/view/article/another-article-slug/',
     77            target_status_code=404)
     78        article = Article.objects.get(pk=1)
     79        self.assertEquals(article.title, "Another Article")
     80   
     81    def test_delete_object_confirm(self):
     82        """Verifies the confirm deletion page is displayed using a GET."""
     83        response = self.client.get('/views/create_update/delete/article/old_article/')
     84        self.assertTemplateUsed(response, 'views/article_confirm_delete.html')
     85   
     86    def test_delete_object_redirect(self):
     87        """Verifies that post_delete_redirect works properly."""
     88        response = self.client.post('/views/create_update/delete/article/old_article/')
     89        self.assertRedirects(response,
     90            'http://testserver/views/create_update/',
     91            target_status_code=404)
     92   
     93    def test_delete_object(self):
     94        """Verifies the object actually gets deleted on a POST."""
     95        response = self.client.post('/views/create_update/delete/article/old_article/')
     96        try:
     97            Article.objects.get(slug='old_article')
     98        except Article.DoesNotExist:
     99            pass
     100        else:
     101            self.fail('Object was not deleted.')
     102       
     103 No newline at end of file
  • tests/regressiontests/views/urls.py

    diff --git a/tests/regressiontests/views/urls.py b/tests/regressiontests/views/urls.py
    index 5ef0c51..cb8e8e4 100644
    a b from os import path  
    33from django.conf.urls.defaults import *
    44
    55from models import *
     6from forms import ArticleForm
    67import views
    78
    89base_dir = path.dirname(path.abspath(__file__))
    date_based_info_dict = {  
    2021    'month_format': '%m',
    2122}
    2223
     24crud_form_info_dict = {
     25    'form_class': ArticleForm,
     26}
     27
     28crud_delete_info_dict = {
     29    'model': Article,
     30}
     31
    2332urlpatterns = patterns('',
    2433    (r'^$', views.index_page),
    2534   
    urlpatterns = patterns('',  
    4453        dict(allow_future=True, slug_field='slug', **date_based_info_dict)),
    4554    (r'^date_based/archive_month/(?P<year>\d{4})/(?P<month>\d{1,2})/$',
    4655        'django.views.generic.date_based.archive_month',
    47         date_based_info_dict),     
     56        date_based_info_dict),
     57   
     58    # crud generic views
     59    (r'^create_update/member/create/article/$', 'django.views.generic.create_update.create_object',
     60        dict(login_required=True, **crud_form_info_dict)),
     61    (r'^create_update/create/article/$', 'django.views.generic.create_update.create_object',
     62        dict(post_save_redirect='/views/create_update/view/article/%(slug)s/', **crud_form_info_dict)),
     63    (r'^create_update/create_custom/article/$', views.custom_slug,
     64        dict(post_save_redirect='/views/create_update/view/article/%(slug)s/', **crud_form_info_dict)),
     65    (r'create_update/update/article/(?P<slug>[-\w]+)/$', 'django.views.generic.create_update.update_object',
     66        dict(post_save_redirect='/views/create_update/view/article/%(slug)s/', slug_field='slug', **crud_form_info_dict)),
     67    (r'create_update/delete/article/(?P<slug>[-\w]+)/$', 'django.views.generic.create_update.delete_object',
     68        dict(post_delete_redirect='/views/create_update/', slug_field='slug', **crud_delete_info_dict)),
    4869)
  • tests/regressiontests/views/views.py

    diff --git a/tests/regressiontests/views/views.py b/tests/regressiontests/views/views.py
    index 956432e..7fd69be 100644
    a b  
     1import datetime
     2
    13from django.http import HttpResponse
     4from django.template import RequestContext
     5from django.views.generic.create_update import create_object
     6
     7from forms import CustomSlugArticleForm
     8
    29
    310def index_page(request):
    411    """Dummy index page"""
    512    return HttpResponse('<html><body>Dummy page</body></html>')
     13 
     14def custom_slug(request, **kwargs):
     15    # change the form_class before calling the create_object generic view
     16    kwargs["form_class"] = CustomSlugArticleForm
     17    return create_object(request, **kwargs)
  • new file tests/templates/views/article_confirm_delete.html

    diff --git a/tests/templates/views/article_confirm_delete.html b/tests/templates/views/article_confirm_delete.html
    new file mode 100644
    index 0000000..3f8ff55
    - +  
     1This template intentionally left blank
     2 No newline at end of file
  • new file tests/templates/views/article_form.html

    diff --git a/tests/templates/views/article_form.html b/tests/templates/views/article_form.html
    new file mode 100644
    index 0000000..3f8ff55
    - +  
     1This template intentionally left blank
     2 No newline at end of file
Back to Top