Ticket #3639: r6635_create_update_newforms_with_docs_and_tests.diff

File r6635_create_update_newforms_with_docs_and_tests.diff, 16.6 KB (added by brosner, 8 years ago)

patch with docs and tests

  • django/views/generic/create_update.py

     
    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
     
    109
    1110def create_object(request, model, 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, form=forms.BaseForm,
     13        formfield_callback=lambda f, **kwargs: f.formfield(**kwargs)):
    1414    """
    1515    Generic object-creation function.
    1616
     
    2323    if login_required and not request.user.is_authenticated():
    2424        return redirect_to_login(request.path)
    2525
    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()
     26    ModelForm = forms.form_for_model(model, form=form, formfield_callback=formfield_callback)
     27    if request.method == 'POST':
     28        form = ModelForm(request.POST, request.FILES)
     29       
     30        if form.is_valid():
     31            new_object = form.save()
    3032
    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 
    4233            if request.user.is_authenticated():
    4334                request.user.message_set.create(message=ugettext("The %(verbose_name)s was created successfully.") % {"verbose_name": model._meta.verbose_name})
    4435
     
    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 = ModelForm()
    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:
    6149        template_name = "%s/%s_form.html" % (model._meta.app_label, model._meta.object_name.lower())
    6250    t = template_loader.get_template(template_name)
     
    7361def update_object(request, model, 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,
    77         template_object_name='object'):
     64        login_required=False, context_processors=None,
     65        template_object_name='object', form=forms.BaseForm,
     66        formfield_callback=lambda f, **kwargs: f.formfield(**kwargs)):
    7867    """
    7968    Generic object-update function.
    8069
     
    10291    except ObjectDoesNotExist:
    10392        raise Http404, "No %s found for %s" % (model._meta.verbose_name, lookup_kwargs)
    10493
    105     manipulator = model.ChangeManipulator(getattr(object, object._meta.pk.attname), follow=follow)
     94    ModelForm = forms.form_for_instance(object, form=form, formfield_callback=formfield_callback)
    10695
    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)
     96    if request.method == 'POST':
     97        form = ModelForm(request.POST, request.FILES)
     98        if form.is_valid():
     99            object = form.save()
    115100
    116101            if request.user.is_authenticated():
    117102                request.user.message_set.create(message=ugettext("The %(verbose_name)s was updated successfully.") % {"verbose_name": model._meta.verbose_name})
     
    124109            else:
    125110                raise ImproperlyConfigured("No URL to redirect to from generic create view.")
    126111    else:
    127         errors = {}
    128         # This makes sure the form acurate represents the fields of the place.
    129         new_data = manipulator.flatten_data()
     112        form = ModelForm()
    130113
    131     form = oldforms.FormWrapper(manipulator, new_data, errors)
    132114    if not template_name:
    133115        template_name = "%s/%s_form.html" % (model._meta.app_label, model._meta.object_name.lower())
    134116    t = template_loader.get_template(template_name)
  • tests/regressiontests/views/tests/__init__.py

     
    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
  • tests/regressiontests/views/tests/generic/create_update.py

     
     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
     46class UpdateDeleteObjectTest(TestCase):
     47    fixtures = ['testdata.json']
     48   
     49    def test_update_object_form_display(self):
     50        """Verifies that the form was created properly and with initial values."""
     51        response = self.client.get('/views/create_update/update/article/old_article/')
     52        self.assertEquals(unicode(response.context['form']['title']),
     53            u'<input id="id_title" type="text" name="title" value="Old Article" maxlength="100" />')
     54   
     55    def test_update_object(self):
     56        """Verifies the form POST data and performs a redirect to
     57        post_save_redirect"""
     58        response = self.client.post('/views/create_update/update/article/old_article/', {
     59            'title': 'Another Article',
     60            'slug': 'another-article-slug',
     61            'author': 1,
     62            'date_created': datetime.datetime(2007, 6, 25),
     63        })
     64        self.assertRedirects(response,
     65            'http://testserver/views/create_update/view/article/another-article-slug/',
     66            target_status_code=404)
     67        article = Article.objects.get(pk=1)
     68        self.assertEquals(article.title, "Another Article")
     69   
     70    def test_delete_object_confirm(self):
     71        """Verifies the confirm deletion page is displayed using a GET."""
     72        response = self.client.get('/views/create_update/delete/article/old_article/')
     73        self.assertTemplateUsed(response, 'views/article_confirm_delete.html')
     74   
     75    def test_delete_object_redirect(self):
     76        """Verifies that post_delete_redirect works properly."""
     77        response = self.client.post('/views/create_update/delete/article/old_article/')
     78        self.assertRedirects(response,
     79            'http://testserver/views/create_update/',
     80            target_status_code=404)
     81   
     82    def test_delete_object(self):
     83        """Verifies the object actually gets deleted on a POST."""
     84        response = self.client.post('/views/create_update/delete/article/old_article/')
     85        try:
     86            Article.objects.get(slug='old_article')
     87        except Article.DoesNotExist:
     88            pass
     89        else:
     90            self.fail('Object was not deleted.')
     91       
     92 No newline at end of file
  • tests/regressiontests/views/urls.py

     
    2020    'month_format': '%m',
    2121}
    2222
     23crud_info_dict = {
     24    'model': Article,
     25}
     26
    2327urlpatterns = patterns('',
    2428    (r'^$', views.index_page),
    2529   
     
    4448        dict(allow_future=True, slug_field='slug', **date_based_info_dict)),
    4549    (r'^date_based/archive_month/(?P<year>\d{4})/(?P<month>\d{1,2})/$',
    4650        'django.views.generic.date_based.archive_month',
    47         date_based_info_dict),     
     51        date_based_info_dict),
     52   
     53    # crud generic views
     54    (r'^create_update/member/create/article/$', 'django.views.generic.create_update.create_object',
     55        dict(login_required=True, **crud_info_dict)),
     56    (r'^create_update/create/article/$', 'django.views.generic.create_update.create_object',
     57        dict(post_save_redirect='/views/create_update/view/article/%(slug)s/', **crud_info_dict)),
     58    (r'create_update/update/article/(?P<slug>[-\w]+)/$', 'django.views.generic.create_update.update_object',
     59        dict(post_save_redirect='/views/create_update/view/article/%(slug)s/', slug_field='slug', **crud_info_dict)),
     60    (r'create_update/delete/article/(?P<slug>[-\w]+)/$', 'django.views.generic.create_update.delete_object',
     61        dict(post_delete_redirect='/views/create_update/', slug_field='slug', **crud_info_dict)),
    4862)
  • tests/templates/views/article_confirm_delete.html

     
     1This template intentionally left blank
     2 No newline at end of file
  • tests/templates/views/article_form.html

     
     1This template intentionally left blank
     2 No newline at end of file
  • docs/generic_views.txt

     
    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
     
    938945    * ``context_processors``: A list of template-context processors to apply to
    939946      the view's template. See the `RequestContext docs`_.
    940947
     948    * **New in Django development version:** ``form``: A ``BaseForm`` subclass
     949      to allow form customization and validation. See an example
     950      `using an alternate base class`_ to fully understand the usage of this
     951      argument.
     952   
     953    * **New in Django development version:** ``formfield_callback``: A callback
     954      function to easily customize the fields and widgets displayed by the form.
     955      See the `newforms documentation`_ for more information.
     956
    941957**Template name:**
    942958
    943959If ``template_name`` isn't specified, this view will use the template
     
    947963
    948964In addition to ``extra_context``, the template's context will be:
    949965
    950     * ``form``: A ``django.oldforms.FormWrapper`` instance representing the form
     966    * ``form``: A ``django.newforms.Form`` instance representing the form
    951967      for editing the object. This lets you refer to form fields easily in the
    952968      template system.
    953969
    954970      For example, if ``model`` has two fields, ``name`` and ``address``::
    955971
    956972          <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>
     973          <p>{{ form.name }}</p>
     974          <p>{{ form.address }}</p>
    959975          </form>
    960976
    961       See the `manipulator and formfield documentation`_ for more information
    962       about using ``FormWrapper`` objects in templates.
     977      See the `newforms documentation`_ for more information about using
     978      ``Form`` objects in templates.
    963979
    964980.. _authentication system: ../authentication/
    965 .. _manipulator and formfield documentation: ../forms/
     981.. _using an alternate base class: ../newforms/#using-an-alternate-base-class
     982.. _newforms documentation: ../newforms/
    966983
    967984``django.views.generic.create_update.update_object``
    968985----------------------------------------------------
     
    10191036
    10201037    * ``template_object_name``:  Designates the name of the template variable
    10211038      to use in the template context. By default, this is ``'object'``.
     1039   
     1040    * **New in Django development version:** ``form``: A ``BaseForm`` subclass
     1041      to allow form customization and validation. See an example
     1042      `using an alternate base class`_ to fully understand the usage of this
     1043      argument.
     1044   
     1045    * **New in Django development version:** ``formfield_callback``: A callback
     1046      function to easily customize the fields and widgets displayed by the form.
     1047      See the `newforms documentation`_ for more information.
    10221048
    10231049**Template name:**
    10241050
     
    10291055
    10301056In addition to ``extra_context``, the template's context will be:
    10311057
    1032     * ``form``: A ``django.oldforms.FormWrapper`` instance representing the form
     1058    * ``form``: A ``django.newforms.Form`` instance representing the form
    10331059      for editing the object. This lets you refer to form fields easily in the
    10341060      template system.
    10351061
    10361062      For example, if ``model`` has two fields, ``name`` and ``address``::
    10371063
    10381064          <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>
     1065          <p>{{ form.name }}</p>
     1066          <p>{{ form.address }}</p>
    10411067          </form>
    10421068
    1043       See the `manipulator and formfield documentation`_ for more information
    1044       about using ``FormWrapper`` objects in templates.
     1069      See the `newforms documentation`_ for more information about using
     1070      ``Form`` objects in templates.
    10451071
    10461072    * ``object``: The original object being edited. This variable's name
    10471073      depends on the ``template_object_name`` parameter, which is ``'object'``
Back to Top