Ticket #3639: create_update_newforms4.diff
File create_update_newforms4.diff, 21.6 KB (added by , 17 years ago) |
---|
-
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 1 1 from django.core.xheaders import populate_xheaders 2 2 from django.template import loader 3 from django import oldforms 4 from django.db.models import FileField 3 from django import newforms as forms 5 4 from django.contrib.auth.views import redirect_to_login 6 5 from django.template import RequestContext 7 6 from django.http import Http404, HttpResponse, HttpResponseRedirect 8 7 from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured 9 8 from django.utils.translation import ugettext 10 9 11 def create_object(request, model, template_name=None,10 def create_object(request, form_class, template_name=None, 12 11 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): 14 13 """ 15 14 Generic object-creation function. 16 15 … … def create_object(request, model, template_name=None, 22 21 if extra_context is None: extra_context = {} 23 22 if login_required and not request.user.is_authenticated(): 24 23 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 42 33 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}) 44 35 45 36 # Redirect to the new object: first by trying post_save_redirect, 46 37 # then by obj.get_absolute_url; fail if neither works. … … def create_object(request, model, template_name=None, 51 42 else: 52 43 raise ImproperlyConfigured("No URL to redirect to from generic create view.") 53 44 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() 57 46 58 # Create the FormWrapper, template, context, response 59 form = oldforms.FormWrapper(manipulator, new_data, errors) 47 # Create the template, context, response 60 48 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()) 62 50 t = template_loader.get_template(template_name) 63 51 c = RequestContext(request, { 64 52 'form': form, … … def create_object(request, model, template_name=None, 70 58 c[key] = value 71 59 return HttpResponse(t.render(c)) 72 60 73 def update_object(request, model, object_id=None, slug=None,61 def update_object(request, form_class, object_id=None, slug=None, 74 62 slug_field='slug', template_name=None, template_loader=loader, 75 63 extra_context=None, post_save_redirect=None, 76 login_required=False, follow=None,context_processors=None,64 login_required=False, context_processors=None, 77 65 template_object_name='object'): 78 66 """ 79 67 Generic object-update function. … … def update_object(request, model, object_id=None, slug=None, 89 77 if login_required and not request.user.is_authenticated(): 90 78 return redirect_to_login(request.path) 91 79 80 opts = form_class._meta 81 92 82 # Look up the object to be edited 93 83 lookup_kwargs = {} 94 84 if object_id: 95 lookup_kwargs['%s__exact' % model._meta.pk.name] = object_id85 lookup_kwargs['%s__exact' % opts.model._meta.pk.name] = object_id 96 86 elif slug and slug_field: 97 87 lookup_kwargs['%s__exact' % slug_field] = slug 98 88 else: 99 89 raise AttributeError("Generic edit view must be called with either an object_id or a slug/slug_field") 100 90 try: 101 object = model.objects.get(**lookup_kwargs)91 object = opts.model.objects.get(**lookup_kwargs) 102 92 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) 104 94 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() 115 99 116 100 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}) 118 102 119 103 # Do a post-after-redirect so that reload works, etc. 120 104 if post_save_redirect: … … def update_object(request, model, object_id=None, slug=None, 124 108 else: 125 109 raise ImproperlyConfigured("No URL to redirect to from generic create view.") 126 110 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) 130 112 131 form = oldforms.FormWrapper(manipulator, new_data, errors)132 113 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()) 134 115 t = template_loader.get_template(template_name) 135 116 c = RequestContext(request, { 136 117 'form': form, … … def update_object(request, model, object_id=None, slug=None, 142 123 else: 143 124 c[key] = value 144 125 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)) 146 127 return response 147 128 148 129 def 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 894 894 The ``django.views.generic.create_update`` module contains a set of functions 895 895 for creating, editing and deleting objects. 896 896 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 901 build and display the form. 902 903 .. _newforms: ../newforms/ 904 897 905 ``django.views.generic.create_update.create_object`` 898 906 ---------------------------------------------------- 899 907 900 908 **Description:** 901 909 902 910 A 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. 911 validation errors (if there are any) and saving the object. 905 912 906 913 **Required arguments:** 907 914 908 * `` model``: The Django model class of the object that the formwill909 create.915 * ``form_class``: A ``django.newforms.ModelForm`` class that will 916 represent the model as a form. See `ModelForm docs`_ for more information. 910 917 911 918 **Optional arguments:** 912 919 … … If ``template_name`` isn't specified, this view will use the template 947 954 948 955 In addition to ``extra_context``, the template's context will be: 949 956 950 * ``form``: A ``django. oldforms.FormWrapper`` instance representing the form951 for editing the object. This lets you refer to form fields easily in the957 * ``form``: A ``django.newforms.ModelForm`` instance representing the form 958 for creating the object. This lets you refer to form fields easily in the 952 959 template system. 953 960 954 For example, if ``model``has two fields, ``name`` and ``address``::961 For example, if the model has two fields, ``name`` and ``address``:: 955 962 956 963 <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> 959 966 </form> 960 967 961 See the ` manipulator and formfield documentation`_ for more information962 about using ``FormWrapper`` objects in templates.968 See the `newforms documentation`_ for more information about using 969 ``Form`` objects in templates. 963 970 964 971 .. _authentication system: ../authentication/ 965 .. _manipulator and formfield documentation: ../forms/ 972 .. _ModelForm docs: ../newforms/modelforms 973 .. _newforms documentation: ../newforms/ 966 974 967 975 ``django.views.generic.create_update.update_object`` 968 976 ---------------------------------------------------- … … object. This uses the automatic manipulators that come with Django models. 975 983 976 984 **Required arguments:** 977 985 978 * `` model``: The Django model class of the object that the formwill979 create.986 * ``form_class``: A ``django.newforms.ModelForm`` class that will 987 represent the model as a form. See `ModelForm docs`_ for more information. 980 988 981 989 * Either ``object_id`` or (``slug`` *and* ``slug_field``) is required. 982 990 … … If ``template_name`` isn't specified, this view will use the template 1029 1037 1030 1038 In addition to ``extra_context``, the template's context will be: 1031 1039 1032 * ``form``: A ``django. oldforms.FormWrapper`` instance representing the form1040 * ``form``: A ``django.newforms.ModelForm`` instance representing the form 1033 1041 for editing the object. This lets you refer to form fields easily in the 1034 1042 template system. 1035 1043 1036 For example, if ``model``has two fields, ``name`` and ``address``::1044 For example, if the model has two fields, ``name`` and ``address``:: 1037 1045 1038 1046 <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> 1041 1049 </form> 1042 1043 See the ` manipulator and formfield documentation`_ for more information1044 about using ``FormWrapper`` objects in templates.1050 1051 See the `newforms documentation`_ for more information about using 1052 ``Form`` objects in templates. 1045 1053 1046 1054 * ``object``: The original object being edited. This variable's name 1047 1055 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 2 from datetime import datetime 3 4 from django import newforms as forms 5 from models import Author, Article 6 7 8 class ArticleForm(forms.ModelForm): 9 """ 10 A form bound to the Article model 11 """ 12 13 class Meta: 14 model = Article 15 16 17 class 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 1 1 from defaults import * 2 2 from i18n import * 3 3 from static import * 4 from generic.date_based import * 5 No newline at end of file 4 from generic.date_based import * 5 from 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 2 import datetime 3 from django.test import TestCase 4 from django.contrib.auth.views import redirect_to_login 5 from regressiontests.views.models import Article, Author 6 7 class 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 57 class 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 3 3 from django.conf.urls.defaults import * 4 4 5 5 from models import * 6 from forms import ArticleForm 6 7 import views 7 8 8 9 base_dir = path.dirname(path.abspath(__file__)) … … date_based_info_dict = { 20 21 'month_format': '%m', 21 22 } 22 23 24 crud_form_info_dict = { 25 'form_class': ArticleForm, 26 } 27 28 crud_delete_info_dict = { 29 'model': Article, 30 } 31 23 32 urlpatterns = patterns('', 24 33 (r'^$', views.index_page), 25 34 … … urlpatterns = patterns('', 44 53 dict(allow_future=True, slug_field='slug', **date_based_info_dict)), 45 54 (r'^date_based/archive_month/(?P<year>\d{4})/(?P<month>\d{1,2})/$', 46 55 '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)), 48 69 ) -
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 1 import datetime 2 1 3 from django.http import HttpResponse 4 from django.template import RequestContext 5 from django.views.generic.create_update import create_object 6 7 from forms import CustomSlugArticleForm 8 2 9 3 10 def index_page(request): 4 11 """Dummy index page""" 5 12 return HttpResponse('<html><body>Dummy page</body></html>') 13 14 def 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
- + 1 This 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
- + 1 This template intentionally left blank 2 No newline at end of file