Ticket #3639: create_update_newforms5.diff
File create_update_newforms5.diff, 22.2 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..838fb2f 100644
a b 1 1 from django.core.xheaders import populate_xheaders 2 2 from django.template import loader 3 from django import oldforms4 from django.db .models import FileField3 from django import newforms as forms 4 from django.db import models 5 5 from django.contrib.auth.views import redirect_to_login 6 6 from django.template import RequestContext 7 7 from django.http import Http404, HttpResponse, HttpResponseRedirect 8 8 from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured 9 9 from django.utils.translation import ugettext 10 10 11 def create_object(request, model, template_name=None,11 def create_object(request, form_class, template_name=None, 12 12 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): 14 14 """ 15 15 Generic object-creation function. 16 16 … … def create_object(request, model, template_name=None, 22 22 if extra_context is None: extra_context = {} 23 23 if login_required and not request.user.is_authenticated(): 24 24 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 object29 new_data = request.POST.copy()30 31 if model._meta.has_field_type(FileField):32 new_data.update(request.FILES)33 34 # Check for errors35 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 42 42 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}) 44 44 45 45 # Redirect to the new object: first by trying post_save_redirect, 46 46 # then by obj.get_absolute_url; fail if neither works. … … def create_object(request, model, template_name=None, 51 51 else: 52 52 raise ImproperlyConfigured("No URL to redirect to from generic create view.") 53 53 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() 57 55 58 # Create the FormWrapper, template, context, response 59 form = oldforms.FormWrapper(manipulator, new_data, errors) 56 # Create the template, context, response 60 57 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()) 62 59 t = template_loader.get_template(template_name) 63 60 c = RequestContext(request, { 64 61 'form': form, … … def create_object(request, model, template_name=None, 70 67 c[key] = value 71 68 return HttpResponse(t.render(c)) 72 69 73 def update_object(request, model, object_id=None, slug=None,70 def update_object(request, form_class, object_id=None, slug=None, 74 71 slug_field='slug', template_name=None, template_loader=loader, 75 72 extra_context=None, post_save_redirect=None, 76 login_required=False, follow=None,context_processors=None,73 login_required=False, context_processors=None, 77 74 template_object_name='object'): 78 75 """ 79 76 Generic object-update function. … … def update_object(request, model, object_id=None, slug=None, 88 85 if extra_context is None: extra_context = {} 89 86 if login_required and not request.user.is_authenticated(): 90 87 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 92 99 # Look up the object to be edited 93 100 lookup_kwargs = {} 94 101 if object_id: 95 lookup_kwargs['%s__exact' % model._meta.pk.name] = object_id102 lookup_kwargs['%s__exact' % opts.model._meta.pk.name] = object_id 96 103 elif slug and slug_field: 97 104 lookup_kwargs['%s__exact' % slug_field] = slug 98 105 else: 99 106 raise AttributeError("Generic edit view must be called with either an object_id or a slug/slug_field") 100 107 try: 101 object = model.objects.get(**lookup_kwargs)108 object = opts.model.objects.get(**lookup_kwargs) 102 109 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) 106 111 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() 115 116 116 117 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}) 118 119 119 120 # Do a post-after-redirect so that reload works, etc. 120 121 if post_save_redirect: … … def update_object(request, model, object_id=None, slug=None, 124 125 else: 125 126 raise ImproperlyConfigured("No URL to redirect to from generic create view.") 126 127 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) 130 129 131 form = oldforms.FormWrapper(manipulator, new_data, errors)132 130 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()) 134 132 t = template_loader.get_template(template_name) 135 133 c = RequestContext(request, { 136 134 'form': form, … … def update_object(request, model, object_id=None, slug=None, 142 140 else: 143 141 c[key] = value 144 142 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)) 146 144 return response 147 145 148 146 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..d7de781 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 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. 910 918 911 919 **Optional arguments:** 912 920 … … If ``template_name`` isn't specified, this view will use the template 947 955 948 956 In addition to ``extra_context``, the template's context will be: 949 957 950 * ``form``: A ``django. oldforms.FormWrapper`` instance representing the form951 for editing the object. This lets you refer to form fields easily in the958 * ``form``: A ``django.newforms.ModelForm`` instance representing the form 959 for creating the object. This lets you refer to form fields easily in the 952 960 template system. 953 961 954 For example, if ``model``has two fields, ``name`` and ``address``::962 For example, if the model has two fields, ``name`` and ``address``:: 955 963 956 964 <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> 959 967 </form> 960 968 961 See the ` manipulator and formfield documentation`_ for more information962 about using ``FormWrapper`` objects in templates.969 See the `newforms documentation`_ for more information about using 970 ``Form`` objects in templates. 963 971 964 972 .. _authentication system: ../authentication/ 965 .. _manipulator and formfield documentation: ../forms/ 973 .. _ModelForm docs: ../newforms/modelforms 974 .. _newforms documentation: ../newforms/ 966 975 967 976 ``django.views.generic.create_update.update_object`` 968 977 ---------------------------------------------------- … … object. This uses the automatic manipulators that come with Django models. 975 984 976 985 **Required arguments:** 977 986 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. 980 990 981 991 * Either ``object_id`` or (``slug`` *and* ``slug_field``) is required. 982 992 … … If ``template_name`` isn't specified, this view will use the template 1029 1039 1030 1040 In addition to ``extra_context``, the template's context will be: 1031 1041 1032 * ``form``: A ``django. oldforms.FormWrapper`` instance representing the form1042 * ``form``: A ``django.newforms.ModelForm`` instance representing the form 1033 1043 for editing the object. This lets you refer to form fields easily in the 1034 1044 template system. 1035 1045 1036 For example, if ``model``has two fields, ``name`` and ``address``::1046 For example, if the model has two fields, ``name`` and ``address``:: 1037 1047 1038 1048 <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> 1041 1051 </form> 1042 1043 See the ` manipulator and formfield documentation`_ for more information1044 about using ``FormWrapper`` objects in templates.1052 1053 See the `newforms documentation`_ for more information about using 1054 ``Form`` objects in templates. 1045 1055 1046 1056 * ``object``: The original object being edited. This variable's name 1047 1057 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