Ticket #3639: r6635_create_update_newforms3.diff

File r6635_create_update_newforms3.diff, 19.7 KB (added by brosner, 8 years ago)

created save_callback for custom saving of form data

  • 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,
     13        save_callback=lambda form, request: form.save(), **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()
    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 
     26    ModelForm = forms.form_for_model(model, **kwargs)
     27   
     28    if request.method == 'POST':
     29        form = ModelForm(request.POST, request.FILES)
     30       
     31        if form.is_valid():
     32            new_object = save_callback(form, request)
     33           
    4234            if request.user.is_authenticated():
    4335                request.user.message_set.create(message=ugettext("The %(verbose_name)s was created successfully.") % {"verbose_name": model._meta.verbose_name})
    4436
     
    5143            else:
    5244                raise ImproperlyConfigured("No URL to redirect to from generic create view.")
    5345    else:
    54         # No POST, so we want a brand new form without any data or errors
    55         errors = {}
    56         new_data = manipulator.flatten_data()
     46        form = ModelForm()
    5747
    58     # Create the FormWrapper, template, context, response
    59     form = oldforms.FormWrapper(manipulator, new_data, errors)
     48    # Create the template, context, response
    6049    if not template_name:
    6150        template_name = "%s/%s_form.html" % (model._meta.app_label, model._meta.object_name.lower())
    6251    t = template_loader.get_template(template_name)
     
    7362def update_object(request, model, object_id=None, slug=None,
    7463        slug_field='slug', template_name=None, template_loader=loader,
    7564        extra_context=None, post_save_redirect=None,
    76         login_required=False, follow=None, context_processors=None,
    77         template_object_name='object'):
     65        login_required=False, context_processors=None,
     66        template_object_name='object',
     67        save_callback=lambda form, request: form.save(), **kwargs):
    7868    """
    7969    Generic object-update function.
    8070
     
    10292    except ObjectDoesNotExist:
    10393        raise Http404, "No %s found for %s" % (model._meta.verbose_name, lookup_kwargs)
    10494
    105     manipulator = model.ChangeManipulator(getattr(object, object._meta.pk.attname), follow=follow)
     95    ModelForm = forms.form_for_instance(object, **kwargs)
    10696
    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)
     97    if request.method == 'POST':
     98        form = ModelForm(request.POST, request.FILES)
     99        if form.is_valid():
     100            object = save_callback(form, request)
    115101
    116102            if request.user.is_authenticated():
    117103                request.user.message_set.create(message=ugettext("The %(verbose_name)s was updated successfully.") % {"verbose_name": model._meta.verbose_name})
     
    124110            else:
    125111                raise ImproperlyConfigured("No URL to redirect to from generic create view.")
    126112    else:
    127         errors = {}
    128         # This makes sure the form acurate represents the fields of the place.
    129         new_data = manipulator.flatten_data()
     113        form = ModelForm()
    130114
    131     form = oldforms.FormWrapper(manipulator, new_data, errors)
    132115    if not template_name:
    133116        template_name = "%s/%s_form.html" % (model._meta.app_label, model._meta.object_name.lower())
    134117    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   
     46    def test_create_custom_save_article(self):
     47        """Creates a new article with a custom save_callback 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/views.py

     
     1import datetime
    12from django.http import HttpResponse
    23from django.template import RequestContext
     4from django.views.generic.create_update import create_object
    35
     6from models import Author
     7
    48def index_page(request):
    59    """Dummy index page"""
    610    return HttpResponse('<html><body>Dummy page</body></html>')
    711
     12def custom_slug(request, **kwargs):
     13    def mysave(form, request):
     14        instance = form.save(commit=False)
     15        instance.slug = 'some-other-slug'
     16        instance.author = Author.objects.get(pk=1)
     17        # this would normally be done through the default value of the field
     18        # this is just here to prove that it can be done in a save_callback
     19        instance.date_created = datetime.datetime.now()
     20        instance.save()
     21        return instance
     22    return create_object(request, save_callback=mysave,
     23        fields=('title',), **kwargs)
     24
  • 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/create_custom/article/$', views.custom_slug,
     59        dict(post_save_redirect='/views/create_update/view/article/%(slug)s/', **crud_info_dict)),
     60    (r'create_update/update/article/(?P<slug>[-\w]+)/$', 'django.views.generic.create_update.update_object',
     61        dict(post_save_redirect='/views/create_update/view/article/%(slug)s/', slug_field='slug', **crud_info_dict)),
     62    (r'create_update/delete/article/(?P<slug>[-\w]+)/$', 'django.views.generic.create_update.delete_object',
     63        dict(post_delete_redirect='/views/create_update/', slug_field='slug', **crud_info_dict)),
    4864)
  • 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:** ``save_callback``: A callback
     949      function to override how the form gets saved to the database.
     950     
     951      The ``save_callback`` function will be given a ``Form`` instance with
     952      the cleaned data and the ``HttpRequest`` instance passed to the view.
     953   
     954    * **New in Django development version:** ``form``: A ``BaseForm`` subclass
     955      to allow form customization and validation. See an example
     956      `using an alternate base class`_ to fully understand the usage of this
     957      argument.
     958
     959    * **New in Django development version:** ``fields``: A list of fields to
     960      include in the ``Form`` object. See an example `using a subset of fields`_
     961      for more information.
     962
     963    * **New in Django development version:** ``formfield_callback``: A callback
     964      function to easily customize the default fields and widgets displayed
     965      by the form. See an example `how to override default field types`_ for
     966      more information.
     967
    941968**Template name:**
    942969
    943970If ``template_name`` isn't specified, this view will use the template
     
    947974
    948975In addition to ``extra_context``, the template's context will be:
    949976
    950     * ``form``: A ``django.oldforms.FormWrapper`` instance representing the form
     977    * ``form``: A ``django.newforms.Form`` instance representing the form
    951978      for editing the object. This lets you refer to form fields easily in the
    952979      template system.
    953980
    954981      For example, if ``model`` has two fields, ``name`` and ``address``::
    955982
    956983          <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>
     984          <p>{{ form.name.label_tag }} {{ form.name }}</p>
     985          <p>{{ form.address.label_tag }} {{ form.address }}</p>
    959986          </form>
    960987
    961       See the `manipulator and formfield documentation`_ for more information
    962       about using ``FormWrapper`` objects in templates.
     988      See the `newforms documentation`_ for more information about using
     989      ``Form`` objects in templates.
    963990
    964991.. _authentication system: ../authentication/
    965 .. _manipulator and formfield documentation: ../forms/
     992.. _using an alternate base class: ../newforms/#using-an-alternate-base-class
     993.. _using a subset of fields: ../newforms/#using-a-subset-of-fields-on-the-form
     994.. _how to override default field types: ../newforms/#overriding-the-default-field-types
     995.. _newforms documentation: ../newforms/
    966996
    967997``django.views.generic.create_update.update_object``
    968998----------------------------------------------------
     
    10191049
    10201050    * ``template_object_name``:  Designates the name of the template variable
    10211051      to use in the template context. By default, this is ``'object'``.
     1052   
     1053    * **New in Django development version:** ``save_callback``: A callback
     1054      function to override how the form gets saved to the database.
     1055     
     1056      The ``save_callback`` function will be given a ``Form`` instance with
     1057      the cleaned data and the ``HttpRequest`` instance passed to the view.
     1058   
     1059    * **New in Django development version:** ``form``: A ``BaseForm`` subclass
     1060      to allow form customization and validation. See an example
     1061      `using an alternate base class`_ to fully understand the usage of this
     1062      argument.
     1063   
     1064    * **New in Django development version:** ``fields``: A list of fields to
     1065      include in the ``Form`` object. See an example `using a subset of fields`_
     1066      for more information.
     1067   
     1068    * **New in Django development version:** ``formfield_callback``: A callback
     1069      function to easily customize the default fields and widgets displayed
     1070      by the form. See an example `how to override default field types`_ for
     1071      more information.
    10221072
    10231073**Template name:**
    10241074
     
    10291079
    10301080In addition to ``extra_context``, the template's context will be:
    10311081
    1032     * ``form``: A ``django.oldforms.FormWrapper`` instance representing the form
     1082    * ``form``: A ``django.newforms.Form`` instance representing the form
    10331083      for editing the object. This lets you refer to form fields easily in the
    10341084      template system.
    10351085
    10361086      For example, if ``model`` has two fields, ``name`` and ``address``::
    10371087
    10381088          <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>
     1089          <p>{{ form.name.label_tag }} {{ form.name }}</p>
     1090          <p>{{ form.address.label_tag }} {{ form.address }}</p>
    10411091          </form>
     1092     
     1093      See the `newforms documentation`_ for more information about using
     1094      ``Form`` objects in templates.
    10421095
    1043       See the `manipulator and formfield documentation`_ for more information
    1044       about using ``FormWrapper`` objects in templates.
    1045 
    10461096    * ``object``: The original object being edited. This variable's name
    10471097      depends on the ``template_object_name`` parameter, which is ``'object'``
    10481098      by default. If ``template_object_name`` is ``'foo'``, this variable's
Back to Top