Ticket #3639: create_update_newforms5.diff

File create_update_newforms5.diff, 22.2 KB (added by brosner, 8 years ago)

updated to r7140 and is now backward compatible by accepting a Model instance.

  • django/views/generic/create_update.py

    diff --git a/django/views/generic/create_update.py b/django/views/generic/create_update.py
    index 46e92fe..838fb2f 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
     4from django.db import models
    55from django.contrib.auth.views import redirect_to_login
    66from django.template import RequestContext
    77from django.http import Http404, HttpResponse, HttpResponseRedirect
    88from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
    99from django.utils.translation import ugettext
    1010
    11 def create_object(request, model, template_name=None,
     11def create_object(request, form_class, template_name=None,
    1212        template_loader=loader, extra_context=None, post_save_redirect=None,
    13         login_required=False, follow=None, context_processors=None):
     13        login_required=False, context_processors=None):
    1414    """
    1515    Generic object-creation function.
    1616
    def create_object(request, model, template_name=None, 
    2222    if extra_context is None: extra_context = {}
    2323    if login_required and not request.user.is_authenticated():
    2424        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 
     25   
     26    # should this be deprecated?
     27    if isinstance(form_class, models.Model):
     28        class ModelForm(forms.ModelForm):
     29            class Meta:
     30                model = form_class
     31    else:
     32        ModelForm = form_class
     33   
     34    opts = ModelForm._meta
     35   
     36    if request.method == 'POST':
     37        form = ModelForm(request.POST, request.FILES)
     38       
     39        if form.is_valid():
     40            new_object = form.save()
     41           
    4242            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})
     43                request.user.message_set.create(message=ugettext("The %(verbose_name)s was created successfully.") % {"verbose_name": opts.model._meta.verbose_name})
    4444
    4545            # Redirect to the new object: first by trying post_save_redirect,
    4646            # then by obj.get_absolute_url; fail if neither works.
    def create_object(request, model, template_name=None, 
    5151            else:
    5252                raise ImproperlyConfigured("No URL to redirect to from generic create view.")
    5353    else:
    54         # No POST, so we want a brand new form without any data or errors
    55         errors = {}
    56         new_data = manipulator.flatten_data()
     54        form = ModelForm()
    5755
    58     # Create the FormWrapper, template, context, response
    59     form = oldforms.FormWrapper(manipulator, new_data, errors)
     56    # Create the template, context, response
    6057    if not template_name:
    61         template_name = "%s/%s_form.html" % (model._meta.app_label, model._meta.object_name.lower())
     58        template_name = "%s/%s_form.html" % (opts.model._meta.app_label, opts.model._meta.object_name.lower())
    6259    t = template_loader.get_template(template_name)
    6360    c = RequestContext(request, {
    6461        'form': form,
    def create_object(request, model, template_name=None, 
    7067            c[key] = value
    7168    return HttpResponse(t.render(c))
    7269
    73 def update_object(request, model, object_id=None, slug=None,
     70def update_object(request, form_class, object_id=None, slug=None,
    7471        slug_field='slug', template_name=None, template_loader=loader,
    7572        extra_context=None, post_save_redirect=None,
    76         login_required=False, follow=None, context_processors=None,
     73        login_required=False, context_processors=None,
    7774        template_object_name='object'):
    7875    """
    7976    Generic object-update function.
    def update_object(request, model, object_id=None, slug=None, 
    8885    if extra_context is None: extra_context = {}
    8986    if login_required and not request.user.is_authenticated():
    9087        return redirect_to_login(request.path)
    91 
     88   
     89    # should this be deprecated?
     90    if isinstance(form_class, models.Model):
     91        class ModelForm(forms.ModelForm):
     92            class Meta:
     93                model = form_class
     94    else:
     95        ModelForm = form_class
     96   
     97    opts = ModelForm._meta
     98   
    9299    # Look up the object to be edited
    93100    lookup_kwargs = {}
    94101    if object_id:
    95         lookup_kwargs['%s__exact' % model._meta.pk.name] = object_id
     102        lookup_kwargs['%s__exact' % opts.model._meta.pk.name] = object_id
    96103    elif slug and slug_field:
    97104        lookup_kwargs['%s__exact' % slug_field] = slug
    98105    else:
    99106        raise AttributeError("Generic edit view must be called with either an object_id or a slug/slug_field")
    100107    try:
    101         object = model.objects.get(**lookup_kwargs)
     108        object = opts.model.objects.get(**lookup_kwargs)
    102109    except ObjectDoesNotExist:
    103         raise Http404, "No %s found for %s" % (model._meta.verbose_name, lookup_kwargs)
    104 
    105     manipulator = model.ChangeManipulator(getattr(object, object._meta.pk.attname), follow=follow)
     110        raise Http404, "No %s found for %s" % (opts.model._meta.verbose_name, lookup_kwargs)
    106111
    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)
     112    if request.method == 'POST':
     113        form = ModelForm(request.POST, request.FILES, instance=object)
     114        if form.is_valid():
     115            object = form.save()
    115116
    116117            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})
     118                request.user.message_set.create(message=ugettext("The %(verbose_name)s was updated successfully.") % {"verbose_name": opts.model._meta.verbose_name})
    118119
    119120            # Do a post-after-redirect so that reload works, etc.
    120121            if post_save_redirect:
    def update_object(request, model, object_id=None, slug=None, 
    124125            else:
    125126                raise ImproperlyConfigured("No URL to redirect to from generic create view.")
    126127    else:
    127         errors = {}
    128         # This makes sure the form acurate represents the fields of the place.
    129         new_data = manipulator.flatten_data()
     128        form = ModelForm(instance=object)
    130129
    131     form = oldforms.FormWrapper(manipulator, new_data, errors)
    132130    if not template_name:
    133         template_name = "%s/%s_form.html" % (model._meta.app_label, model._meta.object_name.lower())
     131        template_name = "%s/%s_form.html" % (opts.model._meta.app_label, opts.model._meta.object_name.lower())
    134132    t = template_loader.get_template(template_name)
    135133    c = RequestContext(request, {
    136134        'form': form,
    def update_object(request, model, object_id=None, slug=None, 
    142140        else:
    143141            c[key] = value
    144142    response = HttpResponse(t.render(c))
    145     populate_xheaders(request, response, model, getattr(object, object._meta.pk.attname))
     143    populate_xheaders(request, response, opts.model, getattr(object, object._meta.pk.attname))
    146144    return response
    147145
    148146def 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..d7de781 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.
     917      For backward compatibility this can also be a model.
    910918
    911919**Optional arguments:**
    912920
    If ``template_name`` isn't specified, this view will use the template 
    947955
    948956In addition to ``extra_context``, the template's context will be:
    949957
    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
     958    * ``form``: A ``django.newforms.ModelForm`` instance representing the form
     959      for creating the object. This lets you refer to form fields easily in the
    952960      template system.
    953961
    954       For example, if ``model`` has two fields, ``name`` and ``address``::
     962      For example, if the model has two fields, ``name`` and ``address``::
    955963
    956964          <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>
     965          <p>{{ form.name.label_tag }} {{ form.name }}</p>
     966          <p>{{ form.address.label_tag }} {{ form.address }}</p>
    959967          </form>
    960968
    961       See the `manipulator and formfield documentation`_ for more information
    962       about using ``FormWrapper`` objects in templates.
     969      See the `newforms documentation`_ for more information about using
     970      ``Form`` objects in templates.
    963971
    964972.. _authentication system: ../authentication/
    965 .. _manipulator and formfield documentation: ../forms/
     973.. _ModelForm docs: ../newforms/modelforms
     974.. _newforms documentation: ../newforms/
    966975
    967976``django.views.generic.create_update.update_object``
    968977----------------------------------------------------
    object. This uses the automatic manipulators that come with Django models. 
    975984
    976985**Required arguments:**
    977986
    978     * ``model``: The Django model class of the object that the form will
    979       create.
     987    * ``form_class``: A ``django.newforms.ModelForm`` class that will
     988      represent the model as a form. See `ModelForm docs`_ for more information.
     989      For backward compatibility this can also be a model.
    980990
    981991    * Either ``object_id`` or (``slug`` *and* ``slug_field``) is required.
    982992
    If ``template_name`` isn't specified, this view will use the template 
    10291039
    10301040In addition to ``extra_context``, the template's context will be:
    10311041
    1032     * ``form``: A ``django.oldforms.FormWrapper`` instance representing the form
     1042    * ``form``: A ``django.newforms.ModelForm`` instance representing the form
    10331043      for editing the object. This lets you refer to form fields easily in the
    10341044      template system.
    10351045
    1036       For example, if ``model`` has two fields, ``name`` and ``address``::
     1046      For example, if the model has two fields, ``name`` and ``address``::
    10371047
    10381048          <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>
     1049          <p>{{ form.name.label_tag }} {{ form.name }}</p>
     1050          <p>{{ form.address.label_tag }} {{ form.address }}</p>
    10411051          </form>
    1042 
    1043       See the `manipulator and formfield documentation`_ for more information
    1044       about using ``FormWrapper`` objects in templates.
     1052     
     1053      See the `newforms documentation`_ for more information about using
     1054      ``Form`` objects in templates.
    10451055
    10461056    * ``object``: The original object being edited. This variable's name
    10471057      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