Ticket #3639: r6635_create_update_newforms_with_docs_and_tests2.diff

File r6635_create_update_newforms_with_docs_and_tests2.diff, 17.2 KB (added by brosner, 7 years ago)

revised docs and allowed fields to be customized

  • 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, **kwargs):
    1413    """
    1514    Generic object-creation function.
    1615
     
    2322    if login_required and not request.user.is_authenticated():
    2423        return redirect_to_login(request.path)
    2524
    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()
     25    ModelForm = forms.form_for_model(model, **kwargs)
     26    if request.method == 'POST':
     27        form = ModelForm(request.POST, request.FILES)
     28       
     29        if form.is_valid():
     30            new_object = form.save()
    3031
    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 
    4232            if request.user.is_authenticated():
    4333                request.user.message_set.create(message=ugettext("The %(verbose_name)s was created successfully.") % {"verbose_name": model._meta.verbose_name})
    4434
     
    5141            else:
    5242                raise ImproperlyConfigured("No URL to redirect to from generic create view.")
    5343    else:
    54         # No POST, so we want a brand new form without any data or errors
    55         errors = {}
    56         new_data = manipulator.flatten_data()
     44        form = ModelForm()
    5745
    58     # Create the FormWrapper, template, context, response
    59     form = oldforms.FormWrapper(manipulator, new_data, errors)
     46    # Create the template, context, response
    6047    if not template_name:
    6148        template_name = "%s/%s_form.html" % (model._meta.app_label, model._meta.object_name.lower())
    6249    t = template_loader.get_template(template_name)
     
    7360def update_object(request, model, object_id=None, slug=None,
    7461        slug_field='slug', template_name=None, template_loader=loader,
    7562        extra_context=None, post_save_redirect=None,
    76         login_required=False, follow=None, context_processors=None,
    77         template_object_name='object'):
     63        login_required=False, context_processors=None,
     64        template_object_name='object', **kwargs):
    7865    """
    7966    Generic object-update function.
    8067
     
    10289    except ObjectDoesNotExist:
    10390        raise Http404, "No %s found for %s" % (model._meta.verbose_name, lookup_kwargs)
    10491
    105     manipulator = model.ChangeManipulator(getattr(object, object._meta.pk.attname), follow=follow)
     92    ModelForm = forms.form_for_instance(object, **kwargs)
    10693
    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)
     94    if request.method == 'POST':
     95        form = ModelForm(request.POST, request.FILES)
     96        if form.is_valid():
     97            object = form.save()
    11598
    11699            if request.user.is_authenticated():
    117100                request.user.message_set.create(message=ugettext("The %(verbose_name)s was updated successfully.") % {"verbose_name": model._meta.verbose_name})
     
    124107            else:
    125108                raise ImproperlyConfigured("No URL to redirect to from generic create view.")
    126109    else:
    127         errors = {}
    128         # This makes sure the form acurate represents the fields of the place.
    129         new_data = manipulator.flatten_data()
     110        form = ModelForm()
    130111
    131     form = oldforms.FormWrapper(manipulator, new_data, errors)
    132112    if not template_name:
    133113        template_name = "%s/%s_form.html" % (model._meta.app_label, model._meta.object_name.lower())
    134114    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:** ``fields``: A list of fields to
     954      include in the ``Form`` object. See an example `using a subset of fields`_
     955      for more information.
     956
     957    * **New in Django development version:** ``formfield_callback``: A callback
     958      function to easily customize the default fields and widgets displayed
     959      by the form. See an example `how to override default field types`_ for
     960      more information.
     961
    941962**Template name:**
    942963
    943964If ``template_name`` isn't specified, this view will use the template
     
    947968
    948969In addition to ``extra_context``, the template's context will be:
    949970
    950     * ``form``: A ``django.oldforms.FormWrapper`` instance representing the form
     971    * ``form``: A ``django.newforms.Form`` instance representing the form
    951972      for editing the object. This lets you refer to form fields easily in the
    952973      template system.
    953974
    954975      For example, if ``model`` has two fields, ``name`` and ``address``::
    955976
    956977          <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>
     978          <p>{{ form.name.label_tag }} {{ form.name }}</p>
     979          <p>{{ form.address.label_tag }} {{ form.address }}</p>
    959980          </form>
    960981
    961       See the `manipulator and formfield documentation`_ for more information
    962       about using ``FormWrapper`` objects in templates.
     982      See the `newforms documentation`_ for more information about using
     983      ``Form`` objects in templates.
    963984
    964985.. _authentication system: ../authentication/
    965 .. _manipulator and formfield documentation: ../forms/
     986.. _using an alternate base class: ../newforms/#using-an-alternate-base-class
     987.. _using a subset of fields: ../newforms/#using-a-subset-of-fields-on-the-form
     988.. _how to override default field types: ../newforms/#overriding-the-default-field-types
     989.. _newforms documentation: ../newforms/
    966990
    967991``django.views.generic.create_update.update_object``
    968992----------------------------------------------------
     
    10191043
    10201044    * ``template_object_name``:  Designates the name of the template variable
    10211045      to use in the template context. By default, this is ``'object'``.
     1046   
     1047    * **New in Django development version:** ``form``: A ``BaseForm`` subclass
     1048      to allow form customization and validation. See an example
     1049      `using an alternate base class`_ to fully understand the usage of this
     1050      argument.
     1051   
     1052    * **New in Django development version:** ``fields``: A list of fields to
     1053      include in the ``Form`` object. See an example `using a subset of fields`_
     1054      for more information.
     1055   
     1056    * **New in Django development version:** ``formfield_callback``: A callback
     1057      function to easily customize the default fields and widgets displayed
     1058      by the form. See an example `how to override default field types`_ for
     1059      more information.
    10221060
    10231061**Template name:**
    10241062
     
    10291067
    10301068In addition to ``extra_context``, the template's context will be:
    10311069
    1032     * ``form``: A ``django.oldforms.FormWrapper`` instance representing the form
     1070    * ``form``: A ``django.newforms.Form`` instance representing the form
    10331071      for editing the object. This lets you refer to form fields easily in the
    10341072      template system.
    10351073
    10361074      For example, if ``model`` has two fields, ``name`` and ``address``::
    10371075
    10381076          <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>
     1077          <p>{{ form.name.label_tag }} {{ form.name }}</p>
     1078          <p>{{ form.address.label_tag }} {{ form.address }}</p>
    10411079          </form>
     1080     
     1081      See the `newforms documentation`_ for more information about using
     1082      ``Form`` objects in templates.
    10421083
    1043       See the `manipulator and formfield documentation`_ for more information
    1044       about using ``FormWrapper`` objects in templates.
    1045 
    10461084    * ``object``: The original object being edited. This variable's name
    10471085      depends on the ``template_object_name`` parameter, which is ``'object'``
    10481086      by default. If ``template_object_name`` is ``'foo'``, this variable's
Back to Top